提问人:jeanlain 提问时间:6/9/2016 最后编辑:zx8754jeanlain 更新时间:11/3/2017 访问量:17059
优雅地更新多个 data.table 列 [复制]
Update multiple data.table columns elegantly [duplicate]
问:
我正在尝试做一件简单的事情,将 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
答:
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
好吧,它似乎比我所做的更优雅,效率应该还可以,因为没有那么多列。我需要对基于 的解决方案进行基准测试。在矩阵上,调用以计算每行/每列的平均值明显慢于 或 。colMeans
apply()
colMeans()
rowMeans
4赞
Roland
6/10/2016
colMeans
复制整个 Data.Table 并将其转换为矩阵。对于相对较大的数据,您希望避免此类副本。此外,如果你有很多列,你可能有一个“宽格式”表,应该考虑它是否不应该是“长格式”。Data.table 使用少列和多行比使用多列和少行更有效。
5赞
akrun
6/9/2016
#2
我们还可以使用 .在这种情况下,与 一起使用应该没有明显的区别,但在必须多次调用的情况下,using 有助于避免这种开销,并且可能会明显更快。set
[.data.table
:=
[.data.table
set()
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
怎么样,一点魔法。这会将数据转换为“长”格式,然后转换回原始的“宽”格式。melt
dcast
首先,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 的开发版本对谓词返回的列进行操作dplyr
mutate_if
TRUE
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 基准测试(稍后减去)。否则,从第二次开始,您将计算 的平均值 ,而不是 。dtplyr
copy()
copy()
double
integer
0赞
talat
6/9/2016
@Arun,感谢您提供的信息,我想知道microbnechmark是否自己制作了原始数据的一些内部副本,或者用户是否必须这样做 - 很高兴知道。这也意味着初始运行是在整数值上,后来在数字/双精度值上运行,对吧?
0赞
Arun
6/9/2016
@docendodiscimus没错。
评论