33  Punteggio totale e modello fattoriale

Prerequisiti

Concetti e Competenze Chiave

Preparazione del Notebook

# Carica il file _common.R per impostazioni di pacchetti e opzioni
here::here("code", "_common.R") |> source()

# Carica pacchetti aggiuntivi
pacman::p_load(lavaan, modelsummary)

In questo capitolo discute l’uso del punteggio totale del test quale misura del costrutto latente. Questa è una pratica largamente usata, ma solo in parte giustificata. Esamineremo a questo proposito le considerazioni di McNeish e Wolf (2020).

33.1 Punteggio totale e modello fattoriale parallelo

McNeish e Wolf (2020) richiamano l’attenzione sul fatto che usare il punteggio totale quale misura di un costrutto è possibile solo quando i dati soddisfano i vincoli di un modello fattoriale parallelo.

Consideriamo l’esempio seguente, nel quale McNeish e Wolf (2020) esaminano i dati “classici” di Holzinger and Swineford (1939), i quali si riferiscono ai seguenti item:

  • Paragraph comprehension
  • Sentence completion
  • Word definitions
  • Speeded addition
  • Speeded dot counting
  • Discrimination between curved and straight letters

Leggiamo i dati in R.

d <- rio::import(
  here::here("data", "1_Factor_Parallel.csv")
)

McNeish e Wolf (2020) sottolineano il fatto che il punteggio totale

\[ \text{Punteggio totale} = \text{Item 1 + Item 2 + Item 3 + Item 4 + Item 5 + Item 6} \]

rappresenta l’idea che ciasun item fornisca la stessa quantità di informazione relativamente alla misura del costrutto. Ciò può essere specificato da un modello fattoriale nel quale le saturazioni fattoriali degli item sono tutte uguali a 1. Questo corrisponde al modello parallelo che abbiamo discusso in precedenza. In tali circostanze, i punteggi fattoriali del test risultano perfettamente associati al punteggio totale (correlazione uguale a 1). Dunque, se tale modello fattoriale è giustificato dai dati, questo giustifica l’uso del punteggio totale del test quale misura del costrutto.

È facile verificare tali affermazioni. Implementiamo il modello parallelo.

m_parallel <-
  "
  # all loadings are fixed to one
  f1 =~ 1*X4 + 1*X5 + 1*X6 + 1*X7 + 1*X8 + 1*X9
  
  # all residual variances constrained to same value
  X4 ~~ theta*X4
  X5 ~~ theta*X5
  X6 ~~ theta*X6
  X7 ~~ theta*X7
  X8 ~~ theta*X8
  X9 ~~ theta*X9
"

Adattiamo il modello parallelo ai dati forniti dagli autori.

fit_parallel <- sem(m_parallel, data=d)

Calcoliamo il punteggio totale.

d$ts <- with(
  d,
  X4 + X5 + X6 + X7 + X8 + X9
)

Calcoliamo i punteggi fattoriali.

scores <- lavPredict(fit_parallel, method="regression")
d$scores <- as.numeric(scores)

Un diagramma a dispersione tra il punteggio totale e i punteggi fattoriali conferma che i due sono perfettamente associati. Quindi, usare il punteggio totale o i punteggi fattoriali è equivalente.

d |> 
  ggplot(aes(x=ts, y=scores)) + 
  geom_point()

Tuttavia, questa conclusione è valida solo se il modello parallelo è giustificato per i dati. Se esaminiamo l’output di lavaan vediamo che, nel caso presente, questo non è vero.

# report output with fit measures and standardized estimates
out = summary(fit_parallel, fit.measures = TRUE, standardized = TRUE)
print(out)
#> lavaan 0.6-19 ended normally after 13 iterations
#> 
#>   Estimator                                         ML
#>   Optimization method                           NLMINB
#>   Number of model parameters                         7
#>   Number of equality constraints                     5
#> 
#>   Number of observations                           301
#> 
#> Model Test User Model:
#>                                                       
#>   Test statistic                               325.899
#>   Degrees of freedom                                19
#>   P-value (Chi-square)                           0.000
#> 
#> Model Test Baseline Model:
#> 
#>   Test statistic                               568.519
#>   Degrees of freedom                                15
#>   P-value                                        0.000
#> 
#> User Model versus Baseline Model:
#> 
#>   Comparative Fit Index (CFI)                    0.446
#>   Tucker-Lewis Index (TLI)                       0.562
#> 
#> Loglikelihood and Information Criteria:
#> 
#>   Loglikelihood user model (H0)              -2680.931
#>   Loglikelihood unrestricted model (H1)      -2517.981
#>                                                       
#>   Akaike (AIC)                                5365.862
#>   Bayesian (BIC)                              5373.276
#>   Sample-size adjusted Bayesian (SABIC)       5366.933
#> 
#> Root Mean Square Error of Approximation:
#> 
#>   RMSEA                                          0.232
#>   90 Percent confidence interval - lower         0.210
#>   90 Percent confidence interval - upper         0.254
#>   P-value H_0: RMSEA <= 0.050                    0.000
#>   P-value H_0: RMSEA >= 0.080                    1.000
#> 
#> Standardized Root Mean Square Residual:
#> 
#>   SRMR                                           0.206
#> 
#> 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
#>   f1 =~                                                                 
#>     X4                1.000                               0.633    0.551
#>     X5                1.000                               0.633    0.551
#>     X6                1.000                               0.633    0.551
#>     X7                1.000                               0.633    0.551
#>     X8                1.000                               0.633    0.551
#>     X9                1.000                               0.633    0.551
#> 
#> Variances:
#>                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
#>    .X4      (thet)    0.920    0.034   27.432    0.000    0.920    0.697
#>    .X5      (thet)    0.920    0.034   27.432    0.000    0.920    0.697
#>    .X6      (thet)    0.920    0.034   27.432    0.000    0.920    0.697
#>    .X7      (thet)    0.920    0.034   27.432    0.000    0.920    0.697
#>    .X8      (thet)    0.920    0.034   27.432    0.000    0.920    0.697
#>    .X9      (thet)    0.920    0.034   27.432    0.000    0.920    0.697
#>     f1                0.400    0.045    8.803    0.000    1.000    1.000

Dunque, per questi dati, il punteggio totale può ovviamente essere calcolato. Ma non fornisce una misura adeguata del costrutto. Dunque, il punteggio totale non dovrebbe essere usato nel caso dei dati ottenuti con questo test.

33.2 Punteggio totale e modello fattoriale congenerico

Gli autori adattano ai dati un modello congenerico.

m_congeneric <- 
'
  #all loadings are uniquely estimated
  f1 =~ NA*X4 + X5 + X6 + X7 + X8 + X9
  #constrain factor variance to 1
  f1 ~~ 1*f1
'
# Fit above model
fit_congeneric <- sem(m_congeneric, data=d)
parameterEstimates(fit_congeneric, standardized = TRUE) %>%
  dplyr::filter(op == "=~") %>%
  dplyr::select(
    "Latent Factor" = lhs,
    Indicator = rhs,
    B = est,
    SE = se,
    Z = z,
    "p-value" = pvalue,
    Beta = std.all
  ) %>%
  knitr::kable(
    digits = 3, booktabs = TRUE, format = "markdown",
    caption = "Factor Loadings"
  )
Factor Loadings
Latent Factor Indicator B SE Z p-value Beta
f1 X4 0.963 0.059 16.27 0.000 0.824
f1 X5 1.121 0.067 16.84 0.000 0.846
f1 X6 0.894 0.058 15.45 0.000 0.792
f1 X7 0.195 0.071 2.77 0.006 0.170
f1 X8 0.185 0.063 2.94 0.003 0.180
f1 X9 0.278 0.065 4.25 0.000 0.258

Si noti che le saturazioni fattoriali sono molto diverse tra loro, suggerendo che il punteggio del costrutto si relaziona in modo diverso con ciascun item e che sarebbe inappropriato stimare il punteggio del costrutto assegnando un peso unitario agli item.

McNeish e Wolf (2020) calcolano poi i punteggi fattoriali del modello congenerico.

scores_cong <- lavPredict(fit_congeneric, method="regression")
d$scores_cong <- as.numeric(scores_cong)

Il grafico seguente mostra la relazione tra i punteggi fattoriali e il punteggio totale.

d |> 
  ggplot(aes(x=ts, y=scores_cong)) + 
  geom_point()

Nel caso presente, il coefficiente di determinazione tra punteggio totale e punteggi fattoriali è 0.77.

cor(d$ts, d$scores_cong)^2
#> [1] 0.766

Secondo gli autori, ciò significa che due persone con un punteggio totale identico potrebbero avere punteggi di modello congenerico potenzialmente diversi perché hanno raggiunto il loro particolare punteggio totale approvando item diversi. Poiché il modello congenerico assegna pesi diversi agli item, ciascun item contribuisce in modo diverso al punteggio fattoriale del modello congenerico, il che non è vero per il punteggio totale.

Si noti che, per i dati di Holzinger and Swineford (1939), neppure un modello congenerico ad un fattore si dimostra adeguato.

out = summary(fit_congeneric, fit.measures = TRUE, standardized = TRUE)
print(out)
#> lavaan 0.6-19 ended normally after 16 iterations
#> 
#>   Estimator                                         ML
#>   Optimization method                           NLMINB
#>   Number of model parameters                        12
#> 
#>   Number of observations                           301
#> 
#> Model Test User Model:
#>                                                       
#>   Test statistic                               115.366
#>   Degrees of freedom                                 9
#>   P-value (Chi-square)                           0.000
#> 
#> Model Test Baseline Model:
#> 
#>   Test statistic                               568.519
#>   Degrees of freedom                                15
#>   P-value                                        0.000
#> 
#> User Model versus Baseline Model:
#> 
#>   Comparative Fit Index (CFI)                    0.808
#>   Tucker-Lewis Index (TLI)                       0.680
#> 
#> Loglikelihood and Information Criteria:
#> 
#>   Loglikelihood user model (H0)              -2575.664
#>   Loglikelihood unrestricted model (H1)      -2517.981
#>                                                       
#>   Akaike (AIC)                                5175.328
#>   Bayesian (BIC)                              5219.813
#>   Sample-size adjusted Bayesian (SABIC)       5181.756
#> 
#> Root Mean Square Error of Approximation:
#> 
#>   RMSEA                                          0.198
#>   90 Percent confidence interval - lower         0.167
#>   90 Percent confidence interval - upper         0.231
#>   P-value H_0: RMSEA <= 0.050                    0.000
#>   P-value H_0: RMSEA >= 0.080                    1.000
#> 
#> Standardized Root Mean Square Residual:
#> 
#>   SRMR                                           0.129
#> 
#> 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
#>   f1 =~                                                                 
#>     X4                0.963    0.059   16.274    0.000    0.963    0.824
#>     X5                1.121    0.067   16.835    0.000    1.121    0.846
#>     X6                0.894    0.058   15.450    0.000    0.894    0.792
#>     X7                0.195    0.071    2.767    0.006    0.195    0.170
#>     X8                0.185    0.063    2.938    0.003    0.185    0.180
#>     X9                0.278    0.065    4.245    0.000    0.278    0.258
#> 
#> Variances:
#>                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
#>     f1                1.000                               1.000    1.000
#>    .X4                0.437    0.056    7.775    0.000    0.437    0.320
#>    .X5                0.500    0.071    6.998    0.000    0.500    0.285
#>    .X6                0.474    0.054    8.777    0.000    0.474    0.372
#>    .X7                1.278    0.105   12.211    0.000    1.278    0.971
#>    .X8                1.023    0.084   12.204    0.000    1.023    0.967
#>    .X9                1.080    0.089   12.132    0.000    1.080    0.933

Se trascuriamo le considerazioni sulla struttura fattoriale e esaminiamo (per esempio) unicamente il coefficiente omega, finiamo per trovare una risposta accettabile, ma sbagliata.

psych::omega(d[, 1:6])
#> Omega 
#> Call: omegah(m = m, nfactors = nfactors, fm = fm, key = key, flip = flip, 
#>     digits = digits, title = title, sl = sl, labels = labels, 
#>     plot = plot, n.obs = n.obs, rotate = rotate, Phi = Phi, option = option, 
#>     covar = covar)
#> Alpha:                 0.72 
#> G.6:                   0.76 
#> Omega Hierarchical:    0.55 
#> Omega H asymptotic:    0.65 
#> Omega Total            0.84 
#> 
#> Schmid Leiman Factor loadings greater than  0.2 
#>       g  F1*  F2*   F3*   h2   h2   u2   p2  com
#> X4 0.73            0.68 1.00 1.00 0.00 0.53 1.99
#> X5 0.96                 0.92 0.92 0.08 1.00 1.01
#> X6 0.69            0.22 0.54 0.54 0.46 0.90 1.22
#> X7           0.56       0.33 0.33 0.67 0.03 1.15
#> X8           0.75       0.59 0.59 0.41 0.05 1.12
#> X9 0.22      0.49       0.29 0.29 0.71 0.16 1.41
#> 
#> With Sums of squares  of:
#>    g  F1*  F2*  F3*   h2 
#> 2.02 0.00 1.11 0.54 2.67 
#> 
#> general/max  0.75   max/min =   622
#> mean percent general =  0.44    with sd =  0.43 and cv of  0.97 
#> Explained Common Variance of the general factor =  0.55 
#> 
#> The degrees of freedom are 0  and the fit is  0 
#> The number of observations was  301  with Chi Square =  0.03  with prob <  NA
#> The root mean square of the residuals is  0 
#> The df corrected root mean square of the residuals is  NA
#> 
#> Compare this with the adequacy of just a general factor and no group factors
#> The degrees of freedom for just the general factor are 9  and the fit is  0.48 
#> The number of observations was  301  with Chi Square =  142  with prob <  3.5e-26
#> The root mean square of the residuals is  0.17 
#> The df corrected root mean square of the residuals is  0.21 
#> 
#> RMSEA index =  0.222  and the 10 % confidence intervals are  0.191 0.255
#> BIC =  90.9 
#> 
#> Measures of factor score adequacy             
#>                                                  g   F1*  F2*  F3*
#> Correlation of scores with factors            0.96  0.08 0.83 0.96
#> Multiple R square of scores with factors      0.93  0.01 0.68 0.91
#> Minimum correlation of factor score estimates 0.86 -0.99 0.36 0.83
#> 
#>  Total, General and Subset omega for each subset
#>                                                  g  F1*  F2*  F3*
#> Omega total for total scores and subscales    0.84 0.92 0.66 0.86
#> Omega general for total scores and subscales  0.55 0.92 0.04 0.61
#> Omega group for total scores and subscales    0.27 0.00 0.61 0.25

È invece necessario ipotizzare un modello congenerico a due fattori.

m2f_cong <- '
  # all loadings are uniquely estimated on each factor
  f1 =~ NA*X4 + X5 + X6
  f2 =~ NA*X7 + X8 + X9
  
  # constrain factor variancse to 1
  f1 ~~ 1*f1
  f2 ~~ 1*f2
  
  # estimate factor covariance
  f1 ~~ f2
'
# Fit above model
fit_2f_congeneric <- sem(m2f_cong, data=d)

Solo questo modello fornisce un adattamento adeguato ai dati.

out = summary(fit_2f_congeneric, fit.measures = TRUE, standardized = TRUE)
print(out)
#> lavaan 0.6-19 ended normally after 18 iterations
#> 
#>   Estimator                                         ML
#>   Optimization method                           NLMINB
#>   Number of model parameters                        13
#> 
#>   Number of observations                           301
#> 
#> Model Test User Model:
#>                                                       
#>   Test statistic                                14.736
#>   Degrees of freedom                                 8
#>   P-value (Chi-square)                           0.064
#> 
#> Model Test Baseline Model:
#> 
#>   Test statistic                               568.519
#>   Degrees of freedom                                15
#>   P-value                                        0.000
#> 
#> User Model versus Baseline Model:
#> 
#>   Comparative Fit Index (CFI)                    0.988
#>   Tucker-Lewis Index (TLI)                       0.977
#> 
#> Loglikelihood and Information Criteria:
#> 
#>   Loglikelihood user model (H0)              -2525.349
#>   Loglikelihood unrestricted model (H1)      -2517.981
#>                                                       
#>   Akaike (AIC)                                5076.698
#>   Bayesian (BIC)                              5124.891
#>   Sample-size adjusted Bayesian (SABIC)       5083.662
#> 
#> Root Mean Square Error of Approximation:
#> 
#>   RMSEA                                          0.053
#>   90 Percent confidence interval - lower         0.000
#>   90 Percent confidence interval - upper         0.095
#>   P-value H_0: RMSEA <= 0.050                    0.402
#>   P-value H_0: RMSEA >= 0.080                    0.159
#> 
#> Standardized Root Mean Square Residual:
#> 
#>   SRMR                                           0.035
#> 
#> 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
#>   f1 =~                                                                 
#>     X4                0.965    0.059   16.296    0.000    0.965    0.826
#>     X5                1.123    0.067   16.845    0.000    1.123    0.847
#>     X6                0.895    0.058   15.465    0.000    0.895    0.793
#>   f2 =~                                                                 
#>     X7                0.659    0.080    8.218    0.000    0.659    0.575
#>     X8                0.733    0.077    9.532    0.000    0.733    0.712
#>     X9                0.599    0.075    8.025    0.000    0.599    0.557
#> 
#> Covariances:
#>                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
#>   f1 ~~                                                                 
#>     f2                0.275    0.072    3.813    0.000    0.275    0.275
#> 
#> Variances:
#>                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
#>     f1                1.000                               1.000    1.000
#>     f2                1.000                               1.000    1.000
#>    .X4                0.433    0.056    7.679    0.000    0.433    0.318
#>    .X5                0.496    0.072    6.892    0.000    0.496    0.282
#>    .X6                0.472    0.054    8.732    0.000    0.472    0.371
#>    .X7                0.881    0.100    8.807    0.000    0.881    0.670
#>    .X8                0.521    0.094    5.534    0.000    0.521    0.492
#>    .X9                0.798    0.087    9.162    0.000    0.798    0.689

Nel contesto di questi dati, l’utilizzo di un modello congenerico non è sufficiente a giustificare l’impiego del punteggio totale, che rappresenta la somma dei punteggi degli item. Questo perché, nel caso specifico, sommando i punteggi di tutti gli item, finiremmo per includere misurazioni di due costrutti distinti.

33.3 Session Info

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] modelsummary_2.3.0 ggokabeito_0.1.0   see_0.10.0        
#>  [4] MASS_7.3-65        viridis_0.6.5      viridisLite_0.4.2 
#>  [7] ggpubr_0.6.0       ggExtra_0.10.1     gridExtra_2.3     
#> [10] patchwork_1.3.0    bayesplot_1.11.1   semTools_0.5-6    
#> [13] semPlot_1.1.6      lavaan_0.6-19      psych_2.4.12      
#> [16] scales_1.3.0       markdown_1.13      knitr_1.49        
#> [19] lubridate_1.9.4    forcats_1.0.0      stringr_1.5.1     
#> [22] dplyr_1.1.4        purrr_1.0.4        readr_2.1.5       
#> [25] tidyr_1.3.1        tibble_3.2.1       ggplot2_3.5.1     
#> [28] 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] rio_1.2.3            emmeans_1.10.7       zoo_1.8-13          
#>  [22] igraph_2.1.4         mime_0.12            lifecycle_1.0.4     
#>  [25] pkgconfig_2.0.3      Matrix_1.7-2         R6_2.6.1            
#>  [28] fastmap_1.2.0        rbibutils_2.3        shiny_1.10.0        
#>  [31] digest_0.6.37        OpenMx_2.21.13       fdrtool_1.2.18      
#>  [34] colorspace_2.1-1     rprojroot_2.0.4      Hmisc_5.2-2         
#>  [37] labeling_0.4.3       timechange_0.3.0     abind_1.4-8         
#>  [40] compiler_4.4.2       withr_3.0.2          glasso_1.11         
#>  [43] htmlTable_2.4.3      backports_1.5.0      carData_3.0-5       
#>  [46] R.utils_2.13.0       ggsignif_0.6.4       GPArotation_2024.3-1
#>  [49] corpcor_1.6.10       gtools_3.9.5         tools_4.4.2         
#>  [52] pbivnorm_0.6.0       foreign_0.8-88       zip_2.3.2           
#>  [55] httpuv_1.6.15        nnet_7.3-20          R.oo_1.27.0         
#>  [58] glue_1.8.0           quadprog_1.5-8       nlme_3.1-167        
#>  [61] promises_1.3.2       lisrelToR_0.3        grid_4.4.2          
#>  [64] checkmate_2.3.2      cluster_2.1.8        reshape2_1.4.4      
#>  [67] generics_0.1.3       gtable_0.3.6         tzdb_0.4.0          
#>  [70] R.methodsS3_1.8.2    data.table_1.17.0    hms_1.1.3           
#>  [73] car_3.1-3            tables_0.9.31        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        yaml_2.3.10          pacman_0.5.1        
#>  [94] boot_1.3-31          evaluate_1.0.3       codetools_0.2-20    
#>  [97] mi_1.1               cli_3.6.4            RcppParallel_5.1.10 
#> [100] rpart_4.1.24         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] jpeg_0.1-10          lme4_1.1-36          mvtnorm_1.3-3       
#> [112] openxlsx_4.2.8       rlang_1.1.5          multcomp_1.4-28     
#> [115] mnormt_2.1.1