【问题标题】:Use of lapply .SD in data.table R在 data.table R 中使用 lapply .SD
【发布时间】:2015-11-23 11:02:12
【问题描述】:

我不太清楚.SD 和by 的使用。

例如,下面的 sn-p 是否意味着:'将DT 中的所有列更改为除A 和B 之外的因子?它还在data.table 手册中说:“.SD 指的是每个组的data.table 的子集(不包括分组列)” - 所以列A 和B 被排除在外了吗?

DT = DT[ ,lapply(.SD, as.factor), by=.(A,B)]

但是,我还读到by 在您进行聚合时的含义类似于 SQL 中的“分组依据”。例如,如果我想对除A 和B 之外的所有列求和(如SQL 中的colsum),我还使用类似的东西吗?或者在这种情况下,下面的代码是否意味着对列A 和B 中的值进行求和和分组? (按 SQL 中的A,B 取和并分组)

DT[,lapply(.SD,sum),by=.(A,B)]

那么我如何在除A 和B 之外的所有列上执行简单的colsum?

【问题讨论】:

  • DT[,colSums(.SD),.SDcols=-c(A, B)] 或 DT[,lapply(.SD, sum),.SDcols=-c(A, B)]
  • 使用by 意味着在每个AxB 配对中,您对DT 中每个other 列的值求和。 @Khashaa 的评论是(一些方法)如何对除 A 和 B 之外的所有列求和,不是按组
  • @MichaelChirico,当我像第一个示例一样更改列类型时,by 表示排除我猜,对吧?哪个更快? colSums 或 lapply ?
  • by 并不意味着排除。只是当使用by 时,.SD 的值仅引用其他列(我认为这是一个奇怪的规则)。关于colSums,请不要使用它,如下所述:github.com/Rdatatable/data.table/wiki/Do%27s-and-Don%27ts
  • @Frank 在colSums 上的参考很好,但是我很好奇如果您不是按组执行,而是在整个桌子上执行,哪个更快。

标签: r data.table


【解决方案1】:

只是为了举例说明上面的cmets,我们来看看

set.seed(10238)
# A and B are the "id" variables within which the
#   "data" variables C and D vary meaningfully
DT = data.table(
  A = rep(1:3, each = 5L), 
  B = rep(1:5, 3L),
  C = sample(15L),
  D = sample(15L)
)
DT
#     A B  C  D
#  1: 1 1 14 11
#  2: 1 2  3  8
#  3: 1 3 15  1
#  4: 1 4  1 14
#  5: 1 5  5  9
#  6: 2 1  7 13
#  7: 2 2  2 12
#  8: 2 3  8  6
#  9: 2 4  9 15
# 10: 2 5  4  3
# 11: 3 1  6  5
# 12: 3 2 12 10
# 13: 3 3 10  4
# 14: 3 4 13  7
# 15: 3 5 11  2

比较以下:

#Sum all columns
DT[ , lapply(.SD, sum)]
#     A  B   C   D
# 1: 30 45 120 120

#Sum all columns EXCEPT A, grouping BY A
DT[ , lapply(.SD, sum), by = A]
#    A  B  C  D
# 1: 1 15 38 43
# 2: 2 15 30 49
# 3: 3 15 52 28

#Sum all columns EXCEPT A
DT[ , lapply(.SD, sum), .SDcols = !"A"]
#     B   C   D
# 1: 45 120 120

#Sum all columns EXCEPT A, grouping BY B
DT[ , lapply(.SD, sum), by = B, .SDcols = !"A"]
#    B  C  D
# 1: 1 27 29
# 2: 2 17 30
# 3: 3 33 11
# 4: 4 23 36
# 5: 5 20 14

几点说明:

  • 您说“下面的 sn-p... 是否更改了 DT... 中的所有列”

答案是否,这对data.table 来说非常重要。返回的对象是一个newdata.table,DT 中的所有列都与运行代码之前完全相同。

  • 您提到想要更改列类型

再次参考上述要点,请注意您的代码 (DT[ , lapply(.SD, as.factor)]) 返回一个 新 data.table 并且根本不会更改 DT。一种(不正确)方法是使用base 中的data.frames 完成此操作,即用您返回的新data.table 覆盖旧的data.table,即DT = DT[ , lapply(.SD, as.factor)].

这很浪费,因为它涉及创建DT 的副本,当DT 很大时,这可能会成为效率杀手。解决此问题的正确data.table 方法是使用`:=` 通过引用更新列,例如DT[ , names(DT) := lapply(.SD, as.factor)],它不会创建数据副本。有关更多信息,请参阅data.table's reference semantics vignette。

  • 您提到将lapply(.SD, sum) 的效率与colSums 的效率进行比较。 sum 在data.table 中进行了内部优化(您可以从在[] 中添加verbose = TRUE 参数的输出中注意到这是真的);要查看实际情况,让我们稍微加强一下您的 DT 并运行基准测试:

结果:

library(data.table)
set.seed(12039)
nn = 1e7; kk = seq(100L)
DT = setDT(replicate(26L, sample(kk, nn, TRUE), simplify=FALSE))
DT[ , LETTERS[1:2] := .(sample(100L, nn, TRUE), sample(100L, nn, TRUE))]

library(microbenchmark)
microbenchmark(
  times = 100L,
  colsums = colSums(DT[ , !c("A", "B")]),
  lapplys = DT[ , lapply(.SD, sum), .SDcols = !c("A", "B")]
)
# Unit: milliseconds
#     expr       min        lq      mean    median        uq       max neval
#  colsums 1624.2622 2020.9064 2028.9546 2034.3191 2049.9902 2140.8962   100
#  lapplys  246.5824  250.3753  252.9603  252.1586  254.8297  266.1771   100

【讨论】:

  • 带有set 的for 循环是转换大量列的另一种选择,在此答案的底部提到并由Arun 和Matt 建议/认可:stackoverflow.com/a/16846530/1191259
  • @Frank 是的,这就是我现在所做的,但我花了相当长的时间来理解那里发生的事情。但是很高兴能早点接触到。
  • @MichaelChirico,感谢您在不创建新 DT 的情况下更改列类型的提示!很有帮助。
猜你喜欢
  • 1970-01-01
  • 2020-03-12
  • 1970-01-01
  • 2016-08-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-27
  • 1970-01-01
相关资源
最近更新 更多