here::here("code", "_common.R") |>
source()
# Load packages
if (!requireNamespace("pacman")) install.packages("pacman")
pacman::p_load(lavaan, semPlot)
52 CFA: confronto tra modelli
- effettuare il confronto tra diversi modelli di CFA.
- Leggere il capitolo Structural Equation Modeling del testo di Petersen (2024).
52.1 Introduzione
In un modello CFA, i parametri possono essere stimati senza vincoli, possono essere fissi o possono essre stimati sulla base di alcuni vincoli. Un parametro libero è sconosciuto e il ricercatore consente all’algoritmo di stima di trovare il suo valore ottimale che, insime agli altri parametri del modello, riduce al minimo le differenze tra le matrici di varianze-covarianze osservate e quelle predette dal modello. Un parametro fisso è pre-specificato dal ricercatore ad un valore specifico, più comunemente 1.0 (ad esempio, per definire la metrica di una variabile latente) o 0 (ad esempio, l’assenza di saturazionoi fattoriali o di covarianze di errore). Come per un parametro libero, anche un parametro vincolato è sconosciuto; tuttavia, un tale parametro non può assumere un valore qualsiasi, ma deve rispettare le restrizioni su suoi valori che il ricercatore ha imposto. I vincoli più comuni sono i vincoli di uguaglianza, in cui i parametri non standardizzati devono assumere valori uguali (ad esempio, in diversi gruppi).
Consideriamo un esempio discusso da Brown (2015). Viene qui esaminato un set di dati in cui le prime tre misure osservate (X1, X2, X3) sono indicatori di un costrutto latente corrispondente alla Memoria uditiva e il secondo insieme di misure (X4, X5, X6) sono indicatori di un altro costrutto latente, Memoria visiva. Le tre misure usate quali indicatori del costrutto di memoria uditiva sono:
- X1 = memoria logica,
- X2 = associazione verbale a coppie,
- X3 = liste di parole;
le tre misure usate come indicatori del costrutto di memoria visiva sono:
- X4 = immagini di facce,
- X5 = foto di famiglia,
- X6 = generiche riproduzioni visive.
I dati sono i seguenti:
sds <- '2.610 2.660 2.590 1.940 2.030 2.050'
cors <-'
1.000
0.661 1.000
0.630 0.643 1.000
0.270 0.300 0.268 1.000
0.297 0.265 0.225 0.805 1.000
0.290 0.287 0.248 0.796 0.779 1.000'
covs <- getCov(cors, sds = sds, names = paste("x", 1:6, sep = ""))
print(covs)
#> x1 x2 x3 x4 x5 x6
#> x1 6.81 4.59 4.26 1.37 1.57 1.55
#> x2 4.59 7.08 4.43 1.55 1.43 1.57
#> x3 4.26 4.43 6.71 1.35 1.18 1.32
#> x4 1.37 1.55 1.35 3.76 3.17 3.17
#> x5 1.57 1.43 1.18 3.17 4.12 3.24
#> x6 1.55 1.57 1.32 3.17 3.24 4.20
Adattiamo i cinque modelli discussi da Brown (2015).
52.2 Modello congenerico
model.congeneric <- '
auditorymemory =~ x1 + x2 + x3
visualmemory =~ x4 + x5 + x6
'
fit.congeneric <- cfa(
model.congeneric,
sample.cov = covs,
sample.nobs = 200,
std.lv = TRUE
)
L’output si ottiene con:
summary(
fit.congeneric,
fit.measures = TRUE,
standardized = TRUE,
rsquare = TRUE
)
#> lavaan 0.6-19 ended normally after 21 iterations
#>
#> Estimator ML
#> Optimization method NLMINB
#> Number of model parameters 13
#>
#> Number of observations 200
#>
#> Model Test User Model:
#>
#> Test statistic 4.877
#> Degrees of freedom 8
#> P-value (Chi-square) 0.771
#>
#> Model Test Baseline Model:
#>
#> Test statistic 719.515
#> Degrees of freedom 15
#> P-value 0.000
#>
#> User Model versus Baseline Model:
#>
#> Comparative Fit Index (CFI) 1.000
#> Tucker-Lewis Index (TLI) 1.008
#>
#> Loglikelihood and Information Criteria:
#>
#> Loglikelihood user model (H0) -2337.980
#> Loglikelihood unrestricted model (H1) -2335.541
#>
#> Akaike (AIC) 4701.959
#> Bayesian (BIC) 4744.837
#> Sample-size adjusted Bayesian (SABIC) 4703.652
#>
#> Root Mean Square Error of Approximation:
#>
#> RMSEA 0.000
#> 90 Percent confidence interval - lower 0.000
#> 90 Percent confidence interval - upper 0.057
#> P-value H_0: RMSEA <= 0.050 0.929
#> P-value H_0: RMSEA >= 0.080 0.010
#>
#> Standardized Root Mean Square Residual:
#>
#> SRMR 0.012
#>
#> Parameter Estimates:
#>
#> Standard errors Standard
#> Information Expected
#> Information saturated (h1) model Structured
#>
#> Latent Variables:
#> Estimate Std.Err z-value P(>|z|) Std.lv Std.all
#> auditorymemory =~
#> x1 2.101 0.166 12.663 0.000 2.101 0.807
#> x2 2.182 0.168 12.976 0.000 2.182 0.823
#> x3 2.013 0.166 12.124 0.000 2.013 0.779
#> visualmemory =~
#> x4 1.756 0.108 16.183 0.000 1.756 0.907
#> x5 1.795 0.115 15.608 0.000 1.795 0.887
#> x6 1.796 0.117 15.378 0.000 1.796 0.878
#>
#> Covariances:
#> Estimate Std.Err z-value P(>|z|) Std.lv Std.all
#> auditorymemory ~~
#> visualmemory 0.382 0.070 5.463 0.000 0.382 0.382
#>
#> Variances:
#> Estimate Std.Err z-value P(>|z|) Std.lv Std.all
#> .x1 2.366 0.372 6.365 0.000 2.366 0.349
#> .x2 2.277 0.383 5.940 0.000 2.277 0.323
#> .x3 2.621 0.373 7.027 0.000 2.621 0.393
#> .x4 0.662 0.117 5.668 0.000 0.662 0.177
#> .x5 0.877 0.134 6.554 0.000 0.877 0.214
#> .x6 0.956 0.139 6.866 0.000 0.956 0.229
#> auditorymemory 1.000 1.000 1.000
#> visualmemory 1.000 1.000 1.000
#>
#> R-Square:
#> Estimate
#> x1 0.651
#> x2 0.677
#> x3 0.607
#> x4 0.823
#> x5 0.786
#> x6 0.771
Il diagramma di percorso del modello è il seguente.
semPaths(
fit.congeneric,
"std",
posCol = c("black"),
edge.label.cex = 1.2,
sizeMan = 7,
edge.width = 0.4, # Set a fixed width for all arrows
fade = FALSE # Disable fading of the arrows
)
52.3 Modello tau-equivalente
Solo memoria auditiva:
model.tau.a <- '
auditorymemory =~ x1 + v1*x1 + v1*x2 + v1*x3
visualmemory =~ x4 + x5 + x6
'
fit.tau.a <- cfa(
model.tau.a,
sample.cov = covs,
sample.nobs = 200,
std.lv = TRUE
)
Memoria auditiva e visiva:
model.tau.av <- '
auditorymemory =~ NA*x1 + v1*x1 + v1*x2 + v1*x3
visualmemory =~ NA*x4 + v2*x4 + v2*x5 + v2*x6
'
fit.tau.av <- cfa(
model.tau.av,
sample.cov = covs,
sample.nobs = 200,
std.lv = TRUE
)
semPaths(
fit.tau.av,
"std",
posCol = c("black"),
edge.label.cex = 1.2,
sizeMan = 7,
edge.width = 0.4, # Set a fixed width for all arrows
fade = FALSE # Disable fading of the arrows
)
52.4 Modello parallelo
Solo memoria auditiva:
model.parallel.a <- '
auditorymemory =~ x1 + v1*x1 + v1*x2 + v1*x3
visualmemory =~ x4 + v2*x4 + v2*x5 + v2*x6
x1 ~~ v3 * x1
x2 ~~ v3 * x2
x3 ~~ v3 * x3
'
fit.parallel.a <- cfa(
model.parallel.a,
sample.cov = covs,
sample.nobs = 200,
std.lv = TRUE
)
Memoria auditiva e visiva:
model.parallel.av <- '
auditorymemory =~ x1 + v1*x1 + v1*x2 + v1*x3
visualmemory =~ x4 + v2*x4 + v2*x5 + v2*x6
x1 ~~ v3 * x1
x2 ~~ v3 * x2
x3 ~~ v3 * x3
x4 ~~ v4 * x4
x5 ~~ v4 * x5
x6 ~~ v4 * x6
'
fit.parallel.av <- cfa(
model.parallel.av,
sample.cov = covs,
sample.nobs = 200,
std.lv = TRUE
)
semPaths(
fit.parallel.av,
"std",
posCol = c("black"),
edge.label.cex = 1.2,
sizeMan = 7,
edge.width = 0.4, # Set a fixed width for all arrows
fade = FALSE # Disable fading of the arrows
)
52.5 Il test del \(\chi^2\)
Il confronto tra modelli nidificati procede attraverso il test \(\chi^2\). Tale test si basa su una proprietà delle variabili casuali distribuite come \(\chi^2\): la differenza tra due v.c. \(X_1\) e \(X_2\) che seguono la distribuzione \(\chi^2\), rispettivamente con \(\nu_1\) e \(\nu_2\), con \(\nu_1 > \nu_2\), è una variabile causale che segue la distribuzione \(\chi^2\) con gradi di libertà pari a \(\nu_1 - \nu_2\).
Un modello nidificato è un modello che impone dei vincoli sui parametri del modello di partenza. L’imposizione di vincoli sui parametri ha la conseguenza che vi sarà un numero minore di parametri da stimare. Il confronto tra i modelli si esegue valutando in maniera relativa la bontà di adattamento di ciascun modello per mezzo della statistica chi-quadrato. La statistica così calcolata avrà un numero di gradi di libertà uguale alla differenza tra i gradi di libertà dei due modelli.
Nel caso dell’esempio in dicussione, abbiamo
out = anova(
fit.congeneric,
fit.tau.a,
fit.tau.av,
fit.parallel.a,
fit.parallel.av,
test = "chisq"
)
print(out)
#>
#> Chi-Squared Difference Test
#>
#> Df AIC BIC Chisq Chisq diff RMSEA Df diff Pr(>Chisq)
#> fit.congeneric 8 4702 4745 4.88
#> fit.tau.a 10 4699 4735 5.66 0.78 0.000 2 0.68
#> fit.tau.av 12 4695 4725 5.88 0.22 0.000 2 0.90
#> fit.parallel.a 14 4691 4714 5.98 0.10 0.000 2 0.95
#> fit.parallel.av 16 4690 4707 9.28 3.30 0.057 2 0.19
I test precedenti indicano come non vi sia una perdita di adattamento passando dal modello congenerico al modello più restrittivo (ovvero, il modello parallelo per entrambi i fattori). Per questi dati, dunque, può essere adottato il modello più semplice, cioè il modello parallelo.
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 magrittr_2.0.3
#> [4] TH.data_1.1-3 estimability_1.5.1 farver_2.1.2
#> [7] nloptr_2.1.1 rmarkdown_2.29 vctrs_0.6.5
#> [10] minqa_1.2.8 base64enc_0.1-3 rstatix_0.7.2
#> [13] htmltools_0.5.8.1 broom_1.0.7 Formula_1.2-5
#> [16] htmlwidgets_1.6.4 plyr_1.8.9 sandwich_3.1-1
#> [19] emmeans_1.10.7 zoo_1.8-13 igraph_2.1.4
#> [22] mime_0.12 lifecycle_1.0.4 pkgconfig_2.0.3
#> [25] Matrix_1.7-2 R6_2.6.1 fastmap_1.2.0
#> [28] rbibutils_2.3 shiny_1.10.0 digest_0.6.37
#> [31] OpenMx_2.21.13 fdrtool_1.2.18 colorspace_2.1-1
#> [34] rprojroot_2.0.4 Hmisc_5.2-2 timechange_0.3.0
#> [37] abind_1.4-8 compiler_4.4.2 withr_3.0.2
#> [40] glasso_1.11 htmlTable_2.4.3 backports_1.5.0
#> [43] carData_3.0-5 ggsignif_0.6.4 corpcor_1.6.10
#> [46] gtools_3.9.5 tools_4.4.2 pbivnorm_0.6.0
#> [49] foreign_0.8-88 zip_2.3.2 httpuv_1.6.15
#> [52] nnet_7.3-20 glue_1.8.0 quadprog_1.5-8
#> [55] nlme_3.1-167 promises_1.3.2 lisrelToR_0.3
#> [58] grid_4.4.2 checkmate_2.3.2 cluster_2.1.8
#> [61] reshape2_1.4.4 generics_0.1.3 gtable_0.3.6
#> [64] tzdb_0.4.0 data.table_1.17.0 hms_1.1.3
#> [67] car_3.1-3 sem_3.1-16 pillar_1.10.1
#> [70] rockchalk_1.8.157 later_1.4.1 splines_4.4.2
#> [73] lattice_0.22-6 survival_3.8-3 kutils_1.73
#> [76] tidyselect_1.2.1 miniUI_0.1.1.1 pbapply_1.7-2
#> [79] reformulas_0.4.0 stats4_4.4.2 xfun_0.51
#> [82] qgraph_1.9.8 arm_1.14-4 stringi_1.8.4
#> [85] pacman_0.5.1 boot_1.3-31 evaluate_1.0.3
#> [88] codetools_0.2-20 mi_1.1 cli_3.6.4
#> [91] RcppParallel_5.1.10 rpart_4.1.24 xtable_1.8-4
#> [94] Rdpack_2.6.2 munsell_0.5.1 Rcpp_1.0.14
#> [97] coda_0.19-4.1 png_0.1-8 XML_3.99-0.18
#> [100] parallel_4.4.2 jpeg_0.1-10 lme4_1.1-36
#> [103] mvtnorm_1.3-3 openxlsx_4.2.8 rlang_1.1.5
#> [106] multcomp_1.4-28 mnormt_2.1.1