首页 > 解决方案 > 将函数应用于 data.table 或 data.frame 中的多对列的最优雅的方法是什么?

问题描述

我经常需要对宽格式的 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]

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

标签: rdata.table

解决方案


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

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

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


推荐阅读