gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-01 20:27:10 27 4
gpt4 key购买 nike

首先:感谢@MattDowle; data.table是最好的事情之一
自从我开始使用 R 后就发生在我身上.

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

  • Select / assign to data.table variables which names are stored in a character vector
  • pass column name in data.table using variable in R
  • Referring to data.table columns by names saved in variables
  • passing column names to data.table programmatically
  • Data.table meta-programming
  • How to write a function that calls a function that calls data.table?
  • Using dynamic column names in `data.table`
  • dynamic column names in data.table, R
  • Assign multiple columns using := in data.table, by group
  • Setting column name in "group by" operation with data.table
  • 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 的任意复杂程序的工作流程的通用策略。对列列表或列列表的操作,在变量中指定或作为参数传递给函数,其中过程必须以编程方式引用在变量/参数中命名的列,并且可能包括更新、连接、分组,拨打 data.table特殊物品 .I , .SD , 等等。;但是,与上述或其他需要频繁 quote 相比,它更简单、更优雅、更短或更易于设计、实现或理解。 -ing 和 eval -ing。

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

    最佳答案

    您所描述的问题与 data.table 并不严格相关。
    复杂的查询无法轻松转换为机器可以解析的代码,因此我们无法在为复杂操作编写查询时避免复杂性。
    您可以尝试想象如何使用 data.table 或 SQL 以编程方式为以下 dplyr 查询构造查询:

    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 )或什至选项( optopt1 、 57914 、 5 4 个变量)应该作为 4 个变量传递
    由于查询表达的复杂性,我认为您无法轻松完成问题中所述的要求:

    is simpler, more elegant, shorter, or easier to design or implement or understand than the one above or others that require frequent quote-ing and eval-ing.


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

    您已经找到了使用 opt2getmgetDT[[col_name]]parsequote 的建议。
  • 正如你提到的 eval 可能不能很好地与 DT[[col_name]] 优化配合使用,因此在这里用处不大。
  • data.table 可能是构建复杂查询的最简单方法,因为您可以只对字符串进行操作,但它不提供基本的语言语法验证。因此,您最终可以尝试解析 R 解析器不接受的字符串。此外,还有一个安全问题,如 2655#issuecomment-376781159 中所示。
  • parse/get 是最常建议用于处理此类问题的方法。 mgetget 在内部被 mget 捕获并转换为预期的列。因此,您假设您的任意复杂查询将能够被 [.data.table 分解并正确输入预期的列。
  • 自从您几年前问过这个问题以来,最近推出了新功能 - 点-点前缀。您可以使用点-点作为变量名称的前缀来引用当前 data.table 范围之外的变量。与您在文件系统中引用父目录类似。 dot-dot 后面的内部结构将与 [.data.table 非常相似,具有前缀的变量将在 get 内部取消引用。 .在 future 的版本中,点-点前缀可能会允许这样的调用:

  • col1="a"; col2="b"; col3="g"; col4="x"; col5="y"
    DT[..col4==..col5, .(s1=sum(..col1), s2=sum(..col2)), by=..col3]
  • 我个人更喜欢 [.data.tablequoteevalquote 几乎被解释为从头开始手写。此方法不依赖于 eval 能力来管理对列的引用。我们可以期望所有优化的工作方式与您手动编写这些查询的方式相同。我发现调试也更容易,因为在任何时候你都可以打印带引号的表达式来查看实际传递给 data.table 查询的内容。此外,发生错误的空间更小。使用 R 语言对象构建复杂的查询有时很棘手,很容易将过程包装到函数中,因此它可以应用于不同的用例并易于重用。需要注意的是,此方法独立于 data.table 。它使用 R 语言结构。您可以在语言章节的计算中的官方 R Language Definition 中找到更多信息。
  • 还有什么?
  • 我在 #1579 提交了一个名为宏的新概念的提案。简而言之,它是 data.table 的包装器,因此您仍然必须对 R 语言对象进行操作。欢迎您在那里发表评论。
  • 最近我在 PR#4304 中提出了另一种元编程接口(interface)的方法。简而言之,它使用新参数 DT[eval(qi), eval(qj), eval(qby)] 将基本 R substitute 功能插入 [.data.table


  • 进入示例。下面我将展示两种方法来解决它。第一个将使用基本 R 元编程,第二个将使用元编程用于 PR#4304 中提出的 data.table(见上文)。
  • 基于语言
  • 的 R 计算

    我会将所有逻辑包装到 env 函数中。调用 do_vars 将打印要在 do_vars(donot=TRUE) 而不是 data.table 上计算的表达式。下面的代码应该在 OP 代码之后运行。
    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]
    #}
    #
  • 提议的数据表元编程

  • 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

    数据和更新的 OP 代码
    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 - 如何完全通用地在 R 中的 data.table 中使用变量中的列名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24833247/

    27 4 0