当前位置: 首页 > 知识库问答 >
问题:

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

宇文俊明
2023-03-14

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

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

  1. 选择/分配给data.table变量,这些变量的名称存储在字符向量中
  2. 使用r
  3. 中的变量传递data.table中的列名
  4. 按保存在变量中的名称引用data.table列
  5. 以编程方式向data.table传递列名
  6. 数据表元编程
  7. 如何编写调用data.table函数的函数?
  8. 在`data.table`
  9. 中使用动态列名
  10. data.table中的动态列名,r
  11. 使用:=in data.table按组分配多列
  12. 使用data.table在“group by”操作中设置列名
  13. r用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操作过程重复应用到列列表或列列表中,这些过程在变量中指定或作为参数传递给函数,其中过程必须以编程方式引用变量/参数中命名的列,并可能包括更新、联接、分组、调用data.table特殊对象.I.sd等;但它比上面的或其他需要频繁quote-ing和eval-ing的方法更简单、更优雅、更简短或更容易设计、实现或理解。

特别是,请注意,由于过程可能相当复杂,并且涉及到反复更新data.table,然后引用更新的列,所以标准的lapply(.sd,...),...sdcols=...方法通常不是一个可行的替代方法。另外,用dt[[a.column.name]]替换eval(a.column.name)的每次调用既不能简化很多,也不能完全正常工作,因为据我所知,这对其他data.table操作不太好用。

共有1个答案

薄龙光
2023-03-14

您描述的问题与data.table并不严格相关。
复杂查询不能轻松转换为计算机可以解析的代码,因此我们无法避免为复杂操作编写查询时的复杂性。
您可以尝试想象如何使用dplyr或SQL以编程方式为以下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)]

假设所有列(ID1ID2v1...v5)甚至选项(optopt1opt2)都应该作为变量传递。

由于查询表达的复杂性,我不认为您可以轻松地完成您的问题中所述的要求:

与上面的方法或其他需要频繁quote-ing和eval-ing的方法相比,它更简单、更优雅、更简短或更易于设计、实现或理解。

尽管与其他编程语言相比,base R提供了非常有用的工具来处理此类问题。

    null
col1="a"; col2="b"; col3="g"; col4="x"; col5="y"
DT[..col4==..col5, .(s1=sum(..col1), s2=sum(..col2)), by=..col3]
  • 我在#1579中提出了一个新的概念,称为宏。简而言之,它是dt[eval(qi),eval(qj),eval(qby)]上的包装器,所以您仍然必须对R语言对象进行操作。欢迎您在此发表评论。
  • 最近,我在PR#4304中提出了另一种元编程接口的方法。简而言之,它使用新参数env.
  • 将基本R substitute功能插入 [.data.table

去例子。下面我将展示两种解决方法。第一个将使用基本R元编程,第二个将使用PR#4304中提出的Data.Table的元编程(见上文)。

  • 基于语言的R计算
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
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]
#}
#
    null
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]

name.suffix = function(x, suffix) as.name(paste(x, suffix, sep="."))
do_var2 = function(var, x, y) {
  x[, .anntot := y[, rep(.var, each=12)],
    env = list(
      .anntot = name.suffix(var, "annual.total"),
      .var = var
    )]
  x[, .monthly := y[, rep(.var/12, each=12)],
    env = list(
      .monthly = name.suffix(var, "monthly"),
      .var = var
    )]
  x[, .rolling := rollapply(.monthly, mean, width=12, fill=c(head(.monthly,1), tail(.monthly,1))),
    env = list(
      .rolling = name.suffix(var, "rolling"),
      .monthly = name.suffix(var, "monthly")
    )]
  x[, .scaled := .anntot/sum(.rolling)*.rolling, by=year,
    env = list(
      .scaled = name.suffix(var, "scaled"),
      .anntot = name.suffix(var, "annual.total"),
      .rolling = name.suffix(var, "rolling")
    )]
  TRUE
}

sapply(setNames(nm=var.names), do_var2, new.table, the.table)
#var1 var2 var3 
#TRUE TRUE TRUE 
all.equal(expected, new.table)
#[1] TRUE
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[,paste0(varname,".annual.total"):=
              the.table[,rep(eval(varname),each=12)]]
  new.table[,paste0(varname,".monthly"):=
              the.table[,rep(eval(varname)/12,each=12)]]
  new.table[,paste0(varname,".rolling"):=
              rollapply(eval(monthly),mean,width=12,
                        fill=c(head(eval(monthly),1),
                               tail(eval(monthly),1)))]
  new.table[,paste0(varname,".scaled"):=
              eval(anntot)/sum(eval(rolling))*eval(rolling),
            by=year]
}
 类似资料:
  • 本文向大家介绍如何通过R中data.table中的列名删除列?,包括了如何通过R中data.table中的列名删除列?的使用技巧和注意事项,需要的朋友参考一下 我们可以通过将列设置为NULL来实现 示例 删除一列x 删除两列

  • 我有一个命名的数据帧列表。我需要用数据帧的名称为每个数据帧创建一个新变量,这样我最终就可以将它们全部rbind起来,以生成统计数据和绘图。 我知道lapply不会传递对象的名称,也知道有很多类似问题的帖子,但我无法将我看到的任何解决方案适用于我的特定问题。 这是我第一次尝试的方法,但显然不起作用,因为names(x)不会返回对象的名称。 TestList<-list(a=data.frame(Va

  • 嗨,我想通过使用mutate函数来创建一个新变量Foldchange。但是我想使用来自同一列的值来计算这个值。有没有什么方法可以计算一个新变量,从同一列中折叠更改,而无需重新排列表格,这样表格就不需要拆分了? 下面是一个清晰的例子: 我想要的输出类似于使用: 获取 显然,我不能使用此代码基于V1和V2变量进行选择,但这只是为了说明。我希望通过这种方式,我可以保持我的附加表的完整性,折叠更改将具有重

  • 最近没有使用当前tidyverse动词来回答这个问题(R 4.1 所需的输出应如下所示。感谢您对此的任何帮助!

  • 问题内容: 例如,我想使用自定义记录器: 如何在其他模块而不是console.log中使用此记录器? 问题答案: 大多数人建议不要使用全局变量。如果要在不同模块中使用相同的记录器类,则可以执行此操作 logger.js foob​​ar.js 如果确实需要全局变量,则可以执行以下操作:

  • cyl V1 N 1:6 138.2 7 2:6 128 3.2 7 O/P: cyl mpg disp hp drat wt qsec vs am gear carb N 6 138.2 1283.2 856 25.10 21.820 125.84 4 3 27 24 7 1:6 138.2 2:6 7.0 3:4 293.3 3:8 211.4 14 为什么会出现这种情况?为什么结果是这样排序