Domanda

Io sono il montaggio di un modello ai dati dei fattori e predire. Se il newdata in predict.lm() contiene un unico livello di fattore che è sconosciuto al modello, tutti di predict.lm() non riesce e restituisce un errore.

C'è un buon modo per avere predict.lm() restituire una previsione per quei livelli di fattore del modello conosce e NA per i livelli incognita, invece che solo un errore?

Esempio di codice:

foo <- data.frame(response=rnorm(3),predictor=as.factor(c("A","B","C")))
model <- lm(response~predictor,foo)
foo.new <- data.frame(predictor=as.factor(c("A","B","C","D")))
predict(model,newdata=foo.new)

Vorrei l'ultimo comando per tornare tre predizioni "reali" corrispondente a livelli di fattore "A", "B" e "C" ed un NA corrispondente al livello sconosciuto "D".

È stato utile?

Soluzione

riordinato e ampliato la funzione da MorgenBall . E 'implementato anche in sperrorest ora .

Caratteristiche aggiuntive

  • scende livelli di fattore non utilizzati e non solo l'impostazione dei valori mancanti per NA.
  • emette un messaggio all'utente che i livelli del fattore sono state abbandonate
  • controlli per l'esistenza di variabili fattore in test_data e restituisce data.frame originale se non sono presenti
  • funziona non solo per lm, glm e ma anche per glmmPQL

Nota: la funzione mostrata qui può cambiare (migliorare) nel corso del tempo.

#' @title remove_missing_levels
#' @description Accounts for missing factor levels present only in test data
#' but not in train data by setting values to NA
#'
#' @import magrittr
#' @importFrom gdata unmatrix
#' @importFrom stringr str_split
#'
#' @param fit fitted model on training data
#'
#' @param test_data data to make predictions for
#'
#' @return data.frame with matching factor levels to fitted model
#'
#' @keywords internal
#'
#' @export
remove_missing_levels <- function(fit, test_data) {

  # https://stackoverflow.com/a/39495480/4185785

  # drop empty factor levels in test data
  test_data %>%
    droplevels() %>%
    as.data.frame() -> test_data

  # 'fit' object structure of 'lm' and 'glmmPQL' is different so we need to
  # account for it
  if (any(class(fit) == "glmmPQL")) {
    # Obtain factor predictors in the model and their levels
    factors <- (gsub("[-^0-9]|as.factor|\\(|\\)", "",
                     names(unlist(fit$contrasts))))
    # do nothing if no factors are present
    if (length(factors) == 0) {
      return(test_data)
    }

    map(fit$contrasts, function(x) names(unmatrix(x))) %>%
      unlist() -> factor_levels
    factor_levels %>% str_split(":", simplify = TRUE) %>%
      extract(, 1) -> factor_levels

    model_factors <- as.data.frame(cbind(factors, factor_levels))
  } else {
    # Obtain factor predictors in the model and their levels
    factors <- (gsub("[-^0-9]|as.factor|\\(|\\)", "",
                     names(unlist(fit$xlevels))))
    # do nothing if no factors are present
    if (length(factors) == 0) {
      return(test_data)
    }

    factor_levels <- unname(unlist(fit$xlevels))
    model_factors <- as.data.frame(cbind(factors, factor_levels))
  }

  # Select column names in test data that are factor predictors in
  # trained model

  predictors <- names(test_data[names(test_data) %in% factors])

  # For each factor predictor in your data, if the level is not in the model,
  # set the value to NA

  for (i in 1:length(predictors)) {
    found <- test_data[, predictors[i]] %in% model_factors[
      model_factors$factors == predictors[i], ]$factor_levels
    if (any(!found)) {
      # track which variable
      var <- predictors[i]
      # set to NA
      test_data[!found, predictors[i]] <- NA
      # drop empty factor levels in test data
      test_data %>%
        droplevels() -> test_data
      # issue warning to console
      message(sprintf(paste0("Setting missing levels in '%s', only present",
                             " in test data but missing in train data,",
                             " to 'NA'."),
                      var))
    }
  }
  return(test_data)
}

Possiamo applicare questa funzione per l'esempio nella questione come segue:

predict(model,newdata=remove_missing_levels (fit=model, test_data=foo.new))

Durante il tentativo di migliorare questa funzione, mi sono imbattuto il fatto che SL metodi come lm apprendimento, glm ecc necessità gli stessi livelli in treno e di prova, mentre ML metodi di apprendimento (svm, randomForest) non riuscire se i livelli sono stati rimossi. Questi metodi hanno bisogno di tutti i livelli in treno e di prova.

Una soluzione generale è molto difficile da realizzare, poiché ogni modello adattato ha un modo diverso di memorizzare loro componente livello fattore (fit$xlevels per lm e fit$contrasts per glmmPQL). Almeno sembra essere coerenti tra modelli correlati lm.

Altri suggerimenti

È necessario rimuovere i livelli extra prima di qualsiasi calcolo, come:

> id <- which(!(foo.new$predictor %in% levels(foo$predictor)))
> foo.new$predictor[id] <- NA
> predict(model,newdata=foo.new)
         1          2          3          4 
-0.1676941 -0.6454521  0.4524391         NA 

Questo è un modo più generale di farlo, sarà impostato tutti i livelli che non si verificano nei dati originali a NA. Come Hadley accennato nei commenti, che avrebbero potuto scegliere di includere questo nella funzione predict(), ma non hanno

Perché quello che dovete fare che diventa evidente se si guarda al calcolo stesso. Internamente, le previsioni sono calcolate come:

model.matrix(~predictor,data=foo) %*% coef(model)
        [,1]
1 -0.1676941
2 -0.6454521
3  0.4524391

In fondo si dispone di entrambe le matrici modello. Si vede che quella per foo.new ha una colonna in più, quindi non è possibile utilizzare il calcolo della matrice più. Se si desidera utilizzare il nuovo set di dati del modello, si potrebbe anche ottenere un modello diverso, essendo uno con una variabile dummy in più per il livello supplementare.

> model.matrix(~predictor,data=foo)
  (Intercept) predictorB predictorC
1           1          0          0
2           1          1          0
3           1          0          1
attr(,"assign")
[1] 0 1 1
attr(,"contrasts")
attr(,"contrasts")$predictor
[1] "contr.treatment"

> model.matrix(~predictor,data=foo.new)
  (Intercept) predictorB predictorC predictorD
1           1          0          0          0
2           1          1          0          0
3           1          0          1          0
4           1          0          0          1
attr(,"assign")
[1] 0 1 1 1
attr(,"contrasts")
attr(,"contrasts")$predictor
[1] "contr.treatment"

È possibile non solo eliminare l'ultima colonna dalla matrice del modello sia, perché, anche se si fa questo, entrambi gli altri livelli sono ancora influenzati. Il codice per A livello sarebbe (0,0). Per questo è B (1,0), per C questo (0,1) ... e per D è ancora una volta (0,0)! Così il vostro modello sarebbe supporre che A e D sono allo stesso livello se sarebbe ingenuamente cadere la variabile di ultima manichino.

In una parte più teorica: è possibile costruire un modello senza avere tutti i livelli. Ora, come ho cercato di spiegare prima, quel modello è solo valida per i livelli che hai utilizzato durante la creazione del modello. Se vi imbattete in nuovi livelli, è necessario costruire un nuovo modello per includere le informazioni supplementari. Se non lo fai, l'unica cosa che puoi fare è eliminare i livelli extra dal set di dati. Ma poi che, fondamentalmente, perde tutte le informazioni che era contenuta in esso, quindi è generalmente considerata buona pratica.

Se si vuole affrontare con i livelli mancanti nei dati dopo aver creato il vostro modello lm, ma prima di chiamare prevedere (dato che non sappiamo esattamente che cosa i livelli potrebbero mancare in anticipo) qui è la funzione che ho costruito per impostare tutti i livelli non nel modello a NA - la previsione sarà anche poi dare NA e quindi è possibile utilizzare un metodo alternativo per prevedere questi valori

.

oggetto sarà la vostra uscita da lm lm (..., data = trainData)

dati sarà la cornice di dati che si desidera creare previsioni per

missingLevelsToNA<-function(object,data){

  #Obtain factor predictors in the model and their levels ------------------

  factors<-(gsub("[-^0-9]|as.factor|\\(|\\)", "",names(unlist(object$xlevels))))
  factorLevels<-unname(unlist(object$xlevels))
  modelFactors<-as.data.frame(cbind(factors,factorLevels))


  #Select column names in your data that are factor predictors in your model -----

  predictors<-names(data[names(data) %in% factors])


  #For each factor predictor in your data if the level is not in the model set the value to NA --------------

  for (i in 1:length(predictors)){
    found<-data[,predictors[i]] %in% modelFactors[modelFactors$factors==predictors[i],]$factorLevels
    if (any(!found)) data[!found,predictors[i]]<-NA
  }

  data

}

Suoni come te potrebbe piacere effetti casuali. Guardate in qualcosa di simile glmer (pacchetto lme4). Con un modello bayesiano, si otterrà gli effetti che si avvicinano a 0 quando c'è poca informazione da utilizzare nella stima di loro. Attenzione, però, che si dovrà fare la previsione da soli, piuttosto che usare prevedere ().

In alternativa, si può semplicemente fare fittizi variabili per i livelli che si desidera includere nel modello, per esempio una variabile 0/1 per il Lunedi, uno per il Martedì, uno per Mercoledì, ecc Domenica verrà automaticamente rimosso dal modello se contiene tutti i 0 di. Ma avere un 1 nella colonna Domenica negli altri dati non mancherà la fase di previsione. E 'solo supporre che Domenica ha un effetto che nella media degli altri giorni (che può o non può essere vero).

Una delle ipotesi di Linear / logistica regressioni è di poco o nessun multi-collinearità; quindi se le variabili predittive sono idealmente indipendenti l'uno dall'altro, quindi il modello non ha bisogno di vedere tutte le possibili varietà di livelli di fattore. Un nuovo livello fattore (D) è un nuovo predittore, e può essere impostato NA senza compromettere la capacità predire dei rimanenti fattori A, B, C. Questo è il motivo per cui il modello dovrebbe essere ancora in grado di fare previsioni. Ma aggiunta del nuovo livello D getta via lo schema previsto. Questo è l'intera questione. Impostazione correzioni NA che.

Il pacchetto lme4 gestirà nuovi livelli se si imposta la allow.new.levels=TRUE bandiera al momento della chiamata predict.

Esempio: se il giorno della settimana è il fattore in un dow variabile e un b_fail esito categorica, è possibile eseguire

M0 <- lmer(b_fail ~ x + (1 | dow), data=df.your.data, family=binomial(link='logit')) M0.preds <- predict(M0, df.new.data, allow.new.levels=TRUE)

Questo è un esempio di un'effetti casuali regressione logistica. Naturalmente, è possibile eseguire la regressione normale ... o la maggior parte dei modelli GLM. Se si vuole la testa più in basso il percorso Bayesiano, sguardo Gelman & Hill del libro eccellente e le infrastrutture del Stan .

Una soluzione veloce-e-sporco per split test, è ricodificare valori rari come "altro". Ecco un'implementazione:

rare_to_other <- function(x, fault_factor = 1e6) {
  # dirty dealing with rare levels:
  # recode small cells as "other" before splitting to train/test,
  # assuring that lopsided split occurs with prob < 1/fault_factor
  # (N.b. not fully kosher, but useful for quick and dirty exploratory).

  if (is.factor(x) | is.character(x)) {
    min.cell.size = log(fault_factor, 2) + 1
    xfreq <- sort(table(x), dec = T)
    rare_levels <- names(which(xfreq < min.cell.size))
    if (length(rare_levels) == length(unique(x))) {
      warning("all levels are rare and recorded as other. make sure this is desirable")
    }
    if (length(rare_levels) > 0) {
      message("recoding rare levels")
      if (is.factor(x)) {
        altx <- as.character(x)
        altx[altx %in% rare_levels] <- "other"
        x <- as.factor(altx)
        return(x)
      } else {
        # is.character(x)
        x[x %in% rare_levels] <- "other"
        return(x)
      }
    } else {
      message("no rare levels encountered")
      return(x)
    }
  } else {
    message("x is neither a factor nor a character, doing nothing")
    return(x)
  }
}

Ad esempio, con data.table, la chiamata sarebbe qualcosa di simile:

dt[, (xcols) := mclapply(.SD, rare_to_other), .SDcol = xcols] # recode rare levels as other

dove xcols è un qualsiasi sottoinsieme di colnames(dt).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top