Imputazione multipla per valori mancanti


13

Vorrei usare l'imputazione per sostituire i valori mancanti nel mio set di dati con determinati vincoli.

Ad esempio, vorrei che la variabile imputata x1fosse maggiore o uguale alla somma delle mie altre due variabili, diciamo x2e x3. Voglio anche x3essere imputato da uno 0o >= 14e voglio x2essere imputato da uno 0o >= 16.

Ho provato a definire questi vincoli in SPSS per imputazione multipla, ma in SPSS posso solo definire i valori massimo e minimo. Esiste un modo per definire ulteriori vincoli in SPSS o conosci qualche pacchetto R che mi consentirebbe di definire tali vincoli per l'imputazione dei valori mancanti?

I miei dati sono i seguenti:

   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)
   dat=data.frame(x1=x1, x2=x2, x3=x3)
   > dat
       x1 x2 x3
   1   21  0  0
   2   50 NA  0
   3   31 18  0
   4   15  0  0
   5   36 19  0
   6   82  0 54
   7   14 NA  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  NA NA NA
   15  NA NA NA
   16  NA NA NA
   17  50 22 NA
   18  52 NA  0
   19  26  0  0
   20  24  0  0

Ho cambiato 0 or 16 or >= 16da 0 or >= 16poiché >=16include il valore 16. Spero che non abbia rovinato il tuo significato. Lo stesso per0 or 14 or >= 14
Alexis,

Risposte:


16

Una soluzione è scrivere le proprie funzioni di imputazione personalizzate per il micepacchetto. Il pacchetto è preparato per questo e l'installazione è sorprendentemente indolore.

Innanzitutto impostiamo i dati come suggerito:

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

Quindi cariciamo il micepacchetto e vediamo quali metodi sceglie per impostazione predefinita:

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

È l' pmmacronimo di matching medio predittivo , probabilmente l'algoritmo di imputazione più popolare per imputare variabili continue. Calcola il valore previsto utilizzando un modello di regressione e seleziona i 5 elementi più vicini al valore previsto (in base alla distanza euclidea ). Questi elementi scelti sono chiamati pool di donatori e il valore finale viene scelto a caso da questo pool di donatori.

Dalla matrice di previsione troviamo che i metodi ottengono le variabili passate che sono interessanti per le restrizioni. Si noti che la riga è la variabile target e la colonna i predittori. Se x1 non avesse 1 nella colonna x3, dovremmo aggiungere questo nella matrice:imp_base$predictorMatrix["x1","x3"] <- 1

Ora per la parte divertente, generando i metodi di imputazione. Ho scelto un metodo piuttosto grezzo qui in cui scarto tutti i valori se non soddisfano i criteri. Ciò può comportare un tempo di ciclo lungo e potrebbe essere potenzialmente più efficiente mantenere le imputazioni valide e ripetere solo quelle rimanenti, tuttavia richiederebbe un po 'più di modifiche.

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

Una volta che abbiamo finito di definire i metodi, cambiamo semplicemente i metodi precedenti. Se vuoi solo cambiare una singola variabile, puoi semplicemente usare, imp_base$method["x2"] <- "pmm_x2"ma per questo esempio cambieremo tutto (la denominazione non è necessaria):

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)

Ora diamo un'occhiata al terzo set di dati imputati:

> 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, questo fa il lavoro. Mi piace questa soluzione in quanto puoi sulle spalle delle funzioni principali e aggiungere solo le restrizioni che ritieni significative.

Aggiornare

Al fine di applicare le restrizioni rigorose @ t0x1n menzionate nei commenti, potremmo voler aggiungere le seguenti abilità alla funzione wrapper:

  1. Salvare valori validi durante i loop in modo che i dati delle precedenti esecuzioni parzialmente riuscite non vengano eliminati
  2. Un meccanismo di fuga per evitare cicli infiniti
  3. Gonfia il pool di donatori dopo aver provato x volte senza trovare una corrispondenza adatta (questo vale principalmente per pmm)

Ciò si traduce in una funzione wrapper leggermente più complicata:

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

Si noti che ciò non funziona così bene, molto probabilmente a causa del fatto che il set di dati suggerito non ha i vincoli per tutti i casi senza perdere. Devo aumentare la lunghezza del loop a 400-500 prima ancora che inizi a comportarsi. Suppongo che questo non sia intenzionale, la tua imputazione dovrebbe imitare il modo in cui vengono generati i dati reali.

Ottimizzazione

L'argomento rycontiene i valori non mancanti e potremmo eventualmente accelerare il ciclo rimuovendo gli elementi che abbiamo trovato imputazioni ammissibili, ma poiché non ho familiarità con le funzioni interne, mi sono astenuto da questo.

Penso che la cosa più importante quando si hanno forti vincoli che richiedono tempo per il riempimento completo sia parallelizzare le proprie imputazioni ( vedere la mia risposta su CrossValidated ). La maggior parte ha oggi computer con 4-8 core e R ne usa solo uno per impostazione predefinita. Il tempo può essere (quasi) diviso in due raddoppiando il numero di core.

Parametri mancanti al momento dell'imputazione

Per quanto riguarda il problema della x2mancanza al momento dell'imputazione, i topi in realtà non immettono mai valori mancanti nel x- data.frame. Il metodo dei topi include l'inserimento di un valore casuale all'inizio. La parte catena dell'imputazione limita l'impatto di questo valore iniziale. Se guardi la funzione micepuoi trovarla prima della chiamata di imputazione (la funzione mice:::sampler):

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

È data.initpossibile fornire la micefunzione e mice.imput.sample è una procedura di campionamento di base.

Sequenza di visite

Se la sequenza di visite è importante, è possibile specificare l'ordine in cui la funzione miceesegue le imputazioni. L'impostazione predefinita è 1:ncol(data)ma è possibile impostare il valore visitSequencecome desiderato.


+1 Questa è roba fantastica, esattamente quello che avevo in mente (vedi il mio commento alla risposta di Frank), e di sicuro il candidato numero 1 per la taglia fino ad ora. Un paio di cose mi preoccupano pmm_x1però: (1) Prendere la somma massima di ogni possibile combinazione di x2e x3dall'intero set di dati è molto più forte del vincolo originale. La cosa giusta sarebbe quella di verificare che per ogni riga , x1 < x2 + x3. Ovviamente più righe hai, minore è la tua possibilità di rispettare un tale vincolo (poiché una singola riga errata rovina tutto) e più lungo può potenzialmente ottenere il ciclo.
t0x1n,

(2) se entrambi x1e x2mancano, è possibile imputare un valore per il x1quale vengono mantenuti i vincoli (diciamo 50), ma una volta x2imputati vengono rotti (diciamo che è imputato a 55). C'è un modo per imputare "orizzontalmente" piuttosto che verticalmente? In questo modo si potrebbe imputare una sola fila di x1, x2e x3e semplicemente ri-impute fino a quel riga specifica cade sotto i vincoli. Dovrebbe essere abbastanza veloce e, una volta fatto, possiamo passare alla riga successiva. Naturalmente se l'MI è "verticale" per sua natura, siamo sfortunati. In tal caso, forse l'approccio menzionato da Aleksandr?
t0x1n,

Soluzione interessante, +1! Potrebbe essere particolarmente utile, poiché attualmente uso il micepacchetto. Grazie per la condivisione.
Aleksandr Blekh,

1
@ t0x1n Ho aggiornato la mia risposta con una funzione wrapper più avanzata in base ai tuoi commenti. Se vuoi immergerti più a fondo, ti consiglio di giocare con il debug()per vedere come mice.impute.pmme i suoi fratelli lavorano sotto il cofano.
Max Gordon,

1
@ t0x1n: suppongo - controlla i tuoi valori imputati. Se sembrano irrealistici, allora puoi scegliere il mio approccio per imputare solo quelli che sono meno centrali nel modello. Nel mio caso ho scelto di escludere quelli senza radiografie di follow-up poiché sono al centro dello studio e le imputazioni non forniscono valori clinicamente plausibili (la gamba diventa più lunga dopo una frattura). Non ne sono completamente soddisfatto, ma sembra un ragionevole compromesso.
Max Gordon,

8

La cosa più vicina che ho trovato è l' inclusione delle informazioni precedenti di Amelia . Vedi capitolo 4.7 nella vignetta , in particolare 4.7.2:

Priori a livello di osservazione

I ricercatori hanno spesso ulteriori informazioni preliminari sui valori di dati mancanti basati su ricerche precedenti, consenso accademico o esperienza personale. Amelia può incorporare queste informazioni per produrre imputazioni notevolmente migliorate. L'algoritmo Amelia consente agli utenti di includere priori bayesiani informativi sulle singole celle di dati mancanti anziché sui parametri del modello più generali, molti dei quali hanno uno scarso significato diretto.

L'incorporazione di priori segue l'analisi bayesiana di base in cui l'imputazione risulta essere una media ponderata dell'imputazione basata sul modello e della media precedente, in cui i pesi sono funzioni della forza relativa dei dati e precedenti: quando il modello prevede molto bene , l'imputazione ridurrà il peso del precedente e viceversa (Honaker e King, 2010).

I priori sulle singole osservazioni dovrebbero descrivere la convinzione dell'analista sulla distribuzione della cella di dati mancante. Questo può assumere la forma di una media e una deviazione standard o un intervallo di condenza. Ad esempio, potremmo sapere che i tari del 1986 si attestano in Thailandia intorno al 40%, ma abbiamo qualche incertezza sul valore esatto. La nostra precedente convinzione sulla distribuzione della cella di dati mancante, quindi, si concentra su 40 con una deviazione standard che riflette la quantità di incertezza che abbiamo sulla nostra precedente convinzione.

Per inserire priori è necessario creare una matrice priori con quattro o cinque colonne. Ogni riga della matrice rappresenta un precedente su una osservazione o una variabile. In ogni riga, la voce nella prima colonna è la riga dell'osservazione e la voce è la seconda colonna è la colonna dell'osservazione. Nella matrice a quattro colonne priori la terza e la quarta colonna sono la deviazione media e standard della distribuzione precedente del valore mancante.

Pertanto, anche se non sarai in grado di dire qualcosa del genere x1<x2+x3, potresti passare in rassegna il tuo set di dati e aggiungere un livello di osservazione prima di ogni caso rilevante. È inoltre possibile applicare limiti costanti (come l'impostazione di x1, x2 e x3 in modo che non siano negativi). Per esempio:

priors = matrix(NA, nrow=0, ncol=5);
for (i in seq(1, length(data))) 
{
    x1 = data$x1[i];
    x2 = data$x2[i];
    x3 = data$x3[i];

    if (is.na(x1) && !is.na(x2) && !is.na(x3))
    {
        priors = rbind(priors, c(i, 1, 0, x2+x3, 0.999999))
    }
}

amelia(data, m=1, bound = rbind(c(1, 0, Inf), c(2, 0, Inf), c(3, 0, Inf)), pr = priors);

5

I vincoli sono probabilmente più facili da implementare nella media predittiva che corrisponde all'imputazione multipla. Ciò presuppone che vi sia un numero significativo di osservazioni con variabili vincolanti non mancanti che soddisfano i vincoli. Sto pensando di implementarlo nella funzione del Hmiscpacchetto R. aregImputePotresti voler ricontrollare tra circa un mese. Sarà importante specificare la distanza massima dall'obiettivo che può essere un'osservazione del donatore, poiché i vincoli spingono i donatori oltre il donatore non vincolato ideale.


Mi piacerebbe avere anche questo. Ho solo bisogno dei vincoli inter-variabili più elementari, diciamo x<y<z.
t0x1n

Perdona la mia ignoranza se sono lontana, ma avevo l'impressione che le tecniche di imputazione multipla comportassero il trarre valori da una distribuzione appropriata. Non dovrebbe essere semplice usare il campionamento del rifiuto? ad esempio, continuare a disegnare fino a quando non x1<x2viene soddisfatto un vincolo specificato (come )?
t0x1n,

Questo è ciò che potrei fare con la aregImputefunzione R con corrispondenza media predittiva. Ma cosa succede se nessuna delle osservazioni del donatore (vicino a corrispondenze di previsioni) soddisfa i vincoli per l'osservazione del bersaglio imputata anche se ovviamente dovevano incontrare i vincoli sull'insieme delle variabili del donatore?
Frank Harrell,

In tal caso, prendi forse direttamente il valore previsto? Ciò si basa solo sulla regressione (senza la fase PMM) per un tale campione?
t0x1n,

L'imputazione della regressione ha una probabilità leggermente maggiore di produrre valori imputati che sono incompatibili con il resto della documentazione del soggetto. Quindi non penso che questo sia un motivo per evitare il PMM.
Frank Harrell,

4

Ritengo che il Ameliapacchetto (Amelia II) attualmente offra il supporto più completo per specificare i vincoli della gamma di valori di dati. Tuttavia, il problema è che si Ameliapresume che i dati siano multivariati normali.

Se nel tuo caso il presupposto della normalità multivariata non si applica, potresti voler controllare il micepacchetto, che implementa l'imputazione multipla (MI) tramite equazioni concatenate . Questo pacchetto non ha il presupposto della normalità multivariata . Ha anche una funzione che potrebbe essere sufficiente per specificare i vincoli , ma non sono sicuro di quale grado. La funzione è chiamata squeeze(). Puoi leggerlo nella documentazione: http://cran.r-project.org/web/packages/mice/mice.pdf . Un ulteriore vantaggio miceè la sua flessibilità in termini di consentire la specifica di funzioni di imputazione definite dall'utente e una più ampia selezione di algoritmi. Ecco un tutorial su come eseguire l'MI, usando mice:http://www.ats.ucla.edu/stat/r/faq/R_pmm_mi.htm .

Per quanto ho capito, il Hmiscpacchetto del Dr. Harrell , usando lo stesso approccio di equazioni concatenate ( corrispondenza predittiva della media ), probabilmente supporta dati non normali (ad eccezione del normpmmmetodo). Forse ha già implementato la funzionalità di specifica dei vincoli per la sua risposta sopra. Non ho usato aregImpute(), quindi non posso dire molto di più (l'ho usato Ameliae mice, ma sicuramente non sono un esperto di statistica, sto solo cercando di imparare il più possibile).

Infine, potresti trovare interessante la seguente, un po 'datata, ma comunque piacevole, panoramica di approcci, metodi e software per imputazione multipla di dati con valori mancanti: http://www.ncbi.nlm.nih.gov/pmc/articles / PMC1839993 . Sono sicuro che ci sono documenti di sintesi più recenti su MI, ma questo è tutto ciò di cui sono a conoscenza al momento. Spero che questo sia in qualche modo utile.


1
Questo bel commento mi fa pensare che la corrispondenza media predittiva, che sostituisce i difetti con i valori effettivamente osservati, potrebbe già incorporare alcuni tipi di contrappunti se tutti i dati osservati soddisfano tali vincoli. Gradirei qualcuno a pensarci bene. Non ho ancora implementato alcun vincolo speciale in aregImpute.
Frank Harrell,

1
Hai ragione. Mi sono appena reso conto che le osservazioni dei donatori forniscono valori coerenti con le loro altre variabili ma non con le altre variabili nella variabile target.
Frank Harrell,

1
A parte le ipotesi di distribuzione fatte da Amelia, sei stato per caso in grado di specificare i vincoli in modo più dettagliato di quanto ho dimostrato nella mia risposta? Il problema squeezeè che i suoi limiti sono costanti, quindi non è possibile specificare nulla di simile x1<x2. Inoltre, sembra essere invocato sul vettore del risultato imputato, che credo sia troppo tardi. Mi sembra che i limiti debbano essere considerati durante il processo di imputazione, quindi hanno più significato di un aggiustamento post-fatto.
t0x1n,

1
@ t0x1n: Sfortunatamente, non ho avuto la possibilità di specificare i vincoli Amelia, perché sono passato da esso a mice, non appena i miei test hanno confermato che i miei dati non sono normali multivariati. Tuttavia, di recente mi sono imbattuto in questo bellissimo set di diapositive di presentazione sull'argomento (metodi e software MI): statistik.lmu.de/~fkreuter/imputation_sose2011/downloads/… . Se ho capito bene, descrive una potenziale soluzione al problema dei vincoli (vedere la pagina 50 del PDF - non la diapositiva numero 50!). Spero che sia di aiuto.
Aleksandr Blekh,

1
@ t0x1n: in realtà, la soluzione è descritta alle pagine 50 e 51.
Aleksandr Blekh

0

Se capisco correttamente la tua domanda, mi sembra che tu sappia già quali valori dovrebbero prendere le variabili mancanti soggette ad alcuni vincoli. Non sono molto esperto in SPSS, ma in RI penso che tu possa scrivere una funzione per farlo (che non dovrebbe essere troppo difficile a seconda della tua esperienza, dovrei dire). Non conosco nessun pacchetto che funzioni con tali vincoli.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.