Frage

Ich bin passend, ein Modell zu Faktordaten und Vorhersage-. Wenn die newdata in predict.lm() enthält einen einzigen Faktor Ebene, die dem Modell nicht bekannt ist, alle von predict.lm() schlägt fehl und gibt einen Fehler zurück.

Gibt es ein guter Weg, predict.lm() haben eine Vorhersage für die Faktorstufen kehrt das Modell kennt und NA für unbekannte Faktor Ebenen, statt nur einen Fehler entdeckt?

Beispielcode:

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)

Ich möchte der letzte Befehl drei „echte“ Prognosen zurück zu Faktorstufen „A“ entspricht, „B“ und „C“ und eine NA zu dem unbekannten Niveau „D“ entspricht.

War es hilfreich?

Lösung

in Ordnung gebracht und die Funktion erweitert von MorgenBall . Es ist ebenfalls implementiert in sperrorest jetzt.

Zusätzliche Funktionen

  • fällt nicht verwendeten Faktorstufen und nicht nur die fehlenden Werte zu NA Einstellung.
  • Fragen eine Nachricht an den Benutzer, dass Faktorstufen wurden fallen gelassen
  • prüft auf die Existenz von Faktor Variablen in test_data und gibt original data.frame wenn nicht vorhanden sind
  • funktioniert nicht nur für lm, glm und aber auch für glmmPQL

Hinweis: Die Funktion hier gezeigt ist, kann (verbessern) im Laufe der Zeit ändern.

#' @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)
}

Wir können diese Funktion zum Beispiel in der Frage gelten, wie folgt:

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

Bei dem Versuch, diese Funktion zu verbessern, stieß ich auf die Tatsache, dass SL Methoden wie lm Lernen, glm usw. müssen die gleichen Niveaus in Zug & Test während ML Methoden Lernen (svm, randomForest) fehlschlagen, wenn die Pegel entfernt werden. Diese Verfahren müssen alle Ebenen in Zug & Test.

Eine allgemeine Lösung ist ziemlich schwer zu erreichen, da jedes angepasste Modell eine andere Art und Weise hat ihre Faktor Pegelkomponente des Speicherns (fit$xlevels für lm und fit$contrasts für glmmPQL). Zumindest scheint es konsequent über lm verwandte Modelle.

Andere Tipps

Sie haben die zusätzliche Ebene vor jeder Berechnung zu entfernen, wie:

> 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 

Dies ist eine allgemeinere Art und Weise tun, werden alle Ebenen, die in der ursprünglichen Daten NA nicht auftreten. Wie Hadley in den Kommentaren erwähnt, haben sie gewählt, könnte dies in der predict() Funktion enthalten, aber sie taten es nicht

Warum Sie tun müssen, das wird offensichtlich, wenn man die Berechnung sieht selbst. Intern werden die Prognosen wie folgt berechnet:

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

Am Boden haben Sie beiden Modell-Matrizen. Sie sehen, dass die für foo.new eine zusätzliche Spalte hat, so dass Sie nicht die Matrix-Berechnung nicht mehr verwenden können. Wenn Sie den neuen Datensatz zu Modell verwenden würden, würden Sie auch ein anderes Modell bekommen, mit einem zusätzlichen Dummy-Variable für die zusätzliche Ebene als einer.

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

Sie können nicht löschen Sie einfach die letzte Spalte aus der Modellmatrix entweder, denn selbst wenn Sie das tun, beide andere Ebene noch beeinflusst werden. Der Code für Level A wäre (0,0). Für B ist dies (1,0), für C dieses (0,1) ... und für D ist es wieder (0,0)! Also Ihr Modell, das A und D annehmen würde, ist auf das gleiche Niveau, wenn sie ganz naiv das letzte Dummy-Variable fallen würden.

Auf einem theoretischen Teil: Es ist möglich, ein Modell zu bauen, ohne alle Ebene aufweisen. Nun, wie ich schon versucht zu erklären, dass Modell ist nur gültig für die Ebenen Sie beim Erstellen des Modells. Wenn Sie auf eine neue Ebene kommen, müssen Sie ein neues Modell erstellen, die zusätzliche Informationen enthalten. Wenn Sie das nicht tun, das einzige, was Sie tun können, ist die Extra-Level aus dem Datensatz löschen. Aber dann verlieren Sie im Grunde alle Informationen, die darin enthalten waren, so ist es in der Regel keine gute Praxis betrachtet.

Wenn Sie mit den fehlenden Ebenen in Ihren Daten umgehen wollen nach dem Filmmodell zu schaffen, sondern vor vorhersagen Aufruf (da wir nicht genau wissen, welche Werte vorher könnte fehlt) ist hier Funktion, die ich gebaut habe auf allen Ebenen eingestellt nicht im Modell NA - wird die Vorhersage auch dann NA geben und Sie können dann eine alternative Methode, um diese Werte zu prognostizieren verwenden

.

Objekt Ihre lm Ausgabe von lm sein (..., data = trainData)

Daten wird der Datenrahmen werden Sie wollen die Prognosen erstellen für

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

}

Sounds wie Sie vielleicht zufällige Effekte mögen. Schauen Sie in so etwas wie glmer (lme4 Paket). Mit einem Bayes-Modell, werden Sie Effekte erhalten, die 0 nähern, wenn es nur wenig Informationen zu verwenden ist, wenn sie zu schätzen. Warnung aber, dass Sie sich selbst Vorhersage zu tun haben werden, anstatt vorhersagen ().

Alternativ können Sie einfach Dummy-Variablen machen für die Ebenen wollen Sie in das Modell aufzunehmen, z.B. eine Variable 0/1 für Montag, einen für Dienstag, einen für Mittwoch, usw. Sonntag automatisch aus dem Modell entfernt werden, wenn sie alle 0'en enthält. Aber mit einem 1 in der Sonntag Spalte in den anderen Daten werden nicht die Vorhersage Schritt fehlschlagen. Es wird einfach davon ausgehen, dass der Sonntag eine Wirkung hat, dass die an den anderen Tagen im Durchschnitt (die können oder auch nicht wahr sein).

Eine der Annahmen von Linear / Logistik Regressionen ist zu wenig oder gar keine Multikollinearität; also, wenn die Prädiktorvariablen idealerweise unabhängig voneinander sind, dann gilt das Modell nicht alle möglichen Vielzahl von Faktorstufen sehen müssen. Ein neuer Faktor Stufe (D) ist ein neuer Prädiktor, und können ohne Beeinträchtigung der Vorhersage Fähigkeit der übrigen Faktoren A, B, C auf NA eingestellt werden. Aus diesem Grunde sollte das Modell noch in der Lage sein, Vorhersagen zu machen. Aber Zugabe der neue Ebene D führt das erwartete Schema ab. Das ist das ganze Problem. Einstellen NA-Fixes das.

Das lme4 Paket wird eine neue Ebene behandeln, wenn Sie das Kennzeichen allow.new.levels=TRUE gesetzt, wenn predict aufrufen.

Beispiel: Wenn Sie den Tag Faktor der Woche in einer Variablen dow ist und ein kategorisches Ergebnis b_fail, könnten Sie führen

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)

Dies ist ein Beispiel mit einer zufälligen Effekten logistischen Regression. Natürlich können Sie regelmäßige Regression ... oder die meisten GLM-Modelle durchzuführen. Wenn Sie möchten, auf dem Bayes-Weg weiter gehen, Blick auf Gelman & Hill ausgezeichnetes Buch und dem Stans Infrastruktur.

Eine schnelle und unsaubere Lösung für Split-Tests, ist selten Wert als „anderen“ neu zu kodieren. Hier ist eine Implementierung:

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

Zum Beispiel mit data.table würde der Anruf so etwas wie:

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

Dabei gilt xcols eine beliebige Teilmenge von colnames(dt) ist.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top