我正在使用精彩的R data.table包。但是,访问(即通过引用操作)具有变量名称的列是非常笨拙的:如果给出一个data.table dt
,它有两列x和y,我们想要添加两列并将其命名为z然后命令是
dt = dt[, z := x + y]
现在让我们编写一个函数add
,它将参数a(引用a)data.table dt
和三个列名summand1Name
,summand2Name
和{{并且只使用通用列名来执行与上面完全相同的命令。我现在使用的解决方案是反思,即
resultName
但是我绝对不满意这个解决方案。首先,它很笨拙,这样的代码并不好玩。它很难调试,只是让我生气,烧掉时间。其次,阅读和理解起来比较困难。这是我的问题:
我们能以更好的方式编写此函数吗?
我知道可以访问具有变量名称的列,如下所示:add = function(dt, summand1Name, summand2Name, resultName) {
cmd = paste0('dt = dt[, ', resultName, ' := ', summand1Name, ' + ', summand2Name, ']')
eval(parse(text=cmd))
return(dt) # optional since manipulated by reference
}
但是当我写
dt[[resultName]]
然后data.table开始抱怨已经复制而不是通过引用工作。我不希望这样。我也喜欢语法dt[[resultName]] = dt[[summand1Name]] + dt[[summand2Name]]
,所以我所做的一切都在一对括号中。为了表明当前使用的名称不是引用数据表的实际列而是实际列名称的占位符,是不是可以使用反引号之类的特殊符号?
答案 0 :(得分:1)
您可以结合使用class='XI' and stream='Medical' and (section='A' or section='all')
的L ()
以及:=
来引用RHS上的变量。
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
<强> EDIT2:强>
有一百万行,结果如下所示。正如预期的那样,原始方法在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
完成后表现良好,这将很快发挥作用。
eval
答案 1 :(得分:1)
new_add <- function(dt, summand1Name, summand2Name, resultName) {
dt[, (resultName) := rowSums(.SD), .SDcols = c(summand1Name, summand2Name)]
}
这只是将列名作为字符串。 把这个加入amatsuo_net的速度测试中,同时加入sindri的两个版本,我们得到如下:
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'),
new_add(dt, 'a', 'b', 'c'),
get_add(dt, 'a', 'b', 'c'),
mget_add(dt, 'a', 'b', 'c'))
## Unit: microseconds
## expr min lq mean median uq max neval
## original_add(dt, "a", "b", "c") 433.3 491.00 635.315 531.4 600.00 6064.0 100
## my_add(dt, "a", "b", "c") 978.0 1062.35 1310.808 1208.8 1357.80 4157.3 100
## list_access_add(dt, "a", "b", "c") 303.9 331.95 432.939 363.8 434.05 3361.6 100
## david_add(dt, "a", "b", "c") 401.3 440.65 659.748 474.5 577.75 11623.0 100
## new_add(dt, "a", "b", "c") 518.9 588.30 765.394 667.1 741.95 5636.5 100
## get_add(dt, "a", "b", "c") 415.1 454.50 674.699 491.1 546.70 9804.3 100
## mget_add(dt, "a", "b", "c") 425.4 474.65 596.165 533.2 590.75 3888.0 100
它不是所有版本中最快的,但如果您正在寻找编写轻松的代码,那么这非常简单。由于它适用于 rowSums
,因此还可以更轻松地将其推广为一次对任意数量的列求和。
此外,由于方括号内未提及 dt
,因此您可以将此列定义添加到 data.table“管道”中,而不是作为函数添加,如果您愿意:
dt[, (resultName) := rowSums(.SD), .SDcols = c(summand1Name, summand2Name)
][, lapply(.SD, range), .SDcols = c(summand1Name, summand2Name, resultName)
][... # etc
]
答案 2 :(得分:1)
使用 get()
:
add <- function(dt, summand1Name, summand2Name, resultName) {
dt[, (resultName) := get(summand1Name) + get(summand1Name)]
}
使用 mget()
:
add2 <- function(dt, summand1Name, summand2Name, resultName) {
dt[, (resultName) := do.call(`+`, mget(c(summand1Name,summand2Name)))]
}
# Let
dt <- data.table(a = 1:5, b = 10:14)
# Then
add(dt, 'x', 'y', 'z')
dt[]
# x y z
# 1: 1 2 2
答案 3 :(得分:0)
这是使用substitute
的另一种解决方案。我通常会尽量避免使用substitute
,但我认为这是使用快速data.table
和:=
代码而不是本机列表访问的唯一方法。
我留在了amatsuo_net的界面。
set.seed(1)
dt <- data.table(a = rnorm(10000), b = rnorm(10000))
snaut_add <- function(dt, summand1, summand2, resultName){
eval(substitute(
dt[, z := x + y],
list(
z=as.symbol(resultName),
x=as.symbol(summand1),
y=as.symbol(summand2)
)
))
}
snaut_add(dt, "a", "b", "c")
dt