如何在函数中正确使用 data.tables 的环境变量
How do I correctly use the env variable for data.tables within a function
举个简单的例子
data <- data.table::data.table(a = 1:10, b = 2:11)
j <- quote(c("c") := list(a + 1))
data[, j, env = list(j = j)][]
# a b c
# <int> <int> <num>
# 1: 1 2 2
# 2: 2 3 3
# 3: 3 4 4
# 4: 4 5 5
# 5: 5 6 6
上面的工作并产生了正确的输出。但是,如果我将它放在一个函数中,我会得到一个非常不同的输出。
data <- data.table::data.table(a = 1:5, b = 2:6)
test <- function(data, ...) {
dots <- eval(substitute(alist(...)))
j <- call(":=", call("c", names(dots)), unname(dots))
print(j)
data[, j, env = list(j = j)][]
}
test(data = data, c = a + 1)
# `:=`(c("c"), list(a + 1))
# a b c
# <int> <int> <list>
# 1: 1 2 <call[3]>
# 2: 2 3 <call[3]>
# 3: 3 4 <call[3]>
# 4: 4 5 <call[3]>
# 5: 5 6 <call[3]>
我 假设 c = a + 1
只是没有在正确的环境(即 data.table
本身)中进行评估。
编辑:我正在使用 data.table 1.14.3
两者的区别
j_quote <- quote(c("c") := list(a + 1))
和
j_call <- call(":=", call("c", names(dots)), unname(dots))
非常微妙:
j_call
#`:=`(c("c"), list(a + 1))
j_quote
#`:=`(c("c"), list(a + 1))
all.equal(j_call,j_quote)
[1] TRUE
identical(j_call,j_quote)
[1] FALSE
区别在于列表结构:
# j_quote
as.list(j_quote)
[[1]]
`:=`
[[2]]
c("c")
[[3]]
list(a + 1)
# j_call
as.list(j_call)
[[1]]
`:=`
[[2]]
c("c")
[[3]]
[[3]][[1]]
a + 1
尝试:
library(data.table)
data <- data.table::data.table(a = 1:5, b = 2:6)
test <- function(data, ...) {
dots <- eval(substitute(alist(...)))
j_call <- call(":=", call("c", names(dots)), call("list",unname(dots)[[1]]))
j_quote <- quote(c("c") := list(a + 1))
cat("j_quote",deparse(j_quote),"\n")
cat("j_call ",deparse(j_call),"\n")
cat("identical:",identical(j_quote,j_call),"\n")
data[, j, env = list(j = j_call)][]
}
test(data = data, c = a + 1)
#> j_quote `:=`(c("c"), list(a + 1))
#> j_call `:=`(c("c"), list(a + 1))
#> identical: TRUE
#> a b c
#> <int> <int> <num>
#> 1: 1 2 2
#> 2: 2 3 3
#> 3: 3 4 4
#> 4: 4 5 5
#> 5: 5 6 6
因为dots
不是电话,而是电话列表。因此,当 data.table 评估 j
时,它试图将该列表插入到新列中。
要解决此问题,您需要将调用列表拼接到单个调用中。您可以直接调用 ':='()
来执行此操作(下面的选项 1),但您也可以将其分解为多个步骤,通过将 dots
转换为对 [= 的调用来反映您在上面所做的事情16=](选项 2)。
library(data.table)
data <- data.table::data.table(a = 1:5, b = 2:6)
# Option 1 - call to ':='
test <- function(data, ...) {
dots <- eval(substitute(alist(...)))
j <- bquote(':='(..(dots)), splice = TRUE)
print(j)
data[, j, env = list(j = j)][]
}
# # Option 2 - convert dots to a call to a list
# test <- function(data, ...) {
# dots <- eval(substitute(alist(...)))
# dots_names <- names(dots)
# dots <- bquote(list(..(unname(dots))), splice = TRUE)
# j <- call(":=", dots_names, dots)
# print(j)
# data[, j, env = list(j = j)][]
# }
test(data = data, c = a + 1, double_b = b * 2)
#> `:=`(c = a + 1, double_b = b * 2)
#> a b c double_b
#> <int> <int> <num> <num>
#> 1: 1 2 2 4
#> 2: 2 3 3 6
#> 3: 3 4 4 8
#> 4: 4 5 5 10
#> 5: 5 6 6 12
编辑:如果您希望能够编辑同一列或使用新创建的列,您也可以使用 test2()
。
test2 <- function(data, ...) {
dots <- eval(substitute(alist(...)))
dots_names <- names(dots)
for (i in seq_along(dots)) {
dot_name <- dots_names[[i]]
dot <- dots[[i]]
j <- call(":=", dot_name, dot)
print(j)
data[, j, env = list(j = j)]
}
data[]
}
举个简单的例子
data <- data.table::data.table(a = 1:10, b = 2:11)
j <- quote(c("c") := list(a + 1))
data[, j, env = list(j = j)][]
# a b c
# <int> <int> <num>
# 1: 1 2 2
# 2: 2 3 3
# 3: 3 4 4
# 4: 4 5 5
# 5: 5 6 6
上面的工作并产生了正确的输出。但是,如果我将它放在一个函数中,我会得到一个非常不同的输出。
data <- data.table::data.table(a = 1:5, b = 2:6)
test <- function(data, ...) {
dots <- eval(substitute(alist(...)))
j <- call(":=", call("c", names(dots)), unname(dots))
print(j)
data[, j, env = list(j = j)][]
}
test(data = data, c = a + 1)
# `:=`(c("c"), list(a + 1))
# a b c
# <int> <int> <list>
# 1: 1 2 <call[3]>
# 2: 2 3 <call[3]>
# 3: 3 4 <call[3]>
# 4: 4 5 <call[3]>
# 5: 5 6 <call[3]>
我 假设 c = a + 1
只是没有在正确的环境(即 data.table
本身)中进行评估。
编辑:我正在使用 data.table 1.14.3
两者的区别
j_quote <- quote(c("c") := list(a + 1))
和
j_call <- call(":=", call("c", names(dots)), unname(dots))
非常微妙:
j_call
#`:=`(c("c"), list(a + 1))
j_quote
#`:=`(c("c"), list(a + 1))
all.equal(j_call,j_quote)
[1] TRUE
identical(j_call,j_quote)
[1] FALSE
区别在于列表结构:
# j_quote
as.list(j_quote)
[[1]]
`:=`
[[2]]
c("c")
[[3]]
list(a + 1)
# j_call
as.list(j_call)
[[1]]
`:=`
[[2]]
c("c")
[[3]]
[[3]][[1]]
a + 1
尝试:
library(data.table)
data <- data.table::data.table(a = 1:5, b = 2:6)
test <- function(data, ...) {
dots <- eval(substitute(alist(...)))
j_call <- call(":=", call("c", names(dots)), call("list",unname(dots)[[1]]))
j_quote <- quote(c("c") := list(a + 1))
cat("j_quote",deparse(j_quote),"\n")
cat("j_call ",deparse(j_call),"\n")
cat("identical:",identical(j_quote,j_call),"\n")
data[, j, env = list(j = j_call)][]
}
test(data = data, c = a + 1)
#> j_quote `:=`(c("c"), list(a + 1))
#> j_call `:=`(c("c"), list(a + 1))
#> identical: TRUE
#> a b c
#> <int> <int> <num>
#> 1: 1 2 2
#> 2: 2 3 3
#> 3: 3 4 4
#> 4: 4 5 5
#> 5: 5 6 6
因为dots
不是电话,而是电话列表。因此,当 data.table 评估 j
时,它试图将该列表插入到新列中。
要解决此问题,您需要将调用列表拼接到单个调用中。您可以直接调用 ':='()
来执行此操作(下面的选项 1),但您也可以将其分解为多个步骤,通过将 dots
转换为对 [= 的调用来反映您在上面所做的事情16=](选项 2)。
library(data.table)
data <- data.table::data.table(a = 1:5, b = 2:6)
# Option 1 - call to ':='
test <- function(data, ...) {
dots <- eval(substitute(alist(...)))
j <- bquote(':='(..(dots)), splice = TRUE)
print(j)
data[, j, env = list(j = j)][]
}
# # Option 2 - convert dots to a call to a list
# test <- function(data, ...) {
# dots <- eval(substitute(alist(...)))
# dots_names <- names(dots)
# dots <- bquote(list(..(unname(dots))), splice = TRUE)
# j <- call(":=", dots_names, dots)
# print(j)
# data[, j, env = list(j = j)][]
# }
test(data = data, c = a + 1, double_b = b * 2)
#> `:=`(c = a + 1, double_b = b * 2)
#> a b c double_b
#> <int> <int> <num> <num>
#> 1: 1 2 2 4
#> 2: 2 3 3 6
#> 3: 3 4 4 8
#> 4: 4 5 5 10
#> 5: 5 6 6 12
编辑:如果您希望能够编辑同一列或使用新创建的列,您也可以使用 test2()
。
test2 <- function(data, ...) {
dots <- eval(substitute(alist(...)))
dots_names <- names(dots)
for (i in seq_along(dots)) {
dot_name <- dots_names[[i]]
dot <- dots[[i]]
j <- call(":=", dot_name, dot)
print(j)
data[, j, env = list(j = j)]
}
data[]
}