有没有比字符串操作更好的替代方案,用于以编程方式构建公式?

54

其他人的函数似乎都接受公式对象,然后在内部进行某些黑魔法,我很羡慕。

我正在编写一个拟合多个模型的函数。这些模型的公式的一部分保持不变,而一部分则从一个模型转到另一个模型。笨拙的方法是让用户将公式部分作为字符字符串输入,对其进行一些字符操作,然后使用 as.formula

但在采取这种方式之前,我只想确定我没有忽略一些更干净的方法,使函数能够接受标准 R 格式的公式(例如从其他使用公式的对象中提取的公式)。

我想要像这样的东西...

> LHS <- y~1; RHS <- ~a+b; c(LHS,RHS);
y ~ a + b
> RHS2 <- ~c;
> c(LHS, RHS, RHS2);
y ~ a + b + c

或者...

> LHS + RHS;
y ~ a + b
> LHS + RHS + RHS2;
y ~ a + b + c

...但不幸的是,两种语法都不起作用。有人知道是否有其他方法吗?谢谢。


尽管我最终意识到我不需要那么高的通用性,而是更好地利用了“update”函数,但下面mnel的回答是一个好的、有用的答案,并且可能已经实现了我最初的目标。总的来说,我会为好的答案点赞,但在我亲自尝试并可以证明它们之前不会接受它们。在许多情况下,我自己找到了更好的答案,应该在有时间的时候提交自我答案。我的接受回答的标准太严格了吗? - bokov
1个回答

75

reformulate 将实现你想要的功能。

reformulate(termlabels = c('x','z'), response = 'y')
## y ~ x + z

或者去掉截距

reformulate(termlabels = c('x','z'), response = 'y', intercept = FALSE)
## y ~ x + z - 1
请注意,您不能构建包含多个reponses的公式,例如x+y ~z+b
reformulate(termlabels = c('x','y'), response = c('z','b'))
z ~ x + y
从一个现有的公式中提取术语(根据您的示例给出)
attr(terms(RHS), 'term.labels')
## [1] "a" "b"

要获得响应有些不同,一个简单的方法(用于单个变量响应)。

as.character(LHS)[2]
## [1] 'y'


combine_formula <- function(LHS, RHS){
  .terms <- lapply(RHS, terms)
  new_terms <- unique(unlist(lapply(.terms, attr, which = 'term.labels')))
  response <- as.character(LHS)[2]

  reformulate(new_terms, response)


}


combine_formula(LHS, list(RHS, RHS2))

## y ~ a + b + c
## <environment: 0x577fb908>

我认为更明智的做法是将响应指定为字符向量,例如:

combine_formula2 <- function(response, RHS, intercept = TRUE){
  .terms <- lapply(RHS, terms)
  new_terms <- unique(unlist(lapply(.terms, attr, which = 'term.labels')))
  response <- as.character(LHS)[2]

  reformulate(new_terms, response, intercept)


}
combine_formula2('y', list(RHS, RHS2))

你也可以定义一个加号运算符来处理公式(更新设置公式对象的新方法)。

`+.formula` <- function(e1,e2){
  .terms <- lapply(c(e1,e2), terms)
  reformulate(unique(unlist(lapply(.terms, attr, which = 'term.labels'))))
}

RHS + RHS2
## ~a + b + c

你也可以谨慎地使用 update.formula,使用 . 符号。

 update(~a+b, y ~ .)
 ##  y~a+b

10
赞一个你的总结。但我们要承认:如果您想编写易于阅读的代码,那么使用字符串方式是正确的方法。我怀疑,就整个生命周期而言,速度上的收益是否足以让您有时间喝杯咖啡。 - Dieter Menne
19
@DieterMenne 增加速度并不重要-增加安全性才是重点。当有人首次尝试使用您的代码时,如果使用了非语法变量名称(例如"a b"),它将会出现奇怪的错误,这将使你花费数小时来查找。请注意,"a b"是一个示例,表示任何非法变量名都会导致此问题。 - hadley
除了使用 update.formula,还有其他使用 . 的方法吗?我最终使用了 setdiff() 语句,而我本来想在 reformulate() 中使用 ~.-var1-var2,但帮助文档没有提到这种用法。 - bright-star
没有直接/明确调用 paste(sep="~"),但我们仍然在操作字符串。 - green diod

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