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

Convert variables in data.table to formula

我有一个示例 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")),但是我无法获得所需的输出。

使用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 这样的映射工具,而不是将所有内容连接到一个数据框中。

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

一种方法是将 Formulae 转换为实际的 one-sided 公式,然后在 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))

如以下评论中所述,这些要求公式按顺序排列。

看到此任务的另一个函数很有趣,它在更复杂(未指定求值顺序)的情况下很有用 -- 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