web-dev-qa-db-fra.com

Comment déboguer "les contrastes ne peuvent être appliqués qu'aux facteurs avec 2 niveaux ou plus" d'erreur?

Voici toutes les variables avec lesquelles je travaille:

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

Voici ce que je cherche à adapter:

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

Je sais que c'est beaucoup de variables (le plan est de réduire via la sélection de variable en avant). Mais même savoir que beaucoup de variables sont soit int ou Factor; qui, si je comprends bien, devrait tout simplement fonctionner avec un médecin. Cependant, chaque fois que j'essaye de m'adapter à ce modèle, je reçois:

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

Quel genre de chose me fait penser que R ne traite pas mes variables de facteur comme des variables de facteur pour une raison quelconque?

Même quelque chose d'aussi simple que:

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

ne fonctionne pas! (même message d'erreur)

Où comme ceci:

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

Marchera!

Quelqu'un sait ce qui se passe ici? Pourquoi ne puis-je pas adapter ces variables de facteur à mon GLM?

Merci d'avance!

-Troy

20
Troy

Introduction

Ce qu'est une "erreur de contraste" a été bien expliqué: vous avez un facteur qui n'a qu'un niveau (ou moins) . Mais en réalité, ce simple fait peut facilement être masqué, car les données réellement utilisées pour l'ajustement du modèle peuvent être très différentes de celles que vous avez transmises. Cela se produit lorsque vous avez NA dans vos données, vous avez sous-configuré vos données, un facteur a des niveaux inutilisés ou vous avez transformé vos variables et obtenez NaN quelque part. Vous êtes rarement dans cette situation idéale où un facteur à un seul niveau peut être repéré directement de str(your_data_frame). De nombreuses questions sur StackOverflow concernant cette erreur ne sont pas reproductibles; par conséquent, les suggestions des utilisateurs peuvent ou non. peut ne pas fonctionner. Par conséquent, bien qu’il y ait à ce jour 118 messages , les utilisateurs ne trouvent toujours pas de solution adaptative, de sorte que cette question est posée à plusieurs reprises. Cette réponse est ma tentative, pour résoudre cette question "une fois pour toutes", ou du moins pour fournir un guide raisonnable.

Cette réponse est riche en informations, alors laissez-moi d’abord faire un bref résumé.

J'ai défini 3 fonctions d'assistance pour vous: debug_contr_error, debug_contr_error2, NA_preproc.

Je vous recommande de les utiliser de la manière suivante.

  1. lancez NA_preproc pour obtenir des cas plus complets;
  2. lancez votre modèle et si vous obtenez une "erreur de contraste", utilisez debug_contr_error2 pour le débogage.

La plupart des réponses vous montrent pas à pas comment et pourquoi ces fonctions sont définies. Il n'y a probablement aucun mal à ignorer ces processus de développement, mais ne sautez pas les sections de "Etudes de cas reproductibles et discussions".


Réponse révisée

Le réponse originalefonctionne parfaitement pour OP , et a aidé avec succès certains autres . Mais il avait échoué ailleurs faute d'adaptabilité. Regardez le résultat de str(ad.train) dans la question. Les variables de OP sont numériques ou des facteurs; il n'y a pas de personnages. La réponse initiale était pour cette situation. Si vous avez des variables de caractère, bien qu'elles soient forcées à prendre en compte des facteurs lors de l'ajustement lm et glm, elles ne seront pas signalées par le code car elles n'ont pas été fournies en tant que facteurs afin que is.factor vont nous manquer. Dans cette extension, je vais rendre la réponse originale plus adaptative.

Soit dat votre ensemble de données passé à lm ou glm. Si vous ne disposez pas facilement d'un tel cadre de données, c'est-à-dire que toutes vos variables sont dispersées dans l'environnement global, vous devez les regrouper dans un cadre de données. Ce qui suit n'est peut-être pas la meilleure solution, mais cela fonctionne.

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

Étape 0: sous-ensemble explicite

Si vous avez utilisé l'argument subset de lm ou glm, commencez par un sous-ensemble explicite:

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

Étape 1: supprimez les cas incomplets

dat <- na.omit(dat)

Vous pouvez ignorer cette étape si vous avez franchi l'étape 0, car subset élimine automatiquement les observations incomplètes .

Étape 2: vérification du mode et conversion

Une colonne de trame de données est généralement un vecteur atomique, avec un mode parmi: "logique", "numérique", "complexe", "caractère", "brut". Pour la régression, les variables de différents modes sont traitées différemment.

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

Une variable logique est délicate. Il peut soit être traité comme une variable muette (1 Pour TRUE; 0 Pour FALSE) et donc un "numérique", ou peut être forcé à un facteur à deux niveaux. Tout dépend de si model.matrix Pense qu'une contrainte "à factoriser" est nécessaire à partir de la spécification de votre formule de modèle. Pour simplifier, nous pouvons le comprendre comme tel: il est toujours contraint de prendre en compte un facteur, mais le résultat de l’application de contrastes peut aboutir à la même matrice de modèle que si elle était traitée directement comme un mannequin.

Certaines personnes peuvent se demander pourquoi "entier" n'est pas inclus. Parce qu'un vecteur entier, comme 1:4, A un mode "numérique" (try mode(1:4)).

Une colonne de trame de données peut également être une matrice avec la classe "AsIs", mais une telle matrice doit avoir le mode "numérique".

Notre vérification est de produire une erreur lorsque

  • un "complexe" ou "brut" est trouvé;
  • une variable de matrice "logique" ou "caractère" est trouvée;

et procéder à la conversion de "logical" et "character" en "numérique" de la classe "factor".

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

Notez que si une colonne de trame de données est déjà une variable de facteur, elle ne sera pas incluse dans ind1, Car une variable de facteur a le mode "numérique" (try mode(factor(letters[1:4]))).

étape 3: supprimez les niveaux de facteur non utilisés

Nous n’aurons pas de niveaux de facteur inutilisés pour les variables de facteur converties à partir de l’étape 2, c’est-à-dire celles indexées par ind1. Cependant, les variables factorielles fournies avec dat peuvent avoir des niveaux inutilisés (souvent à la suite des étapes 0 et 1). Nous devons supprimer tous les niveaux inutilisés possibles.

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

étape 4: récapitulatif des variables factorielles

Nous sommes maintenant prêts à voir quels et combien de niveaux de facteurs sont réellement utilisés par lm ou glm:

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

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

Pour vous simplifier la vie, j'ai résumé ces étapes dans une fonction debug_contr_error.

Entrée:

  • dat est votre trame de données passée à lm ou glm via data argument;
  • subset_vec Est le vecteur d'index transmis à l'argument lm ou glm via subset.

Sortie: une liste avec

  • nlevels (une liste) donne le nombre de niveaux de facteur pour toutes les variables de facteur;
  • levels (un vecteur) donne les niveaux pour toutes les variables factorielles.

La fonction produit un avertissement s'il n'y a pas de cas complets ni de variables factorielles à résumer.

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

Voici un petit exemple construit.

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

Bien, nous voyons une erreur. Maintenant, mon debug_contr_error Expose que f2 Se termine avec un seul niveau.

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

Notez que la réponse courte originale est sans espoir ici, car f2 Est fourni sous forme de variable de caractère et non de facteur.

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

Voyons maintenant un exemple avec une variable matricielle 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

Notez qu'une variable factorielle sans niveaux peut également causer une "erreur de contraste". Vous pouvez vous demander comment un facteur de niveau 0 est possible. Et bien c'est légitime: nlevels(factor(character(0))). Ici, vous vous retrouverez avec un facteur de niveau 0 si vous n'avez pas de cas complet.

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

Enfin, voyons quelques cas où f2 Est une variable logique.

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

Notre débogueur prédira une "erreur de contraste", mais cela arrivera-t-il vraiment?

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

Non, au moins celui-ci n'échoue pas ( le coefficient NA est dû au manque de rang du modèle; ne vous inquiétez pas ):

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

Il m'est difficile de trouver un exemple d'erreur, mais ce n'est pas nécessaire. En pratique, nous n'utilisons pas le débogueur pour la prédiction; on l'utilise quand on a vraiment une erreur; et dans ce cas, le débogueur peut localiser la variable de facteur incriminée.

Certains diront peut-être qu'une variable logique n'est pas différente d'un mannequin. Mais essayez l’exemple simple ci-dessous: cela dépend de votre formule.

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

Implémentation plus flexible en utilisant la méthode "model.frame" De lm

Il est également conseillé de passer R: comment déboguer l'erreur "facteur a de nouveaux niveaux" pour le modèle linéaire et la prédiction , ce qui explique ce que lm et glm font sous le capot sur votre jeu de données. Vous comprendrez que les étapes 0 à 4 énumérées ci-dessus ne font qu'essayer d'imiter un tel processus interne. N'oubliez pas que les données réellement utilisées pour l'ajustement du modèle peuvent être très différentes de celles que vous avez transmises .

Nos étapes ne sont pas complètement compatibles avec un tel traitement interne. Pour une comparaison, vous pouvez récupérer le résultat du traitement interne en utilisant method = "model.frame" Dans lm et glm. Essayez ceci sur le petit exemple précédemment construit datf2 Est une variable de caractère.

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

En pratique, model.frame N'exécutera que les étapes 0 et 1. Il supprimera également les variables fournies dans votre jeu de données mais pas dans votre formule de modèle. Ainsi, un cadre de modèle peut contenir à la fois moins de lignes et de colonnes que ce que vous avez alimenté lm et glm. La contrainte de type comme effectuée à l'étape 2 est effectuée par le dernier model.matrix Où une "erreur de contraste" peut être générée.

Il existe quelques avantages à obtenir d’abord ce cadre de modèle interne, puis à le transmettre à debug_contr_error (Afin qu’il effectue uniquement les étapes 2 à 4).

avantage 1: les variables non utilisées dans votre formule de modèle sont ignorées

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

avantage 2: capable de gérer des variables transformées

Il est valide de transformer des variables dans la formule du modèle et model.frame Enregistrera celles transformées au lieu de celles d'origine. Notez que, même si votre variable d'origine n'a pas de NA, celle qui a été transformée peut en avoir.

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

Compte tenu de ces avantages, j'écris une autre fonction qui résume model.frame Et debug_contr_error.

Entrée :

  • form est votre formule modèle;
  • dat est l'ensemble de données passé à lm ou glm via data argument;
  • subset_vec Est le vecteur d'index transmis à l'argument lm ou glm via subset.

Sortie: une liste avec

  • mf (un cadre de données) donne le cadre du modèle (attribut "termes" supprimé);
  • nlevels (une liste) donne le nombre de niveaux de facteur pour toutes les variables de facteur;
  • levels (un vecteur) donne les niveaux pour toutes les variables factorielles.
## 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))
  }

Essayez le précédent exemple de transformation 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

Essayez aussi 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

Ajustement du modèle par groupe et NA comme niveaux de facteur

Si vous adaptez le modèle par groupe, vous aurez plus de risques d’obtenir une "erreur de contraste". Vous devez

  1. divisez votre trame de données par la variable de regroupement (voir ?split.data.frame);
  2. travaillez un par un avec ces trames de données en appliquant debug_contr_error2 (la fonction lapply peut être utile pour effectuer cette boucle).

Certains m'ont aussi dit qu'ils ne peuvent pas utiliser na.omit Sur leurs données, car il y aura trop de lignes pour faire quoi que ce soit de raisonnable. Cela peut être assoupli. En pratique, ce sont les NA_integer_ Et NA_real_ Qui doivent être omis, mais NA_character_ Peut être conservé: ajoutez simplement NA en tant que niveau de facteur. Pour ce faire, vous devez parcourir les variables de votre bloc de données:

  • si une variable x est déjà un facteur et que anyNA(x) est TRUE, faites x <- addNA(x). Le "et" est important. Si x n'a pas de NA, addNA(x) ajoutera un niveau inutilisé <NA>.
  • si une variable x est un caractère, faites x <- factor(x, exclude = NULL) pour la contraindre à un facteur. exclude = NULL Conservera <NA> En tant que niveau.
  • si x est "logique", "numérique", "brut" ou "complexe", rien ne doit être changé. NA n'est que NA.

Le niveau de facteur <NA> Ne sera pas supprimé de droplevels ou na.omit, Et il est valide pour la construction d'une matrice de modèle. Vérifiez les exemples suivants.

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

Une fois que vous ajoutez NA en tant que niveau dans un facteur/caractère, votre jeu de données peut soudainement comporter des observations plus complètes. Ensuite, vous pouvez exécuter votre modèle. Si vous obtenez toujours une "erreur de contraste", utilisez debug_contr_error2 Pour voir ce qui s'est passé.

Pour votre commodité, j’écris une fonction pour ce prétraitement NA.

Entrée :

  • dat est votre jeu de données complet .

Sortie:

  • une trame de données, avec NA ajouté en tant que niveau pour facteur/caractère.
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
  }

Etudes de cas et discussions reproductibles

Les suivants sont spécialement sélectionnés pour des études de cas reproductibles, comme je viens de leur répondre avec les trois fonctions d'aide créées ici.

Il existe également quelques autres threads de bonne qualité résolus par d'autres utilisateurs de StackOverflow:

Cette réponse vise à déboguer "l'erreur de contraste" lors de l'ajustement du modèle. Cependant, cette erreur peut également apparaître lorsque vous utilisez predict pour la prédiction. Un tel comportement ne se produit pas avec predict.lm Ou predict.glm, Mais avec les méthodes de prédiction de certains paquets. Voici quelques discussions sur StackOverflow.

Notez également que la philosophie de cette réponse est basée sur celle de lm et glm. Ces deux fonctions constituent une norme de codage pour de nombreuses routines d’ajustement de modèles , mais toutes les routines d’ajustement de modèles ne se comportent peut-être pas de la même manière. Par exemple, ce qui suit ne semble pas clair pour moi si les fonctions d'assistance seraient réellement utiles.

Bien qu'un peu hors sujet, il est toujours utile de savoir que parfois une "erreur de contraste" vient simplement de l'écriture d'un mauvais morceau de code. Dans les exemples suivants, OP a passé le nom de leurs variables plutôt que leurs valeurs à lm. Dans la mesure où un nom est un caractère à valeur unique, il est ensuite contraint à un facteur à niveau unique et provoque l'erreur.


Comment résoudre cette erreur après le débogage?

En pratique, les gens veulent savoir comment résoudre ce problème, que ce soit au niveau statistique ou au niveau de la programmation.

Si vous ajustez des modèles sur l'ensemble de vos données, il n'existe probablement aucune solution statistique, à moins que vous ne puissiez imputer des valeurs manquantes ou collecter davantage de données. Ainsi, vous pouvez simplement vous tourner vers une solution de codage pour supprimer la variable incriminée. debug_contr_error2 Renvoie nlevels, ce qui vous aide à les localiser facilement. Si vous ne voulez pas les supprimer, remplacez-les par un vecteur de 1 (comme expliqué dans Comment faire un GLM lorsque "les contrastes ne peuvent être appliqués qu'à des facteurs à 2 ou plusieurs niveaux"? ) et laissez lm ou glm traiter de la déficience de rang qui en résulte.

Si vous ajustez des modèles sur un sous-ensemble, il peut exister des solutions statistiques.

L'ajustement de modèles par groupe ne nécessite pas nécessairement la division de votre jeu de données par groupe et l'ajustement de modèles indépendants. Ce qui suit peut vous donner une idée approximative:

Si vous scindez vos données de manière explicite, vous pouvez facilement obtenir une "erreur de contraste". Vous devez donc ajuster votre formule de modèle par groupe (en d'autres termes, vous devez générer de manière dynamique des formules de modèle). Une solution plus simple consiste à ignorer la construction d'un modèle pour ce groupe.

Vous pouvez également partitionner votre jeu de données de manière aléatoire en un sous-ensemble d'apprentissage et un sous-ensemble de test afin de pouvoir effectuer une validation croisée. R: comment déboguer "le facteur a de nouveaux niveaux" erreur pour le modèle linéaire et la prédiction le mentionne brièvement, et vous feriez mieux de faire un échantillonnage stratifié pour assurer le succès de l’estimation du modèle pour prédiction sur la partie test.

45
李哲源

Une première étape consiste peut-être à vérifier que vous avez bien au moins 2 facteurs. Le moyen rapide que j'ai trouvé était:

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