如何调试 "contrasts can be applied only to factors with 2 or more levels" 错误?

How to debug "contrasts can be applied only to factors with 2 or more levels" error?

以下是我正在使用的所有变量:

str(ad.train)
$ Date                : Factor w/ 427 levels "2012-03-24","2012-03-29",..: 4 7 12 14 19 21 24 29 31 34 ...
 $ Team                : Factor w/ 18 levels "Adelaide","Brisbane Lions",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ Season              : int  2012 2012 2012 2012 2012 2012 2012 2012 2012 2012 ...
 $ Round               : Factor w/ 28 levels "EF","GF","PF",..: 5 16 21 22 23 24 25 26 27 6 ...
 $ Score               : int  137 82 84 96 110 99 122 124 49 111 ...
 $ Margin              : int  69 18 -56 46 19 5 50 69 -26 29 ...
 $ WinLoss             : Factor w/ 2 levels "0","1": 2 2 1 2 2 2 2 2 1 2 ...
 $ Opposition          : Factor w/ 18 levels "Adelaide","Brisbane Lions",..: 8 18 10 9 13 16 7 3 4 6 ...
 $ Venue               : Factor w/ 19 levels "Adelaide Oval",..: 4 7 10 7 7 13 7 6 7 15 ...
 $ Disposals           : int  406 360 304 370 359 362 365 345 324 351 ...
 $ Kicks               : int  252 215 170 225 221 218 224 230 205 215 ...
 $ Marks               : int  109 102 52 41 95 78 93 110 69 85 ...
 $ Handballs           : int  154 145 134 145 138 144 141 115 119 136 ...
 $ Goals               : int  19 11 12 13 16 15 19 19 6 17 ...
 $ Behinds             : int  19 14 9 16 11 6 7 9 12 6 ...
 $ Hitouts             : int  42 41 34 47 45 70 48 54 46 34 ...
 $ Tackles             : int  73 53 51 76 65 63 65 67 77 58 ...
 $ Rebound50s          : int  28 34 23 24 32 48 39 31 34 29 ...
 $ Inside50s           : int  73 49 49 56 61 45 47 50 49 48 ...
 $ Clearances          : int  39 33 38 52 37 43 43 48 37 52 ...
 $ Clangers            : int  47 38 44 62 49 46 32 24 31 41 ...
 $ FreesFor            : int  15 14 15 18 17 15 19 14 18 20 ...
 $ ContendedPossessions: int  152 141 149 192 138 164 148 151 160 155 ...
 $ ContestedMarks      : int  10 16 11 3 12 12 17 14 15 11 ...
 $ MarksInside50       : int  16 13 10 8 12 9 14 13 6 12 ...
 $ OnePercenters       : int  42 54 30 58 24 56 32 53 50 57 ...
 $ Bounces             : int  1 6 4 4 1 7 11 14 0 4 ...
 $ GoalAssists         : int  15 6 9 10 9 12 13 14 5 14 ...

这是我试图适应的 glm:

ad.glm.all <- glm(WinLoss ~ factor(Team) + Season  + Round + Score  + Margin + Opposition + Venue + Disposals + Kicks + Marks + Handballs + Goals + Behinds + Hitouts + Tackles + Rebound50s + Inside50s+ Clearances+ Clangers+ FreesFor + ContendedPossessions + ContestedMarks + MarksInside50 + OnePercenters + Bounces+GoalAssists, 
                  data = ad.train, family = binomial(logit))

我知道它有很多变量(计划是通过前向变量选择来减少)。但即使知道有很多变量,它们要么是 int 要么是 Factor;据我所知,事情应该只是用 glm 工作。然而,每次我尝试拟合这个模型时,我都会得到:

Error in `contrasts<-`(`*tmp*`, value = contr.funs[1 + isOF[nn]]) : contrasts can be applied only to factors with 2 or more levels

哪种情况在我看来 R 出于某种原因没有将我的因子变量视为因子变量?

甚至像这样简单的东西:

ad.glm.test <- glm(WinLoss ~ factor(Team), data = ad.train, family = binomial(logit))

不工作! (相同的错误消息)

其中:

ad.glm.test <- glm(WinLoss ~ Clearances, data = ad.train, family = binomial(logit))

会起作用的!

有人知道这是怎么回事吗?为什么我不能将这些因子变量拟合到我的 glm 中?

提前致谢!

-特洛伊

简介

What a "contrasts error" is has been well explained: you have a factor that only has one level (or less). But in reality this simple fact can be easily obscured because the data that are actually used for model fitting can be very different from what you've passed in. This happens when you have NA in your data, you've subsetted your data, a factor has unused levels, or you've transformed your variables and get NaN somewhere. Many questions on Whosebug regarding this error are not reproducible, thus suggestions by people may or may not work. Therefore, although there are by now 118 posts关于这个问题,用户至今找不到适合的解决方案,所以这个问题被反复提出。这个回答是我的尝试,解决这个问题"once for all",或者至少提供一个合理的指导。

这个回答信息丰富,我先简单总结一下。

我为您定义了 3 个辅助函数:debug_contr_errordebug_contr_error2NA_preproc

我建议您按以下方式使用它们。

  1. 运行 NA_preproc获取更完整的案例;
  2. 运行 你的模型,如果你得到 "contrasts error",使用 debug_contr_error2 进行调试。

大部分答案会逐步向您展示这些函数的定义方式和原因。跳过这些开发过程可能没有什么坏处,但不要跳过 "Reproducible case studies and Discussions".

中的部分

修改后的答案

original answer , and has successfully helped some others. But it had failed somewhere else 缺乏适应性。查看问题中 str(ad.train) 的输出。 OP的变量是数字或因子;没有字符。最初的答案是针对这种情况。如果您有字符变量,尽管它们会在 lmglm 拟合期间被强制转换为因子,但代码不会报告它们,因为它们没有作为因子提供,因此 is.factor 将想念他们。在此扩展中,我将使原始答案更具适应性。

dat 成为您传递给 lmglm 的数据集。如果你没有现成的数据框,也就是说,你所有的变量都分散在全局环境中,你需要把它们收集到一个数据框中。以下可能不是最好的方法,但它有效。

## `form` is your model formula, here is an example
y <- x1 <- x2 <- x3 <- 1:4
x4 <- matrix(1:8, 4)
form <- y ~ bs(x1) + poly(x2) + I(1 / x3) + x4

## to gather variables `model.frame.default(form)` is the easiest way 
## but it does too much: it drops `NA` and transforms variables
## we want something more primitive

## first get variable names
vn <- all.vars(form)
#[1] "y"  "x1" "x2" "x3" "x4"

## `get_all_vars(form)` gets you a data frame
## but it is buggy for matrix variables so don't use it
## instead, first use `mget` to gather variables into a list
lst <- mget(vn)

## don't do `data.frame(lst)`; it is buggy with matrix variables
## need to first protect matrix variables by `I()` then do `data.frame`
lst_protect <- lapply(lst, function (x) if (is.matrix(x)) I(x) else x)
dat <- data.frame(lst_protect)
str(dat)
#'data.frame':  4 obs. of  5 variables:
# $ y : int  1 2 3 4
# $ x1: int  1 2 3 4
# $ x2: int  1 2 3 4
# $ x3: int  1 2 3 4
# $ x4: 'AsIs' int [1:4, 1:2] 1 2 3 4 5 6 7 8

## note the 'AsIs' for matrix variable `x4`
## in comparison, try the following buggy ones yourself
str(get_all_vars(form))
str(data.frame(lst))

第 0 步:显式子集化

如果您使用了 lmglmsubset 参数,请从显式子集开始:

## `subset_vec` is what you pass to `lm` via `subset` argument
## it can either be a logical vector of length `nrow(dat)`
## or a shorter positive integer vector giving position index
## note however, `base::subset` expects logical vector for `subset` argument
## so a rigorous check is necessary here
if (mode(subset_vec) == "logical") {
  if (length(subset_vec) != nrow(dat)) {
    stop("'logical' `subset_vec` provided but length does not match `nrow(dat)`")
    }
  subset_log_vec <- subset_vec
  } else if (mode(subset_vec) == "numeric") {
  ## check range
  ran <- range(subset_vec)
  if (ran[1] < 1 || ran[2] > nrow(dat)) {
    stop("'numeric' `subset_vec` provided but values are out of bound")
    } else {
    subset_log_vec <- logical(nrow(dat))
    subset_log_vec[as.integer(subset_vec)] <- TRUE
    } 
  } else {
  stop("`subset_vec` must be either 'logical' or 'numeric'")
  }
dat <- base::subset(dat, subset = subset_log_vec)

第 1 步:删除不完整的案例

dat <- na.omit(dat)

如果您已完成第 0 步,则可以跳过此步骤,因为

第二步:模式检查和转换

数据框列通常是一个原子向量,具有以下模式:"logical"、"numeric"、"complex"、 "character"、"raw"。对于回归,不同模式的变量处理不同。

"logical",   it depends
"numeric",   nothing to do
"complex",   not allowed by `model.matrix`, though allowed by `model.frame`
"character", converted to "numeric" with "factor" class by `model.matrix`
"raw",       not allowed by `model.matrix`, though allowed by `model.frame`

逻辑变量很棘手。它可以被视为虚拟变量(1 代表 TRUE0 代表 FALSE)因此是 "numeric",或者它可以被强制为 two-level 因素。这完全取决于 model.matrix 是否认为 "to-factor" 从模型公式的规范中强制转换是必要的。为了简单起见,我们可以这样理解它:它总是被强制转换为一个因子,但是应用对比的结果最终可能会得到与直接作为虚拟对象处理时相同的模型矩阵。

有些人可能会奇怪为什么不包括"integer"。因为整数向量,如 1:4,具有 "numeric" 模式(尝试 mode(1:4))。

数据框的列也可以是"AsIs"class的矩阵,但是这样的矩阵必须有"numeric"模

我们的检查是在

时产生错误
  • 找到一个"complex"或"raw";
  • 找到一个"logical"或"character"矩阵变量;

并继续将 "logical" 和 "character" 转换为 "factor" class.

的 "numeric"
## get mode of all vars
var_mode <- sapply(dat, mode)

## produce error if complex or raw is found
if (any(var_mode %in% c("complex", "raw"))) stop("complex or raw not allowed!")

## get class of all vars
var_class <- sapply(dat, class)

## produce error if an "AsIs" object has "logical" or "character" mode
if (any(var_mode[var_class == "AsIs"] %in% c("logical", "character"))) {
  stop("matrix variables with 'AsIs' class must be 'numeric'")
  }

## identify columns that needs be coerced to factors
ind1 <- which(var_mode %in% c("logical", "character"))

## coerce logical / character to factor with `as.factor`
dat[ind1] <- lapply(dat[ind1], as.factor)

请注意,如果数据框列已经是一个因子变量,它将不会包含在 ind1 中,因为因子变量具有 "numeric" 模式(尝试 mode(factor(letters[1:4])))。

第 3 步:降低未使用的因子水平

对于从步骤 2 转换而来的因子变量,即由 ind1 索引的因子变量,我们不会有未使用的因子水平。但是,dat 附带的因子变量可能具有未使用的水平(通常是步骤 0 和步骤 1 的结果)。我们需要从中删除任何可能未使用的级别。

## index of factor columns
fctr <- which(sapply(dat, is.factor))

## factor variables that have skipped explicit conversion in step 2
## don't simply do `ind2 <- fctr[-ind1]`; buggy if `ind1` is `integer(0)`
ind2 <- if (length(ind1) > 0L) fctr[-ind1] else fctr

## drop unused levels
dat[ind2] <- lapply(dat[ind2], droplevels)

第 4 步:汇总因子变量

现在我们已经准备好查看 lmglm 实际使用的因子水平:

## export factor levels actually used by `lm` and `glm`
lev <- lapply(dat[fctr], levels)

## count number of levels
nl <- lengths(lev)

为了让您的生活更轻松,我将这些步骤打包到一个函数中 debug_contr_error

输入:

  • dat 是通过 data 参数传递给 lmglm 的数据框;
  • subset_vec 是通过 subset 参数传递给 lmglm 的索引向量。

输出:一个包含

的列表
  • nlevels(列表)给出所有因子变量的因子水平数;
  • levels(向量)给出所有因子变量的水平。

如果没有完整个案或没有要汇总的因子变量,该函数会发出警告。

debug_contr_error <- function (dat, subset_vec = NULL) {
  if (!is.null(subset_vec)) {
    ## step 0
    if (mode(subset_vec) == "logical") {
      if (length(subset_vec) != nrow(dat)) {
        stop("'logical' `subset_vec` provided but length does not match `nrow(dat)`")
        }
      subset_log_vec <- subset_vec
      } else if (mode(subset_vec) == "numeric") {
      ## check range
      ran <- range(subset_vec)
      if (ran[1] < 1 || ran[2] > nrow(dat)) {
        stop("'numeric' `subset_vec` provided but values are out of bound")
        } else {
        subset_log_vec <- logical(nrow(dat))
        subset_log_vec[as.integer(subset_vec)] <- TRUE
        } 
      } else {
      stop("`subset_vec` must be either 'logical' or 'numeric'")
      }
    dat <- base::subset(dat, subset = subset_log_vec)
    } else {
    ## step 1
    dat <- stats::na.omit(dat)
    }
  if (nrow(dat) == 0L) warning("no complete cases")
  ## step 2
  var_mode <- sapply(dat, mode)
  if (any(var_mode %in% c("complex", "raw"))) stop("complex or raw not allowed!")
  var_class <- sapply(dat, class)
  if (any(var_mode[var_class == "AsIs"] %in% c("logical", "character"))) {
    stop("matrix variables with 'AsIs' class must be 'numeric'")
    }
  ind1 <- which(var_mode %in% c("logical", "character"))
  dat[ind1] <- lapply(dat[ind1], as.factor)
  ## step 3
  fctr <- which(sapply(dat, is.factor))
  if (length(fctr) == 0L) warning("no factor variables to summary")
  ind2 <- if (length(ind1) > 0L) fctr[-ind1] else fctr
  dat[ind2] <- lapply(dat[ind2], base::droplevels.factor)
  ## step 4
  lev <- lapply(dat[fctr], base::levels.default)
  nl <- lengths(lev)
  ## return
  list(nlevels = nl, levels = lev)
  }

这是一个构造好的小例子。

dat <- data.frame(y = 1:4,
                  x = c(1:3, NA),
                  f1 = gl(2, 2, labels = letters[1:2]),
                  f2 = c("A", "A", "A", "B"),
                  stringsAsFactors = FALSE)

#  y  x f1 f2
#1 1  1  a  A
#2 2  2  a  A
#3 3  3  b  A
#4 4 NA  b  B

str(dat)
#'data.frame':  4 obs. of  4 variables:
# $ y : int  1 2 3 4
# $ x : int  1 2 3 NA
# $ f1: Factor w/ 2 levels "a","b": 1 1 2 2
# $ f2: chr  "A" "A" "A" "B"

lm(y ~ x + f1 + f2, dat)
#Error in `contrasts<-`(`*tmp*`, value = contr.funs[1 + isOF[nn]]) : 
#  contrasts can be applied only to factors with 2 or more levels

很好,我们看到一个错误。现在我的 debug_contr_error 暴露了 f2 以单一级别结束。

debug_contr_error(dat)
#$nlevels
#f1 f2 
# 2  1 
#
#$levels
#$levels$f1
#[1] "a" "b"
#
#$levels$f2
#[1] "A"

注意这里原来的简答是没有希望的,因为f2是作为字符提供的变量不是因子变量。

## old answer
tmp <- na.omit(dat)
fctr <- lapply(tmp[sapply(tmp, is.factor)], droplevels)
sapply(fctr, nlevels)
#f1 
# 2 
rm(tmp, fctr)

现在让我们看一个带有矩阵变量的例子x

dat <- data.frame(X = I(rbind(matrix(1:6, 3), NA)),
                  f = c("a", "a", "a", "b"),
                  y = 1:4)

dat
#  X.1 X.2 f y
#1   1   4 a 1
#2   2   5 a 2
#3   3   6 a 3
#4  NA  NA b 4

str(dat)
#'data.frame':  4 obs. of  3 variables:
# $ X: 'AsIs' int [1:4, 1:2] 1 2 3 NA 4 5 6 NA
# $ f: Factor w/ 2 levels "a","b": 1 1 1 2
# $ y: int  1 2 3 4

lm(y ~ X + f, data = dat)
#Error in `contrasts<-`(`*tmp*`, value = contr.funs[1 + isOF[nn]]) : 
#  contrasts can be applied only to factors with 2 or more levels

debug_contr_error(dat)$nlevels
#f 
#1

请注意,没有水平的因子变量也可能导致 "contrasts error"。您可能想知道 0 级因子怎么可能。那么它是合法的:nlevels(factor(character(0)))。如果您没有完整的案例,在这里您将以 0 级因素结束。

dat <- data.frame(y = 1:4,
                  x = rep(NA_real_, 4),
                  f1 = gl(2, 2, labels = letters[1:2]),
                  f2 = c("A", "A", "A", "B"),
                  stringsAsFactors = FALSE)

lm(y ~ x + f1 + f2, dat)
#Error in `contrasts<-`(`*tmp*`, value = contr.funs[1 + isOF[nn]]) : 
#  contrasts can be applied only to factors with 2 or more levels

debug_contr_error(dat)$nlevels
#f1 f2 
# 0  0    ## all values are 0
#Warning message:
#In debug_contr_error(dat) : no complete cases

最后我们来看一个if f2是逻辑变量的情况

dat <- data.frame(y = 1:4,
                  x = c(1:3, NA),
                  f1 = gl(2, 2, labels = letters[1:2]),
                  f2 = c(TRUE, TRUE, TRUE, FALSE))

dat
#  y  x f1    f2
#1 1  1  a  TRUE
#2 2  2  a  TRUE
#3 3  3  b  TRUE
#4 4 NA  b FALSE

str(dat)
#'data.frame':  4 obs. of  4 variables:
# $ y : int  1 2 3 4
# $ x : int  1 2 3 NA
# $ f1: Factor w/ 2 levels "a","b": 1 1 2 2
# $ f2: logi  TRUE TRUE TRUE FALSE

我们的调试器会预测 "contrasts error",但它真的会发生吗?

debug_contr_error(dat)$nlevels
#f1 f2 
# 2  1 

没有,至少这一个没有失败():

lm(y ~ x + f1 + f2, data = dat)
#Coefficients:
#(Intercept)            x          f1b       f2TRUE  
#          0            1            0           NA

我很难想出一个错误的例子,但也没有必要。实际上,我们不使用调试器进行预测;当我们真的出错时我们使用它;在这种情况下,调试器可以找到有问题的因子变量。

也许有人会争辩说逻辑变量与虚拟变量没有什么不同。但请尝试下面的简单示例:它确实取决于您的公式。

u <- c(TRUE, TRUE, FALSE, FALSE)
v <- c(1, 1, 0, 0)  ## "numeric" dummy of `u`

model.matrix(~ u)
#  (Intercept) uTRUE
#1           1     1
#2           1     1
#3           1     0
#4           1     0

model.matrix(~ v)
#  (Intercept) v
#1           1 1
#2           1 1
#3           1 0
#4           1 0

model.matrix(~ u - 1)
#  uFALSE uTRUE
#1      0     1
#2      0     1
#3      1     0
#4      1     0

model.matrix(~ v - 1)
#  v
#1 1
#2 1
#3 0
#4 0

使用lm

"model.frame"方法实现更灵活

还建议您阅读 R: how to debug "factor has new levels" error for linear model and prediction,其中解释了 lmglm 在您的数据集上所做的事情。你会明白上面列出的步骤 0 到 4 只是试图模仿这样的内部过程。请记住,实际用于模型拟合的数据可能与您传入的数据大不相同

我们的步骤与此类内部处理并不完全一致。为了进行比较,您可以使用 lmglm 中的 method = "model.frame" 来检索内部处理的结果。在之前构造的小示例 dat 上尝试此操作,其中 f2 是字符变量。

dat_internal <- lm(y ~ x + f1 + f2, dat, method = "model.frame")

dat_internal
#  y x f1 f2
#1 1 1  a  A
#2 2 2  a  A
#3 3 3  b  A

str(dat_internal)
#'data.frame':  3 obs. of  4 variables:
# $ y : int  1 2 3
# $ x : int  1 2 3
# $ f1: Factor w/ 2 levels "a","b": 1 1 2
# $ f2: chr  "A" "A" "A"
## [.."terms" attribute is truncated..]

实际上,model.frame 只会执行第 0 步和第 1 步。它还会删除数据集中提供的变量,但不会删除模型公式中提供的变量。因此,模型框架的行数和列数可能比您输入的 lmglm 少。在我们的步骤 2 中完成的类型强制由稍后的 model.matrix 完成,其中可能会产生 "contrasts error"。

首先获取此内部模型框架,然后将其传递给 debug_contr_error(这样它基本上只执行步骤 2 到 4)有一些优点。

优点 1:模型公式中未使用的变量将被忽略

## no variable `f1` in formula
dat_internal <- lm(y ~ x + f2, dat, method = "model.frame")

## compare the following
debug_contr_error(dat)$nlevels
#f1 f2 
# 2  1 

debug_contr_error(dat_internal)$nlevels
#f2 
# 1 

优势2:能够应对变换后的变量

对模型公式中的变量进行变换是有效的,model.frame将记录变换后的变量而不是原始变量。请注意,即使您的原始变量没有 NA,转换后的变量也可以有。

dat <- data.frame(y = 1:4, x = c(1:3, -1), f = rep(letters[1:2], c(3, 1)))
#  y  x f
#1 1  1 a
#2 2  2 a
#3 3  3 a
#4 4 -1 b

lm(y ~ log(x) + f, data = dat)
#Error in `contrasts<-`(`*tmp*`, value = contr.funs[1 + isOF[nn]]) : 
#  contrasts can be applied only to factors with 2 or more levels
#In addition: Warning message:
#In log(x) : NaNs produced

# directly using `debug_contr_error` is hopeless here
debug_contr_error(dat)$nlevels
#f 
#2 

## this works
dat_internal <- lm(y ~ log(x) + f, data = dat, method = "model.frame")
#  y    log(x) f
#1 1 0.0000000 a
#2 2 0.6931472 a
#3 3 1.0986123 a

debug_contr_error(dat_internal)$nlevels
#f 
#1

鉴于这些好处,我编写了另一个函数来包装 model.framedebug_contr_error

输入:

  • form是你的模型公式;
  • dat是通过data参数传递给lmglm的数据集;
  • subset_vec 是通过 subset 参数传递给 lmglm 的索引向量。

输出:一个包含

的列表
  • mf(一个数据框)给出模型框("terms"属性被删除);
  • nlevels(列表)给出所有因子变量的因子水平数;
  • levels(向量)给出所有因子变量的水平。

## note: this function relies on `debug_contr_error`
debug_contr_error2 <- function (form, dat, subset_vec = NULL) {
  ## step 0
  if (!is.null(subset_vec)) {
    if (mode(subset_vec) == "logical") {
      if (length(subset_vec) != nrow(dat)) {
        stop("'logical' `subset_vec` provided but length does not match `nrow(dat)`")
        }
      subset_log_vec <- subset_vec
      } else if (mode(subset_vec) == "numeric") {
      ## check range
      ran <- range(subset_vec)
      if (ran[1] < 1 || ran[2] > nrow(dat)) {
        stop("'numeric' `subset_vec` provided but values are out of bound")
        } else {
        subset_log_vec <- logical(nrow(dat))
        subset_log_vec[as.integer(subset_vec)] <- TRUE
        } 
      } else {
      stop("`subset_vec` must be either 'logical' or 'numeric'")
      }
    dat <- base::subset(dat, subset = subset_log_vec)
    }
  ## step 0 and 1
  dat_internal <- stats::lm(form, data = dat, method = "model.frame")
  attr(dat_internal, "terms") <- NULL
  ## rely on `debug_contr_error` for steps 2 to 4
  c(list(mf = dat_internal), debug_contr_error(dat_internal, NULL))
  }

尝试前面的 log 转换示例。

debug_contr_error2(y ~ log(x) + f, dat)
#$mf
#  y    log(x) f
#1 1 0.0000000 a
#2 2 0.6931472 a
#3 3 1.0986123 a
#
#$nlevels
#f 
#1 
#
#$levels
#$levels$f
#[1] "a"
#
#
#Warning message:
#In log(x) : NaNs produced

也试试 subset_vec

## or: debug_contr_error2(y ~ log(x) + f, dat, c(T, F, T, T))
debug_contr_error2(y ~ log(x) + f, dat, c(1,3,4))
#$mf
#  y   log(x) f
#1 1 0.000000 a
#3 3 1.098612 a
#
#$nlevels
#f 
#1 
#
#$levels
#$levels$f
#[1] "a"
#
#
#Warning message:
#In log(x) : NaNs produced

每组模型拟合和 NA 作为因子水平

If you are fitting model by group, you are more likely to get a "contrasts error". 你需要

  1. 按分组变量拆分数据框(参见 ?split.data.frame);
  2. 一个一个地处理这些数据帧,应用debug_contr_error2lapply函数可以帮助完成这个循环)。

Some also told me that they can not use na.omit on their data, because it will end up too few rows to do anything sensible.这个可以放宽。实际上,必须省略 NA_integer_NA_real_,但可以保留 NA_character_:只需添加 NA 作为因子水平。为此,您需要遍历数据框中的变量:

  • 如果变量 x 已经是一个因子 并且 anyNA(x)TRUE,则执行 x <- addNA(x)。 "and" 很重要。如果 x 没有 NAaddNA(x) 将添加一个未使用的 <NA> 级别。
  • 如果变量 x 是一个字符,执行 x <- factor(x, exclude = NULL) 将其强制转换为一个因子。 exclude = NULL 将保留 <NA> 作为一个级别。
  • 如果 x 是 "logical"、"numeric"、"raw" 或 "complex",则不应更改任何内容。 NA 就是 NA.

<NA>因子水平不会被droplevelsna.omit降低,对构建模型矩阵有效。检查以下示例。

## x is a factor with NA

x <- factor(c(letters[1:4], NA))  ## default: `exclude = NA`
#[1] a    b    c    d    <NA>     ## there is an NA value
#Levels: a b c d                  ## but NA is not a level

na.omit(x)  ## NA is gone
#[1] a b c d
#[.. attributes truncated..]
#Levels: a b c d

x <- addNA(x)  ## now add NA into a valid level
#[1] a    b    c    d    <NA>
#Levels: a b c d <NA>  ## it appears here

droplevels(x)    ## it can not be dropped
#[1] a    b    c    d    <NA>
#Levels: a b c d <NA>

na.omit(x)  ## it is not omitted
#[1] a    b    c    d    <NA>
#Levels: a b c d <NA>

model.matrix(~ x)   ## and it is valid to be in a design matrix
#  (Intercept) xb xc xd xNA
#1           1  0  0  0   0
#2           1  1  0  0   0
#3           1  0  1  0   0
#4           1  0  0  1   0
#5           1  0  0  0   1

## x is a character with NA

x <- c(letters[1:4], NA)
#[1] "a" "b" "c" "d" NA 

as.factor(x)  ## this calls `factor(x)` with default `exclude = NA`
#[1] a    b    c    d    <NA>     ## there is an NA value
#Levels: a b c d                  ## but NA is not a level

factor(x, exclude = NULL)      ## we want `exclude = NULL`
#[1] a    b    c    d    <NA>
#Levels: a b c d <NA>          ## now NA is a level

一旦您将 NA 添加为因素/字符中的一个级别,您的数据集可能会突然拥有更完整的案例。然后你可以运行你的模型。如果您仍然得到 "contrasts error",请使用 debug_contr_error2 查看发生了什么。

对于方便起见,我为此 NA 预处理编写了一个函数。

输入:

  • dat 是您的 完整 数据集。

输出:

  • 一个数据框,添加了 NA 作为因子/字符的水平。

NA_preproc <- function (dat) {
  for (j in 1:ncol(dat)) {
    x <- dat[[j]]
    if (is.factor(x) && anyNA(x)) dat[[j]] <- base::addNA(x)
    if (is.character(x)) dat[[j]] <- factor(x, exclude = NULL)
    }
  dat
  }

可重复的案例研究和讨论

以下是专门为可重现的案例研究选择的,因为我刚刚用这里创建的三个辅助函数回答了它们。

  • R: Error in contrasts when fitting linear models with `lm`

还有其他一些 good-quality 线程已由其他 Whosebug 用户解决:

  • (这是按组拟合模型)
  • (这与前面列表中的情况 1 类似)
  • Factor/level error in mixed model(另一个post关于按组拟合模型)

此答案旨在调试模型拟合期间的 "contrasts error"。但是,使用 predict 进行预测时也会出现此错误。这种行为不是 predict.lmpredict.glm,而是来自某些包的预测方法。以下是 Whosebug 上的一些相关主题。

  • Probability predictions with cumulative link mixed models

另请注意,此答案的理念是基于 lmglm 的理念。 These two functions are a coding standard for many model fitting routines,但可能并非所有模型拟合例程的行为都相似。例如,以下内容对我来说并不清楚我的辅助函数是否真的有用。

  • Error with svychisq - 'contrast can be applied to factors with 2 or more levels'
  • R packages effects & plm : "error in contrasts" when trying to plot marginal effects
  • Contrasts can be applied only to factor
  • R: lawstat::levene.test fails while Fligner Killeen works, as well as car::leveneTest
  • R - geeglm Error: contrasts can be applied only to factors with 2 or more levels

虽然有点 off-topic,但了解有时 "contrasts error" 仅仅是因为编写了一段错误的代码仍然很有用。在以下示例中,OP 将其变量的名称而不是它们的值传递给 lm。由于名称是单值字符,它后来被强制转换为 single-level 因子并导致错误。

  • Error in `contrasts<-`(`*tmp*`, value = contr.funs[1 + isOF[nn]]) : contrasts can be applied only to factors with 2 or more levels

调试后如何解决这个错误?

实际上人们想知道如何在统计层面或编程层面解决这个问题。

如果您在完整数据集上拟合模型,那么可能没有统计解决方案,除非您可以估算缺失值或收集更多数据。因此,您可以简单地转向编码解决方案以删除有问题的变量。 debug_contr_error2 returns nlevels 这可以帮助您轻松找到它们。如果不想丢弃它们,将它们替换为 1 的向量(如 中所述)并让 lmglm 处理结果 rank-deficiency。

如果您在子集上拟合模型,可以有统计解决方案。

按组拟合模型并不一定需要按组拆分数据集并拟合独立模型。以下可能会给你一个大概的概念:

如果你明确地拆分你的数据,你可以很容易地得到"contrasts error",因此必须每组调整你的模型公式(即你需要动态生成模型公式)。一个更简单的解决方案是跳过为该组构建模型。

您还可以将数据集随机划分为训练子集和测试子集,这样您就可以 cross-validation。 R: how to debug "factor has new levels" error for linear model and prediction简单提一下,最好分层抽样,保证训练部分的模型估计和测试部分的预测都成功。

也许一个非常快速的步骤是验证您确实至少有 2 个因素。我找到的快速方法是:

df %>% dplyr::mutate_all(as.factor) %>% str

根据我十分钟前的经验,这种情况可能发生在有多个类别但有很多 NA 的情况下。以 Kaggle Houseprice Dataset 为例, 如果你加载了数据并且运行一个简单的回归,

train.df = read.csv('train.csv')
lm1 = lm(SalePrice ~ ., data = train.df)

你会得到同样的错误。我也尝试测试每个因素的水平数,但 none 说它少于 2 个水平。

cols = colnames(train.df)
for (col in cols){
  if(is.factor(train.df[[col]])){
    cat(col, ' has ', length(levels(train.df[[col]])), '\n')
  }
}

所以弄了半天用summary(train.df)查看每个col的细节,去掉了一些,终于成功了:

train.df = subset(train.df, select=-c(Id, PoolQC,Fence, MiscFeature, Alley, Utilities))
lm1 = lm(SalePrice ~ ., data = train.df)

并删除其中任何一个,回归失败 运行 再次出现相同的错误(我自己测试过)。

使用大量 NA 调试此错误的另一种方法是,用列的最常见属性替换每个 NA。请注意以下方法 cannot debug,其中 NA 是列的模式,我建议单独删除这些列或手动替换这些列,而不是像这样在整个数据集上应用一个函数:

fill.na.with.mode = function(df){
    cols = colnames(df)
    for (col in cols){
        if(class(df[[col]])=='factor'){
            x = summary(df[[col]])
            mode = names(x[which.max(x)])
            df[[col]][is.na(df[[col]])]=mode
        }
        else{
            df[[col]][is.na(df[[col]])]=0
        }
    }
    return (df)
}

以上属性一般有1400+ NA和10个有用值,所以你可能想删除这些垃圾属性,即使它们有3或4个级别。我想计算每列中有多少 NA 的函数会有所帮助。