在列的子集(.SDcols)上应用函数,同时在另一列(在组内)应用不同的函数

Mat*_*ler 34 r subset lapply data.table

这非常类似于将一个共同功能应用于一个data.table完整.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]
Run Code Online (Sandbox Code Playgroud)

产生以下错误:

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

现在这是有道理的,因为v1列不包含在必须首先计算的列子集中.所以我通过将其包含在我的列子集中进一步探索:

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

现在这不会导致错误,但它提供了一个包含9行(对于3组)的答案,其中总和在列中重复三次,V1并且所有3列的均值(如预期但不想要的)放置在V2如下所示:

> 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
Run Code Online (Sandbox Code Playgroud)

使用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
Run Code Online (Sandbox Code Playgroud)

我确定这是一个我想念的相当简单的事情,提前感谢任何指导.

Aru*_*run 29

更新:问题#495现在通过最近的提交解决了,我们现在可以做到这一点:

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]
Run Code Online (Sandbox Code Playgroud)

但请注意,在这种情况下,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
Run Code Online (Sandbox Code Playgroud)

查看旧答案的历史记录.

  • 很多次`lavally`的'eval`很慢,而不是'.SD`.在C级查看`base :: lapply`的来源.无论如何,它通过构造一个`list(...)`调用然后评估它来实现它.当'lapply`循环时,同样的结构会一遍又一遍地进行,浪费.因此优化是将该构造提前一次(并且在R级别将在`[.data.table`内部]进行,然后将其传递给`dogroups`.但目前只对"lapply"的单一调用进行了优化.结合"c()"没有被选中.cc @eddi (5认同)

JBe*_*ker 8

试试这个:

dt[,list(sum(v1), mean(v2), mean(v3)), by=grp]
Run Code Online (Sandbox Code Playgroud)

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]]
Run Code Online (Sandbox Code Playgroud)

为了使用mergefrom data.table,你需要首先使用setkey()你的,data.table所以它知道如何匹配.

所以,首先你需要:

setkey(dt, grp)
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用上面的行生成相同的结果.

[^ 1]:我发现这一点尤其正确,因为您的组数接近总行数.例如,这可能发生在您的密钥是个人ID且许多人只有一两个观察值的情况下.