【问题标题】:Update multiple data.table columns elegantly [duplicate]优雅地更新多个 data.table 列[重复]
【发布时间】:2016-10-09 19:16:09
【问题描述】:

我正在尝试做一件简单的事情,将 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]

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

【问题讨论】:

    标签: r data.table


    【解决方案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]
    

    【讨论】:

    • 好的,但是如果有很多列,使用 lapply 会比 colMeans 慢很多。
    • @jeanlain 我不这么认为。
    • 非常优雅的解决方案!
    • 好吧,它看起来比我做的更优雅,效率应该还可以,因为没有那么多列。我需要对基于colMeans 的解决方案进行基准测试。在矩阵上,调用apply() 计算每行/列的平均值明显慢于colMeans()rowMeans
    • colMeans 复制整个 data.table 并将其转换为矩阵。对于相对较大的数据,您希望避免此类副本。此外,如果你有很多列,你可能有一个“宽格式”表,应该考虑它是否不应该是“长格式”。与多列少行相比,少列多行的 data.table 效率更高。
    【解决方案2】:

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

    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)
    

    【讨论】:

    • [.data.table 在这里只调用一次。就像我在另一条评论中提到的那样。
    • for( ) set( ) 构造是一个非常好的解决方案,并且避免了讨厌的.SD allocaiton
    • @SymbolixAU,好点,添加了#1735
    【解决方案3】:

    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

    如果您只想划分数字列,dplyr 的开发版本具有 mutate_if,它对谓词返回 TRUE 的列进行操作

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

    【讨论】:

    • dplyr 令人印象深刻。
    • 您可能需要在 dplyr 调用中重新分配输出以获得相同的结果
    • 如果您使用的是开发版本,则必须加载 dtplyr 才能运行 data.table 等效方法。否则,这是在 data.frame 等效的上运行的。此外,在运行 data.table 函数之前,您需要copy() 以获得相同的输出,并单独对copy() 进行额外的基准测试(稍后减去)。否则,从第二次开始,您将计算 double 的平均值,而不是 integer
    • @Arun,感谢您提供的信息,我想知道 microbnechmark 是否会自行制作原始数据的一些内部副本,或者用户是否必须这样做 - 很高兴知道。这也意味着初始运行在整数值上,然后在数字/双精度上运行,对吧?
    • @docendodiscimus 完全正确。
    【解决方案4】:

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

    首先,melt ID 上的变量:

    # 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
    

    【讨论】:

      猜你喜欢
      • 2013-06-01
      • 2015-12-05
      • 2019-07-22
      • 2021-06-25
      • 1970-01-01
      • 2015-05-04
      • 1970-01-01
      • 2010-11-27
      • 1970-01-01
      相关资源
      最近更新 更多