【问题标题】:Convert variables in data.table to formula将 data.table 中的变量转换为公式
【发布时间】:2022-02-22 21:11:25
【问题描述】:

我有一个示例 data.table data 如下:

   VarName Formulae
1:       A      1+1
2:       B      A+3
3:       C     B*10
4:       D      A+C
5:       E      D/2

我想将Formulae列转换成公式,这样输出可以变成这样:

  VarName Result
1:       A      2
2:       B      5
3:       C      50
4:       D      52
5:       E      26

基本上VarName 列是变量名,Formulae 列是对应的公式。

A = 1+1
B = A+3
C = B*10
D = A+C
E = D/2

我曾尝试使用evalparse 之类的data$VarName = eval(parse(text = "data$Formulae")) 函数,但是我无法获得所需的输出。

【问题讨论】:

  • 您能添加更多示例行吗?
  • @zx8754 编辑了问题,添加了更多示例行并进行了详细说明
  • 太好了,公式总是那么简单,并且遵循模式,将前一行值加 1 吗?
  • @zx8754 不,它们不是连续的,我已经编辑了问题,以便公式更类似于我的实际数据。可以是任意运算符,不只是加法
  • 提供了答案,似乎过于简单,用真实数据测试一下,告诉我们。

标签: r data.table


【解决方案1】:

循环遍历 VarName 用括号内的 Formulae 替换它们,然后计算:

res <- setNames(x$Formulae, x$VarName)

while(any(grepl(paste0(names(res), collapse = "|"), res))) {
  for(i in names(res)){
    res <- gsub(i, paste0("(", res[ i ], ")"), res, fixed = TRUE)
  }
}

#res, after replacements:
#                          A                          B 
#                      "1+1"                  "(1+1)+3" 
#                          C                          D 
#             "((1+1)+3)*10"     "(1+1)+(((1+1)+3)*10)" 
#                          E 
# "((1+1)+(((1+1)+3)*10))/2" 

# evaluate
sapply(res, function(i) eval(parse(text = i)))
#A  B  C  D  E 
#2  5 50 52 26 

【讨论】:

  • 像魅力一样工作! :D
  • 为了使其更通用,您可以将 while 条件重新表述为:any(grepl(paste0(names(res), collapse = "|"), res))
【解决方案2】:

执行此操作的一种方法是将Formulae 转换为实际的单边公式,然后在lst() 内部评估函数,从而允许顺序构建对象。这依赖于tidyverse 框架的元编程功能,而不是data.table

library(dplyr)
library(purrr)

df <- data.frame(VarName = LETTERS[1:5],
                 Formulae = c("1+1", "A+3", "B*10", "A+C", "D/2"))

lst(!!!map(set_names(df$Formulae, df$VarName),
           ~ quo(
             as_mapper(reformulate(.x))()
           )))
$A
[1] 2

$B
[1] 5

$C
[1] 50

$D
[1] 52

$E
[1] 26

或者:

lst(!!!set_names(df$Formulae, df$VarName) %>% map(str2lang))

正如下面的 cmets 所述,这些要求公式按顺序排列。

【讨论】:

  • 输出是一个列表?
  • @zx8754 - 是的,它是一个列表。
  • 这也有效!我认为这种方法比@zx8754的方法更直接,因此我将其标记为解决方案。
  • @debster 这仅在数据处于此特定顺序时才有效。更改此数据框的顺序,此解决方案将不再有效。
【解决方案3】:

有趣的是,该任务的另一个函数在更复杂(未指定评估顺序)的情况下很有用——delayedAssign 将值分配给名称并仅在请求时对其进行评估。这样,每个对象都会按顺序进行评估,直到达到其值。例如,考虑以下“data.frame”:

d = structure(list(v = c("a", "b", "A", "B", "C", "D", "E"), 
                   f = c("C+b", "A+B/D", "1+1", "A+3", "B*10", "A+C", "D/2")), 
              class = "data.frame", row.names = c(NA, -7L))

然后我们设置一个新环境(以避免混乱.GlobalEnv)并分配我们的变量:

e = new.env()
forms = parse(text = d$f)
for(i in 1:nrow(d)) do.call(delayedAssign, list(d$v[i], forms[[i]], e, e))

并评估:

unlist(mget(ls(e), e)) #or
unlist(eapply(e, eval))
#        A         B         C         D         a         E         b 
# 2.000000  5.000000 50.000000 52.000000 52.096154 26.000000  2.096154 

【讨论】:

    【解决方案4】:

    使用apply

    df <- data.frame("VarName"=c("X","Y"),"Formulae"=c("1+1","X+1"))
    df$formulas <- apply(df,1,function(x)eval(parse(text = paste0(x["VarName"]," ~ ",x["Formulae"]))))
    

    使用eval(parse(...)) 结构是正确的,但这应该可以正常工作。不过,也许有人会回答一个更干净的提议。

    注意“公式”列不能是向量,所以它是一个列表。

    str(df)
    'data.frame':   2 obs. of  3 variables:
     $ VarName : chr  "X" "Y"
     $ Formulae: chr  "1+1" "X+1"
     $ formulas:List of 2
      ..$ :Class 'formula'  language X ~ 1 + 1
      .. .. ..- attr(*, ".Environment")=<environment: 0x000002933f8904a8> 
      ..$ :Class 'formula'  language Y ~ X + 1
      .. .. ..- attr(*, ".Environment")=<environment: 0x000002933fb6f3b8> 
    

    这可能会在数据框的使用中引起一些麻烦。我建议使用像purrr 这样的映射工具,而不是在这种情况下将所有内容连接到数据框中。

    【讨论】:

    • 感谢您的建议!但是代码似乎没有产生我想要的输出:/
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-30
    • 2010-10-20
    • 1970-01-01
    • 1970-01-01
    • 2017-02-28
    • 1970-01-01
    相关资源
    最近更新 更多