6

ビッグデータに関するもう 1 つの初心者の質問です。時系列データを含む大規模なデータセット (3.5m 行) を使用しています。data.table一意の識別子が最初に表示されたときに検索する列を作成したいと考えています。

df は でdata.tabledf$timestampはクラス の日付でPOSIXctdf$id一意の数値識別子です。私は次のコードを使用しています:

# UPDATED - DATA KEYED
setkey(df, id)
sub_df<-df[,(min(timestamp)), by=list(id)] # Finding first timestamp for each unique ID

これがキャッチです。80,000 を超える一意の ID を集約しています。Rは窒息しています。アプローチを最適化するためにできることはありますか?

4

4 に答える 4

6

これは、どの動作に多くの時間がかかるかをテストするための小さなコードです

require(data.table)
dt <- data.table(sample(seq(as.Date("2012-01-01"), as.Date("2013-12-31"), 
          by="days"), 1e5, replace=T), val=sample(1e4, 1e5, replace = T))

FUN1 <- function() {
    out <- dt[, min(dt$V1), by=val]  # min of entire V1 for each group i.e. wrong
}

FUN2 <- function() {
    out <- dt[, min(V1), by=val]     # min of V1 within group as intended
}

require(rbenchmark)
> benchmark(FUN1(), FUN2(), replications = 1, order="elapsed")
#     test replications elapsed relative user.self sys.self user.child sys.child
# 2 FUN2()            1   0.271    1.000     0.242    0.002          0         0
# 1 FUN1()            1  38.378  141.616    32.584    4.153          0         0

FUN2()それが速く燃えていることは非常に明白です。どちらの場合も、KEYが設定されていないことを忘れないでください

于 2013-01-29T19:53:46.700 に答える
5

@Arun が述べたように、本当の鍵 (しゃれは意図されていません) は、 .data.tableではなく適切な構文を使用することですsetkey

df[, min(timestamp), by=id]

80,000 個の一意の ID は多くのように聞こえますが、 のkey機能を使用data.tableすると、管理しやすい見込み客にすることができます。

setkey(df, id)

その後、元のように処理します。その価値のために、並べ替えというキーの便利な副作用をよく使用できます。

set.seed(1)
dat <- data.table(x = sample(1:10, 10), y = c('a', 'b'))

    x y
 1:  3 a
 2:  4 b
 3:  5 a
 4:  7 b
 5:  2 a
 6:  8 b
 7:  9 a
 8:  6 b
 9: 10 a
10:  1 b

setkey(dat, y, x)

     x y
 1:  2 a
 2:  3 a
 3:  5 a
 4:  9 a
 5: 10 a
 6:  1 b
 7:  4 b
 8:  6 b
 9:  7 b
10:  8 b

次に、minまたは別のより複雑な関数は単なるサブセット操作です。

dat[, .SD[1], by=y]
于 2013-01-29T19:42:40.457 に答える
4

Arun回答を補完するものとして、OPと同様のサイズのデータ​​セット(350万行、80K ID)を使用したものがあります。これは、キー付き/キーなしの集計にそれほど違いがないことを示しています。したがって、スピードアップは、$オペレーターを回避したことが原因である可能性があります。

set.seed(10)
eg <- function(x) data.table(id=sample(8e4,x,replace=TRUE),timestamp=as.POSIXct(runif(x,min=ISOdatetime(2013,1,1,0,0,0) - 60*60*24*30, max=ISOdatetime(2013,1,1,0,0,0)),origin="1970-01-01"))
df <- eg(3.5e6)
dfk <- copy(df)
setkey(dfk,id)
require(microbenchmark)
microbenchmark(
    unkeyed = df[,min(timestamp),by=id][,table(weekdays(V1))]
    ,keyed = dfk[,min(timestamp),by=id][,table(weekdays(V1))]
    ,times=5
)
#Unit: seconds
#     expr      min       lq   median       uq      max
#1   keyed 7.330195 7.381879 7.476096 7.486394 7.690694
#2 unkeyed 7.882838 7.888880 7.924962 7.927297 7.931368

マシューから編集します。

実際、上記はほぼ完全にタイプと関係がありPOSIXctます。

> system.time(dfk[,min(timestamp),by=id])
   user  system elapsed 
   8.71    0.02    8.72 
> dfk[,timestamp:=as.double(timestamp)]  # discard POSIXct type to demonstrate
> system.time(dfk[,min(timestamp),by=id])
   user  system elapsed 
   0.14    0.02    0.15     # that's more like normal data.table speed

POSIXctに戻ってRprofを使用するとmin()、そのタイプの内部が97%であることがわかります(つまり、関係ありませんdata.table):

$by.total
                total.time total.pct self.time self.pct
system.time           8.70    100.00      0.00     0.00
[.data.table          8.64     99.31      0.12     1.38
[                     8.64     99.31      0.00     0.00
min                   8.46     97.24      0.46     5.29
Summary.POSIXct       8.00     91.95      0.86     9.89
do.call               5.86     67.36      0.26     2.99
check_tzones          5.46     62.76      0.20     2.30
unique                5.26     60.46      2.04    23.45
sapply                3.74     42.99      0.46     5.29
simplify2array        2.38     27.36      0.16     1.84
NextMethod            1.28     14.71      1.28    14.71
unique.default        1.10     12.64      0.92    10.57
lapply                1.10     12.64      0.76     8.74
unlist                0.60      6.90      0.28     3.22
FUN                   0.24      2.76      0.24     2.76
match.fun             0.22      2.53      0.22     2.53
is.factor             0.18      2.07      0.18     2.07
parent.frame          0.14      1.61      0.14     1.61
gc                    0.06      0.69      0.06     0.69
duplist               0.04      0.46      0.04     0.46
[.POSIXct             0.02      0.23      0.02     0.23

のオブジェクトサイズに注意してくださいdfk

> object.size(dfk)
40.1 Mb

data.tableこの小さなサイズで7秒かかることはありません!欠陥のない100倍の大きさ(4GB)である必要があります。そうすれjば、keyedbyとadhocbyの違いを確認できます。

ブルーマジスターから編集:

Matthew Dowleの答えを考慮に入れると、キー付きコマンドとキーなしコマンドには違いがあります。

df <- eg(3.5e6)
df[,timestamp := as.double(timestamp)]
dfk <- copy(df)
setkey(dfk,id)
require(microbenchmark)
microbenchmark(
    unkeyed = df[,min(timestamp),by=id][,table(weekdays(as.POSIXct(V1,origin="1970-01-01")))]
    ,keyed = dfk[,min(timestamp),by=id][,table(weekdays(as.POSIXct(V1,origin="1970-01-01")))]
    ,times=10
)
#Unit: milliseconds
#     expr      min       lq   median       uq       max
#1   keyed 340.3177 346.8308 348.7150 354.7337  358.1348
#2 unkeyed 886.1687 888.7061 901.1527 945.6190 1036.3326
于 2013-01-29T19:58:53.257 に答える
2

ここにdata.tableバージョンがあります

dtBy <- function(dt)
    dt[, min(timestamp), by=id]

少し昔ながらの方法で、各グループの最小値を返す関数を次に示します

minBy <- function(x, by) {
    o <- order(x)
    by <- by[o]
    idx <- !duplicated(by)
    data.frame(by=by[idx], x=x[o][idx])
}

BlueMagister のサンプルデータとしては妥当な性能を持っているようです

> system.time(res0 <- dtBy(dt))
   user  system elapsed 
 11.165   0.216  11.894 
> system.time(res1 <- minBy(dt$timestamp, dt$id))
   user  system elapsed 
  4.784   0.036   4.836 
> all.equal(res0[order(res0$id),], res1[order(res1$by),],
+           check.attributes=FALSE)
[1] TRUE

マシューから編集(ほとんど)

はい、これは、繰り返すのが非常に遅い部分である POSIXct 型minByを回避するためです。min()これは とは関係ありませんdata.table

dfkBlue Mの回答からの使用:

dfk[,timestamp:=as.double(timestamp)]  # discard POSIXct type to demonstrate

system.time(res0 <- dtBy(dfk))
   user  system elapsed 
   0.16    0.02    0.17 
system.time(res1 <- minBy(dfk$timestamp, dfk$id))
   user  system elapsed 
   4.87    0.04    4.92 

古い学校の方法は、data.table と比較して非常に遅く見えます。min()すべての時間がPOSIXct 型に費やされていました。出力については、Arun の回答の編集を参照してくださいRprof

于 2013-01-29T20:30:28.383 に答える