【问题标题】:How to reliably get dependent variable name from formula object?如何可靠地从公式对象中获取因变量名称?
【发布时间】:2012-10-24 09:59:10
【问题描述】:

假设我有以下公式:

myformula<-formula("depVar ~ Var1 + Var2")

如何可靠地从公式对象中获取因变量名称?

我没有找到任何用于此目的的内置函数。 我知道as.character(myformula)[[2]]

一样有效
sub("^(\\w*)\\s~\\s.*$","\\1",deparse(myform))

在我看来,这些方法更像是一种骇客,而不是一种可靠且标准的方法。


有谁知道究竟是什么方法,例如lm 使用?我已经看过它的代码,但它对我来说有点神秘......为了您的方便,这里引用一个:

    > lm
function (formula, data, subset, weights, na.action, method = "qr", 
    model = TRUE, x = FALSE, y = FALSE, qr = TRUE, singular.ok = TRUE, 
    contrasts = NULL, offset, ...) 
{
    ret.x <- x
    ret.y <- y
    cl <- match.call()
    mf <- match.call(expand.dots = FALSE)
    m <- match(c("formula", "data", "subset", "weights", "na.action", 
        "offset"), names(mf), 0L)
    mf <- mf[c(1L, m)]
    mf$drop.unused.levels <- TRUE
    mf[[1L]] <- as.name("model.frame")
    mf <- eval(mf, parent.frame())
    if (method == "model.frame") 
        return(mf)
    else if (method != "qr") 
        warning(gettextf("method = '%s' is not supported. Using 'qr'", 
            method), domain = NA)
    mt <- attr(mf, "terms")
    y <- model.response(mf, "numeric")
    w <- as.vector(model.weights(mf))
    if (!is.null(w) && !is.numeric(w)) 
        stop("'weights' must be a numeric vector")
    offset <- as.vector(model.offset(mf))
    if (!is.null(offset)) {
        if (length(offset) != NROW(y)) 
            stop(gettextf("number of offsets is %d, should equal %d (number of observations)", 
                length(offset), NROW(y)), domain = NA)
    }
    if (is.empty.model(mt)) {
        x <- NULL
        z <- list(coefficients = if (is.matrix(y)) matrix(, 0, 
            3) else numeric(), residuals = y, fitted.values = 0 * 
            y, weights = w, rank = 0L, df.residual = if (!is.null(w)) sum(w != 
            0) else if (is.matrix(y)) nrow(y) else length(y))
        if (!is.null(offset)) {
            z$fitted.values <- offset
            z$residuals <- y - offset
        }
    }
    else {
        x <- model.matrix(mt, mf, contrasts)
        z <- if (is.null(w)) 
            lm.fit(x, y, offset = offset, singular.ok = singular.ok, 
                ...)
        else lm.wfit(x, y, w, offset = offset, singular.ok = singular.ok, 
            ...)
    }
    class(z) <- c(if (is.matrix(y)) "mlm", "lm")
    z$na.action <- attr(mf, "na.action")
    z$offset <- offset
    z$contrasts <- attr(x, "contrasts")
    z$xlevels <- .getXlevels(mt, mf)
    z$call <- cl
    z$terms <- mt
    if (model) 
        z$model <- mf
    if (ret.x) 
        z$x <- x
    if (ret.y) 
        z$y <- y
    if (!qr) 
        z$qr <- NULL
    z
}

【问题讨论】:

  • 您是否只有公式,或者您有拟合模型或编写的代码,使用这些公式接口的标准非标准评估规则? R 通常使用 terms 组件来进行这种思考
  • @Gavin 不。我有一个公式之前计算模型。我正在编写一个用于引导的函数,它将用给定的残差替换 data.frame 中的因变量。我唯一可以得到 dep 名称的地方。变量是公式对象。如果我只是为了使用 $terms 组件来拟合模型,那将是非常浪费时间。
  • 好的,我只记得terms() 也适用于公式,但处理该对象也会很麻烦而且很痛苦。我认为as.character(myformula)[[2]] 是最不老套的——排序不会很快改变,我会冒险。
  • 你编辑;这就是我的意思。 lm 使用标准的非标准评估习语将公式与数据框对象匹配。您至少需要一个 data 参数加上其他一些参数,然后很容易获得模型框架并从中获得响应。
  • 查看document (PDF),它解释了lm() 中所做的事情

标签: r


【解决方案1】:

使用all.vars 非常棘手,因为它不会检测到单边公式的响应。例如

all.vars(~x+1)
[1] "x"

这是错误的。

这是获得响应的最可靠方法:

    getResponseFromFormula = function(formula) {
        if (attr(terms(as.formula(formula))    , which = 'response'))
            all.vars(formula)[1]
        else
            NULL
    }


getResponseFromFormula(~x+1)
NULL

 getResponseFromFormula(y~x+1)
[1] "y"

请注意,如果公式包含多个响应变量,您可以将函数中的all.vars(formula)[1] 替换为formula[2]

【讨论】:

  • 如果我们的公式是y ~ .,即通过所有其他变量预测y,则此解决方案不起作用。见getResponseFromFormula(y ~ .)
  • 您描述的公式是数据驱动的。不确定,但可能所有其他方法在这种情况下都不起作用。
【解决方案2】:

我知道这个问题已经很老了,但我想我会添加一个不需要索引的基本 R 答案,不依赖于对 all.vars 的调用中列出的变量的顺序,并且当存在多个时,将响应变量作为单独的元素给出:

myformula <- formula("depVar1 + depVar2 ~ Var1 + Var2")
all_vars <- all.vars(myformula)
response <- all_vars[!(all_vars %in% labels(terms(myformula)))]

> response
[1] "depVar1" "depVar2"

【讨论】:

  • 请注意,如果公式中包含任何函数,此方法将引发错误。这是因为 terms(myformula) 包含公式元素中的函数,而 all.vars() 不包含。 %in% 调用失败,因为它无法正确匹配。例如,公式“dv1 + dv2 ~ v1 + v2 + log(v3)”将错误地生成 dv1、dv2、 v3 的响应向量,因为 all.vars 将 v3 标记为 v3,而术语将 v3 标记为 log(v3)。
【解决方案3】:

我找到了一个适用于您的任务的有用包“formula.tools”。

代码示例:

f

lhs.vars(f) #获取因变量

[1]“a1”“a2”

rhs.vars(f) #获取自变量

[1]“a3”“a4”

【讨论】:

    【解决方案4】:

    这应该总是给你所有依赖的变量:

    myformula<-formula("depVar1 + depVar2 ~ Var1 + Var2")
    as.character(myformula[[2]])[-1]
    #[1] "depVar1" "depVar2"
    

    而且我不会认为这特别“hacky”。​​

    编辑:

    3 个家属发生了一些奇怪的事情:

    myformula<-formula("depVar1 + depVar2 + depVar3 ~ Var1 + Var2")
    as.character(myformula[[2]])
    #[1] "+"                 "depVar1 + depVar2" "depVar3" 
    

    所以这可能不像我想象的那么可靠。

    编辑2:

    好的,myformula[[2]] 是一个语言对象,as.character 似乎做了类似于languageEl 的事情。

    length(myformula[[2]])
    #[1] 3
    languageEl(myformula[[2]],which=1)
    #`+`
    languageEl(myformula[[2]],which=2)
    #depVar1 + depVar2
    languageEl(myformula[[2]],which=3)
    #depVar3
    languageEl(languageEl(myformula[[2]],which=2),which=2)
    #depVar1
    

    如果您检查每个元素的长度,您可以创建自己的提取函数。但这可能太过分了。

    编辑3: 根据@seancarmody all.vars(myformula[[2]]) 的回答是正确的选择。

    【讨论】:

    • getFormulaVars = function(x,dependent=TRUE) { if(class(x) != 'formula') stop("x argument must be a formula",call.=FALSE) all.vars(x[[if(dependent) 3 else 2]]) }
    【解决方案5】:

    根据您的编辑以获得实际响应,而不仅仅是它的名称,我们可以使用lm() 和大多数其他建模函数使用的非标准评估习语,并在基础 R 中使用公式接口

    form <- formula("depVar ~ Var1 + Var2")
    dat <- data.frame(depVar = rnorm(10), Var1 = rnorm(10), Var2 = rnorm(10))
    
    getResponse <- function(form, data) {
        mf <- match.call(expand.dots = FALSE)
        m <- match(c("formula", "data"), names(mf), 0L)
        mf <- mf[c(1L, m)]
        mf$drop.unused.levels <- TRUE
        mf[[1L]] <- as.name("model.frame")
        mf <- eval(mf, parent.frame())
        y <- model.response(mf, "numeric")
        y
    } 
    
    > getResponse(form, dat)
              1           2           3           4           5 
    -0.02828573 -0.41157817  2.45489291  1.39035938 -0.31267835 
              6           7           8           9          10 
    -0.39945771 -0.09141438  0.81826105  0.37448482 -0.55732976
    

    如您所见,这会从提供的数据框中获取实际的响应变量数据。

    其工作原理是该函数首先捕获函数调用而不扩展 ... 参数,因为其中包含评估公式数据所需的内容。

    接下来,"formula""data" 参数与调用匹配。 mf[c(1L, m)] 行从调用中选择函数名 (1L) 和两个匹配参数的位置。下一行model.frame()drop.unused.levels参数设置为TRUE,然后调用更新,将调用中的函数名从lm切换为model.frame。上面的代码所做的就是调用lm(),并处理调用model.frame()函数的过程。

    然后在函数的父环境(在本例中为全局环境)中评估此修改后的调用。

    最后一行使用model.response() 提取函数从模型框架中获取响应变量。

    【讨论】:

    • 非常感谢。我不知道model.frame 功能。尽管它提供了一些不同的范例(需要提供实际的 data.frame),但我相信它应该是最通用和最值得信赖的解决方案,尤其是当我只使用 glm 模型时。我觉得我需要更多地挖掘这个成语。并感谢您提供“nonstandard-eval.pdf”。
    • 你能告诉我,这种方法的速度与其他(更“hacky”)的方法相比如何? - 我相信我需要在引导循环中使用这个功能,所以速度也是我关心的问题。
    • 在您的用例中自己进行基准测试。我怀疑它会比foo &lt;- as.character(form)[[2]] 后跟resp &lt;- data[ , foo] 更快,但它 R Core 选择可靠地识别由公式表示的模型的数据的方式。根据您在应用程序中执行的操作,您可能不需要model.frame 惯用语那样的额外公式功能处理/通用性。
    【解决方案6】:

    我想你也可以编写自己的函数来使用terms()

    getResponse <- function(formula) {
        tt <- terms(formula)
        vars <- as.character(attr(tt, "variables"))[-1] ## [1] is the list call
        response <- attr(tt, "response") # index of response var
        vars[response] 
    }
    
    R> myformula <- formula("depVar ~ Var1 + Var2")
    R> getResponse(myformula)
    [1] "depVar"
    

    它和as.character(myformyula)[[2]] 一样老套,但您可以确保获得正确的变量,因为调用解析树的顺序不会很快改变。

    这对于多个因变量来说不是很好:

    R> myformula <- formula("depVar1 + depVar2 ~ Var1 + Var2")
    R> getResponse(myformula)
    [1] "depVar1 + depVar2"
    

    因为他们需要进一步处理。

    【讨论】:

      【解决方案7】:

      尝试使用all.vars

      all.vars(myformula)[1]
      

      【讨论】:

      • 谢谢。您确定因变量将始终位于向量all.vars(myformula) 中的第一位吗?
      • +1 没有比as.character(formula)[[2]] 更“hacky”,并且可能是最可靠的,无需求助于其他更冗长的操作。
      • 此变体不依赖于all.vars 变量的返回顺序:all.vars(update(myformula, . ~ 1))
      • 无需更新公式:all.vars(myformula[[2]]) 工作正常。
      • 请注意all.vars(~b+c)会给你错误的结果。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多