如何调试“对比度只能应用于具有 2 个或更多水平的因子”错误?

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

提问人:Troy 提问时间:5/26/2017 最后编辑:starballTroy 更新时间:12/22/2022 访问量:162438

问:

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

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 中?

回归 LM GLM R-FAQ

评论

1赞 Troy 5/26/2017
@李哲源ZheyuanLi 哦,我去过,几乎看了我能找到的每一个!他们要么说原因是“NA”值,要么是只有 1 个水平的因子变量。但是正如你所看到的,我使用的所有因子都没有接近 1 个级别,并且我运行了一个 Hmisc 描述,它没有显示缺失值!
0赞 Mohamed Rahouma 8/3/2022
请参阅此链接 (statology.org/...)。
0赞 Simon Woodward 4/3/2023
原因之一可能是因为输入 data.frame 是分组的,而其中一个组只有单因子水平。只需使用 dplyr::ungroup()。

答:

139赞 Zheyuan Li 5/26/2017 #1

介绍

什么是“对比误差”已经得到了很好的解释:你有一个只有一个水平(或更少)的因子。但实际上,这个简单的事实很容易被掩盖,因为实际用于模型拟合的数据可能与你传入的数据有很大不同。当您拥有数据、已对数据进行子集化、因子具有未使用的水平,或者已转换变量并到达某个位置时,就会发生这种情况。您很少处于这种理想情况下,可以直接从 str(your_data_frame) 中发现单水平因子。StackOverflow 上关于此错误的许多问题都是不可重现的,因此人们的建议可能会起作用,也可能不会起作用。因此,尽管到目前为止有 118 个关于此问题的帖子,但用户仍然无法找到自适应解决方案,因此这个问题一次又一次地被提出。这个答案是我尝试,“一劳永逸”地解决这个问题,或者至少提供一个合理的指导。NANaN

这个答案信息丰富,让我先做一个快速的总结。

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

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

  1. 运行以获取更完整的案例;NA_preproc
  2. 运行模型,如果出现“对比度错误”,请使用 For Debugging。debug_contr_error2

大多数答案都一步一步地告诉你如何以及为什么定义这些函数。跳过这些开发过程可能没有坏处,但不要跳过“可重现的案例研究和讨论”中的部分。


修订后的答案

最初的答案非常适合 OP,成功地帮助了其他一些人。但由于缺乏适应性,它在其他地方失败了。查看问题中的输出。OP 的变量是数值或因子;没有字符。最初的答案是针对这种情况。如果您有字符变量,尽管它们在拟合期间会被强制转换为因子,但代码不会报告它们,因为它们不是作为因子提供的,因此会错过它们。在这个扩展中,我将使原始答案更具适应性。str(ad.train)lmglmis.factor

假设将数据集传递给 或 。如果您不容易拥有这样的数据框,即所有变量都分散在全局环境中,则需要将它们收集到数据框中。以下可能不是最好的方法,但它有效。datlmglm

## `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:显式子集

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

## `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 步,则可以跳过此步骤,因为子集会自动删除未完成的事例

第 2 步:模式检查和转换

数据框列通常是一个原子向量,其模式如下:“逻辑”、“数值”、“复杂”、“字符”、“原始”。对于回归,不同模式的变量的处理方式不同。

"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`

逻辑变量很棘手。它可以被视为虚拟变量 ( for ; for ),因此是一个“数字”,或者它可以被强制转换为两级因子。这完全取决于是否需要从模型公式的规范中考虑“因素”强制。为简单起见,我们可以这样理解它:它总是被强制到一个因子上,但应用对比的结果最终可能会得到相同的模型矩阵,就好像它被直接作为虚拟处理一样。1TRUE0FALSEmodel.matrix

有些人可能想知道为什么不包括“整数”。因为整数向量,如 ,具有“数值”模式(try )。1:4mode(1:4)

数据框列也可以是具有“AsIs”类的矩阵,但此类矩阵必须具有“数值”模式。

我们的检查是在以下情况下产生错误

  • 发现“复合体”或“原始”;
  • 找到“逻辑”或“字符”矩阵变量;

并继续将“逻辑”和“字符”转换为“因子”类的“数字”。

## 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)

请注意,如果数据框列已经是因子变量,则它不会包含在 中,因为因子变量具有“数值”模式(try )。ind1mode(factor(letters[1:4]))

第 3 步:删除未使用的因子水平

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

## 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 步:汇总因子变量

现在我们准备看看 or 实际使用了什么因子水平以及使用了多少因子水平: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您的数据帧是传递给参数还是通过参数传递;lmglmdata
  • subset_vec是传递给参数或通过参数传递的索引向量。lmglmsubset

输出:一个列表,其中包含

  • 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_errorf2

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

请注意,没有水平的因子变量也会导致“对比度误差”。您可能想知道 0 级因子是如何实现的。嗯,这是合法的:.在这里,如果您没有完整的案例,您最终将得到 0 级因子。nlevels(factor(character(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

我们的调试器将预测“对比度错误”,但它真的会发生吗?

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

不,至少这个不会失败(NA系数是由于模型的秩缺陷;别担心):

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

使用方法更灵活地实现"model.frame"lm

还建议您阅读 R:如何调试线性模型和预测的“因子具有新水平”错误,该错误解释了数据集的幕后操作和操作。您将了解上面列出的步骤 0 到 4 只是试图模仿这种内部过程。请记住,实际用于模型拟合的数据可能与传入的数据大不相同lmglm

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

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..]

在实践中,只会执行步骤 0 和步骤 1。它还会删除数据集中提供的变量,但不会删除模型公式中提供的变量。因此,模型框架的行数和列数可能比您输入的行数和列数少。在我们的步骤 2 中完成的类型强制是由后者完成的,其中可能会产生“对比度错误”。model.framelmglmmodel.matrix

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

优点 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 

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

在模型公式中转换变量是有效的,并且会记录转换后的变量而不是原始变量。请注意,即使您的原始变量没有 ,转换后的变量也可以有。model.frameNA

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

鉴于这些好处,我编写了另一个函数 wrap up 和 .model.framedebug_contr_error

输入

  • form是您的模型公式;
  • dat是传递给参数或通过参数传递的数据集;lmglmdata
  • subset_vec是传递给参数或通过参数传递的索引向量。lmglmsubset

输出:一个列表,其中包含

  • 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 作为因子水平

如果按组拟合模型,则更有可能出现“对比误差”。你需要

  1. 按分组变量拆分数据框(请参阅?split.data.frame);
  2. 逐个处理这些数据帧,应用(函数有助于执行此循环)。debug_contr_error2lapply

有些人还告诉我,他们不能在他们的数据上使用 na.omit,因为它最终会变得太少,无法做任何明智的事情。这可以放松。在实践中,必须省略 和,但可以保留:只需添加为因子水平即可。为此,您需要遍历数据框中的变量:NA_integer_NA_real_NA_character_NA

  • 如果变量已经是一个因子,并且 anyNA(x)TRUE,则执行 。“和”很重要。如果没有,将添加一个未使用的级别。xx <- addNA(x)xNAaddNA(x)<NA>
  • 如果变量是一个字符,则将其强制转换为因子。 将保留为一个级别。xx <- factor(x, exclude = NULL)exclude = NULL<NA>
  • 如果是“逻辑”、“数字”、“原始”或“复杂”,则不应更改任何内容。 只是.xNANA

<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

一旦您添加因子/字符中的级别,您的数据集可能会突然有更完整的案例。然后,您可以运行模型。如果仍然收到“对比度错误”,请使用以查看发生了什么。NAdebug_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
  }

可重复的案例研究和讨论

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

其他 StackOverflow 用户还解决了一些其他高质量的线程:

本答案旨在调试模型拟合过程中的“对比误差”。但是,在用于预测时,此错误也可能出现。这种行为不是使用 或 ,而是使用某些包中的预测方法。以下是 StackOverflow 上的一些相关线程。predictpredict.lmpredict.glm

另请注意,此答案的哲学基于 和 的哲学。这两个函数是许多模型拟合例程的编码标准,但可能并非所有模型拟合例程的行为都相似。例如,以下内容对我来说并不透明,我的帮助程序函数是否真的有帮助。lmglm

虽然有点跑题了,但知道有时“对比错误”仅仅来自编写错误的代码仍然很有用。在以下示例中,OP 将其变量的名称(而不是其值)传递给 。由于名称是单值字符,因此稍后会强制转换为单级因子并导致错误。lm


调试后如何解决此错误?

在实践中,人们想知道如何解决这个问题,无论是在统计层面还是在编程层面。

如果要对整个数据集进行模型拟合,则可能没有统计解决方案,除非您可以插补缺失值或收集更多数据。因此,您可以简单地求助于编码解决方案来删除有问题的变量。 退货,帮助您轻松找到它们。如果您不想删除它们,请将它们替换为向量 1(如“对比只能应用于具有 2 个或更多水平的因子”时如何进行 GLM 中所述?),并让或处理由此产生的秩缺陷。debug_contr_error2nlevelslmglm

如果要对子集拟合模型,则可以有统计解。

按组拟合模型不一定需要按组拆分数据集并拟合独立模型。以下内容可能会给您一个粗略的想法:

如果显式拆分数据,则很容易出现“对比误差”,因此必须调整每组的模型公式(即,需要动态生成模型公式)。更简单的解决方案是跳过为此组构建模型。

您还可以将数据集随机划分为训练子集和测试子集,以便进行交叉验证。R:如何调试线性模型和预测的“因子有新水平”错误简要提到这一点,最好进行分层抽样,以确保训练部分的模型估计和测试部分的预测都成功。

评论

2赞 Troy 5/26/2017
谢谢!这表明我的一个变量是“撒谎”,说明其因子中的水平。(我很愚蠢,只使用一个只包含其中一个因素的子集 - 因此是错误的)。你是冠军!
0赞 Zheyuan Li 9/4/2018
在下一次修订中要做的事情: 1>修复语法:在这个扩展中,我将使原始答案更具适应性。2> 修复语法:最后让我们看看一些 if 是逻辑变量的情况。3>添加所有链接进行欺骗;4>将每组模型拟合和NA作为因子水平一分为二;5> 使用时添加链接 stackoverflow.com/q/51952588/4891738 对比错误。f2&noredirection=1predict
0赞 Zheyuan Li 12/14/2019
旁注:从 R 3.6.2 开始,可以正常工作。get_all_vars
0赞 Nakx 9/12/2023
您可以使用函数 droplevels() 来删除未使用的级别。
3赞 Amit Kohli 6/21/2019 #2

也许作为一个非常快速的步骤,一个是验证你确实至少有 2 个因素。我发现的快速方法是:

df %>% dplyr::mutate_all(as.factor) %>% str
2赞 Pistachio Guoguo 3/3/2020 #3

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

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

你会得到同样的错误。我还尝试测试每个因子的水平数,但没有一个说它少于 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)

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

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

使用大量 NA 调试此错误的另一种方法是,将每个 NA 替换为列的最常见属性。请注意,以下方法无法调试 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 的函数会有所帮助。