2013-12-08 22 views
30

這與將多個列應用到data.table的多列的問題非常相似.SDcolsanswered thoroughly here對列的另一個子集(.SDcols)應用函數,同時在另一列(組內)應用不同的函數

不同之處在於我想同時在不屬於.SD子集的另一列上應用不同的函數。我張貼下面一個簡單的例子來說明我的努力來解決這個問題:

dt = data.table(grp = sample(letters[1:3],100, replace = TRUE), 
       v1 = rnorm(100), 
       v2 = rnorm(100), 
       v3 = rnorm(100)) 
sd.cols = c("v2", "v3") 
dt.out = dt[, list(v1 = sum(v1), lapply(.SD,mean)), by = grp, .SDcols = sd.cols] 

產生以下錯誤:

Error in `[.data.table`(dt, , list(v1 = sum(v1), lapply(.SD, mean)), by = grp, 
: object 'v1' not found 

現在,這是有道理的,因爲v1列不在列的子集,包括必須首先對其進行評估。所以,我進一步探討將其包含在我的專欄的子集:

sd.cols = c("v1","v2", "v3") 
dt.out = dt[, list(sum(v1), lapply(.SD,mean)), by = grp, .SDcols = sd.cols] 

現在,這不會導致錯誤,但它在V1欄提供了包含9行(3組)的答案,與和重複三次,如下所示爲放置在V2所有3列(如預期但不想要的)的裝置:

> dt.out 
    grp  V1     V2 
1: c -1.070608 -0.0486639841313638 
2: c -1.070608 -0.178154270921521 
3: c -1.070608 -0.137625003604012 
4: b -2.782252 -0.0794929150464099 
5: b -2.782252 -0.149529237116445 
6: b -2.782252 0.199925178109264 
7: a 6.091355 0.141659419355985 
8: a 6.091355 -0.0272192037753071 
9: a 6.091355 0.00815760216214876 

解決方法解決方案使用2步驟

顯然這可以通過按組計算mean爲列的子集,並通過組爲單柱也加入到sum如下解決多個步驟的問題:

dt.out1 = dt[, sum(v1), by = grp] 
dt.out2 = dt[, lapply(.SD,mean), by = grp, .SDcols = sd.cols] 
dt.out = merge(dt.out1, dt.out2, by = "grp") 

> dt.out 
    grp  V1   v2   v3 
1: a 6.091355 -0.0272192 0.008157602 
2: b -2.782252 -0.1495292 0.199925178 
3: c -1.070608 -0.1781543 -0.137625004 

我敢肯定,這是一個相當簡單的事情我我錯過了,提前感謝任何指導。

+0

第一個表達式不起作用的事實是一個錯誤imo,所以請提交一個錯誤報告 – eddi

回答

23

更新:問題#495this recent commit現在問題解決了,我們現在可以做到這一點就好了:

require(data.table) # v1.9.7+ 
set.seed(1L) 
dt = data.table(grp = sample(letters[1:3],100, replace = TRUE), 
       v1 = rnorm(100), 
       v2 = rnorm(100), 
       v3 = rnorm(100)) 
sd.cols = c("v2", "v3") 
dt.out = dt[, list(v1 = sum(v1), lapply(.SD,mean)), by = grp, .SDcols = sd.cols] 

但是請注意,在這種情況下,v2將返回一個列表。那是因爲你有效地做了list(val, list())。你打算也許做的是:

dt[, c(list(v1=sum(v1)), lapply(.SD, mean)), by=grp, .SDcols = sd.cols] 
# grp  v1   v2   v3 
# 1: a -6.440273 0.16993940 0.2173324 
# 2: b 4.304350 -0.02553813 0.3381612 
# 3: c 0.377974 -0.03828672 -0.2489067 

見歷史舊的答案。

+0

Arun,在這種情況下,我認爲'.SD'瓶頸不適用 - 正常的'.SD'瓶頸與'[.data.table'的開銷有關,這裏沒有。 – eddi

+0

你是對的,它*是*慢,我不明白爲什麼atm - 我認爲這意味着有另一個大開銷計算在其他地方(或換句話說 - 我懷疑瓶頸是從Cdogroups調用eval ) – eddi

+4

這是'lapply'的'eval'多次慢,而不是'.SD'。在C級看「base :: lapply」的來源。它通過構建一個「list(...)'調用,然後評估它。當「lapply」循環時,同樣的構造會一遍又一遍地浪費。所以優化就是先建立這個構造(在R級別將在'[.data.table'內部執行],然後將其傳遞給'dogroups'。但是現在只有對'lapply'的單一調用進行了優化。結合'c()'沒有拿起。 cc @eddi –

6

試試這個:

dt[,list(sum(v1), mean(v2), mean(v3)), by=grp] 

data.table,第二個參數中使用list()允許您描述一組導致最終data.table列。

對於什麼值得,.SD可能會很慢[^ 1],所以你可能想要避免它,除非你真的需要所有在子集data.table中提供的數據,就像你可能需要一個更復雜的函數。

如果.SDcols有很多列,則使用data.table合併語法在另一行中執行合併。

例如:

dt[, sum(v1), by=grp][dt[,lapply(.SD,mean), by=grp, .SDcols=sd.cols]] 

爲了使用從data.tablemerge,您需要在您的data.table所以它知道如何搭配的事情了首先使用setkey()

所以真的,首先你需要:

setkey(dt, grp) 

然後你就可以使用上面的線產生相當的結果。

[^ 1]:當你的組數接近總行數時,我覺得這是特別真實的。例如,這可能發生在你的密鑰是個人ID並且許多人只有一個或兩個觀察值的地方。

+0

+ 1爲合併語法和一個可行的解決方案 –

+0

使用'wmean'在這裏證明有點頭痛,因爲我會需要'.SDcols'部分中指定的權重列,即使我不想使用它!因爲我已經在該列上使用了'sum',所以在列上計算'weighted.mean'也是一件痛苦的事情...... 我想我必須在執行'data.table之前排除該列合併。 –

相關問題