Une solution consiste à écrire vos propres fonctions d'imputation personnalisées pour le mice
package. Le package est préparé pour cela et la configuration est étonnamment indolore.
D'abord, nous configurons les données comme suggéré:
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))
Ensuite, nous chargeons le mice
package et voyons quelles méthodes il choisit par défaut:
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
Le pmm
représente l' appariement moyen prédictif - probablement l'algorithme d'imputation le plus populaire pour imputer des variables continues. Il calcule la valeur prédite à l'aide d'un modèle de régression et sélectionne les 5 éléments les plus proches de la valeur prédite (par distance euclidienne ). Ces éléments choisis sont appelés le pool de donateurs et la valeur finale est choisie au hasard dans ce pool de donateurs.
À partir de la matrice de prédiction, nous constatons que les méthodes obtiennent les variables qui sont intéressantes pour les restrictions. Notez que la ligne est la variable cible et la colonne les prédicteurs. Si x1 n'avait pas 1 dans la colonne x3, nous devrions ajouter ceci dans la matrice:imp_base$predictorMatrix["x1","x3"] <- 1
Passons maintenant à la partie amusante, générant les méthodes d'imputation. J'ai choisi une méthode plutôt grossière ici où je rejette toutes les valeurs si elles ne répondent pas aux critères. Cela peut entraîner un long temps de boucle et il peut être potentiellement plus efficace de conserver les imputations valides et de ne refaire que les autres, cela nécessiterait cependant un peu plus de réglages.
# 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)
}
Une fois que nous avons fini de définir les méthodes, nous changeons simplement les méthodes précédentes. Si vous ne souhaitez modifier qu'une seule variable, vous pouvez simplement l'utiliser, imp_base$method["x2"] <- "pmm_x2"
mais pour cet exemple, nous allons tout changer (la dénomination n'est pas nécessaire):
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)
Voyons maintenant le troisième ensemble de données imputé:
> 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, ça fait l'affaire. J'aime cette solution car vous pouvez vous superposer aux fonctions principales et ajouter simplement les restrictions que vous trouvez significatives.
Mise à jour
Afin d'appliquer les restrictions rigoureuses @ t0x1n mentionnées dans les commentaires, nous pouvons vouloir ajouter les capacités suivantes à la fonction wrapper:
- Enregistrer les valeurs valides pendant les boucles afin que les données des exécutions précédentes partiellement réussies ne soient pas supprimées
- Un mécanisme d'échappement pour éviter les boucles infinies
- Gonflez le pool de donneurs après avoir essayé x fois sans trouver une correspondance appropriée (cela s'applique principalement à pmm)
Il en résulte une fonction wrapper légèrement plus compliquée:
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)
}
Notez que cela ne fonctionne pas très bien, probablement en raison du fait que l'ensemble de données suggéré ne respecte pas les contraintes pour tous les cas sans manquer. J'ai besoin d'augmenter la longueur de la boucle à 400-500 avant même qu'elle ne commence à se comporter. Je suppose que cela n'est pas intentionnel, votre imputation devrait imiter la façon dont les données réelles sont générées.
Optimisation
L'argument ry
contient les valeurs non manquantes et nous pourrions éventuellement accélérer la boucle en supprimant les éléments que nous avons trouvés des imputations éligibles, mais comme je ne connais pas les fonctions internes, je m'en suis abstenu.
Je pense que la chose la plus importante lorsque vous avez de fortes contraintes qui prennent du temps à remplir est de paralléliser vos imputations ( voir ma réponse sur CrossValidated ). La plupart ont aujourd'hui des ordinateurs avec 4 à 8 cœurs et R n'en utilise qu'un par défaut. Le temps peut être (presque) divisé par deux en doublant le nombre de cœurs.
Paramètres manquants à l'imputation
Concernant le problème d' x2
être manquant au moment de l'imputation - les souris n'alimentent jamais les valeurs manquantes dans le x
- data.frame
. La méthode des souris comprend le remplissage d'une valeur aléatoire au début. La partie chaîne de l'imputation limite l'impact de cette valeur initiale. Si vous regardez la mice
fonction-vous pouvez le trouver avant l'appel d'imputation (la mice:::sampler
fonction-):
...
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))
}
}
...
Le data.init
peut être fourni à la mice
fonction et le mice.imput.sample est une procédure d'échantillonnage de base.
Séquence de visites
Si la séquence de visites est importante, vous pouvez spécifier l'ordre dans lequel la mice
fonction-exécute les imputations. La valeur par défaut est de 1:ncol(data)
mais vous pouvez définir visitSequence
ce que vous voulez.
0 or 16 or >= 16
à0 or >= 16
depuis>=16
inclut la valeur16
. J'espère que cela n'a pas gâché votre sens. Pareil pour0 or 14 or >= 14