如何在基本R公式中使用类似quasure的语法?

时间:2018-07-16 10:25:14

标签: r formula tidyverse

我想编写一个这样的函数:

library(survival)
getFit = function(x, data){
  survfit(Surv(start, stop, event) ~ x, data = data)
}
getFit(surgery, heart)
getFit("surgery", heart) #if not possible, this would be fine too

当然,不会读取x。请注意,我将survfitheart用作示例,但是对于几乎每个基于公式的函数(lm,glm等),我都深感困惑。

我知道我可以用pasteas.formula来写东西,但是我想知道是否有类似的事情可以用tidyverse来完成,例如:

getSurvPlot = function(x, data=db){
  xx = enquo(x)
  survfit(Surv(start, stop, event) ~ !!xx, data = data)
}

最后一个代码也不起作用,但是我认为这是因为survfit不是tidyverse的一部分。

有什么干净的方法可以在基数R中写这样的东西吗?

编辑:在这个示例中,我现在使用的是survminer::surv_fit,它是survfit的包装,可以在公式中提供更大的灵活性。

3 个答案:

答案 0 :(得分:2)

这里有3个选项,第一个是纯基R,接下来的两个是rlang

基数R

getFit1 = function(x, data){
  survfit(eval(substitute(Surv(start, stop, event) ~ x)), data = data)
}

rlang

这里没有理由使用enquo来构建包含父环境的对象,在您的示例中,对象surgery在您的全局环境中不存在,只需要在公式的上下文中进行评估。因此substitute在这里也是合适的功能。

要使用!!参数,我们需要一个支持拟定额参数的函数,然后我们需要对其求值或将其转换为公式(我不知道在一步)。

因此,我们最终得到的结果看起来并不比基本版本好。

getFit2 = function(x, data){
  xx <- substitute(x)
  survfit(eval(expr(Surv(start, stop, event) ~ !!xx)), data = data)
}
使用new_formula再次

rlang

我们可以根据其lhs和rhs建立一个公式,但是现在我们需要引用lhs,并且我们仍然需要expr在rhs上,因此基本解似乎仍然更适合这种情况。

getFit3 = function(x, data){
  xx <- substitute(x)
  survfit(new_formula(quote(Surv(start, stop, event)), expr(!!xx)), data = data)
}

输出

getFit1(surgery, heart)
# Call: survfit(formula = eval(substitute(Surv(start, stop, event) ~ 
#                                           x)), data = data)
# 
# records n.max n.start events median 0.95LCL 0.95UCL
# surgery=0     143    87       0     66     80      66     188
# surgery=1      29    16       0      9    980     186      NA

getFit2(surgery, heart)
# Call: survfit(formula = eval(expr(Surv(start, stop, event) ~ !!xx)), 
#               data = data)
# 
# records n.max n.start events median 0.95LCL 0.95UCL
# surgery=0     143    87       0     66     80      66     188
# surgery=1      29    16       0      9    980     186      NA

getFit3(surgery, heart)
# Call: survfit(formula = new_formula(quote(Surv(start, stop, event)), 
#                                     expr(!!xx)), data = data)
# 
# records n.max n.start events median 0.95LCL 0.95UCL
# surgery=0     143    87       0     66     80      66     188
# surgery=1      29    16       0      9    980     186      NA

答案 1 :(得分:2)

Moody_mudskipper提供了一个非常详细的答案。我只想指出,您对getSurvPlot的定义几乎是正确的。您的问题不在于rlang / tidyverse,而是在另一个公式中使用带引号的参数(这是一个公式)。

调用getSurvPlot(surgery, heart)时,enquo会将第一个参数捕获为~surgery,它已经是一个公式。您无需使用~xxSurv来创建新公式,只需更新已有公式的左侧即可。可以使用来自基数R的stats::update()来完成:

getSurvPlot <- function(x, data=db){
  xx <- enquo(x)
  survfit(stats::update( xx, Surv(start, stop, event) ~ . ), data = data)
}

getSurvPlot(surgery, heart)现在应该可以正常工作了。

如@Moody_mudskipper所指出的,实际工作由stats::update.formula()完成,stats::update()S3 generic xx对公式对象(例如scanf)的实现。

答案 2 :(得分:0)

我们可以用所需的变量从字面上替换公式的RHS:

getFit = function(var, data){
  var=as.name(substitute(var))

  survfit(`[<-`(Surv(time, status)~. ,3,list(var)), data = data)
}

getFit(x,aml)
Call: survfit(formula = `[<-`(Surv(time, status) ~ ., 3, list(var)), 
    data = data)

                 n events median 0.95LCL 0.95UCL
x=Maintained    11      7     31      18      NA
x=Nonmaintained 12     11     23       8      NA

getFit("x",aml)
Call: survfit(formula = `[<-`(Surv(time, status) ~ ., 3, list(var)), 
    data = data)

                 n events median 0.95LCL 0.95UCL
x=Maintained    11      7     31      18      NA
x=Nonmaintained 12     11     23       8      NA

我们怎么知道它是正确的?

survfit(Surv(time, status) ~ x, data = aml) 
Call: survfit(formula = Surv(time, status) ~ x, data = aml)

                 n events median 0.95LCL 0.95UCL
x=Maintained    11      7     31      18      NA
x=Nonmaintained 12     11     23       8      NA

您可以使用:

getFit = function(var, data){
  var=as.name(substitute(var))
  a = `[<-`(Surv(time, status)~. ,3,list(var))
  survfit(a, data = data)
}

getFit = function(var, data){
  var=as.name(substitute(var))

  survfit(formula(substitute(Surv(time, status)~var,list(var=var))), data = data)
}