【问题标题】:How to do row wise operations on .SD columns in data.table如何对 data.table 中的 .SD 列进行逐行操作
【发布时间】:2015-10-26 18:28:06
【问题描述】:

虽然我之前已经弄清楚了,但我仍然发现自己在 stackoverflow 上搜索(并且无法找到)这种语法,所以...

我想使用.SD.SDcols 对data.table 的列的子集执行逐行操作。我永远不记得操作是否需要sapplylapply,或者是否属于.SD 的括号内。

例如,假设您有两个季度内 10 名学生的数据。在两个季度,他们都有两次考试和一次期末考试。您如何对从 q1 开始的列进行直接平均?

由于过于琐碎的例子很烦人,我还想计算以 q2 开头的列的加权平均值? (权重 = 25% 25% 和 50% 对于 q2)

library(data.table)

set.seed(10)
dt <- data.table(id = paste0("student_", sprintf("%02.f" , 1:10)),
                 q1_exam1 = round(rnorm(10, .78, .05), 2),
                 q1_exam2 = round(rnorm(10, .68, .02), 2),
                 q1_final = round(rnorm(10, .88, .08), 2),
                 q2_exam1 = round(rnorm(10, .78, .05), 2),
                 q2_exam2 = round(rnorm(10, .68, .10), 2),
                 q2_final = round(rnorm(10, .88, .04), 2))

dt
# > dt
#             id q1_exam1 q1_exam2 q1_final q2_exam1 q2_exam2 q2_final
#  1: student_01     0.78     0.70     0.83     0.69     0.79     0.86
#  2: student_02     0.77     0.70     0.71     0.78     0.60     0.87
#  3: student_03     0.71     0.68     0.83     0.83     0.60     0.93
#  4: student_04     0.75     0.70     0.71     0.79     0.76     0.97
#  5: student_05     0.79     0.69     0.78     0.71     0.58     0.90
#  6: student_06     0.80     0.68     0.85     0.71     0.68     0.91
#  7: student_07     0.72     0.66     0.82     0.80     0.70     0.84
#  8: student_08     0.76     0.68     0.81     0.69     0.65     0.90
#  9: student_09     0.70     0.70     0.87     0.76     0.61     0.85
# 10: student_10     0.77     0.69     0.86     0.75     0.75     0.89

【问题讨论】:

  • 如果您想在data.table 中进行逐行操作,您可以使用.I 创建一个索引变量并在by = 部分中使用它。
  • 如果您关心效率,eddi 在这里对我的问题的回答非常好:stackoverflow.com/a/19279500/1191259(而且,我认为是相关的)
  • 我猜您可以通过将数据作为“data.frame”并使用 ["id" "quarter" "exam"] 等列并使用数据库操作或通过“矩阵”的“列表”(取决于方便的因素)并使用更多的“算术”运算。
  • @Jaap 你的意思是像这样使用.Istackoverflow.com/questions/16573995/…? (我最初并没有意识到这个例子可能是相关的,感谢 Frank 在 github 上向我展示了这个)
  • 使用by = 1:nrow(dt)。或者根据操作,您可能可以在j 中使用Reduce()

标签: r data.table


【解决方案1】:

以下是关于您的选择的一些想法,主要来自 cmets:

apply沿行

OP 的方法使用apply(.,1,.) 进行逐行操作,但不鼓励这样做,因为它不必要地将 data.table 强制转换为矩阵。 lapply/sapply 也不合适,因为它们被设计为单独处理每一列,而不是组合它们。

rowMeans 和类似名称的函数也强制转换为矩阵。

按行拆分

正如@Jaap 所说,您可以将by=1:nrow(dt) 用于任何按行操作,但它可能会很慢。

高效地创建新列

如果您必须以宽格式保存数据,This approach taken from eddi 可能是最有效的:

jwts = list( 
  q1_AVG  = c(q1_exam1 = 1  , q1_exam2 = 1  , q1_final =   1)/3, 
  q2_WAVG = c(q1_exam1 = 1/4, q2_exam2 = 1/4, q2_final = 1/2)
)


for (newj in names(jwts)){
  w = jwts[[newj]]
  dt[, (newj) := Reduce("+", lapply(names(w), function(x) dt[[x]] * w[x]))]
}

这避免了对矩阵的强制转换,并允许使用不同的加权规则(与 rowMeans 不同)。

做多

正如@alexis_laz 建议的那样,您可能会通过不同的结构获得清晰和效率,例如

# reshape
m = melt(dt, id.vars="id", value.name="score")[,
  c("quarter","exam") := tstrsplit(variable, "_")][, variable := NULL]

# input your weighting rules
w = unique(m[,c("quarter","exam")])
w[quarter=="q1"                , wt := 1/.N]
w[quarter=="q2" & exam=="final", wt := .5]
w[quarter=="q2" & exam!="final", wt := (1-.5)/.N]

# merge and compute
m[w, on=c("quarter","exam")][, sum(score*wt), by=.(id,quarter)]

这就是我会做的。


无论如何,如果您想增加季度的数量,您应该将您的加权规则明确存储在某处,而不是即时输入。

【讨论】:

  • “做多”中的逻辑很有意义(并且对我的工作更有效)。冒着听起来基于意见的风险,我会说这似乎是思考它的“正确”方式。事实上,这听起来像是解决问题的“正确”方式。
  • 另外,这是一个很好的例子来展示新的tstrspliton功能的价值。
【解决方案2】:

在这种情况下,可以在基本 R 中使用 apply 函数,但这并没有利用 data.table 框架。此外,它没有一概而论,因为有些情况需要更多的条件检查。

apply(dt[ , .SD, .SDcols = grep("^q1", colnames(dt))], 1, mean)

# > apply(dt[ , .SD, .SDcols = grep("^q1", colnames(dt))], 1, mean)
#  [1] 0.7700000 0.7266667 0.7400000 0.7200000 0.7533333 0.7766667 0.7333333 0.7500000 0.7566667 0.7733333

在这种情况下,同样可以将 apply 放入 data.table 的 j 参数中,并在 .SD 列上使用它:

dt[i = TRUE,
   q1_AVG := round(apply(.SD, 1, mean), 2), 
   .SDcols = grep("^q1", colnames(dt))]
dt
# > dt
#             id q1_exam1 q1_exam2 q1_final q2_exam1 q2_exam2 q2_final q1_AVG
#  1: student_01     0.78     0.70     0.83     0.69     0.79     0.86   0.77
#  2: student_02     0.77     0.70     0.71     0.78     0.60     0.87   0.73
#  3: student_03     0.71     0.68     0.83     0.83     0.60     0.93   0.74
#  4: student_04     0.75     0.70     0.71     0.79     0.76     0.97   0.72
#  5: student_05     0.79     0.69     0.78     0.71     0.58     0.90   0.75
#  6: student_06     0.80     0.68     0.85     0.71     0.68     0.91   0.78
#  7: student_07     0.72     0.66     0.82     0.80     0.70     0.84   0.73
#  8: student_08     0.76     0.68     0.81     0.69     0.65     0.90   0.75
#  9: student_09     0.70     0.70     0.87     0.76     0.61     0.85   0.76
# 10: student_10     0.77     0.69     0.86     0.75     0.75     0.89   0.77

加权平均的情况可以用矩阵乘法计算;

dt[i = TRUE,
   q2_WAVG := round(as.matrix(.SD) %*% c(.25, .25, .50), 2), 
   .SDcols = grep("^q2", colnames(dt))]
dt
# > dt
#             id q1_exam1 q1_exam2 q1_final q2_exam1 q2_exam2 q2_final q1_AVG q2_WAVG
#  1: student_01     0.78     0.70     0.83     0.69     0.79     0.86   0.77    0.80
#  2: student_02     0.77     0.70     0.71     0.78     0.60     0.87   0.73    0.78
#  3: student_03     0.71     0.68     0.83     0.83     0.60     0.93   0.74    0.82
#  4: student_04     0.75     0.70     0.71     0.79     0.76     0.97   0.72    0.87
#  5: student_05     0.79     0.69     0.78     0.71     0.58     0.90   0.75    0.77
#  6: student_06     0.80     0.68     0.85     0.71     0.68     0.91   0.78    0.80
#  7: student_07     0.72     0.66     0.82     0.80     0.70     0.84   0.73    0.80
#  8: student_08     0.76     0.68     0.81     0.69     0.65     0.90   0.75    0.78
#  9: student_09     0.70     0.70     0.87     0.76     0.61     0.85   0.76    0.77
# 10: student_10     0.77     0.69     0.86     0.75     0.75     0.89   0.77    0.82

【讨论】:

  • 我认为如果您在matrix 上使用rowMeans,然后在该矩阵上使用%*%,会很有效。在您显示的代码中,使用了 MARGIN=1 的 apply,这并不是那么快。此外,通过将.SD 转换为matrix,它没有使用data.table 技术。
猜你喜欢
  • 2011-12-14
  • 1970-01-01
  • 1970-01-01
  • 2014-02-12
  • 1970-01-01
  • 2020-07-18
  • 1970-01-01
  • 1970-01-01
  • 2019-11-22
相关资源
最近更新 更多