【问题标题】:What is the most elegant way to apply a function to multiple pairs of columns in a data.table or data.frame?将函数应用于 data.table 或 data.frame 中的多对列的最优雅的方法是什么?
【发布时间】:2021-04-30 10:26:50
【问题描述】:

我经常需要对宽格式的 data.table 或 data.frame 中的一对列应用一些函数或操作。例如,计算患者治疗前后体重的差异。

通常,存在多对列,需要应用相同的操作。例如,计算患者在治疗前后的体重、bmi、血压、白细胞计数等之间的差异。

在 R 中最简洁的方法是什么,尤其是在使用 data.table 包时?我发现以下解决方案可行,但是当变量名称不遵循完美模式时,它们会在现实世界中产生开销。

考虑以下最小的工作示例。目标是计算 a.1 和 a.2、b.1 和 b.2、c.1 和 c.2 的差异,并将它们命名为 a.3、b.3、c.3。我特别不喜欢在最后“手动”重命名列。

library(data.table)

prefixes <- c("a", "b", "c")

one.cols <- paste0(prefixes, ".1")
two.cols <- paste0(prefixes, ".2")
result.cols <- paste0(prefixes, ".3")

# Data usually read from file
DT <- data.table(id = LETTERS[1:5],
                 a.1 = 1:5,
                 b.1 = 11:15,
                 c.1 = 21:25,
                 a.2 = 6:10,
                 b.2 = 16:20,
                 c.2 = 26:30)

DT.res <- cbind(DT[,.(id)], 
      result = DT[,..one.cols] - DT[,..two.cols] 
      )

old <- grep(pattern = "result.*", x = colnames(DT.res), value = T)

setnames(DT.res, old = old, new = result.cols)

DT <- DT[DT.res, on = "id"]

# Gives desired result:
print(DT)
#    id a.1 b.1 c.1 a.2 b.2 c.2 a.3 b.3 c.3
# 1:  A   1  11  21   6  16  26  -5  -5  -5
# 2:  B   2  12  22   7  17  27  -5  -5  -5
# 3:  C   3  13  23   8  18  28  -5  -5  -5
# 4:  D   4  14  24   9  19  29  -5  -5  -5
# 5:  E   5  15  25  10  20  30  -5  -5  -5

DT <- data.table(id = LETTERS[1:5],
                 a.1 = 1:5,
                 b.1 = 11:15,
                 c.1 = 21:25,
                 a.2 = 6:10,
                 b.2 = 16:20,
                 c.2 = 26:30)

DT.reshaped <- reshape(DT, direction = "long",
        varying = mapply(FUN = "c", one.cols, two.cols, SIMPLIFY = F)
)

DT.reshaped <- 
  DT.reshaped[, lapply(.SD, 
                       function(x){ x[1] - x[2] }), 
              keyby = .(id), .SDcols = one.cols]

setnames(DT.reshaped, old = one.cols, new = result.cols)

DT <- DT[DT.reshaped, on = "id"]

# Gives desired result, too:    
print(DT)

 

为了得到相同的结果,我更愿意写如下内容:

DT[, (result.cols) := ..one.cols - ..two.cols]

有没有办法做这样的事情?

【问题讨论】:

    标签: r data.table


    【解决方案1】:

    1) gv 在折叠包中使用 gv 我们可以这样做:

    library(collapse)
    
    DT[, (result.cols) := gv(.SD, one.cols) - gv(.SD, two.cols)]
    

    2) gvr 我们可以交替使用 gv 的正则表达式变体来消除 one.cols 和 two.cols:

    library(collapse)
    
    result.cols <- sub(1, 3, gvr(DT, "1$", "names"))
    DT[, (result.cols) := gvr(.SD, "1$") - gvr(.SD, "2$")]
    

    3)cross使用dplyr,我们也可以使用cross 消除result.cols。

    library(dplyr)
    
    DT %>%
      mutate(across(ends_with("1"), .names="{sub(1,3,.col)}") - across(ends_with("2")))
    

    4) data.table 如果我们这样写,在 data.table 中就很简单了:

    DT[, result.cols] <- DT[, ..one.cols] - DT[, ..two.cols]
    

    DT[, (result.cols) := .SD[, one.cols, with=FALSE] - .SD[, two.cols, with=FALSE]]
    

    【讨论】:

    • 这个collapse 库似乎很有趣。 :)
    • 我接受了这个答案,因为提出的解决方案是最不冗长的(不考虑对 library() 的调用 :-) )并且它们最接近我的想法。我发现有趣的是,当使用 tracemem 检查时,解决方案 4 的两个版本的行为似乎完全不同:版本 1 产生了副本,而版本 2 没有。
    【解决方案2】:

    您可以使用mgetMap 来执行此操作:

    DT[, (result.cols) := Map(`-`, mget(one.cols), mget(two.cols))]
    
    DT
    #    id a.1 b.1 c.1 a.2 b.2 c.2 a.3 b.3 c.3
    # 1:  A   1  11  21   6  16  26  -5  -5  -5
    # 2:  B   2  12  22   7  17  27  -5  -5  -5
    # 3:  C   3  13  23   8  18  28  -5  -5  -5
    # 4:  D   4  14  24   9  19  29  -5  -5  -5
    # 5:  E   5  15  25  10  20  30  -5  -5  -5
    

    但一般而言,您可能需要考虑将数据保留为长格式以进行此类计算,并为时间(处理前/处理后)创建一个单独的列。

    【讨论】:

    • 我很难在这个答案和我最终标记的答案之间做出决定。我真的很喜欢它的优雅和灵活性,我将来肯定会使用它。谢谢!
    【解决方案3】:

    另一种(高度灵活)的方法是根据名称的特征拆分 DT data.table,然后对结果列表的元素执行减法

    L <- split.default(DT, gsub( ".*\\.([0-9])", "\\1", names(DT) ) )
    DT[, (result.cols) := L$`2` - L$`1`]
    #    id a.1 b.1 c.1 a.2 b.2 c.2 a.3 b.3 c.3
    # 1:  A   1  11  21   6  16  26   5   5   5
    # 2:  B   2  12  22   7  17  27   5   5   5
    # 3:  C   3  13  23   8  18  28   5   5   5
    # 4:  D   4  14  24   9  19  29   5   5   5
    # 5:  E   5  15  25  10  20  30   5   5   5
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-03-14
      • 1970-01-01
      • 1970-01-01
      • 2012-07-09
      • 2012-08-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多