我正在使用很棒的 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'>],这样我所做的一切都被放在一对括号中。是否可以使用反引号等特殊符号来指示当前使用的名称不是引用数据表的实际列,而是实际列名称的占位符?
您可以结合使用()左侧的:=以及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)