R data.table:使用变量名访问列

Fab*_*ner 5 r data.table

我正在使用很棒的 R data.table 包。然而,访问(即通过引用操作)具有变量名的列非常笨拙:如果给定一个dt具有两列 x 和 y 的 data.table,并且我们想要添加两列并将其命名为 z,那么命令是

dt = dt[, z := x + y]
Run Code Online (Sandbox Code Playgroud)

现在让我们编写一个函数,它以 data.table和三个列名add作为参数,并且假设仅使用通用列名执行与上面完全相同的命令。我现在使用的解决方案是反射,即dtsummand1Namesummand2NameresultName

add = function(dt, summand1Name, summand2Name, resultName) {
  cmd = paste0('dt = dt[, ', resultName, ' := ', summand1Name, ' + ', summand2Name, ']')
  eval(parse(text=cmd))
  return(dt) # optional since manipulated  by reference
}
Run Code Online (Sandbox Code Playgroud)

但是我对这个解决方案绝对不满意。首先,它很笨拙,这样的代码没有乐趣。它很难调试,而且只会让我生气并浪费时间。其次,它更难阅读和理解。这是我的问题:

我们可以用更好的方式编写这个函数吗?

我知道这样一个事实,即可以像这样访问具有变量名称的列:dt[[resultName]]但是当我写

dt[[resultName]] = dt[[summand1Name]] + dt[[summand2Name]]
Run Code Online (Sandbox Code Playgroud)

然后 data.table 开始抱怨已获取副本并且无法通过引用工作。我不想要这样。我也喜欢这种语法dt = dt[<all 'database related operations'>],这样我所做的一切都被放在一对括号中。是否可以使用反引号等特殊符号来指示当前使用的名称不是引用数据表的实际列,而是实际列名称的占位符?

ama*_*net 1

您可以结合使用()左侧的:=以及with = FALSE引用右侧的变量。

dt <- data.table(a = 1:5, b = 10:14)
my_add <- function(dt, summand1Name, summand2Name, resultName) {
  dt[, (resultName) := dt[, summand1Name, with = FALSE] + 
       dt[, summand1Name, with = FALSE]]
}
my_add(dt, 'a', 'b', 'c')
dt
Run Code Online (Sandbox Code Playgroud)

编辑:

比较了三个版本。我的效率最低...(但仅供参考)。

set.seed(1)
dt <- data.table(a = rnorm(10000), b = rnorm(10000))
original_add <- function(dt, summand1Name, summand2Name, resultName) {
  cmd = paste0('dt = dt[, ', resultName, ' := ', summand1Name, ' + ', summand2Name, ']')
  eval(parse(text=cmd))
  return(dt) # optional since manipulated  by reference
}
my_add <- function(dt, summand1Name, summand2Name, resultName) {
  dt[, (resultName) := dt[, summand1Name, with = FALSE] + 
       dt[, summand1Name, with = FALSE]]
}
list_access_add <- function(dt, summand1Name, summand2Name, resultName) {
  dt[, (resultName) := dt[[summand1Name]] + dt[[summand2Name]]]
}
david_add <- function(dt, summand1Name, summand2Name, resultName) {
  dt[, (resultName) := .SD[[summand1Name]] + .SD[[summand2Name]]]
}

microbenchmark::microbenchmark(
  original_add(dt, 'a', 'b', 'c'),
  my_add(dt, 'a', 'b', 'c'),
  list_access_add(dt, 'a', 'b', 'c'),
  david_add(dt, 'a', 'b', 'c'))

## Unit: microseconds
##                                expr      min        lq      mean    median        uq      max
##     original_add(dt, "a", "b", "c")  604.397  659.6395  784.2206  713.0315  776.1295 5070.541
##           my_add(dt, "a", "b", "c") 1063.984 1168.6140 1460.5329 1247.7990 1486.9730 6134.959
##  list_access_add(dt, "a", "b", "c")  272.822  310.9680  422.6424  334.3110  380.6885 3620.463
##        david_add(dt, "a", "b", "c")  389.389  431.9080  542.7955  454.5335  493.4895 3696.992
##  neval
##    100
##    100
##    100
##    100
Run Code Online (Sandbox Code Playgroud)

编辑2:

对于一百万行,结果如下所示。正如预期的那样,原始方法表现良好,一旦eval完成,工作速度就会很快。

## Unit: milliseconds
##                                expr       min        lq      mean    median        uq      max
##     original_add(dt, "a", "b", "c")  2.493553  3.499039  6.585651  3.607101  4.390051 114.0612
##           my_add(dt, "a", "b", "c") 11.821820 14.512878 28.387841 17.412433 19.642231 117.6359
##  list_access_add(dt, "a", "b", "c")  2.161276  3.133110  6.874885  3.218185  3.407776 107.6853
##        david_add(dt, "a", "b", "c")  2.237089  3.313133  6.047832  3.381757  3.788558 103.7532
##  neval
##    100
##    100
##    100
##    100
Run Code Online (Sandbox Code Playgroud)