12 Messmodelle in Strukturgleichungsmodellen
12.1 Folien
12.2 Daten zur heutigen Sitzung
12.3 Code und Ausgaben aus der Vorlesung
Laden der relevanten Pakete
library(correlation) # Schönere Darstellung von Korrelationen
library(performance) # Hier nur für Cronbachs alpha
library(semPlot) # Automatische Plots von Strukturgleichungsmodellen
library(semTools) # Funktionen zum Arbeiten mit StrukturgleichungsmodellenLoading required package: lavaan
This is lavaan 0.6-21
lavaan is FREE software! Please report any bugs.
###############################################################################
This is semTools 0.5-8
All users of R (or SEM) are invited to submit functions or ideas for functions.
###############################################################################
library(lavaan) # Strukturgleichungsmodelle
library(report) # Einfaches Erstellen von statistischen Berichten // unterstützt keine robusten fit measures
library(tidyverse) # Datenmanagement und Visualisierung: https://www.tidyverse.org/── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.2.0 ✔ readr 2.1.6
✔ forcats 1.0.1 ✔ stringr 1.6.0
✔ ggplot2 4.0.2 ✔ tibble 3.3.1
✔ lubridate 1.9.4 ✔ tidyr 1.3.2
✔ purrr 1.2.1
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ readr::clipboard() masks semTools::clipboard()
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
Laden und Aufbereiten der Daten
# from https://osf.io/5mjr4/
d <- readxl::read_excel(here::here("data/Kubin_etal_2025.xlsx")) |>
rename(
Censor_1 = Censorship_1,
Censor_2 = Censorship_2,
Censor_3 = Censorship_3,
Censor_4 = Censorship_4
) |>
mutate(
Age = as.numeric(Age),
Gender = factor(Gender, labels = c("male", "female")),
M_Harm = rowMeans(pick(starts_with("Harm"))), # harmful
M_Lie = rowMeans(pick(starts_with("Lie"))), # is a lie
M_Censor = rowMeans(pick(starts_with("Censor"))), # censor her
Condition = factor(Condition, labels = c("Fact", "Experience")) # Treamtment
) |>
filter(DogAtt == 1) # 14 did not pass attention checks leave 397 participantsWarning: There was 1 warning in `mutate()`.
ℹ In argument: `Age = as.numeric(Age)`.
Caused by warning:
! NAs introduced by coercion
Correlationen zwischen den 4 Items
d |>
select(starts_with("Censor_")) |>
correlation() |>
summary()# Correlation Matrix (pearson-method)
Parameter | Censor_4 | Censor_3 | Censor_2
------------------------------------------
Censor_1 | 0.87*** | 0.88*** | 0.91***
Censor_2 | 0.92*** | 0.93*** |
Censor_3 | 0.93*** | |
p-value adjustment method: Holm (1979)
Cronbachs alpha der 4 Items
d |>
select(starts_with("Censor_")) |>
cronbachs_alpha()[1] 0.9743875
Mittelwertindex bilden
d |>
select(starts_with("Censor_")) |>
rowwise() |>
mutate(M_Censor = mean(c(Censor_1, Censor_2, Censor_3, Censor_4))) |>
ungroup() |>
sample_n(size = 10)# A tibble: 10 × 5
Censor_1 Censor_2 Censor_3 Censor_4 M_Censor
<dbl> <dbl> <dbl> <dbl> <dbl>
1 7 7 7 7 7
2 2 2 2 2 2
3 7 6 6 6 6.25
4 7 7 7 7 7
5 1 1 1 1 1
6 2 2 4 2 2.5
7 1 1 1 1 1
8 6 5 4 4 4.75
9 2 2 2 2 2
10 1 1 1 1 1
Messmodell spezifizieren und schätzen
Censor_L <- "Censor =~ Censor_1 + Censor_2 + Censor_3 + Censor_4"
Censor_L_fit <- cfa(Censor_L, data = d, std.lv = TRUE, estimator = "MLR")
# std.lv = TRUE: Varianz der latenten Variable = 1
# estimator = "MLR" Robuste Maximum-Likelihood SchätzungFaktorladungen
Censor_L_fit |>
parameterestimates(standardized = "std.all") |>
filter(op == "=~") # Nur Faktorladungen darstellen lhs op rhs est se z pvalue ci.lower ci.upper std.all
1 Censor =~ Censor_1 1.530 0.061 25.141 0 1.411 1.650 0.923
2 Censor =~ Censor_2 1.550 0.063 24.745 0 1.427 1.672 0.967
3 Censor =~ Censor_3 1.577 0.063 25.006 0 1.453 1.701 0.963
4 Censor =~ Censor_4 1.618 0.060 27.100 0 1.501 1.735 0.954
Resdiualvarianzen
Censor_L_fit |>
parameterestimates(standardized = "std.all") |>
filter(op == "~~") # Nur Resdiualvarianzen darstellen lhs op rhs est se z pvalue ci.lower ci.upper std.all
1 Censor_1 ~~ Censor_1 0.406 0.086 4.716 0 0.237 0.575 0.148
2 Censor_2 ~~ Censor_2 0.169 0.042 4.022 0 0.086 0.251 0.066
3 Censor_3 ~~ Censor_3 0.194 0.041 4.701 0 0.113 0.275 0.072
4 Censor_4 ~~ Censor_4 0.256 0.058 4.395 0 0.142 0.371 0.089
5 Censor ~~ Censor 1.000 0.000 NA NA 1.000 1.000 1.000
omega
Censor_L_fit |>
compRelSEM()$Censor
Composite `Censor` is composed of observed variables:
Censor_1, Censor_2, Censor_3, Censor_4
True-score variance is represented by common factor(s):
Censor
Total variance of composite `Censor` determined from the unrestricted model.
The proportion attributable to "true" scores is its model-based estimate of reliability ("omega"):
[1] 0.975
Fit indices
Censor_L_fit |>
fitmeasures(
fit.measures = c(
"chisq.scaled", "df.scaled", "pvalue.scaled",
"rmsea.robust", "rmsea.ci.lower.robust", "rmsea.ci.upper.robust",
"srmr", "cfi.robust"
),
output = "matrix"
)
chisq.scaled 4.176
df.scaled 2.000
pvalue.scaled 0.124
rmsea.robust 0.102
rmsea.ci.lower.robust 0.000
rmsea.ci.upper.robust 0.241
srmr 0.006
cfi.robust 0.996
Dynamic fit index cutoffs
# remotes::install_github("melissagwolf/dynamic") # Muss ggf. von Github installiert werden
library(dynamic)
Censor_L_fit |>
likertOne(estimator = "MLR", data = d)Your DFI cutoffs:
SRMR RMSEA CFI
Level-0 0.007 0.088 0.997
Specificity 95% 95% 95%
Level-1 0.007 0.089 0.997
Sensitivity 65% 72% 72%
Empirical fit indices:
Chi-Square df p-value SRMR RMSEA CFI
4.176 2 0.124 0.006 0.102 0.996
Notes:
-'Sensitivity' is % of hypothetically misspecified models correctly identified by cutoff in DFI simulation
-Cutoffs with 95% sensitivity are reported when possible
-If sensitivity is <50%, cutoffs will be supressed
Modification Indices
modificationindices(Censor_L_fit, sort. = TRUE) lhs op rhs mi epc sepc.lv sepc.all sepc.nox
15 Censor_3 ~~ Censor_4 16.372 0.090 0.090 0.406 0.406
10 Censor_1 ~~ Censor_2 16.372 0.084 0.084 0.321 0.321
12 Censor_1 ~~ Censor_4 6.021 -0.055 -0.055 -0.170 -0.170
13 Censor_2 ~~ Censor_3 6.021 -0.054 -0.054 -0.300 -0.300
14 Censor_2 ~~ Censor_4 2.826 -0.037 -0.037 -0.179 -0.179
11 Censor_1 ~~ Censor_3 2.826 -0.036 -0.036 -0.128 -0.128
Kongenerisches Messmodell
Censor_L <- "Censor =~ Censor_1 + Censor_2 + Censor_3 + Censor_4"
Censor_L_fit <- cfa(Censor_L, data = d, std.lv = TRUE, estimator = "MLR")
Censor_L_fit |>
parameterestimates(standardized = "std.all") lhs op rhs est se z pvalue ci.lower ci.upper std.all
1 Censor =~ Censor_1 1.530 0.061 25.141 0 1.411 1.650 0.923
2 Censor =~ Censor_2 1.550 0.063 24.745 0 1.427 1.672 0.967
3 Censor =~ Censor_3 1.577 0.063 25.006 0 1.453 1.701 0.963
4 Censor =~ Censor_4 1.618 0.060 27.100 0 1.501 1.735 0.954
5 Censor_1 ~~ Censor_1 0.406 0.086 4.716 0 0.237 0.575 0.148
6 Censor_2 ~~ Censor_2 0.169 0.042 4.022 0 0.086 0.251 0.066
7 Censor_3 ~~ Censor_3 0.194 0.041 4.701 0 0.113 0.275 0.072
8 Censor_4 ~~ Censor_4 0.256 0.058 4.395 0 0.142 0.371 0.089
9 Censor ~~ Censor 1.000 0.000 NA NA 1.000 1.000 1.000
tau-aequivalentes Messmodell
Censor_L_tau <- "Censor =~ l * Censor_1 + l * Censor_2 + l * Censor_3 + l * Censor_4 # gleiche Ladung l für alle Items"
Censor_L_tau_fit <- cfa(Censor_L_tau, data = d, std.lv = TRUE, estimator = "MLR")
Censor_L_tau_fit |>
parameterestimates(standardized = "std.all") lhs op rhs label est se z pvalue ci.lower ci.upper
1 Censor =~ Censor_1 l 1.569 0.058 27.010 0 1.456 1.683
2 Censor =~ Censor_2 l 1.569 0.058 27.010 0 1.456 1.683
3 Censor =~ Censor_3 l 1.569 0.058 27.010 0 1.456 1.683
4 Censor =~ Censor_4 l 1.569 0.058 27.010 0 1.456 1.683
5 Censor_1 ~~ Censor_1 0.401 0.085 4.699 0 0.234 0.568
6 Censor_2 ~~ Censor_2 0.163 0.039 4.160 0 0.086 0.240
7 Censor_3 ~~ Censor_3 0.198 0.041 4.883 0 0.119 0.277
8 Censor_4 ~~ Censor_4 0.268 0.060 4.491 0 0.151 0.385
9 Censor ~~ Censor 1.000 0.000 NA NA 1.000 1.000
std.all
1 0.927
2 0.968
3 0.962
4 0.950
5 0.140
6 0.062
7 0.074
8 0.098
9 1.000
Paralleles Messmodell
Censor_L_parallel <- "
Censor =~ l * Censor_1 + l * Censor_2 + l * Censor_3 + l * Censor_4 # gleiche Ladung l für alle Items
Censor_1 ~~ v * Censor_1 # gleiche Varianz v für alle Items
Censor_2 ~~ v * Censor_2 # gleiche Varianz v für alle Items
Censor_3 ~~ v * Censor_3 # gleiche Varianz v für alle Items
Censor_4 ~~ v * Censor_4 # gleiche Varianz v für alle Items
"
Censor_L_parallel_fit <- cfa(Censor_L_parallel, data = d, std.lv = TRUE, estimator = "MLR")
Censor_L_parallel_fit |>
parameterestimates(standardized = "std.all") lhs op rhs label est se z pvalue ci.lower ci.upper
1 Censor =~ Censor_1 l 1.568 0.057 27.454 0 1.456 1.680
2 Censor =~ Censor_2 l 1.568 0.057 27.454 0 1.456 1.680
3 Censor =~ Censor_3 l 1.568 0.057 27.454 0 1.456 1.680
4 Censor =~ Censor_4 l 1.568 0.057 27.454 0 1.456 1.680
5 Censor_1 ~~ Censor_1 v 0.259 0.033 7.926 0 0.195 0.323
6 Censor_2 ~~ Censor_2 v 0.259 0.033 7.926 0 0.195 0.323
7 Censor_3 ~~ Censor_3 v 0.259 0.033 7.926 0 0.195 0.323
8 Censor_4 ~~ Censor_4 v 0.259 0.033 7.926 0 0.195 0.323
9 Censor ~~ Censor 1.000 0.000 NA NA 1.000 1.000
std.all
1 0.951
2 0.951
3 0.951
4 0.951
5 0.095
6 0.095
7 0.095
8 0.095
9 1.000
Modellvergleich 1
compareFit(Censor_L_fit, Censor_L_tau_fit, Censor_L_parallel_fit) |>
summary(fit.measures = c("rmsea.robust", "srmr", "cfi.robust"))################### Nested Model Comparison #########################
Scaled Chi-Squared Difference Test (method = "satorra.bentler.2001")
lavaan->unknown():
lavaan NOTE: The "Chisq" column contains standard test statistics, not the
robust test that should be reported per model. A robust difference test is
a function of two standard (not robust) statistics.
Df AIC BIC Chisq Chisq diff RMSEA Df diff
Censor_L_fit 2 3769.2 3801.1 15.756
Censor_L_tau_fit 5 3768.9 3788.8 21.432 5.5385 0.046737 3
Censor_L_parallel_fit 8 3817.7 3825.6 76.219 12.8183 0.187709 3
Pr(>Chisq)
Censor_L_fit
Censor_L_tau_fit 0.136354
Censor_L_parallel_fit 0.005046 **
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
####################### Model Fit Indices ###########################
rmsea.robust srmr cfi.robust
Censor_L_fit .102 .006† 0.996†
Censor_L_tau_fit .074† .028 0.995
Censor_L_parallel_fit .129 .031 .977
################## Differences in Fit Indices #######################
rmsea.robust srmr cfi.robust
Censor_L_tau_fit - Censor_L_fit -0.028 0.022 -0.001
Censor_L_parallel_fit - Censor_L_tau_fit 0.055 0.003 -0.018
Configural Invariance spezifizieren
Censor_L_config <- Censor_L |>
measEq.syntax(data = d, ID.fac = "std.lv", group = "Condition")
Censor_L_config_fit <- cfa(as.character(Censor_L_config),
data = d,
group = "Condition", std.lv = TRUE, estimator = "MLR"
)Configural Invariance Ergebnisse Gruppe Facts
Censor_L_config_fit |>
parameterestimates(standardized = "std.all") |>
filter(op %in% c("=~", ~1)) |> # Nur Faktorladungen und Item Intercepts
filter(group == 1) |>
select(-block, -label, -se, -z, -pvalue) lhs op rhs group est ci.lower ci.upper std.all
1 Censor =~ Censor_1 1 1.630 1.469 1.791 0.920
2 Censor =~ Censor_2 1 1.676 1.514 1.838 0.967
3 Censor =~ Censor_3 1 1.734 1.571 1.897 0.954
4 Censor =~ Censor_4 1 1.734 1.583 1.886 0.942
5 Censor_1 ~1 1 3.071 2.824 3.317 1.734
6 Censor_2 ~1 1 2.864 2.622 3.105 1.651
7 Censor_3 ~1 1 2.934 2.681 3.188 1.614
8 Censor_4 ~1 1 3.040 2.784 3.297 1.651
9 Censor ~1 1 0.000 0.000 0.000 0.000
Configural Invariance Ergebnisse Gruppe Experience
Censor_L_config_fit |>
parameterestimates(standardized = "std.all") |>
filter(op %in% c("=~", ~1)) |> # Nur Faktorladungen und Item Intercepts
filter(group == 2) |>
select(-block, -label, -se, -z, -pvalue) lhs op rhs group est ci.lower ci.upper std.all
1 Censor =~ Censor_1 2 1.333 1.163 1.502 0.921
2 Censor =~ Censor_2 2 1.347 1.176 1.519 0.967
3 Censor =~ Censor_3 2 1.311 1.142 1.480 0.972
4 Censor =~ Censor_4 2 1.383 1.207 1.558 0.966
5 Censor_1 ~1 2 2.337 2.136 2.538 1.615
6 Censor_2 ~1 2 2.236 2.043 2.430 1.606
7 Censor_3 ~1 2 2.236 2.049 2.424 1.658
8 Censor_4 ~1 2 2.256 2.057 2.455 1.576
9 Censor ~1 2 0.000 0.000 0.000 0.000
Metric Invariance spezifizieren
Censor_L_metric <- Censor_L |>
measEq.syntax(
data = d, ID.fac = "std.lv", group = "Condition",
group.equal = "loadings"
)
Censor_L_metric_fit <- cfa(as.character(Censor_L_metric),
data = d,
group = "Condition", std.lv = TRUE, estimator = "MLR"
)Metric Invariance Ergebnisse Gruppe Facts
Censor_L_metric_fit |>
parameterestimates(standardized = "std.all") |>
filter(op %in% c("=~", ~1)) |> # Nur Faktorladungen und Item Intercepts
filter(group == 1) |>
select(-block, -label, -se, -z, -pvalue) lhs op rhs group est ci.lower ci.upper std.all
1 Censor =~ Censor_1 1 1.658 1.512 1.804 0.924
2 Censor =~ Censor_2 1 1.691 1.538 1.844 0.968
3 Censor =~ Censor_3 1 1.684 1.527 1.841 0.949
4 Censor =~ Censor_4 1 1.744 1.589 1.898 0.942
5 Censor_1 ~1 1 3.071 2.824 3.317 1.711
6 Censor_2 ~1 1 2.864 2.622 3.105 1.640
7 Censor_3 ~1 1 2.934 2.681 3.188 1.654
8 Censor_4 ~1 1 3.040 2.784 3.297 1.642
9 Censor ~1 1 0.000 0.000 0.000 0.000
Metric Invariance Ergebnisse Gruppe Experience
Censor_L_metric_fit |>
parameterestimates(standardized = "std.all") |>
filter(op %in% c("=~", ~1)) |> # Nur Faktorladungen und Item Intercepts
filter(group == 2) |>
select(-block, -label, -se, -z, -pvalue) lhs op rhs group est ci.lower ci.upper std.all
1 Censor =~ Censor_1 2 1.658 1.512 1.804 0.918
2 Censor =~ Censor_2 2 1.691 1.538 1.844 0.966
3 Censor =~ Censor_3 2 1.684 1.527 1.841 0.973
4 Censor =~ Censor_4 2 1.744 1.589 1.898 0.965
5 Censor_1 ~1 2 2.337 2.136 2.538 1.638
6 Censor_2 ~1 2 2.236 2.043 2.430 1.618
7 Censor_3 ~1 2 2.236 2.049 2.424 1.636
8 Censor_4 ~1 2 2.256 2.057 2.455 1.581
9 Censor ~1 2 0.000 0.000 0.000 0.000
Scalar Invariance spezifizieren
Censor_L_scalar <- Censor_L |>
measEq.syntax(
data = d, ID.fac = "std.lv", group = "Condition",
group.equal = c("loadings", "intercepts")
)
Censor_L_scalar_fit <- cfa(as.character(Censor_L_scalar),
data = d,
group = "Condition", std.lv = TRUE, estimator = "MLR"
)Scalar Invariance Ergebnisse Gruppe Facts
Censor_L_scalar_fit |>
parameterestimates(standardized = "std.all") |>
filter(op %in% c("=~", ~1)) |> # Nur Faktorladungen und Item Intercepts
filter(group == 1) |>
select(-block, -label, -se, -z, -pvalue) lhs op rhs group est ci.lower ci.upper std.all
1 Censor =~ Censor_1 1 1.665 1.519 1.811 0.924
2 Censor =~ Censor_2 1 1.683 1.531 1.835 0.967
3 Censor =~ Censor_3 1 1.686 1.530 1.842 0.950
4 Censor =~ Censor_4 1 1.752 1.599 1.905 0.942
5 Censor_1 ~1 1 3.041 2.801 3.281 1.687
6 Censor_2 ~1 1 2.903 2.660 3.145 1.668
7 Censor_3 ~1 1 2.931 2.684 3.177 1.651
8 Censor_4 ~1 1 2.993 2.739 3.248 1.610
9 Censor ~1 1 0.000 0.000 0.000 0.000
Scalar Invariance Ergebnisse Gruppe Experience
Censor_L_scalar_fit |>
parameterestimates(standardized = "std.all") |>
filter(op %in% c("=~", ~1)) |> # Nur Faktorladungen und Item Intercepts
filter(group == 2) |>
select(-block, -label, -se, -z, -pvalue) lhs op rhs group est ci.lower ci.upper std.all
1 Censor =~ Censor_1 2 1.665 1.519 1.811 0.919
2 Censor =~ Censor_2 2 1.683 1.531 1.835 0.965
3 Censor =~ Censor_3 2 1.686 1.530 1.842 0.973
4 Censor =~ Censor_4 2 1.752 1.599 1.905 0.966
5 Censor_1 ~1 2 3.041 2.801 3.281 2.126
6 Censor_2 ~1 2 2.903 2.660 3.145 2.110
7 Censor_3 ~1 2 2.931 2.684 3.177 2.144
8 Censor_4 ~1 2 2.993 2.739 3.248 2.090
9 Censor ~1 2 -0.411 -0.579 -0.243 -0.521
Modellvergleich 2
compareFit(Censor_L_config_fit, Censor_L_metric_fit, Censor_L_scalar_fit) |>
summary(fit.measures = c("rmsea.robust", "srmr", "cfi.robust"))################### Nested Model Comparison #########################
Scaled Chi-Squared Difference Test (method = "satorra.bentler.2001")
lavaan->unknown():
lavaan NOTE: The "Chisq" column contains standard test statistics, not the
robust test that should be reported per model. A robust difference test is
a function of two standard (not robust) statistics.
Df AIC BIC Chisq Chisq diff RMSEA Df diff
Censor_L_config_fit 4 3674.5 3770.2 16.368
Censor_L_metric_fit 7 3671.9 3755.5 19.689 3.2173 0.019407 3
Censor_L_scalar_fit 10 3670.8 3742.6 24.668 4.7468 0.055470 3
Pr(>Chisq)
Censor_L_config_fit
Censor_L_metric_fit 0.3593
Censor_L_scalar_fit 0.1913
####################### Model Fit Indices ###########################
rmsea.robust srmr cfi.robust
Censor_L_config_fit .043 .005† 0.999†
Censor_L_metric_fit .035† .018 0.999
Censor_L_scalar_fit .042 .021 0.998
################## Differences in Fit Indices #######################
rmsea.robust srmr cfi.robust
Censor_L_metric_fit - Censor_L_config_fit -0.008 0.013 0.000
Censor_L_scalar_fit - Censor_L_metric_fit 0.007 0.003 -0.001
Messmodell von Harm und Lie spezifizieren und schaetzen
Harm_Lie_L <- "
# Konstrukte
Harm =~ Harm_1 + Harm_2 + Harm_3
Lie =~ Lie_1 + Lie_2 + Lie_3
# Korrelation zwischen Konstrukten
Harm ~~ Lie
"
Harm_Lie_L_fit <- cfa(Harm_Lie_L, data = d, std.lv = TRUE, estimator = "MLR")Messmodell von Harm und Lie Parameter
Harm_Lie_L_fit |>
parameterestimates(standardized = "std.all") |>
filter(op %in% c("=~", "~~") & lhs != rhs) lhs op rhs est se z pvalue ci.lower ci.upper std.all
1 Harm =~ Harm_1 1.685 0.050 33.641 0 1.587 1.783 0.956
2 Harm =~ Harm_2 1.719 0.049 35.281 0 1.624 1.815 0.955
3 Harm =~ Harm_3 1.620 0.058 27.845 0 1.506 1.734 0.907
4 Lie =~ Lie_1 1.620 0.051 31.675 0 1.520 1.720 0.959
5 Lie =~ Lie_2 1.593 0.055 28.964 0 1.485 1.701 0.932
6 Lie =~ Lie_3 1.573 0.055 28.599 0 1.466 1.681 0.926
7 Harm ~~ Lie 0.593 0.044 13.572 0 0.508 0.679 0.593
Messmodell von Harm und Lie omega
Harm_Lie_L_fit |>
compRelSEM()$Harm
Composite `Harm` is composed of observed variables:
Harm_1, Harm_2, Harm_3
True-score variance is represented by common factor(s):
Harm
Total variance of composite `Harm` determined from the unrestricted model.
The proportion attributable to "true" scores is its model-based estimate of reliability ("omega"):
[1] 0.958
$Lie
Composite `Lie` is composed of observed variables:
Lie_1, Lie_2, Lie_3
True-score variance is represented by common factor(s):
Lie
Total variance of composite `Lie` determined from the unrestricted model.
The proportion attributable to "true" scores is its model-based estimate of reliability ("omega"):
[1] 0.957
Messmodelle von Harm und Lie Fit indices
Harm_Lie_L_fit |>
fitmeasures(
fit.measures = c(
"chisq.scaled", "df.scaled", "pvalue.scaled",
"rmsea.robust", "rmsea.ci.lower.robust", "rmsea.ci.upper.robust",
"srmr", "cfi.robust"
),
output = "matrix"
)
chisq.scaled 14.072
df.scaled 8.000
pvalue.scaled 0.080
rmsea.robust 0.048
rmsea.ci.lower.robust 0.000
rmsea.ci.upper.robust 0.089
srmr 0.014
cfi.robust 0.997
Messmodell von Harm und Lie Dynamic fit index cutoffs
# remotes::install_github("melissagwolf/dynamic") # Muss ggf. von Github installiert werden
library(dynamic)
Harm_Lie_L_fit |>
likertHB(estimator = "MLR", data = d)Your DFI cutoffs:
SRMR RMSEA CFI Magnitude
Level-0 0.018 0.049 0.997 NONE
Specificity 95% 95% 95%
Level-1 0.02 0.07 0.995 0.138
Sensitivity 95% 95% 95%
Empirical fit indices:
Chi-Square df p-value SRMR RMSEA CFI
14.072 8 0.08 0.014 0.048 0.997
Notes:
-'Sensitivity' is % of hypothetically misspecified models correctly identified by cutoff in DFI simulation
-Cutoffs with 95% sensitivity are reported when possible
-If sensitivity is <50%, cutoffs will be supressed
Fit des Messmodells von Harm
cfa("Harm =~ Harm_1 + Harm_2 + Harm_3", data = d, std.lv = TRUE, estimator = "MLR") |>
fitmeasures(
fit.measures = c(
"chisq.scaled", "df.scaled", "pvalue.scaled",
"rmsea.robust", "rmsea.ci.lower.robust", "rmsea.ci.upper.robust",
"srmr", "cfi.robust"
),
output = "matrix"
)
chisq.scaled 0
df.scaled 0
pvalue.scaled NA
rmsea.robust 0
rmsea.ci.lower.robust 0
rmsea.ci.upper.robust 0
srmr 0
cfi.robust NA
Modification Indices 2
modificationindices(Harm_Lie_L_fit, sort. = TRUE, minimum.value = 3) lhs op rhs mi epc sepc.lv sepc.all sepc.nox
35 Lie_1 ~~ Lie_3 6.179 0.209 0.209 0.676 0.676
17 Harm =~ Lie_2 6.179 0.120 0.120 0.070 0.070
30 Harm_2 ~~ Lie_3 5.742 0.060 0.060 0.175 0.175
28 Harm_2 ~~ Lie_1 5.671 -0.053 -0.053 -0.205 -0.205
26 Harm_1 ~~ Lie_3 4.264 -0.050 -0.050 -0.152 -0.152
31 Harm_3 ~~ Lie_1 3.209 0.048 0.048 0.133 0.133
Spezifikation des Mediationsmodell mit Mittelwertindices
mediation_I <- "
# Modelle
# Mediator 1: Harm
M_Harm ~ a1 * Condition
# Mediator 2: False
M_Lie ~ a2 * Condition
# aV: Endorsement of Censorship
M_Censor ~ c_ * Condition + b1 * M_Harm + b2 * M_Lie
# Kovarianz zwischen Mediatoren
M_Harm ~~ M_Lie
# Effekte
# Indirekte Effekte
indirect_harm := a1 * b1
indirect_lie := a2 * b2
# Direkter Effekt
direct := c_
# Totaler Effekt
total := direct + indirect_harm + indirect_lie
"Mediationsmodell mit Mittelwertindices schaetzen
mediation_I_fit <- sem(mediation_I, data = d, estimator = "MLR")
mediation_I_fit |>
parameterestimates(standardized = "std.all") |>
filter(op %in% c("~", ":=")) lhs op rhs label est se
1 M_Harm ~ Condition a1 -0.815 0.167
2 M_Lie ~ Condition a2 -1.551 0.144
3 M_Censor ~ Condition c_ -0.203 0.165
4 M_Censor ~ M_Harm b1 0.212 0.052
5 M_Censor ~ M_Lie b2 0.216 0.062
6 indirect_harm := a1*b1 indirect_harm -0.173 0.055
7 indirect_lie := a2*b2 indirect_lie -0.335 0.102
8 direct := c_ direct -0.203 0.165
9 total := direct+indirect_harm+indirect_lie total -0.711 0.156
z pvalue ci.lower ci.upper std.all
1 -4.886 0.000 -1.142 -0.488 -0.238
2 -10.774 0.000 -1.833 -1.269 -0.476
3 -1.229 0.219 -0.528 0.121 -0.064
4 4.088 0.000 0.110 0.314 0.229
5 3.455 0.001 0.093 0.338 0.221
6 -3.162 0.002 -0.280 -0.066 -0.054
7 -3.274 0.001 -0.535 -0.134 -0.105
8 -1.229 0.219 -0.528 0.121 -0.064
9 -4.571 0.000 -1.016 -0.406 -0.224
Spezifikation des Mediationsmodell mit latenten Variablen
mediation_L <- "
# Konstrukte
Harm =~ Harm_1 + Harm_2 + Harm_3
Lie =~ Lie_1 + Lie_2 + Lie_3
Censor =~ Censor_1 + Censor_2 + Censor_3 + Censor_4
# Korrelation zwischen Konstrukten
Harm ~~ Lie
# Modelle
# Mediator 1: Harm
Harm ~ a1 * Condition
# Mediator 2: False
Lie ~ a2 * Condition
# aV: Endorsement of Censorship
Censor ~ c_ * Condition + b1 * Harm + b2 * Lie
# Effekte
# Indirekte Effekte
indirect_harm := a1 * b1
indirect_lie := a2 * b2
# Direkter Effekt
direct := c_
# Totaler Effekt
total := direct + indirect_harm + indirect_lie
"Mediationsmodell mit latenten Variablen schaetzen
mediation_L_fit <- sem(mediation_L, data = d, estimator = "MLR")
mediation_L_fit |>
parameterestimates(standardized = "std.all") |>
filter(op %in% c("~", ":=", "=~")) lhs op rhs label est
1 Harm =~ Harm_1 1.000
2 Harm =~ Harm_2 1.022
3 Harm =~ Harm_3 0.961
4 Lie =~ Lie_1 1.000
5 Lie =~ Lie_2 0.984
6 Lie =~ Lie_3 0.973
7 Censor =~ Censor_1 1.000
8 Censor =~ Censor_2 1.012
9 Censor =~ Censor_3 1.032
10 Censor =~ Censor_4 1.058
11 Harm ~ Condition a1 -0.828
12 Lie ~ Condition a2 -1.573
13 Censor ~ Condition c_ -0.175
14 Censor ~ Harm b1 0.228
15 Censor ~ Lie b2 0.202
16 indirect_harm := a1*b1 indirect_harm -0.189
17 indirect_lie := a2*b2 indirect_lie -0.317
18 direct := c_ direct -0.175
19 total := direct+indirect_harm+indirect_lie total -0.682
se z pvalue ci.lower ci.upper std.all
1 0.000 NA NA 1.000 1.000 0.956
2 0.021 49.049 0.000 0.981 1.063 0.956
3 0.026 37.110 0.000 0.910 1.012 0.906
4 0.000 NA NA 1.000 1.000 0.958
5 0.022 44.556 0.000 0.941 1.027 0.932
6 0.020 47.575 0.000 0.933 1.013 0.927
7 0.000 NA NA 1.000 1.000 0.923
8 0.025 40.719 0.000 0.963 1.061 0.966
9 0.030 34.332 0.000 0.973 1.091 0.964
10 0.028 38.015 0.000 1.003 1.112 0.955
11 0.167 -4.947 0.000 -1.156 -0.500 -0.246
12 0.147 -10.719 0.000 -1.860 -1.285 -0.486
13 0.164 -1.070 0.284 -0.496 0.146 -0.057
14 0.056 4.045 0.000 0.118 0.339 0.252
15 0.067 3.028 0.002 0.071 0.332 0.213
16 0.060 -3.157 0.002 -0.307 -0.072 -0.062
17 0.109 -2.913 0.004 -0.531 -0.104 -0.104
18 0.164 -1.070 0.284 -0.496 0.146 -0.057
19 0.152 -4.491 0.000 -0.979 -0.384 -0.223
12.4 Hausaufgabe
1) Reproduzieren Sie die Messmdodelle und das Strukturmodell aus der Vorlesung.
- Schreiben Sie kurze Ergebnistexte zur Beantwortung der Fragen bzw. zum Test der Hypothesen:
- Wie hoch sind die Korrelationen zwischen den Indikatoren von Endorsement of Censorship?
- Beschreiben Sie das kongenerische Messmodell von Endorsement of Censorship.
- Zu welchem Ergebnis kommt der Vergleich von kongenerischem, \(\tau\)-äquivalentem und parallelen Messmodell von Endorsement of Censorship?
- Wird Endorsement of Censorship in den beiden Treatment-Gruppen vergleichbar gemessen?
- Werden Harm und Lie als distinkte Variablen gemessen?
Lösung
- Zu 1) Siehe Code und Ausgaben aus der Vorlesung
12.5 Transkript
Das folgende Transkript wurde auf Basis der Aufzeichnung der Vorlesung erstellt. Die vollständigen Aufzeichnungen inklusive der Bildschirminhalte sind in Blackboard🔒 verfügbar. Die Tonspur wurde zuerst mit Hilfe der Werkzeuge des Oral-History.Digital Projekts wörtlich transkribiert. Die wörtliche Transkription wurde in Kombination mit den Vorlesungsfolien mithilfe von Sprachmodellen (v. a. Claude Sonnet 4.5 und GPT 5.2) zu einem übersichtlichen Transkript zusammengefasst. Im Anschluss wurde das Transkript von einer studentischen Hilfskraft überprüft, geglättet und ggf. angepasst. In diesem Prozess kann es an verschiedenen Stellen zu Fehlern kommen. Im Zweifel gilt das gesprochene Wort, und auch beim Vortrag mache ich Fehler.
Ich stelle das Transkript hier als experimentelles, ergänzendes Material zur Dokumentation der Vorlesung zur Verfügung. Noch bin ich mir unsicher, ob es eine sinnvolle Ergänzung ist und behalte mir vor, es weiter zu bearbeiten oder zu löschen.
Messmodelle in Strukturgleichungsmodellen
In dieser Vorlesung geht es um Messmodelle in Strukturgleichungsmodellen und darum, wie man latente Konstrukte mit mehreren Items sinnvoll abbildet. Die Grundidee ist, dass uns in den Sozialwissenschaften oft etwas interessiert, das wir nicht direkt beobachten können, etwa Information Overload oder Censorship Endorsement, und dass wir solche Konstrukte deshalb über mehrere manifeste Indikatoren erfassen müssen.
- Latente Konstrukte sind nicht direkt beobachtbar, aber theoretisch wichtig.
- Typisch ist ein reflektives Messmodell: Das latente Konstrukt beeinflusst die Antworten auf die Items.
- Psychometrische Skalenentwicklung ist ein eigenes Feld; hier wird nur ein Ausschnitt davon behandelt.
Beispieldaten
Die Vorlesung arbeitet mit Daten aus der Studie von Kubin et al. (2025), die nicht selbst Teil der publizierten Analyse sind, sondern in der Vorlesung weiterverarbeitet werden. Die zentrale Idee ist, dass Personen je nach experimenteller Bedingung einen Tweet als schädlich, als Lüge oder als zensierbar wahrnehmen und daraufhin bestimmte Antworten geben.
- Es gibt zwei Gruppen: Facts und Experience.
- Gemessen werden unter anderem Harm, Lie und Censorship Endorsement.
- Für Censorship Endorsement gibt es vier Items, etwa ob man die Person zensieren, blockieren oder am Veröffentlichen hindern würde.
Mittelwertindex und Cronbachs Alpha
Das übliche Vorgehen bei solchen Skalen ist zuerst, die Korrelationen zwischen den Items anzusehen und dann eine Reliabilität wie Cronbachs Alpha (\(\alpha\)) zu berechnen. Die Items sollten hoch und möglichst ähnlich miteinander korrelieren; außerdem gilt als grobe Daumenregel, dass Alpha über \(0.7\) liegen sollte.
- Cronbachs Alpha (\(\alpha\)) misst die interne Konsistenz einer Skala.
- Die Schätzung beruht auf der Annahme, dass alle Items gleich wichtig sind.
- Diese Annahme wird in der Regel nicht geprüft, sondern einfach vorausgesetzt.
Anschließend wird häufig ein Mittelwertindex gebildet, also der Durchschnitt über alle Items. Dadurch entsteht eine neue Variable, die dann in weiteren Analysen verwendet wird. Diese Vorgehensweise hat aber zwei zentrale Annahmen: Alle Items repräsentieren das Konstrukt gleich gut, und es gibt keinen Messfehler.
- Alle Items bekommen im Index dasselbe Gewicht.
- Der gesamte beobachtete Wert wird als „echte“ Messung behandelt, Messfehler werden ignoriert.
- Genau diese Annahmen sind oft problematisch, weil sie selten empirisch geprüft werden.
Messmodelle spezifizieren
Messmodelle machen die Idee der latenten Variable explizit. In der Darstellung sind die latenten Variablen meist Kreise, die manifesten Items Vierecke. Die Pfeile zeigen von der latenten Variable zu den Items; das heißt, das latente Konstrukt verursacht die Itemantworten.
- Die latente Variable wird oft auch Faktor genannt.
- Pro Item werden eine Faktorladung und eine Residualvarianz geschätzt.
- Faktorladungen sind Regressionsgewichte und zeigen, wie stark das Item vom Faktor beeinflusst wird.
- Residualvarianzen sind der nicht durch die latente Variable erklärte Anteil.
Für ein einzelnes latentes Konstrukt braucht man mindestens drei Items, besser vier, damit das Modell sinnvoll identifiziert und beurteilt werden kann. Außerdem muss die Skala des Modells technisch identifiziert werden, etwa indem man die Varianz der latenten Variable fixiert.
Faktorladungen und Reliabilität
Im Beispiel zeigen die standardisierten Faktorladungen für die Censorship-Skala sehr hohe Werte, ungefähr über \(0.9\). Das bedeutet, dass alle vier Items stark durch das latente Konstrukt erklärt werden und jeweils viel gemeinsame Varianz aufweisen.
- Hohe standardisierte Faktorladungen sind erwünscht.
- Die Daumenregel lautet meist: größer als \(0.7\).
- Hohe Faktorladungen bedeuten, dass die Items das Konstrukt gut abbilden.
Zusätzlich kann man Omega (\(\omega\)) als Reliabilitätsmaß berechnen. Omega basiert auf den geschätzten Faktorladungen und macht nicht die Annahme, dass alle Items gleich wichtig sind. Im Beispiel liegt Omega sehr hoch und damit klar über der groben Orientierungsgrenze von \(0.7\).
Modellfit beurteilen
Ein großer Vorteil von Messmodellen ist, dass man nicht nur einzelne Parameter, sondern auch die Gesamtpassung des Modells beurteilen kann. Dazu werden Fit-Indizes verwendet, die vergleichen, wie gut das Modell die beobachtete Korrelations- oder Kovarianzmatrix der Items vorhersagt.
- Der Chi-Quadrat-Test (\(\chi^2\)) prüft, ob das Modell exakt zu den Daten passt.
- Für einen guten Fit möchte man hier möglichst keinen signifikanten Test.
- RMSEA misst Abweichungen pro Freiheitsgrad; kleiner ist besser.
- SRMR misst ebenfalls Abweichungen; kleiner ist besser.
- CFI vergleicht das Modell mit einem Nullmodell; größer ist besser.
Im Beispiel ist der Modellfit insgesamt gut bis grenzwertig: Der Chi-Quadrat-Test ist nicht signifikant, SRMR und CFI sind sehr gut, und RMSEA ist im Punktschätzer nicht ideal, aber wegen des breiten Konfidenzintervalls nicht besonders eindeutig. Die Vorlesung betont außerdem, dass traditionelle Cutoffs aus älteren Simulationsstudien stammen und heute durch dynamische, datenspezifische Cutoffs ergänzt werden können.
Modification Indices
Modification Indices zeigen, an welcher Stelle sich der Modellfit verbessern würde, wenn man einen bisher fixierten Parameter freigeben würde. Inhaltlich interpretierbar werden diese Werte erst in Kombination mit Theorie; reine Datenanpassung ohne theoretische Begründung ist riskant.
- Ein hoher Modification Index weist auf mögliche Modellfehlspezifikation hin.
- Im Beispiel deuten einige Indizes darauf hin, dass bestimmte Itempaare noch zusätzlich korrelieren könnten.
- Solche Hinweise helfen bei der Verbesserung von Skalen, ersetzen aber keine Theorie.
Arten von Messmodellen
Die Vorlesung unterscheidet drei wichtige Modelltypen. Beim kongenerischen Messmodell werden alle Faktorladungen und Residualvarianzen frei geschätzt. Beim tauäquivalenten Messmodell sind die Faktorladungen für alle Items gleich, beim parallelen Messmodell sind zusätzlich auch die Residualvarianzen gleich.
- Kongenerisch: keine Gleichheitsrestriktionen.
- Tauäquivalent: gleiche Faktorladungen.
- Parallel: gleiche Faktorladungen und gleiche Residualvarianzen.
Im Beispiel passt das tauäquivalente Modell noch gut, während das parallele Modell signifikant schlechter zu den Daten passt. Das heißt: Die Items sind zwar ungefähr gleich wichtig, aber nicht vollständig austauschbar. Deshalb wäre ein einfacher Mittelwertindex hier zwar plausibel, aber das parallele Ideal ist zu streng.
Messinvarianz über Gruppen
Ein weiterer wichtiger Punkt ist, ob ein Messmodell in verschiedenen Gruppen dasselbe misst. Dafür prüft man konfigurale, metrische und skalare Invarianz. Das ist wichtig, wenn man Mittelwerte oder Zusammenhänge zwischen Gruppen vergleichen möchte.
- Konfigurale Invarianz: gleiche Faktorstruktur in allen Gruppen.
- Metrische Invarianz: gleiche Faktorladungen in allen Gruppen.
- Skalare Invarianz: gleiche Intercepts in allen Gruppen.
Im Beispiel mit den Gruppen Facts und Experience zeigen die Modellvergleiche, dass metrische und skalare Invarianz ausreichend gegeben sind. Das bedeutet, dass Mittelwertvergleiche zwischen den Gruppen zulässig sind, weil das Konstrukt in beiden Gruppen vergleichbar gemessen wurde.
Gemeinsame Messung von Harm und Lie
Die Vorlesung erweitert das Messmodell schließlich auf mehrere latente Konstrukte gleichzeitig, hier Harm und Lie. Damit kann man prüfen, ob Items nur mit ihrem eigenen Konstrukt zusammenhängen und keine problematischen Cross-Loadings auf andere Faktoren haben.
- Harm wird über drei Items gemessen, die Aussagen wie „causes harm“, „is dangerous“ oder „leads to suffering“ abdecken.
- Lie wird über drei Items gemessen, die Aussagen wie „is false“, „is wrong“ oder „is a lie“ abdecken.
- Im gemeinsamen Modell korrelieren beide Faktoren deutlich, sind aber nicht identisch.
Die Faktorladungen beider Konstrukte sind hoch, und beide Skalen zeigen eine gute Reliabilität. Der Modellfit ist insgesamt gut, und die Vorlesung betont, dass man mit so einem gemeinsamen Modell mehr Information bekommt, als wenn man beide Skalen getrennt und ohne Messprüfung behandelt.
Kombination mit Strukturmodellen
Am Ende zeigt die Vorlesung, wie Messmodelle in ein Strukturgleichungsmodell eingebaut werden können, zum Beispiel in eine Mediation. Statt Mittelwertindizes direkt zu verwenden, kann man dann die latenten Variablen selbst als Mediatoren und Outcomes modellieren.
- Das strukturelle Modell wird auf Basis der geschätzten latenten Variablen formuliert.
- Die Ergebnisse mit latenten Variablen sind im Beispiel fast identisch mit denen auf Basis von Mittelwertindizes.
- Der Vorteil ist, dass die Messung zuvor explizit geprüft wurde, also mehr Vertrauen in die Ergebnisse entsteht.
Fazit
Die zentrale Botschaft der Vorlesung ist, dass Messmodelle in vielen Studien gute Praxis sind, aber oft zu selten verwendet werden. Statt einfach Items zu einem Index zusammenzufassen, sollte man prüfen, wie gut die Messung tatsächlich funktioniert, ob die Items gleichartig funktionieren und ob die Messung über Gruppen hinweg vergleichbar ist. Dadurch werden Analysen informativer und theoretisch sauberer.
- Latente Variablen lassen sich in Strukturgleichungsmodellen direkt schätzen.
- Messmodelle machen implizite Annahmen der Indexbildung explizit.
- Wer Messungen ernst nimmt, kann die Grenzen der eigenen Interpretation besser einschätzen.
