如何在R中使用公式来排除主效应但保留相互作用

时间:2016-11-21 21:26:48

标签: r regression linear-regression lm categorical-data

我不想要主效果,因为它与更精细的因子固定效果共线,所以有这些NA很烦人。

在这个例子中:

lm(y ~ x * z)

我想要x(数字)和z(因素)的互动,而不是z的主要效果。

3 个答案:

答案 0 :(得分:6)

简介

?formula的R文档说:

  

'*'运算符表示因子交叉:'a * b'被解释为'a + b + a:b

因此,只需执行以下操作之一就可以直接降低主要效果:

a + a:b  ## main effect on `b` is dropped
b + a:b  ## main effect on `a` is dropped
a:b      ## main effects on both `a` and `b` are dropped

哦,真的吗?不不不(太简单,太天真)。实际上,它取决于ab的变量类。

  • 如果它们都不是因素,或者只有一个是因素,那么这是真的;
  • 如果两者都是因素,没有。当交互(高阶效应)出现时,你永远不会放弃主效果(低阶效应)。

这种行为是由一个名为model.matrix.default的神奇函数引起的,它从公式构造一个设计矩阵。数值变量仅包含在列中,但因子变量自动编码为多个虚拟列。正是这种虚拟重新编码是一种魔力。通常认为我们可以启用或禁用对比来控制它,但不是真的。即使在this simplest example,我们也无法控制对比。问题是model.matrix.default在进行虚拟编码时有自己的规则,并且对指定模型公式的方式非常敏感。正是由于这个原因,当两个因素之间存在相互作用时,我们才能放弃主效应。

数字与因子

之间的相互作用

根据您的问题,x是数字,z是一个因素。您可以指定具有交互的模型,但不能通过

指定z的主效应
y ~ x + x:z

由于x是数字,因此等同于执行

y ~ x:z

这里唯一的区别是参数化(或model.matrix.default如何进行虚拟编码)。考虑一个小例子:

set.seed(0)
y <- rnorm(10)
x <- rnorm(10)
z <- gl(2, 5, labels = letters[1:2])

fit1 <- lm(y ~ x + x:z)
#Coefficients:
#(Intercept)            x         x:zb  
#     0.1989      -0.1627      -0.5456  

fit2 <- lm(y ~ x:z)
#Coefficients:
#(Intercept)         x:za         x:zb  
#     0.1989      -0.1627      -0.7082 

从系数的名称我们看到,在第一个规范中,z被对比,因此它的第一级&#34; a&#34;不是虚拟编码,而在第二个规范中,z没有对比,并且两个级别都是&#34; a&#34;和&#34; b&#34;是虚拟编码的。鉴于两个规范都以三个系数结束,它们实际上是等价的(从数学上讲,两种情况下的设计矩阵具有相同的列空间),您可以通过比较它们的拟合值来验证这一点:

all.equal(fit1$fitted, fit2$fitted)
# [1] TRUE

那么为什么z与第一种情况形成鲜明对比?因为否则我们有x:z的两个虚拟列,这两列的总和只是x,与公式中现有的模型术语x别名。事实上,在这种情况下,即使您要求不要进行对比,model.matrix.default也不会遵守:

model.matrix.default(y ~ x + x:z,
      contrast.arg = list(z = contr.treatment(nlevels(z), contrasts = FALSE)))
#   (Intercept)          x       x:zb
#1            1  0.7635935  0.0000000
#2            1 -0.7990092  0.0000000
#3            1 -1.1476570  0.0000000
#4            1 -0.2894616  0.0000000
#5            1 -0.2992151  0.0000000
#6            1 -0.4115108 -0.4115108
#7            1  0.2522234  0.2522234
#8            1 -0.8919211 -0.8919211
#9            1  0.4356833  0.4356833
#10           1 -1.2375384 -1.2375384

那么为什么第二种情况z没有对比?因为如果是的话,我们就会失去水平&#34; a&#34;在构建交互时。即使你需要对比,model.matrix.default也会忽略你:

model.matrix.default(y ~ x:z,
      contrast.arg = list(z = contr.treatment(nlevels(z), contrasts = TRUE)))
#   (Intercept)       x:za       x:zb
#1            1  0.7635935  0.0000000
#2            1 -0.7990092  0.0000000
#3            1 -1.1476570  0.0000000
#4            1 -0.2894616  0.0000000
#5            1 -0.2992151  0.0000000
#6            1  0.0000000 -0.4115108
#7            1  0.0000000  0.2522234
#8            1  0.0000000 -0.8919211
#9            1  0.0000000  0.4356833
#10           1  0.0000000 -1.2375384

哦,太棒了model.matrix.default。它能够做出正确的决定!

两个因素之间的相互作用

让我重申一下:当互动存在时,没有办法放弃主要效果。

我不会在这里提供额外的例子,因为我在Why do I get NA coefficients and how does lm drop reference level for interaction中有一个例子。请参阅&#34;对比互动&#34;那边的部分。简而言之,以下所有规格都给出了相同的模型(它们具有相同的拟合值):

~ year:treatment
~ year:treatment + 0
~ year + year:treatment
~ treatment + year:treatment
~ year + treatment + year:treatment
~ year * treatment

特别是,第一个规范导致NA系数。

因此,一旦~的RHS包含year:treatment,您就永远不会要求model.matrix.default放弃主要效果。

People inexperienced with this behavior are to be surprised when producing ANOVA tables

绕过model.matrix.default

有些人认为model.matrix.default很烦人,因为在虚拟编码中似乎没有一致的方式。 A&#34;一致的方式&#34;在他们看来,总是放弃第一个因素水平。好吧,没问题,您可以通过手动执行虚拟编码来绕过model.matrix.default,并将生成的虚拟矩阵作为变量提供给lm等。

但是,您仍需要model.matrix.default的帮助才能轻松对 a (是的,只有一个)因子变量进行虚拟编码。例如,对于前一个示例中的变量z,其完整的虚拟编码如下,您可以保留其全部或部分列以进行回归。

Z <- model.matrix.default(~ z + 0)  ## no contrasts (as there is no intercept)
#   za zb
#1   1  0
#2   1  0
#3   1  0
#4   1  0
#5   1  0
#6   0  1
#7   0  1
#8   0  1
#9   0  1
#10  0  1
#attr(,"assign")
#[1] 1 1
#attr(,"contrasts")
#attr(,"contrasts")$z
#[1] "contr.treatment"

回到我们的简单示例,如果我们不想zy ~ x + x:z的对比,我们可以做

Z2 <- Z[, 1:2]  ## use "[" to remove attributes of `Z`
lm(y ~ x + x:Z2)
#Coefficients:
#(Intercept)            x       x:Z2za       x:Z2zb  
#     0.1989      -0.7082       0.5456           NA

毫不奇怪,我们看到NA(因为colSums(Z2)的别名为x)。如果我们想在y ~ x:z中强制执行对比,我们可以执行以下任一操作:

Z1 <- Z[, 1]
lm(y ~ x:Z1)
#Coefficients:
#(Intercept)         x:Z1  
#    0.34728     -0.06571

Z1 <- Z[, 2]
lm(y ~ x:Z1)
#Coefficients:
#(Intercept)         x:Z1  
#     0.2318      -0.6860  

And the latter case is probably what contefranz is trying to do

但是,我并不真的推荐这种黑客攻击。当您将模型公式传递给lm等时,model.matrix.default会尝试为您提供最明智的构造。而且,实际上我们想用拟合模型进行预测。如果您自己进行了虚拟编码,那么在向newdata提供predict时您会遇到困难。

答案 1 :(得分:2)

这是一个非常好的解释,但在选择重要预测因子的过程中,让我再添加一件事。

让我们再考虑以下模型:

fit1 <- lm(y ~ x + x:z)
#Coefficients:
#(Intercept)            x         x:zb  
#     0.1989      -0.1627      -0.5456

假设主效果x没有统计意义,你想摆脱它。至少对我来说,最直观的是写上面的第二个模型:

fit2 <- lm(y ~ x:z)
#Coefficients:
#(Intercept)         x:za         x:zb  
#     0.1989      -0.1627      -0.7082 

最终将掩盖的主效应作为与因子基线水平的相互作用。现在,我能够找到的唯一解决方案是为了真正不包含主要效果,就是利用lm.fit,正如大家所知,它不会返回类lm的对象,而是list 1}}。所以问题是:你知道任何摆脱主效应而不失去lm类的方法吗?

答案 2 :(得分:1)

使用lm.fit但获得lm类对象: 我不是程序员,但对lm函数的简单改编对我有用:我添加了lm.fit所需的两个参数(模型矩阵和响应变量),并替换了(在lm函数中)x和y(用于lm中的lm.fit)由我的模型矩阵Xmat和响应:

a_list[!grepl("a",names(a_list))]

然后,照常创建模型矩阵,但是删除不需要的列:

lm.2 <- function (formula, data, subset, weights, na.action, method = "qr", 
                  model = TRUE, x = FALSE, y = FALSE, qr = TRUE, singular.ok = TRUE, 
                  contrasts = NULL, offset, ..., Xmat=NA, response=NA)
{
  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]] <- quote(stats::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(NA_real_, 
                                                      0, ncol(y)) 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 {
    z <- if (is.null(w)) 
      lm.fit(Xmat, response, offset = offset, singular.ok = singular.ok, 
             ...)
    else lm.wfit(Xmat, response, 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
}

当然,所有这些都可以进一步详细说明,但是对我来说,它确实有效->它返回一个lm对象,可以将其与summary,resid,sim,plot一起使用...

最佳 庇护