29

コードのパフォーマンスを向上させるために、R で data.table パッケージを使い始めました。次のコードを使用しています。

sp500 <- read.csv('../rawdata/GMTSP.csv')
days <- c("Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday")

# Using data.table to get the things much much faster
sp500 <- data.table(sp500, key="Date")
sp500 <- sp500[,Date:=as.Date(Date, "%m/%d/%Y")]
sp500 <- sp500[,Weekday:=factor(weekdays(sp500[,Date]), levels=days, ordered=T)]
sp500 <- sp500[,Year:=(as.POSIXlt(Date)$year+1900)]
sp500 <- sp500[,Month:=(as.POSIXlt(Date)$mon+1)]

平日などを作成する他の関数と比較すると、as.Date 関数による変換が非常に遅いことに気付きました。それはなぜですか? 日付形式に変換する方法、より良い/より高速なソリューションはありますか? (日付形式が本当に必要かどうかを尋ねる場合は、おそらく必要です.ggplot2を使用してプロットを作成すると、このタイプのデータで魅力的に機能します。)

より正確には

> system.time(sp500 <- sp500[,Date:=as.Date(Date, "%m/%d/%Y")])
   user  system elapsed 
 92.603   0.289  93.014 
> system.time(sp500 <- sp500[,Weekday:=factor(weekdays(sp500[,Date]), levels=days, ordered=T)])
   user  system elapsed 
  1.938   0.062   2.001 
> system.time(sp500 <- sp500[,Year:=(as.POSIXlt(Date)$year+1900)])
   user  system elapsed 
  0.304   0.001   0.305 

MacAir i5 では、観測数が 3000000 をわずかに下回りました。

4

6 に答える 6

24

を使用してviaにas.Date変換characterするだけだと思います。そして、非常に遅いと思います。DatePOSIXltstrptimestrptime

自分でトレースするには、 を入力してから を入力しas.Date、メソッドmethods(as.Date)を調べます。character

> as.Date
function (x, ...) 
UseMethod("as.Date")
<bytecode: 0x2cf4b20>
<environment: namespace:base>

> methods(as.Date)
[1] as.Date.character as.Date.date      as.Date.dates     as.Date.default  
[5] as.Date.factor    as.Date.IDate*    as.Date.numeric   as.Date.POSIXct  
[9] as.Date.POSIXlt  
   Non-visible functions are asterisked

> as.Date.character
function (x, format = "", ...) 
{
    charToDate <- function(x) {
        xx <- x[1L]
        if (is.na(xx)) {
            j <- 1L
            while (is.na(xx) && (j <- j + 1L) <= length(x)) xx <- x[j]
            if (is.na(xx)) 
                f <- "%Y-%m-%d"
        }
        if (is.na(xx) || !is.na(strptime(xx, f <- "%Y-%m-%d", 
            tz = "GMT")) || !is.na(strptime(xx, f <- "%Y/%m/%d", 
            tz = "GMT"))) 
            return(strptime(x, f))
        stop("character string is not in a standard unambiguous format")
    }
    res <- if (missing(format)) 
        charToDate(x)
    else strptime(x, format, tz = "GMT")       ####  slow part, I think  ####
    as.Date(res)
}
<bytecode: 0x2cf6da0>
<environment: namespace:base>
> 

なぜas.POSIXlt(Date)$year+1900比較的速いのですか?繰り返しますが、それをたどってください:

> as.POSIXct
function (x, tz = "", ...) 
UseMethod("as.POSIXct")
<bytecode: 0x2936de8>
<environment: namespace:base>

> methods(as.POSIXct)
[1] as.POSIXct.date    as.POSIXct.Date    as.POSIXct.dates   as.POSIXct.default
[5] as.POSIXct.IDate*  as.POSIXct.ITime*  as.POSIXct.numeric as.POSIXct.POSIXlt
   Non-visible functions are asterisked

> as.POSIXlt.Date
function (x, ...) 
{
    y <- .Internal(Date2POSIXlt(x))
    names(y$year) <- names(x)
    y
}
<bytecode: 0x395e328>
<environment: namespace:base>
> 

興味をそそられたので、Date2POSIXlt を掘り下げてみましょう。このビットでは、参照する .c ファイルを知るために main/src を grep する必要があります。

~/R/Rtrunk/src/main$ grep Date2POSIXlt *
names.c:{"Date2POSIXlt",do_D2POSIXlt,   0,  11, 1,  {PP_FUNCALL, PREC_FN,   0}},
$

これで、 D2POSIXlt を探す必要があることがわかりました。

~/R/Rtrunk/src/main$ grep D2POSIXlt *
datetime.c:SEXP attribute_hidden do_D2POSIXlt(SEXP call, SEXP op, SEXP args, SEXP env)
names.c:{"Date2POSIXlt",do_D2POSIXlt,   0,  11, 1,  {PP_FUNCALL, PREC_FN,   0}},
$

おお、datetime.c を推測できたはずです。とにかく、最新のライブコピーを見てください:

日時.c

そこを検索するD2POSIXltと、Date (数値) から POSIXlt への移行がいかに簡単であるかがわかります。また、POSIXlt が 1 つの実数ベクトル (8 バイト) と 7 つの整数ベクトル (それぞれ 4 バイト) であることがわかります。1 日あたり 40 バイトです。

したがって、問題の核心 (私が思う) はなぜstrptimeが非常に遅いかということであり、R で改善できる可能性があります。またはPOSIXlt、直接的または間接的に を回避するだけです。


質問に記載されているアイテムの数 (3,000,000) を使用した再現可能な例を次に示します。

> Range = seq(as.Date("2000-01-01"),as.Date("2012-01-01"),by="days")
> Date = format(sample(Range,3000000,replace=TRUE),"%m/%d/%Y")
> system.time(as.Date(Date, "%m/%d/%Y"))
   user  system elapsed 
 21.681   0.060  21.760 
> system.time(strptime(Date, "%m/%d/%Y"))
   user  system elapsed 
 29.594   8.633  38.270 
> system.time(strptime(Date, "%m/%d/%Y", tz="GMT"))
   user  system elapsed 
 19.785   0.000  19.802 

追い越しtzはスピードアップするように見えますが、実際strptimeas.Date.characterそうです。だから多分それはあなたのロケールに依存します。しかしstrptime、犯人ではなく、犯人のようdata.tableです。この例を再実行して、自分のマシンで 90 秒かかるかどうかを確認してください。

于 2012-10-08T20:28:05.033 に答える
8

提案をありがとう。日付のガウス アルゴリズムを自分で作成して解決したところ、はるかに優れた結果が得られました。以下を参照してください。

getWeekDay <- function(year, month, day) {
  # Implementation of the Gaussian algorithm to get weekday 0 - Sunday, ... , 7 - Saturday
  Y <- year
  Y[month<3] <- (Y[month<3] - 1)

  d <- day
  m <- ((month + 9)%%12) + 1
  c <- floor(Y/100)
  y <- Y-c*100
  dayofweek <- (d + floor(2.6*m - 0.2) + y + floor(y/4) + floor(c/4) - 2*c) %% 7
  return(dayofweek)
}

sp500 <- read.csv('../rawdata/GMTSP.csv')
days <- c("Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday")

# Using data.table to get the things much much faster
sp500 <- data.table(sp500, key="Date")
sp500 <- sp500[,Month:=as.integer(substr(Date,1,2))]
sp500 <- sp500[,Day:=as.integer(substr(Date,4,5))]
sp500 <- sp500[,Year:=as.integer(substr(Date,7,10))]
#sp500 <- sp500[,Date:=as.Date(Date, "%m/%d/%Y")]
#sp500 <- sp500[,Weekday:=factor(weekdays(sp500[,Date]), levels=days, ordered=T)]
sp500 <- sp500[,Weekday:=factor(getWeekDay(Year, Month, Day))]
levels(sp500$Weekday) <- days

上記のブロック全体を実行すると (csv からの日付の読み取りを含む)... Data.table は本当に印象的です。

user  system elapsed 
 19.074   0.803  20.284 

変換自体のタイミングは 3.49 経過しています。

于 2012-10-09T06:56:30.013 に答える
2

私は当初、「上記の as.Date の引数にフォーマットが指定されていない」と考えていました。

私は今、次のように考えています。あなたが入力した Date 値は標準形式であると想定しました。ないと思います。つまり、2 つのプロセスを実行しています。文字から日付形式に再フォーマットしており、まったく異なる照合シーケンスを持つ新しい値に基づいて再ソートしています。

于 2012-10-08T18:27:22.657 に答える