如何在R中的data.table中完全使用变量中的列名

问题描述 投票:43回答:3

首先:感谢@MattDowle;自从我开始使用data.table以来,R是我遇到过的最好的事情之一。

第二:我知道data.table中变量列名的各种用例的许多变通方法,包括:

  1. Select / assign to data.table variables which names are stored in a character vector
  2. pass column name in data.table using variable in R
  3. Referring to data.table columns by names saved in variables
  4. passing column names to data.table programmatically
  5. Data.table meta-programming
  6. How to write a function that calls a function that calls data.table?
  7. Using dynamic column names in `data.table`
  8. dynamic column names in data.table, R
  9. Assign multiple columns using := in data.table, by group
  10. Setting column name in "group by" operation with data.table
  11. R summarizing multiple columns with data.table

可能更多我没有参考。

但是:即使我学会了上面记录的所有技巧,以至于我从来不必查看它们以提醒自己如何使用它们,我仍然会发现使用作为参数传递给函数的列名非常繁琐的任务。

我正在寻找的是以下解决方法/工作流程的“最佳实践认可”替代方案。考虑到我有一堆类似数据的列,并且希望对这些列或它们的集合执行一系列类似的操作,其中操作具有任意高的复杂性,并且列名称组传递给指定的每个操作在变量中。

我意识到这个问题听起来很人为,但我却以惊人的频率遇到它。这些示例通常非常混乱,很难将与此问题相关的功能分开,但我最近偶然发现了一个相当简单的简化用作MWE的方法:

library(data.table)
library(lubridate)
library(zoo)

the.table <- data.table(year=1991:1996,var1=floor(runif(6,400,1400)))
the.table[,`:=`(var2=var1/floor(runif(6,2,5)),
                var3=var1/floor(runif(6,2,5)))]

# Replicate data across months
new.table <- the.table[, list(asofdate=seq(from=ymd((year)*10^4+101),
                                           length.out=12,
                                           by="1 month")),by=year]

# Do a complicated procedure to each variable in some group.
var.names <- c("var1","var2","var3")

for(varname in var.names) {
    #As suggested in an answer to Link 3 above
    #Convert the column name to a 'quote' object
    quote.convert <- function(x) eval(parse(text=paste0('quote(',x,')')))

    #Do this for every column name I'll need
    varname <- quote.convert(varname)
    anntot <- quote.convert(paste0(varname,".annual.total"))
    monthly <- quote.convert(paste0(varname,".monthly"))
    rolling <- quote.convert(paste0(varname,".rolling"))
    scaled <- quote.convert(paste0(varname,".scaled"))

    #Perform the relevant tasks, using eval()
    #around every variable columnname I may want
    new.table[,eval(anntot):=
               the.table[,rep(eval(varname),each=12)]]
    new.table[,eval(monthly):=
               the.table[,rep(eval(varname)/12,each=12)]]
    new.table[,eval(rolling):=
               rollapply(eval(monthly),mean,width=12,
                         fill=c(head(eval(monthly),1),
                                tail(eval(monthly),1)))]
    new.table[,eval(scaled):=
               eval(anntot)/sum(eval(rolling))*eval(rolling),
              by=year]
}

当然,这里对数据和变量的特殊影响是无关紧要的,所以请不要关注它或建议改进以完成它在这种特定情况下所完成的事情。我正在寻找的是一个通用策略,用于将data.table动作的任意复杂过程重复应用于列列表或列表列表,在变量中指定或作为参数传递给a的工作流程。函数,其中过程必须以编程方式引用变量/参数中指定的列,并且可能包括更新,连接,分组,调用data.table特殊对象.I.SD等;但是比上面的那个更简单,更优雅,更短或更容易设计或实现或理解,或者需要经常进行quoteing和eval-ing的其他设计。

特别请注意,因为程序可能相当复杂并且涉及重复更新data.table然后引用更新的列,标准的lapply(.SD,...), ... .SDcols = ...方法通常不是可行的替代方法。同时用eval(a.column.name)替换DT[[a.column.name]]的每次调用既不简化也不完全一般,因为据我所知,这与其他data.table操作不相称。

r data.table calculated-columns programmatically-created
3个回答
5
投票

你描述的问题与data.table没有严格的关系。 复杂的查询不能轻易地转换为机器可以解析的代码,因此我们无法逃避为复杂操作编写查询的复杂性。 想象一下如何以编程方式构造以下data.table查询的查询

DT[, c(f1(v1, v2, opt=TRUE),
       f2(v3, v4, v5, opt1=FALSE, opt2=TRUE),
       lapply(.SD, f3, opt1=TRUE, opt2=FALSE))
   , by=.(id1, id2)]

使用dplyr或SQL - 假设所有列(id1,id2,v1 ... v5)或甚至选项(opt,opt1,opt2)都应作为变量传递。

由于上述原因,我认为您不能轻易完成您的问题中所述的要求:

比上面的或其他需要经常进行quoteing和eval-ing的更简单,更优雅,更短或更容易设计或实现或理解。

虽然与其他编程语言相比,base R提供了非常有用的工具来处理这些问题。


你已经找到了使用getmgetDT[[col_name]]parsequoteeval的建议。

  • 正如你所提到的,qazxsw poi可能不适合qazxsw poi优化,这在这里没有用。
  • DT[[col_name]]可能是构建复杂查询的最简单方法,因为您可以只对字符串进行操作,但它不提供基本的语言语法验证。因此,您最终可能会尝试解析R解析器不接受的字符串。此外,data.table提出了一个安全问题。
  • parse / 2655#issuecomment-376781159是最常建议处理此类问题的人。 getmget在内部被get捕获并翻译成预期的列。所以你假设你的任意复杂查询将能够被mget和正确输入的预期列分解。
  • 几年前你就问过这个问题,新功能 - 点 - 点前缀 - 最近正在推出。使用点 - 点对变量名称作为前缀,以引用当前data.table范围之外的变量。与您在文件系统中引用父目录类似。 dot-dot背后的内部将与[.data.table非常相似,具有前缀的变量将在[.data.table内被取消引用。 。在将来的版本中,点阵前缀可能允许以下调用:
get
  • 我个人更喜欢[.data.tablecol1="a"; col2="b"; col3="g"; col4="x"; col5="y" DT[..col4==..col5, .(s1=sum(..col1), s2=sum(..col2)), by=..col3] quoteeval几乎被解释为从头开始手写。此方法不依赖于quote功能来管理对列的引用。我们可以期望所有优化的工作方式与您手动编写这些查询的方式相同。我发现它也更容易调试,因为在任何时候你都可以打印引用的表达式来查看实际传递给eval查询的内容。此外,发生错误的空间也较小。使用R语言对象构造复杂查询有时很棘手,很容易将过程包装到函数中,因此它可以应用于不同的用例并且可以轻松地重复使用。需要注意的是,此方法独立于data.table。它使用R语言结构。您可以在语言章节的计算机官方data.table中找到更多相关信息。
  • 还有什么?我在data.table提交了一个名为macro的新概念提案。简而言之,它是R Language Definition的包装器,因此您仍然需要对R语言对象进行操作。欢迎您在那里发表评论。

举个例子。我将所有逻辑包装到#1579函数中。调用DT[eval(qi), eval(qj), eval(qby)]将打印在do_vars而不是do_vars(donot=TRUE)上计算的表达式。下面的代码应该在OP代码之后运行。

data.table
eval

2
投票

我试图在data.table中做到这一点。“这不是那么糟糕”......但是经过一段尴尬的时间后,我放弃了。 Matt说的是“片段然后加入”,但我无法找到优雅的方法来做这些片段,特别是因为最后一个取决于之前的步骤。

我不得不说,这是一个非常出色的问题,我也经常遇到类似的问题。我喜欢data.table,但有时我仍然很挣扎。我不知道我是否在使用data.table或问题的复杂性。

这是我采取的不完整的方法。

实际上,我可以想象,在正常过程中,您将存储更多的中间变量,这些变量可用于计算这些值。

expected = copy(new.table)
new.table = the.table[, list(asofdate=seq(from=ymd((year)*10^4+101), length.out=12, by="1 month")), by=year]

do_vars = function(x, y, vars, donot=FALSE) {
  name.suffix = function(x, suffix) as.name(paste(x, suffix, sep="."))
  do_var = function(var, x, y) {
    substitute({
      x[, .anntot := y[, rep(.var, each=12)]]
      x[, .monthly := y[, rep(.var/12, each=12)]]
      x[, .rolling := rollapply(.monthly, mean, width=12, fill=c(head(.monthly,1), tail(.monthly,1)))]
      x[, .scaled := .anntot/sum(.rolling)*.rolling, by=year]
    }, list(
      .var=as.name(var),
      .anntot=name.suffix(var, "annual.total"),
      .monthly=name.suffix(var, "monthly"),
      .rolling=name.suffix(var, "rolling"),
      .scaled=name.suffix(var, "scaled")
    ))
  }
  ql = lapply(setNames(nm=vars), do_var, x, y)
  if (donot) return(ql)
  lapply(ql, eval.parent)
  invisible(x)
}
do_vars(new.table, the.table, c("var1","var2","var3"))
all.equal(expected, new.table)
#[1] TRUE

1
投票

谢谢你的提问。您最初的方法在解决大多数问题方面都有很长的路要走。

这里我稍微调整了引用函数,并改变了解析方法并将整个RHS表达式作为字符串而不是单个变量进行评估。

理由是:

  • 您可能不希望通过声明在循环开始时需要使用的每个变量来重复自己。
  • 字符串将更好地扩展,因为它们可以通过编程生成。我在下面添加了一个计算行数百分比的示例来说明这一点。

do_vars(new.table, the.table, c("var1","var2","var3"), donot=TRUE)
#$var1
#{
#    x[, `:=`(var1.annual.total, y[, rep(var1, each = 12)])]
#    x[, `:=`(var1.monthly, y[, rep(var1/12, each = 12)])]
#    x[, `:=`(var1.rolling, rollapply(var1.monthly, mean, width = 12, 
#        fill = c(head(var1.monthly, 1), tail(var1.monthly, 1))))]
#    x[, `:=`(var1.scaled, var1.annual.total/sum(var1.rolling) * 
#        var1.rolling), by = year]
#}
#
#$var2
#{
#    x[, `:=`(var2.annual.total, y[, rep(var2, each = 12)])]
#    x[, `:=`(var2.monthly, y[, rep(var2/12, each = 12)])]
#    x[, `:=`(var2.rolling, rollapply(var2.monthly, mean, width = 12, 
#        fill = c(head(var2.monthly, 1), tail(var2.monthly, 1))))]
#    x[, `:=`(var2.scaled, var2.annual.total/sum(var2.rolling) * 
#        var2.rolling), by = year]
#}
#
#$var3
#{
#    x[, `:=`(var3.annual.total, y[, rep(var3, each = 12)])]
#    x[, `:=`(var3.monthly, y[, rep(var3/12, each = 12)])]
#    x[, `:=`(var3.rolling, rollapply(var3.monthly, mean, width = 12, 
#        fill = c(head(var3.monthly, 1), tail(var3.monthly, 1))))]
#    x[, `:=`(var3.scaled, var3.annual.total/sum(var3.rolling) * 
#        var3.rolling), by = year]
#}
#
© www.soinside.com 2019 - 2024. All rights reserved.