将data.table中的变量转换为公式。

7

我有一个样本数据表 data,如下所示:

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

我希望将公式列转换为公式,以便输出结果变成下面这样:

  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")),但是我没有获得所需的输出。


很棒,公式总是这么简单,遵循一种模式,将1加到前一行的值? - zx8754
它们总是按顺序进行吗?即下一个将基于前一个吗? - Sotos
不,实际的公式要复杂得多,我只是用这个简单的例子来说明。 - debster
1
@zx8754 不,它们不是连续的,我已经编辑了问题,使公式更类似于我的实际数据。它可以是任何运算符,不仅限于加法。 - debster
1
提供了答案,似乎过于简单,使用真实数据进行测试并告诉我们结果。 - zx8754
显示剩余3条评论
4个回答

7
循环遍历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 

3
为了更加通用,你可以将while条件重新表述为:any(grepl(paste0(names(res), collapse = "|"), res)) - Jaap

4

有一种方法是将 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))

如下方评论所述,这些要求公式必须按顺序排列。


输出是一个列表吗? - zx8754
@zx8754 - 是的,它是一个列表。 - Ritchie Sacramento
1
这也可以!我认为这种方法与@zx8754的方法相比更直接,因此我会将其标记为解决方案。 - debster
1
@debster 这仅适用于数据按照特定顺序排列的情况。如果更改此数据框的顺序,此解决方案将不再起作用。 - Jaap

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 

1

使用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,而不是将所有内容连接成一个数据框。


谢谢您的建议!但是这些代码似乎没有产生我想要的输出 :/ - debster

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