Как отлаживать "контрасты могут быть применены только к факторам с 2 или более уровнями"?

Вот все переменные, с которыми я работаю:

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?

Спасибо заранее!

-Troy

Ответ 1

Вступление

Что такое "ошибка контрастов", хорошо объяснено: у вас есть фактор, который имеет только один уровень (или меньше). Но в действительности этот простой факт может быть легко скрыт, потому что данные, которые фактически используются для подгонки модели, могут сильно отличаться от того, что вы передали. Это происходит, когда у вас есть NA в ваших данных, вы поднастроили свои данные, фактор имеет неиспользуемые уровни, или вы преобразовали свои переменные и где-то получили NaN. Вы редко находитесь в этой идеальной ситуации, когда одноуровневый фактор может быть обнаружен непосредственно из str(your_data_frame) напрямую. Многие вопросы в StackOverflow относительно этой ошибки не воспроизводимы, поэтому предложения людей могут работать, а могут и не работать. Поэтому, хотя на данный момент существует 118 сообщений по этой проблеме, пользователи все еще не могут найти адаптивное решение, чтобы этот вопрос поднимался снова и снова. Этот ответ - моя попытка решить этот вопрос "раз и навсегда" или, по крайней мере, предоставить разумное руководство.

Этот ответ содержит богатую информацию, поэтому позвольте мне сначала сделать краткое резюме.

Я определил для вас 3 вспомогательные функции: debug_contr_error, debug_contr_error2, NA_preproc.

Я рекомендую вам использовать их следующим образом.

  1. запустить NA_preproc чтобы получить более полные случаи;
  2. запустите вашу модель, и если вы получаете "ошибку контрастов", используйте debug_contr_error2 для отладки.

Большая часть ответа показывает вам шаг за шагом, как и почему эти функции определены. Вероятно, нет никакого вреда, чтобы пропустить этот процесс разработки, но не пропустите разделы из "Воспроизводимых тематических исследований и обсуждений".


Пересмотренный ответ

Оригинальный ответ отлично работает для ОП и успешно помог другим. Но он потерпел неудачу где-то еще из- за отсутствия адаптивности. Посмотрите на вывод str(ad.train) в вопросе. Переменные OP являются числовыми или факторами; нет персонажей. Первоначальный ответ был для этой ситуации. Если у вас есть символьные переменные, хотя они будут приведены к факторам во время glm lm и glm, они не будут сообщаться кодом, так как они не были предоставлены как факторы, поэтому is.factor будет их пропускать. В этом расширении я сделаю оригинальный ответ более адаптивным.

Пусть dat будет вашим набором данных, переданным в lm или glm. Если у вас нет такого фрейма данных, то есть все ваши переменные разбросаны в глобальной среде, вам нужно собрать их в фрейм данных. Следующее может быть не лучшим способом, но это работает.

## '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: явное подмножество

Если вы использовали аргумент subset lm или glm, начните с явного подмножества:

## '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, так как subset автоматически удаляет незавершенные случаи.

Шаг 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'

Логическая переменная хитрая. Она может быть либо обработана как фиктивная переменная (1 для TRUE; 0 для FALSE), следовательно, "числовая", либо может быть приведена к двухуровневому коэффициенту. Все зависит от того, считает ли model.matrix принуждение "к фактору" из спецификации формулы вашей модели. Для простоты мы можем понять это как таковое: оно всегда приводится к фактору, но результат применения контрастов может в итоге привести к той же матрице модели, как если бы она была обработана как фиктивная напрямую.

Некоторые люди могут задаться вопросом, почему "целое число" не включено. Потому что целочисленный вектор, как 1:4, имеет "числовой" режим (попробуйте mode(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)

Обратите внимание, что если столбец фрейма данных уже является факторной переменной, он не будет включен в ind1, поскольку факторная переменная имеет "числовой" режим (попробуйте mode(factor(letters[1:4]))).

Шаг 3: сбросить неиспользованные уровни факторов

У нас не будет неиспользованных факторных уровней для факторных переменных, преобразованных из шага 2, т. ind1 Индексированных по 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: суммировать факторные переменные

Теперь мы готовы увидеть, что и сколько факторных уровней фактически используется lm или glm:

## 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 - ваш фрейм данных, переданный в lm или glm через аргумент data;
  • subset_vec - индексный вектор, передаваемый в lm или glm через аргумент subset.

Вывод: список с

  • 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

Обратите внимание, что фактор-переменная без уровней также может вызывать "ошибку контрастов". Вы можете задаться вопросом, как возможен фактор нулевого уровня. Ну это законно: 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

Наконец, давайте посмотрим на ситуацию, когда 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: как отлаживать ошибку "фактор имеет новые уровни" для линейной модели и прогнозирования, которая объясняет, что делают lm и glm под капотом в вашем наборе данных. Вы поймете, что шаги с 0 по 4, перечисленные выше, просто пытаются имитировать такой внутренний процесс. Помните, что данные, которые фактически используются для подбора модели, могут сильно отличаться от данных, которые вы передали.

Наши шаги не полностью соответствуют такой внутренней обработке. Для сравнения вы можете получить результат внутренней обработки, используя method = "model.frame" в lm и glm. Попробуйте это на ранее построенном крошечном примере данных 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. Он также удаляет переменные, предоставленные в вашем наборе данных, но не в формуле модели. Таким образом, у рамки модели может быть меньше строк и столбцов, чем у lm и glm. Принудительное приведение типов, как было сделано в нашем шаге 2, выполняется более поздней model.matrix где может возникнуть "ошибка контрастов".

Есть несколько преимуществ: сначала получить этот внутренний фрейм модели, а затем передать его в 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.frame и debug_contr_error.

Вход:

  • form - это ваша модельная формула;
  • dat - набор данных, переданный в lm или glm через аргумент data;
  • subset_vec - индексный вектор, передаваемый в lm или glm через аргумент subset.

Вывод: список с

  • 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_error2 (функция lapply может быть полезна для выполнения этого цикла).

Некоторые также сказали мне, что они не могут использовать na.omit в своих данных, потому что в итоге будет слишком мало строк, чтобы сделать что-нибудь разумное. Это может быть расслаблено. На практике это NA_integer_ и NA_real_ которые должны быть опущены, но NA_character_ может быть сохранено: просто добавьте NA как уровень фактора. Для этого вам нужно перебрать переменные в вашем фрейме данных:

  • если переменная x уже является фактором, а anyNA(x) - TRUE, выполните x <- addNA(x). "И" важно. Если у x нет NA, addNA(x) добавит неиспользуемый уровень <NA>.
  • если переменная x является символом, выполните x <- factor(x, exclude = NULL) чтобы привести его к фактору. exclude = NULL сохранит <NA> в качестве уровня.
  • если x является "логическим", "числовым", "необработанным" или "сложным", ничего не должно быть изменено. NA это просто NA.

Уровень фактора <NA> не будет сбрасываться ни по droplevels ни по na.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 в качестве уровня фактора/персонажа, ваш набор данных может внезапно иметь более полные случаи. Тогда вы можете запустить свою модель. Если вы по-прежнему получаете "ошибку контрастов", используйте 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
  }

Воспроизводимые тематические исследования и обсуждения

Следующие материалы специально отобраны для воспроизводимых тематических исследований, поскольку я только что ответил на них с помощью трех вспомогательных функций, созданных здесь.

Есть также несколько других высококачественных потоков, решаемых другими пользователями StackOverflow:

Этот ответ направлен на отладку "ошибки контрастов" во время подбора модели. Однако, эта ошибка может оказаться при использовании predict для прогнозирования. Такое поведение не с помощью predict.lm или predict.glm, а с методами прогнозирования из некоторых пакетов. Вот несколько связанных тем на StackOverflow.

Также обратите внимание, что философия этого ответа основана на философии lm и glm. Эти две функции являются стандартом кодирования для многих процедур подбора моделей, но, возможно, не все процедуры подбора моделей ведут себя одинаково. Например, следующее не выглядит прозрачным для меня, будут ли полезны мои вспомогательные функции.

Хотя это и немного не по теме, все же полезно знать, что иногда "контрастная ошибка" просто возникает из-за написания неправильного фрагмента кода. В следующих примерах OP передал имя своих переменных, а не их значения, lm. Поскольку имя является символом с одним значением, оно позднее приводится к одноуровневому коэффициенту и вызывает ошибку.


Как устранить эту ошибку после отладки?

На практике люди хотят знать, как решить этот вопрос, либо на статистическом уровне, либо на уровне программирования.

Если вы подбираете модели в своем полном наборе данных, то, вероятно, не существует статистического решения, если только вы не можете рассчитать пропущенные значения или собрать больше данных. Таким образом, вы можете просто обратиться к решению для кодирования, чтобы удалить некорректную переменную. debug_contr_error2 возвращает nlevels которые помогут вам легко их найти. Если вы не хотите отбрасывать их, замените их вектором 1 (как описано в разделе Как сделать GLM, когда "контрасты могут применяться только к факторам с 2 или более уровнями"?) И разрешите lm или glm иметь дело с результирующий ранг-дефицит.

Если вы подбираете модели на подмножестве, могут быть статистические решения.

Подгонка моделей по группам не обязательно требует разделения набора данных по группам и подбора независимых моделей. Следующее может дать вам приблизительное представление:

Если вы делите свои данные явно, вы можете легко получить "ошибку контрастов", поэтому вам придется скорректировать формулу модели для каждой группы (то есть вам нужно динамически генерировать формулы модели). Более простое решение - пропустить построение модели для этой группы.

Вы также можете произвольно разделить ваш набор данных на обучающее подмножество и подмножество тестирования, чтобы вы могли выполнить перекрестную проверку. Р: как отлаживать ошибку "фактор имеет новые уровни" для линейной модели и прогнозирования, вкратце упоминается об этом, и вам лучше сделать стратифицированную выборку, чтобы обеспечить успех как оценки модели в обучающей части, так и прогнозирования в тестирующей части.

Ответ 2

Возможно, в качестве очень быстрого шага нужно убедиться, что у вас действительно есть как минимум 2 фактора. Быстрый способ, который я нашел, был:

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