Eine Lösung besteht darin, Ihre eigenen benutzerdefinierten Imputationsfunktionen für das mice
Paket zu schreiben . Das Paket ist darauf vorbereitet und das Setup überraschend schmerzfrei.
Zuerst richten wir die Daten wie vorgeschlagen ein:
dat=data.frame(x1=c(21, 50, 31, 15, 36, 82, 14, 14, 19, 18, 16, 36, 583, NA,NA,NA, 50, 52, 26, 24),
x2=c(0, NA, 18,0, 19, 0, NA, 0, 0, 0, 0, 0, 0,NA,NA, NA, 22, NA, 0, 0),
x3=c(0, 0, 0, 0, 0, 54, 0 ,0, 0, 0, 0, 0, 0, NA, NA, NA, NA, 0, 0, 0))
Als nächstes laden wir das mice
Paket und sehen, welche Methoden es standardmäßig auswählt:
library(mice)
# Do a non-imputation
imp_base <- mice(dat, m=0, maxit = 0)
# Find the methods that mice chooses
imp_base$method
# Returns: "pmm" "pmm" "pmm"
# Look at the imputation matrix
imp_base$predictorMatrix
# Returns:
# x1 x2 x3
#x1 0 1 1
#x2 1 0 1
#x3 1 1 0
Das pmm
steht für Predictive Mean Matching - der wahrscheinlich beliebteste Imputationsalgorithmus zur Imputation kontinuierlicher Variablen. Er berechnet den vorhergesagten Wert mithilfe eines Regressionsmodells und wählt die 5 Elemente aus, die dem vorhergesagten Wert am nächsten liegen (nach euklidischem Abstand ). Diese ausgewählten Elemente werden als Spenderpool bezeichnet, und der endgültige Wert wird zufällig aus diesem Spenderpool ausgewählt.
Aus der Vorhersagematrix ergibt sich, dass die Methoden die Variablen übergeben bekommen, die für die Einschränkungen von Interesse sind. Beachten Sie, dass die Zeile die Zielvariable und die Spalte die Prädiktoren ist. Wenn x1 nicht 1 in der x3-Spalte hätte, müssten wir dies in die Matrix einfügen:imp_base$predictorMatrix["x1","x3"] <- 1
Nun zum lustigen Teil, der die Imputationsmethoden generiert. Ich habe hier eine ziemlich grobe Methode gewählt, bei der ich alle Werte verwerfe, wenn sie die Kriterien nicht erfüllen. Dies kann zu einer langen Schleifenzeit führen und es kann möglicherweise effizienter sein, die gültigen Imputationen beizubehalten und nur die verbleibenden zu wiederholen.
# Generate our custom methods
mice.impute.pmm_x1 <-
function (y, ry, x, donors = 5, type = 1, ridge = 1e-05, version = "",
...)
{
max_sum <- sum(max(x[,"x2"], na.rm=TRUE),
max(x[,"x3"], na.rm=TRUE))
repeat{
vals <- mice.impute.pmm(y, ry, x, donors = 5, type = 1, ridge = 1e-05,
version = "", ...)
if (all(vals < max_sum)){
break
}
}
return(vals)
}
mice.impute.pmm_x2 <-
function (y, ry, x, donors = 5, type = 1, ridge = 1e-05, version = "",
...)
{
repeat{
vals <- mice.impute.pmm(y, ry, x, donors = 5, type = 1, ridge = 1e-05,
version = "", ...)
if (all(vals == 0 | vals >= 14)){
break
}
}
return(vals)
}
mice.impute.pmm_x3 <-
function (y, ry, x, donors = 5, type = 1, ridge = 1e-05, version = "",
...)
{
repeat{
vals <- mice.impute.pmm(y, ry, x, donors = 5, type = 1, ridge = 1e-05,
version = "", ...)
if (all(vals == 0 | vals >= 16)){
break
}
}
return(vals)
}
Sobald wir die Methoden definiert haben, ändern wir einfach die vorherigen Methoden. Wenn Sie nur eine einzelne Variable ändern möchten, können Sie einfach verwenden, imp_base$method["x2"] <- "pmm_x2"
aber für dieses Beispiel werden wir alle ändern (die Benennung ist nicht erforderlich):
imp_base$method <- c(x1 = "pmm_x1", x2 = "pmm_x2", x3 = "pmm_x3")
# The predictor matrix is not really necessary for this example
# but I use it just to illustrate in case you would like to
# modify it
imp_ds <-
mice(dat,
method = imp_base$method,
predictorMatrix = imp_base$predictorMatrix)
Schauen wir uns nun den dritten unterstellten Datensatz an:
> complete(imp_ds, action = 3)
x1 x2 x3
1 21 0 0
2 50 19 0
3 31 18 0
4 15 0 0
5 36 19 0
6 82 0 54
7 14 0 0
8 14 0 0
9 19 0 0
10 18 0 0
11 16 0 0
12 36 0 0
13 583 0 0
14 50 22 0
15 52 19 0
16 14 0 0
17 50 22 0
18 52 0 0
19 26 0 0
20 24 0 0
Ok, das macht den Job. Ich mag diese Lösung, da Sie die Hauptfunktionen huckepack nehmen und nur die Einschränkungen hinzufügen können, die Sie für sinnvoll halten.
Aktualisieren
Um die in den Kommentaren erwähnten rigorosen Einschränkungen @ t0x1n durchzusetzen, möchten wir der Wrapper-Funktion möglicherweise die folgenden Funktionen hinzufügen:
- Speichern Sie während der Schleifen gültige Werte, damit Daten aus vorherigen, teilweise erfolgreichen Durchläufen nicht verworfen werden
- Ein Fluchtmechanismus, um Endlosschleifen zu vermeiden
- Pumpen Sie den Spenderpool auf, nachdem Sie x- mal versucht haben, ohne eine passende Übereinstimmung zu finden (dies gilt hauptsächlich für pmm).
Dies führt zu einer etwas komplizierteren Wrapper-Funktion:
mice.impute.pmm_x1_adv <- function (y, ry,
x, donors = 5,
type = 1, ridge = 1e-05,
version = "", ...) {
# The mice:::remove.lindep may remove the parts required for
# the test - in those cases we should escape the test
if (!all(c("x2", "x3") %in% colnames(x))){
warning("Could not enforce pmm_x1 due to missing column(s):",
c("x2", "x3")[!c("x2", "x3") %in% colnames(x)])
return(mice.impute.pmm(y, ry, x, donors = 5, type = 1, ridge = 1e-05,
version = "", ...))
}
# Select those missing
max_vals <- rowSums(x[!ry, c("x2", "x3")])
# We will keep saving the valid values in the valid_vals
valid_vals <- rep(NA, length.out = sum(!ry))
# We need a counter in order to avoid an eternal loop
# and for inflating the donor pool if no match is found
cntr <- 0
repeat{
# We should be prepared to increase the donor pool, otherwise
# the criteria may become imposs
donor_inflation <- floor(cntr/10)
vals <- mice.impute.pmm(y, ry, x,
donors = min(5 + donor_inflation, sum(ry)),
type = 1, ridge = 1e-05,
version = "", ...)
# Our criteria check
correct <- vals < max_vals
if (all(!is.na(valid_vals) |
correct)){
valid_vals[correct] <-
vals[correct]
break
}else if (any(is.na(valid_vals) &
correct)){
# Save the new valid values
valid_vals[correct] <-
vals[correct]
}
# An emergency exit to avoid endless loop
cntr <- cntr + 1
if (cntr > 200){
warning("Could not completely enforce constraints for ",
sum(is.na(valid_vals)),
" out of ",
length(valid_vals),
" missing elements")
if (all(is.na(valid_vals))){
valid_vals <- vals
}else{
valid_vals[is.na(valid_vals)] <-
vals[is.na(valid_vals)]
}
break
}
}
return(valid_vals)
}
Beachten Sie, dass dies nicht so gut funktioniert. Dies liegt höchstwahrscheinlich daran, dass der vorgeschlagene Datensatz die Einschränkungen in allen Fällen nicht erfüllt, ohne dass sie fehlen. Ich muss die Schleifenlänge auf 400-500 erhöhen, bevor sie sich überhaupt verhält. Ich gehe davon aus, dass dies unbeabsichtigt ist. Ihre Unterstellung sollte nachahmen, wie die tatsächlichen Daten generiert werden.
Optimierung
Das Argument ry
enthält die nicht fehlenden Werte, und wir könnten die Schleife möglicherweise beschleunigen, indem wir die Elemente entfernen, die wir als zutreffend eingestuft haben, aber da ich mit den inneren Funktionen nicht vertraut bin, habe ich darauf verzichtet.
Ich denke, das Wichtigste, wenn Sie starke Einschränkungen haben, die Zeit zum Ausfüllen benötigen , ist die Parallelisierung Ihrer Anrechnungen ( siehe meine Antwort auf CrossValidated ). Die meisten haben heute Computer mit 4-8 Kernen und R verwendet standardmäßig nur einen von ihnen. Die Zeit kann (fast) halbiert werden, indem die Anzahl der Kerne verdoppelt wird.
Fehlende Parameter bei der Imputation
In Bezug auf das Problem des x2
Fehlens zum Zeitpunkt der Imputation - Mäuse geben tatsächlich niemals fehlende Werte in die x
- ein data.frame
. Die Mäusemethode beinhaltet das Ausfüllen eines zufälligen Wertes zu Beginn. Der Kettenteil der Imputation begrenzt die Auswirkung von diesem Anfangswert. Wenn Sie sich die mice
-Funktion ansehen, finden Sie diese vor dem Aufruf der Imputation (die mice:::sampler
-Funktion):
...
if (method[j] != "") {
for (i in 1:m) {
if (nmis[j] < nrow(data)) {
if (is.null(data.init)) {
imp[[j]][, i] <- mice.impute.sample(y,
ry, ...)
}
else {
imp[[j]][, i] <- data.init[!ry, j]
}
}
else imp[[j]][, i] <- rnorm(nrow(data))
}
}
...
Das data.init
kann an die mice
Funktion geliefert werden und das mice.imput.sample ist ein grundlegendes Stichprobenverfahren.
Besuchsreihenfolge
Wenn die Besuchsreihenfolge wichtig ist, können Sie die Reihenfolge angeben, in der die Funktion mice
die Imputationen ausführt. Die Standardeinstellung ist von, 1:ncol(data)
aber Sie können festlegen, dass das visitSequence
beliebig ist.
0 or 16 or >= 16
um0 or >= 16
da>=16
den Wert enthält16
. Hoffe, das hat deine Bedeutung nicht durcheinander gebracht. Gleiches gilt für0 or 14 or >= 14