here::here("code", "_common.R") |>
source()
# Load packages
if (!requireNamespace("pacman")) install.packages("pacman")
pacman::p_load(lavaan, semTools, semPlot)
57 Dati mancanti
- Identificare le diverse tipologie di dati mancanti (MCAR, MAR, MNAR) e comprenderne le implicazioni.
- Applicare metodi di gestione come listwise deletion, pairwise deletion e imputazione dei dati.
- Utilizzare il metodo Full Information Maximum Likelihood (FIML) per analisi robusta in contesti CFA e SEM.
57.1 Introduzione
Raramente un ricercatore si trova nella situazione fortunata nella quale un’analisi statistica (di tipo CFA/SEM o altro) può essere condotta utilizzando un set di dati in cui tutte le variabili sono state osservate su tutte le unità statistiche: nella pratica ricerca i dati mancanti sono la norma piuttosto che l’eccezione.
57.2 Tipologie di dati mancanti
Ci sono molti motivi che possono stare alla base dei dati mancanti. Ad esempio, i dati possono mancare per disegno dello studio (“mancanza pianificata”), come ad esempio nei progetti di ricerca in cui i partecipanti al campione vengono selezionati casualmente per completare sottoinsiemi diversi della batteria di valutazione (una scelta di questo tipo viene motivata, ad esempio, a causa di considerazioni pratiche come i vincoli di tempo). In tali condizioni, si presume che i dati mancanti si distribuiscano in un modo completamente casuale rispetto a tutte le altre variabili nello studio.
In generale, i meccanismi che determinano la presenza di dati mancanti possono essere classificati in tre categorie:
- valori mancanti completamente casuali (Missing Completely At Random, MCAR). La probabilità di dati mancanti su una variabile non è collegata né al valore mancante sulla variabile, né al valore di ogni altra variabile presente nella matrice dati che si sta analizzando;
- valori mancanti casuali (Missing At Random, MAR). I valori mancanti sono indipendenti dal valore che viene a mancare, ma dipendono da altre variabili, cioè i dati sulla variabile sono mancanti per categorie di partecipanti che potrebbero essere identificati dai valori assunti dalle altre variabili presenti nello studio;
- valori mancanti non ignorabili (Missing Not At Random, MNAR). La mancanza di un dato può dipendere sia dal valore del dato stesso che dalle altre variabili. Per esempio, se si studia la salute mentale e le persone depresse riferiscono meno volentieri informazioni riguardanti il loro stato di salute, allora i dati non sono mancanti per caso.
57.3 La gestione dei dati mancanti
Il passo successivo dopo la definizione dei meccanismi è quello della gestione dei dati mancanti. Sostanzialmente le scelte possibili sono due: l’eliminazione dei casi o la sostituzione dei dati mancanti. Un metodo semplice, indicato solo nel caso in cui l’ammontare dei dati mancanti è limitato e questi sono mancanti completamente a caso (MCAR), è quello di rimuovere i casi con dati mancanti (case deletion).
Ci sono due metodi per eliminare le osservazioni con valori mancanti: listwise deletion e pairwise deletion. Nel primo caso si elimina dal campione ogni osservazione che contiene dati mancanti. Le analisi avverranno quindi solo sui casi che hanno valori validi su tutte le variabili in esame. In questo modo si ottiene una maggiore semplicità di trattazione nell’analisi statistica, tuttavia non si utilizza tutta l’informazione osservata (si riduce la numerosità campionaria e, quindi, l’informazione). Il secondo metodo è la pairwise deletion, che utilizza tutti i casi che hanno i dati validi su due variabili volta per volta. In questo modo si riesce a massimizzare la numerosità del campione da utilizzare, ma si tratta comunque di un metodo che presenta dei problemi, per esempio il fatto che con questo approccio i parametri del modello saranno basati su differenti insiemi di dati, con differenti numerosità campionarie e differenti errori standard.
Quando i dati non sono MNAR è opportuno sostituirli con appropriate funzioni dei dati effettivamente osservati. Questa procedura è chiamata imputazione (imputation). Di seguito sono indicati alcuni metodi.
- Mean Imputation. Il dato mancante viene sostituito con la media della variabile. Questo metodo, utilizzato troppo spesso per la sua semplicità, riducendo la variabilità dei dati, ha effetti importanti su molte analisi dei dati e, in generale, dovrebbe essere evitato.
- Regression Imputation. Si tratta di un approccio basato sulle informazioni disponibili sulle altre variabili. Si stima una equazione di regressione lineare per ogni variabile utilizzando le altre variabili come predittori. Questo metodo offre il vantaggio di poter utilizzare i rapporti esistenti tra le variabili per effettuare le valutazioni dei dati mancanti; tuttavia esso è usato raramente, in quanto amplifica le correlazioni tra le variabili; quindi, se le analisi si basano su regressioni o modelli SEM, questo metodo è sconsigliato.
- Multiple Imputation. La tecnica di multiple imputation, applicabile in caso di MAR, prevede che un dato mancante su una variabile sia sostituito, sulla base dei dati esistenti sulle altre variabili, con un valore che però comprende anche una componente di errore ricavata dalla distribuzione dei residui della variabile.
- Expectation-Maximization. Un altro approccio moderno del trattamento dei dati mancanti è l’applicazione dell’algoritmo Expectation Maximization (EM). La tecnica è quella di stimare i parametri sulla base dei dati osservati, e di stimare poi i dati mancanti sulla base di questi parametri (fase E). Poi i parametri vengono nuovamente stimati sulla base della nuova matrice di dati (fase M), e così via. Questo processo viene iterato fino a quando i valori stimati convergono. Tuttavia, una limitazione fondamentale dell’utilizzo dell’algoritmo EM per calcolare le matrici di input per le analisi CFA/SEM è che gli errori standard risultanti delle stime dei parametri non sono consistenti. Pertanto, gli intervalli di confidenza e i test di significatività possono risultare compromessi.
57.3.1 Metodo Direct ML
Benché i metodi precedenti vengano spesso usati, nella pratica concreta è preferibile usare il metodo Direct ML, conosciuto anche come “raw ML” o “full information ML” (FIML), in quanto è generalmente considerano come il metodo migliore per gestire i dati mancanti nella maggior parte delle applicazioni CFA e SEM. Il metodo full information ML è esente dai problemi associati all’utilizzo dell’algoritmo EM e produce stime consistenti sotto l’ipotesi di normalità multivariata per dati mancanti MAR.
Intuitivamente, l’approccio utilizza la relazione tra le variabili per dedurre quali siano i valori mancanti con maggiore probabilità. Ad esempio, se due variabili, \(X\) e \(Y\), sono correlate positivamente, allora se, per alcune osservazioni \(i\), \(X_i\) è il valore più alto nella variabile, è probabile che anche il valore mancante \(Y_i\) sia un valore alto. FIML utilizza queste informazioni senza procedere all’imputazione dei valori mancanti, ma invece basandosi sulle stime più verosimili dei parametri della popolazione, ovvero massimizzando direttamente la verosimiglianza del modello specificato. Sotto l’assunzione di normalità multivariata, la funzione di verosimiglianza diventa
\[ L(\mu, \Sigma) = \prod_i f(y_i \mid \mu_i, \Sigma_i), \]
dove \(y_i\) sono i dati, \(\mu_i\) e \(\Sigma_i\) sono i parametri della popolazione se gli elementi mancanti in \(y_i\) vengono rimossi. Si cercano i valori \(\mu\) e \(\Sigma\) che massimizzano la verosimiglianza.
In lavaan
l’applicazione di tale metodo si ottiene specificando l’argomento missing = "ml"
.
57.3.2 Un esempio concreto
Per applicare il metodo direct ML, Brown (2015) prende in esame i dati reali di un questionario (un singolo fattore, quattro item, una covarianza di errore) caratterizzato dalla presenza di dati mancanti. Importiamo i dati in R:
Abbiamo 650 osservazioni:
dim(d)
#> [1] 650 5
Le frequenze di dati mancanti vengono ottentute mediante la funzione summary()
summary(d)
#> subject s1 s2 s3 s4
#> Min. :5756 Min. :0.00 Min. :0.00 Min. :0.00 Min. :0.0
#> 1st Qu.:5934 1st Qu.:2.00 1st Qu.:2.00 1st Qu.:1.00 1st Qu.:2.0
#> Median :6102 Median :3.00 Median :3.00 Median :2.00 Median :3.0
#> Mean :6104 Mean :2.93 Mean :2.56 Mean :2.21 Mean :2.4
#> 3rd Qu.:6275 3rd Qu.:4.00 3rd Qu.:4.00 3rd Qu.:4.00 3rd Qu.:3.0
#> Max. :6451 Max. :4.00 Max. :4.00 Max. :4.00 Max. :4.0
#> NA's :25 NA's :25 NA's :25 NA's :190
Il modello viene specificato come segue Brown (2015):
model <- '
esteem =~ s1 + s2 + s3 + s4
s2 ~~ s4
'
Adattiamo il modello ai dati specificanto l’utilizzo del metodo full information ML per la gestione dei dati mancanti:
fit <- cfa(model, data = d, missing = "fiml")
È possibile identificare le configurazioni di risposte agli item che contengono dati mancanti:
fit@Data@Mp[[1]]$npatterns
#> [1] 5
Possiamo esaminare la proporzione di dati disponibili per ciascun indicatore e per ciascuna coppia di indicatori:
Ad esempio, consideriamo l’item s1
; se moltiplichiamo la copertura di questo elemento per la numerosità campionaria possiamo concludere che questa variabile contiene 25 osservazioni mancanti; e così via.
650 * 0.9615385
#> [1] 625
Procediamo poi come sempre per esaminare la soluzione ottenuta.
effectsize::interpret(fit)
#> Name Value Threshold Interpretation
#> 1 GFI 0.99945 0.95 satisfactory
#> 2 AGFI 0.99229 0.90 satisfactory
#> 3 NFI 0.99919 0.90 satisfactory
#> 4 NNFI 0.99898 0.90 satisfactory
#> 5 CFI 0.99983 0.90 satisfactory
#> 6 RMSEA 0.02024 0.05 satisfactory
#> 7 SRMR 0.00485 0.08 satisfactory
#> 8 RFI 0.99516 0.90 satisfactory
#> 9 PNFI 0.16653 0.50 poor
#> 10 IFI 0.99983 0.90 satisfactory
standardizedSolution(fit)
#> lhs op rhs est.std se z pvalue ci.lower ci.upper
#> 1 esteem =~ s1 0.737 0.020 37.09 0 0.698 0.776
#> 2 esteem =~ s2 0.920 0.013 68.65 0 0.894 0.947
#> 3 esteem =~ s3 0.880 0.013 66.43 0 0.854 0.906
#> 4 esteem =~ s4 0.905 0.016 55.40 0 0.873 0.937
#> 5 s2 ~~ s4 -0.886 0.216 -4.11 0 -1.309 -0.463
#> 6 s1 ~~ s1 0.456 0.029 15.55 0 0.399 0.514
#> 7 s2 ~~ s2 0.153 0.025 6.19 0 0.104 0.201
#> 8 s3 ~~ s3 0.225 0.023 9.64 0 0.179 0.271
#> 9 s4 ~~ s4 0.182 0.030 6.15 0 0.124 0.240
#> 10 esteem ~~ esteem 1.000 0.000 NA NA 1.000 1.000
#> 11 s1 ~1 2.375 0.078 30.61 0 2.223 2.527
#> 12 s2 ~1 1.881 0.066 28.59 0 1.752 2.010
#> 13 s3 ~1 1.584 0.059 26.78 0 1.468 1.700
#> 14 s4 ~1 1.850 0.071 26.05 0 1.710 1.989
#> 15 esteem ~1 0.000 0.000 NA NA 0.000 0.000
57.4 Dati mancanti in R
Per completezza, aggiungiamo qualche breve accenno alla gestione dei dati mancanti in R.
In R, i valori mancanti vengono indicati dal codice NA
, che significa not available — non disponibile.
Se una variabile contiene valori mancanti, R
non è in grado di applicare ad essa alcune funzioni, come ad esempio la media. Per questa ragione, la gran parte delle funzioni di R
prevedono modi specifici per trattare i valori mancanti.
Ci sono diversi tipi di dati “mancanti” in R
;
-
NA
- generico dato mancante; -
NaN
- il codiceNaN
(Not a Number) indica i valori numerici impossibili, quali ad esempio un valore 0/0; -
Inf
e-Inf
- Infinity, si verifca, ad esempio, quando si divide un numero per 0.
La funzione is.na()
ritorna un output che indica con TRUE le celle che contengono NA o NaN.
Si noti che
- se
is.na(x)
è TRUE, allora!is.na(x)
è FALSE; -
all(!is.na(x))
ritorna TRUE se tutti i valorix
sono NOT NA; -
any(is.na(x))
risponde alla domanda: c’è qualche valore NA (almeno uno) inx
?; -
complete.cases(x)
ritorna TRUE se ciascun elemento dix
è is NOT NA; ritorna FALSE se almeno un elemento dix
è NA;
Le funzioni R
is.nan()
e is.infinite()
si applicano ai tipi di dati NaN
e Inf
.
Per esempio, consideriamo il seguente data.frame:
Per creare un nuovo Dataframe senza valori mancanti:
d_clean <- d[complete.cases(d), ]
d_clean
#> # A tibble: 2 × 5
#> w x y z q
#> <dbl> <int> <dbl> <dbl> <dbl>
#> 1 1 1 1 2 3
#> 2 3 4 1 17 1
Oppure, se vogliamo eliminare le righe con NA solo in una variabile:
d1 <- d[!is.na(d$q), ]
d1
#> # A tibble: 4 × 5
#> w x y z q
#> <dbl> <int> <dbl> <dbl> <dbl>
#> 1 1 1 1 2 3
#> 2 NA 3 1 10 5
#> 3 3 4 1 17 1
#> 4 NA 5 1 26 4
Se vogliamo esaminare le righe con i dati mancanti in qualunque colonna:
d_na <- d[!complete.cases(d), ]
d_na
#> # A tibble: 3 × 5
#> w x y z q
#> <dbl> <int> <dbl> <dbl> <dbl>
#> 1 2 2 1 5 NA
#> 2 NA 3 1 10 5
#> 3 NA 5 1 26 4
57.5 Riflessioni Conclusive
In conclusione, questo capitolo ha fornito una panoramica completa sui dati mancanti, affrontando le loro tipologie, le possibili cause e le strategie di gestione più appropriate in base al contesto e al meccanismo sottostante. Abbiamo esplorato tecniche tradizionali come la listwise e pairwise deletion, metodi di imputazione semplici e avanzati, e approcci più robusti come l’algoritmo Expectation-Maximization e il Full Information Maximum Likelihood (FIML), evidenziandone vantaggi e limiti.
In particolare, è stato sottolineato come il metodo FIML rappresenti una soluzione ottimale per molte applicazioni in CFA e SEM, grazie alla sua capacità di sfruttare tutte le informazioni disponibili senza introdurre i bias tipici dell’imputazione. Esempi pratici e codice R hanno illustrato come implementare queste tecniche, rendendo il capitolo una risorsa preziosa sia per comprendere i fondamenti teorici sia per affrontare casi applicativi concreti. La gestione adeguata dei dati mancanti non solo migliora l’affidabilità delle analisi, ma contribuisce a una migliore interpretazione dei risultati, garantendo robustezza e validità nelle conclusioni di ricerca.
Informazioni sull’Ambiente di Sviluppo
sessionInfo()
#> R version 4.4.2 (2024-10-31)
#> Platform: aarch64-apple-darwin20
#> Running under: macOS Sequoia 15.3.1
#>
#> Matrix products: default
#> BLAS: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib
#> LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.0
#>
#> locale:
#> [1] C/UTF-8/C/C/C/C
#>
#> time zone: Europe/Rome
#> tzcode source: internal
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] ggokabeito_0.1.0 see_0.10.0 MASS_7.3-65 viridis_0.6.5
#> [5] viridisLite_0.4.2 ggpubr_0.6.0 ggExtra_0.10.1 gridExtra_2.3
#> [9] patchwork_1.3.0 bayesplot_1.11.1 semTools_0.5-6 semPlot_1.1.6
#> [13] lavaan_0.6-19 psych_2.4.12 scales_1.3.0 markdown_1.13
#> [17] knitr_1.49 lubridate_1.9.4 forcats_1.0.0 stringr_1.5.1
#> [21] dplyr_1.1.4 purrr_1.0.4 readr_2.1.5 tidyr_1.3.1
#> [25] tibble_3.2.1 ggplot2_3.5.1 tidyverse_2.0.0 here_1.0.1
#>
#> loaded via a namespace (and not attached):
#> [1] rstudioapi_0.17.1 jsonlite_1.9.0 datawizard_1.0.0
#> [4] magrittr_2.0.3 TH.data_1.1-3 estimability_1.5.1
#> [7] farver_2.1.2 nloptr_2.1.1 rmarkdown_2.29
#> [10] vctrs_0.6.5 minqa_1.2.8 effectsize_1.0.0
#> [13] base64enc_0.1-3 rstatix_0.7.2 htmltools_0.5.8.1
#> [16] broom_1.0.7 Formula_1.2-5 htmlwidgets_1.6.4
#> [19] plyr_1.8.9 sandwich_3.1-1 rio_1.2.3
#> [22] emmeans_1.10.7 zoo_1.8-13 igraph_2.1.4
#> [25] mime_0.12 lifecycle_1.0.4 pkgconfig_2.0.3
#> [28] Matrix_1.7-2 R6_2.6.1 fastmap_1.2.0
#> [31] rbibutils_2.3 shiny_1.10.0 digest_0.6.37
#> [34] OpenMx_2.21.13 fdrtool_1.2.18 colorspace_2.1-1
#> [37] rprojroot_2.0.4 Hmisc_5.2-2 timechange_0.3.0
#> [40] abind_1.4-8 compiler_4.4.2 withr_3.0.2
#> [43] glasso_1.11 htmlTable_2.4.3 backports_1.5.0
#> [46] carData_3.0-5 performance_0.13.0 R.utils_2.13.0
#> [49] ggsignif_0.6.4 corpcor_1.6.10 gtools_3.9.5
#> [52] tools_4.4.2 pbivnorm_0.6.0 foreign_0.8-88
#> [55] zip_2.3.2 httpuv_1.6.15 nnet_7.3-20
#> [58] R.oo_1.27.0 glue_1.8.0 quadprog_1.5-8
#> [61] nlme_3.1-167 promises_1.3.2 lisrelToR_0.3
#> [64] grid_4.4.2 checkmate_2.3.2 cluster_2.1.8
#> [67] reshape2_1.4.4 generics_0.1.3 gtable_0.3.6
#> [70] tzdb_0.4.0 R.methodsS3_1.8.2 data.table_1.17.0
#> [73] hms_1.1.3 car_3.1-3 sem_3.1-16
#> [76] pillar_1.10.1 rockchalk_1.8.157 later_1.4.1
#> [79] splines_4.4.2 lattice_0.22-6 survival_3.8-3
#> [82] kutils_1.73 tidyselect_1.2.1 miniUI_0.1.1.1
#> [85] pbapply_1.7-2 reformulas_0.4.0 stats4_4.4.2
#> [88] xfun_0.51 qgraph_1.9.8 arm_1.14-4
#> [91] stringi_1.8.4 pacman_0.5.1 boot_1.3-31
#> [94] evaluate_1.0.3 codetools_0.2-20 mi_1.1
#> [97] cli_3.6.4 RcppParallel_5.1.10 rpart_4.1.24
#> [100] parameters_0.24.1 xtable_1.8-4 Rdpack_2.6.2
#> [103] munsell_0.5.1 Rcpp_1.0.14 coda_0.19-4.1
#> [106] png_0.1-8 XML_3.99-0.18 parallel_4.4.2
#> [109] bayestestR_0.15.2 jpeg_0.1-10 lme4_1.1-36
#> [112] mvtnorm_1.3-3 insight_1.0.2 openxlsx_4.2.8
#> [115] rlang_1.1.5 multcomp_1.4-28 mnormt_2.1.1