优雅地更新多个 data.table 列 [复制]

Update multiple data.table columns elegantly [duplicate]

提问人:jeanlain 提问时间:6/9/2016 最后编辑:zx8754jeanlain 更新时间:11/3/2017 访问量:17059

问:

我正在尝试做一件简单的事情,将 data.table 的 40 列除以它们的平均值。我无法提供实际数据(并非所有列都是数字,我有 8M 行>),但这里有一个示例:

library(data.table)   

dt <- data.table(matrix(sample(1:100,4000,T),ncol=40))
colmeans <- colMeans(dt)

接下来我想我会做:

for (col in names(colmeans)) dt[,col:=dt[,col]/colmeans[col]]   

但这会返回一个错误,因为要求列名不加引号。使用并不能削减它。 现在dt[,col]as.name(col)

res <- t(t(dt[,1:40,with=F]/colmeans))

包含加速的结果,但我无法将其插入回 data.table 中,因为

dt[,1:40] <- res

不起作用,也不起作用.dt[,1:40:=res, with=F]

以下作品,但我觉得它很丑:

for (i in seq_along(colmeans)) dt[,i:=dt[,i,with=F]/colmeans[i],with=F]

当然,我也可以通过调用我的 data.table 的其他非数字列来重新创建一个新的 data.table,但它们不是更有效吗?data.table()res

r 数据表

评论


答:

44赞 talat 6/9/2016 #1

怎么样

dt[, (names(dt)) := lapply(.SD, function(x) x/mean(x))]

如果需要指定某些列,可以使用

dt[, 1:40 := lapply(.SD, function(x) x/mean(x)), .SDcols = 1:40]

cols <- names(dt)[c(1,5,10)]
dt[, (cols) := lapply(.SD, function(x) x/mean(x)), .SDcols = cols]

评论

0赞 jeanlain 6/9/2016
好的,但是如果有很多列,使用 lapply 会比 colMeans 慢得多。
4赞 Roland 6/9/2016
@jeanlain我不这么认为。
0赞 Ken Benoit 6/9/2016
非常优雅的解决方案!
0赞 jeanlain 6/9/2016
好吧,它似乎比我所做的更优雅,效率应该还可以,因为没有那么多列。我需要对基于 的解决方案进行基准测试。在矩阵上,调用以计算每行/每列的平均值明显慢于 或 。colMeansapply()colMeans()rowMeans
4赞 Roland 6/10/2016
colMeans复制整个 Data.Table 并将其转换为矩阵。对于相对较大的数据,您希望避免此类副本。此外,如果你有很多列,你可能有一个“宽格式”表,应该考虑它是否不应该是“长格式”。Data.table 使用少列和多行比使用多列和少行更有效。
5赞 akrun 6/9/2016 #2

我们还可以使用 .在这种情况下,与 一起使用应该没有明显的区别,但在必须多次调用的情况下,using 有助于避免这种开销,并且可能会明显更快。set[.data.table:=[.data.tableset()

for(j in names(dt)) {
 set(dt, i=NULL, j = j, value = dt[[j]]/mean(dt[[j]]))
}

它也可以在选定的列上完成,即

nm1 <- names(dt)[1:5]
for(j in nm1){
 set(dt, i = NULL, j = j, value = dt[[j]]/mean(dt[[j]]))
}

数据

set.seed(24)
dt <- as.data.frame(matrix(sample(1:100,4000,TRUE),ncol=40))
setDT(dt)

评论

1赞 Arun 6/9/2016
[.data.table此处仅调用一次。就像我在另一条评论中提到的那样。
2赞 SymbolixAU 6/9/2016
该结构是一个非常好的解决方案,并且避免了讨厌的分配for( ) set( ).SD
0赞 Arun 6/9/2016
@SymbolixAU,好点子,添加了#1735
1赞 Ken Benoit 6/9/2016 #3

怎么样,一点魔法。这会将数据转换为“长”格式,然后转换回原始的“宽”格式。meltdcast

首先,ID 上的变量:melt

# make an ID variable
dt[, idvar := 1:nrow(dt)]
# melt the data on the ID variable
dt2 <- melt(dt, "idvar")

然后对每组进行平均运算除法:

# use data.table by = to do a fast division by group mean
dt2[, divByMean := value / mean(value), by = variable]
dt2
## idvar variable value divByMean
## 1:     1       V1    15 0.2859867
## 2:     2       V1    92 1.7540515
## 3:     3       V1    27 0.5147760
## 4:     4       V1     7 0.1334604
## 5:     5       V1    18 0.3431840
## ---                               
## 3996:    96      V40    54 1.1111111
## 3997:    97      V40    51 1.0493827
## 3998:    98      V40    23 0.4732510
## 3999:    99      V40     8 0.1646091
## 4000:   100      V40    11 0.2263374

然后回到原来的宽格式:

# now dcast back to "wide"
dt3 <- dcast(dt2, idvar ~ variable, mean, value.var = "divByMean")
dt3[1:5, 1:5]
##   idvar        V1        V2        V3        V4
## 1     1 0.2859867 0.6913303 0.2110919 1.6156624
## 2     2 1.7540515 0.7847534 0.5948954 1.8817715
## 3     3 0.5147760 0.2615845 0.8827480 0.4181715
## 5     5 0.3431840 0.3550075 0.3646133 0.3231325
## 4     4 0.1334604 1.7937220 1.3241220 1.3685611
3赞 Steven Beaupré 6/9/2016 #4

dplyr 0.4.3

若要将所有列除以均值,可以执行以下操作:

dplyr::mutate_each(dt, funs(. / mean(.)))

或者指定列位置:

dplyr::mutate_each(dt, funs(. / mean(.)), 5:10)

或列名:

dplyr::mutate_each_(dt, funs(. / mean(.)), colnames(dt)[5:10])

dplyr 0.4.3.9000

如果只想划分数值列,则 has 的开发版本对谓词返回的列进行操作dplyrmutate_ifTRUE

dplyr::mutate_if(dt, is.numeric, funs(. / mean(.)))

评论

1赞 akrun 6/9/2016
这令人印象深刻。dplyr
0赞 talat 6/9/2016
您可能需要在 dplyr 调用中重新分配输出才能获得相同的结果
0赞 Arun 6/9/2016
如果您使用的是开发版本,则必须加载才能运行 data.table 等效方法。否则,这将在 data.frame 等效的上运行。此外,在运行 data.table 函数之前,您需要一个 before 才能获得相同的输出,并额外进行 alone 基准测试(稍后减去)。否则,从第二次开始,您将计算 的平均值 ,而不是 。dtplyrcopy()copy()doubleinteger
0赞 talat 6/9/2016
@Arun,感谢您提供的信息,我想知道microbnechmark是否自己制作了原始数据的一些内部副本,或者用户是否必须这样做 - 很高兴知道。这也意味着初始运行是在整数值上,后来在数字/双精度值上运行,对吧?
0赞 Arun 6/9/2016
@docendodiscimus没错。