在data.table中将变量名作为参数传递

13

我试图创建一个修改data.table的函数,并希望使用一些非标准的评估方法,但我意识到我不知道如何在data.table内部使用它。

我的函数基本上是这样的:

do_stuff <- function(dt, col) {
  copy(dt)[, new_col := some_fun(col)][]
}

我希望用这个名字来称呼它:

do_stuff(data, column)

“column”是存在于“data”内的列的名称。如果我运行该函数,会出现错误:

#> Error in some_fun(col) : object 'column' not found 

这意味着data.table显然将正确的名称传递给函数("column"),但由于某种原因它没有找到。下面是一个最小可重现的示例:

library(data.table)

data <- data.table(x = 1:10, y = rnorm(10))

plus <- function(x, y) {
   x + y
}

add_one <- function(data, col) {
   copy(data)[, z := plus(col, 1)][]
}

add_one(data, y)
#> Error in plus(col, 1): object 'y' not found

使用 deparse(substitute(col)) 似乎不起作用,很不幸 :(

add_one <- function(data, col) {
   copy(data)[, z := plus(deparse(substitute(col)), 1)][]
}

add_one(data, y)
#> Error in x + y: non-numeric argument to binary operator

如果您正在传递未引用的内容,则请使用 deparse(substitute - akrun
谢谢,但似乎不起作用。请查看更新的问题 :( - Elio Campitelli
请检查下面的解决方案。 - akrun
5个回答

15

通常情况下,引用和求值将起作用:

library(data.table)
plus <- function(x, y) {
   x + y
}

add_one <- function(data, col) {
   expr0 = quote(copy(data)[, z := plus(col, 1)][])

   expr  = do.call(substitute, list(expr0, list(col = substitute(col))))
   cat("Evaluated expression:\n"); print(expr); cat("\n")

   eval(expr)
}

set.seed(1)
library(magrittr)
data.table(x = 1:10, y = rnorm(10)) %>% 
   add_one(y)

提供

Evaluated expression:
copy(data)[, `:=`(z, plus(y, 1))][]

     x          y         z
 1:  1 -0.6264538 0.3735462
 2:  2  0.1836433 1.1836433
 3:  3 -0.8356286 0.1643714
 4:  4  1.5952808 2.5952808
 5:  5  0.3295078 1.3295078
 6:  6 -0.8204684 0.1795316
 7:  7  0.4874291 1.4874291
 8:  8  0.7383247 1.7383247
 9:  9  0.5757814 1.5757814
10: 10 -0.3053884 0.6946116

我猜这可以用单个替换完成,但我很少使用这个功能,所以不确定。 - Frank
看起来很棒,但在管道内无法工作! :'( Error in:=(z, plus(hgt, 1)) : 请检查is.data.table(DT) == TRUE。否则,:=和:=(...)仅在j中定义为一次性使用,并且以特定方式使用。请参阅help(":="). - Elio Campitelli
划掉之前的话。只要删除第一个替换,它就可以正常工作。 - Elio Campitelli
@ElioCampitelli,您介意修改或添加答案以展示如何使其工作吗?我尝试使用data %>% add_one(y)时遇到了“未找到 .”的错误。 - Frank

4
一种选择是使用 deparse(substitute 提取未加引号的参数作为字符串,并在 .SDcols 中指定。
add_one <- function(data, col) {
   copy(data)[, z := plus(.SD[[1]], 1), .SDcols = deparse(substitute(col))][]
 }

add_one(data, y)
#     x           y          z
# 1:  1  0.50269855  1.5026986
# 2:  2 -0.33022414  0.6697759
# 3:  3  0.57517246  1.5751725
# 4:  4  1.09928586  2.0992859
# 5:  5  0.84683311  1.8468331
# 6:  6 -1.42023443 -0.4202344
# 7:  7  0.04539331  1.0453933
# 8:  8  0.11870596  1.1187060
# 9:  9 -1.11735007 -0.1173501
#10: 10 -1.94834136 -0.9483414

或者使用get
add_one <- function(data, col) {
   copy(data)[, z := plus(get(deparse(substitute(col)))][]
 }

或者使用tidyverse
library(tidyverse)
add_one <- function(data, col, col2) {
   data %>%
         dplyr::mutate(z =plus({{col}}, {{col2}}))
  }

add_one(data, x, y)
#    x           y         z
#1   1 -0.53389875 0.4661013
#2   2  1.28743777 3.2874378
#3   3 -1.26674091 1.7332591
#4   4  0.95017120 4.9501712
#5   5  0.06741833 5.0674183
#6   6 -0.70212949 5.2978705
#7   7 -0.38003803 6.6199620
#8   8 -0.50941072 7.4905893
#9   9  0.54055720 9.5405572
#10 10 -0.87486953 9.1251305

谢谢!它能运行,但是感觉非常笨拙,而且在处理更复杂的操作时不太容易扩展。也许有一种更直接的方法吗? - Elio Campitelli
@ElioCampitelli。直接写法为get(deparse(substitute(col))) - akrun
get(deparse(substitute(col))) doesn't work either :. I get Error in get(deparse(substitute(col))) : object 'y' not found - Elio Campitelli

4

另一个选择是引用列名并使用get

add_one <- function(data, col) {
  copy(data)[, z := plus(get(col), 1)][]
}

add_one(data, "y")

3

虽然可能更容易出错,但您可以依赖于...参数。

data <- data.table(x = 1:10, y = rnorm(10))

plus <- function(x, y) {
  x + y
}

add_one <- function(data, ...) {
  copy(data)[, z:= plus(data[, ...], 1)][]
}

add_one(data, y)

#or
library(dplyr)
data.table(x = 1:10, y = rnorm(10))%>%
  add_one(y)

     x           y          z
 1:  1 -1.29851891 -0.2985189
 2:  2 -1.36494928 -0.3649493
 3:  3  0.38282492  1.3828249
 4:  4  1.24578886  2.2457889
 5:  5  1.12897695  2.1289770
 6:  6 -0.80122005  0.1987800
 7:  7  1.89093661  2.8909366
 8:  8 -0.34525212  0.6547479
 9:  9 -0.07070159  0.9292984
10: 10 -1.94145962 -0.9414596

很不幸,将此扩展到多个变量将导致失败。尽管如此,您可能仍然可以利用...

add_one2 <- function(data, ...){
  copy(data)[...][]
}
add_one2(data, , z:=plus(y, 1))

     x          y          z
 1:  1 -0.1565010  0.8434990
 2:  2  0.6516824  1.6516824
 3:  3  0.5355833  1.5355833
 4:  4  0.1941661  1.1941661
 5:  5  0.2994167  1.2994167
 6:  6 -2.5681215 -1.5681215
 7:  7 -1.4587147 -0.4587147
 8:  8  0.9375132  1.9375132
 9:  9  1.3984343  2.3984343
10: 10 -0.6498709  0.3501291

2

以下是我 Rnotebooks 的摘录…用于时间序列数据的整理和分析。我使用这个模式来为自己的包装数据表和时间序列相关的包(如 xts)。

最初的回答:

这里是我在 Rnotebooks 中的一段摘录,用于处理和分析时间序列数据。我将这种模式用于我的自有软件包,以包装 data.table 和与时间序列相关的软件包(例如 xts)。

# Non-standard evaluation & wrapper for data.table

data <- data.table(a = 1:2, b = 3:4)    

## Non-in-place update - - - - - - - - -

do_something <- function(data, col) {
  col <- eval( substitute(col), data )
  data[ , col + 123]
}
data %>% do_something(a)

## In-place update without copies (fast, memory efficient) - - - - - - - - -

# Minimalistic example        
do_something <- function(data, col) {
  col <- eval( substitute(col), data )
  data[ , new_col := col + 123]
}
data %>% do_something(a)   # print `data` to see results

# More example
# optional multi-assignment %<-% operator from {zeallot} for cleaner syntax    
my_func <- function(x, y) x + y

do_something <- function(data, col_1, col_2, col_name) {
  c(col_1, col_2) %<-% map(
    c( substitute(col_1), substitute(col_2) ), ~ eval( ., data )
  )
  data[ , (col_name) := my_func(col_1, col_2)]
}
data %>% do_something(a, b, 'new_col_name')

# Advanced example
# ...

谢谢分享。当使用by=子句时,似乎无法正常工作,例如:do_another = function(data, x, g){x = eval(substitute(x), data); data[, sum(x), by=g]}; do_another(data, a, "b")。这是正确的吗?还是有解决方法? - Frank

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接