Op deze pagina vind je een demonstratie van een statistische techniek aan de hand van een voorbeeld.
Meer informatie over hoe je deze pagina kan gebruiken vind je in deze handleiding.
De analyse gebeurt met behulp van R en RStudio. Een inleiding tot deze software vind je hier.
Het voorbeeld op deze pagina is afkomstig uit een studie van Schmitz, Luminet, Klein, Morbée, Van den Bergh, Van Oost, Waterschoot, Yzerbyt & Van Steenkiste (2022). Om praktische en didactische redenen werden de hypothesen en de data enigszins aangepast.
Structural Equation Modelling (SEM)1 is een techniek die je kan gebruiken om de onderliggende mechanismen bloot te leggen die een fenomeen kunnen verklaren. In die zin lijkt het sterk op padanalyse.
Een belangrijk extra element is dat er bij SEM ook latente variabelen in het model aanwezig kunnen zijn. Dat zijn variabelen die niet rechtstreeks meetbaar zijn. Ze moeten indirect gemeten worden aan de hand van een reeks “indicatoren”, bv. vragen (“items”) in een enquête. Je mag er natuurlijk niet zomaar vanuit gaan dat een reeks indicatoren samen een goede meting zijn voor een bepaalde latente variabele. Dat moet je nagaan. Je moet bevestigen dat een vooropgestelde factorstructuur (= een aantal latente variabelen of “factoren”, elk met hun eigen set van indicatoren) daadwerkelijk terug te vinden is in de data. Je moet met andere woorden een confirmatorische factoranalyse (CFA) uitvoeren.
Met SEM ben je in staat om in één model padanalyse te combineren met confirmatorische factoranalyse.
Met een SEM kan je verschillende zaken proberen te achterhalen. Belangrijke doelen van de analyse zijn
De volgorde is hier belangrijk! Het heeft geen zin om conclusies te trekken over het al dan niet significant zijn van de afzonderlijke coëfficiënten in een model dat in zijn geheel niet goed fit met de data.
Schmitz et al. (2022) probeerden de vaccinatie-intentie te verklaren. Dat is de intentie die mensen al dan niet hadden om zich te laten vaccineren tegen het coronavirus.2 Op basis van eerder onderzoek en van de Self-Determination Theory vermoedden de onderzoekers dat verschillende soorten motivatie een rol zouden kunnen spelen in de vaccinatie-intentie. Daarnaast zouden ook percepties van risico’s met betrekking tot infectie en de pandemie een effect kunnen hebben.
Op basis van deze hypothesen kwamen de onderzoekers tot een specifiek systeem van variabelen waarvan ze dachten dat het terug te vinden zou zijn in de data. Dit systeem van variabelen vind je in het diagram hieronder, binnen de rode stippellijn. Het wordt ook het “structurele gedeelte” van het SEM-model genoemd.
Belangrijk is dat de meeste variabelen “latent” zijn. Ze zijn met andere woorden niet rechtstreeks meetbaar. Ze moeten worden gemeten door verschillende indicatoren te combineren (in dit geval items in een vragenlijst). Er is naast het structurele gedeelte dus ook een “meetgedeelte” of “meetmodel” (in het Engels: “measurement model”). Dat is alles wat binnen de blauwe stippellijn staat in het diagram hieronder.
Klik op het diagram om het te vergroten.
Hieronder vind je een overzicht van alle variabelen in dit model. De linkerkolom bevat de variabelen die samen het structurele gedeelte van het model uitmaken. De tabel bevat de Engelse naam uit het artikel, een Nederlandse vertaling en de naam die de onderzoekers aan de variabele hebben gegeven in R.
Bij elke latente variabele vind je ook de indicatoren terug die gezamenlijk die variabele zouden meten. Hiervan geven we enkel de naam in R weer.
Engels | Nederlands | Naam in R | Indicatoren |
---|---|---|---|
Pandemic-related health concerns | Pandemie-gerelateerde zorgen over gezondheid | worries |
|
Infection-related risk perception | Infectie-gerelateerde perceptie van risico | risks |
|
Autonomous motivation | Autonome motivatie | motv_ident |
|
Controlled motivation | Gecontroleerde motivatie | motv_exter |
|
Distrust-based amotivation | Demotivatie door wantrouwen | motv_distr |
|
Effort-based amotivation | Demotivatie door inspanning | motv_effrt |
|
Vaccination intention | Vaccinatie-intentie | vaccination_intention | Niet van toepassing3 |
Voor de analyse van een SEM kan je functies uit het R-package lavaan
gebruiken.
install.packages('lavaan') # eenmalig installeren
library(lavaan) # bij de start van elke sessie
De dataset bevat gegevens van 32 variabelen geobserveerd bij 8887 individuen.
Deze dataset kan je inladen met read.csv()
. De data kan je best meteen in een object vaccin
onderbrengen zodat je die later makkelijk opnieuw kan oproepen.
vaccin <- read.csv('https://statlas.ugent.be/datasets/vaccin.csv')
Met str()
krijg je een opsomming van alle variabelen in de dataset. Je vindt er ook telkens bij om welk datatype het gaat. Afhankelijk van het datatype zal je sommige functies wel of juist niet kunnen gebruiken. Het heeft bijvoorbeeld geen zin om een gemiddelde te berekenen van een variabele van type chr
.
str(vaccin)
'data.frame': 8887 obs. of 32 variables:
$ X : int 1 2 3 4 5 6 7 8 9 10 ...
$ date : chr "2020-11-25" "2020-11-25" "2020-11-25" "2020-11-25" ...
$ age : int 70 40 30 51 34 63 65 25 54 66 ...
$ gender : chr "Female" "Female" "Female" "Male" ...
$ comorbidity : chr "No" "No" "No" "No" ...
$ comorbidity.N_Y : int 0 0 0 0 0 0 0 0 0 0 ...
$ edu.cat : chr "Master" "Secundary superior" "Master" "Bachelor technical" ...
$ edu : int 7 4 7 5 4 4 3 5 5 3 ...
$ vaccination_intention : int 5 2 3 1 1 5 5 3 5 4 ...
$ vaccin_campaign : int 3 1 3 2 1 3 4 4 4 3 ...
$ vaccin_encouragement : int 5 2 4 3 3 5 4 4 4 4 ...
$ risk_probability_self : int 2 3 3 1 2 3 3 2 2 3 ...
$ risk_consequence_self : int 3 2 2 3 2 5 5 2 3 3 ...
$ risk_self : num 1.83 1.83 1.83 1.33 1.5 ...
$ risk_probability_other: int 3 4 3 4 4 3 4 4 3 3 ...
$ risk_consequence_other: int 4 3 3 3 4 5 5 4 3 3 ...
$ risk_other : num 2.83 2.83 2.33 2.83 3.5 ...
$ worries_relatives : int 3 3 2 2 4 3 5 4 4 3 ...
$ worries_health : int 3 1 1 2 3 2 4 4 2 3 ...
$ motv_vaccin_ident_2 : int 5 2 3 2 1 5 5 2 5 4 ...
$ motv_vaccin_ident_3 : int 5 2 4 2 1 5 5 3 5 4 ...
$ motv_vaccin_ident_4 : int 5 2 4 2 1 5 5 3 5 4 ...
$ motv_vaccin_exter_1 : int 1 3 2 5 2 4 2 4 2 2 ...
$ motv_vaccin_exter_2 : int 1 3 2 3 1 3 1 4 2 2 ...
$ motv_vaccin_exter_3 : int 3 3 3 4 2 2 3 4 2 2 ...
$ motv_vaccin_distrust_1: int 1 4 5 4 4 4 4 5 2 3 ...
$ motv_vaccin_distrust_4: int 1 5 4 3 3 3 2 4 3 2 ...
$ motv_vaccin_distrust_5: int 1 5 5 3 3 3 2 5 3 2 ...
$ motv_vaccin_effort_1 : int 1 2 2 2 3 1 1 1 1 2 ...
$ motv_vaccin_effort_2 : int 1 2 1 1 2 1 1 1 1 2 ...
$ motv_vaccin_effort_3 : int 1 1 1 1 1 1 1 1 1 2 ...
$ gender.M_F : int 1 1 1 0 1 1 1 1 1 1 ...
In de output van str()
kan je zien dat er inderdaad 32 variabelen zijn met telkens 8887 observaties.
Het is een goed idee om niet overhaast te werk te gaan. Specifieker, het is aan te raden om eerst het meetmodel goed te krijgen om pas in een volgende fase het volledige SEM-model (meetmodel + structuurmodel) te fitten. Waarom? De reden om een SEM-analyse uit te voeren is op de eerste plaats om na te gaan of het structureel model goed fit met de data. Daarin zitten namelijk de wetenschappelijke probleemstelling en hypothesen vervat. Als je meteen een SEM-model zou fitten en de fit blijkt niet goed te zijn, dan kan je niet zomaar achterhalen of het ligt aan een slecht fittend meetmodel of een slecht fittend structureel model.
Welke stappen kan je ondernemen om het meetmodel goed te krijgen?
Om het meetmodel goed te krijgen kan je bijvoorbeeld eerst een CFA uitvoeren op elke latente variabele afzonderlijk. Voor latente variabelen waarvoor een gevestigd, algemeen aanvaard meetinstrument bestaat, mag je er meestal wel van uitgaan dat de items een goede meting zijn van de latente variabele. Als je echter zelf een meetinstrument hebt samengesteld om een latente variabele te meten, dan kan het zeker de moeite lonen om eerst, vooraf, een CFA uit te voeren voor die latente variabele apart.
In de demonstratie hieronder tonen we enkel de SEM-analyse. Meer info over CFA, inclusief een demonstratie, vind je hier.
Het is dus aan te raden om pas aan de “echte” SEM-analyse te beginnen nadat je enige zekerheid hebt gekregen over het meetmodel. Maar zelfs die SEM-analyse zou je stapsgewijs kunnen uitvoeren, te beginnen met een deel van het model om geleidelijk aan uit te breiden en samen te voegen in een groter geheel. Dat kan helpen om oorzaken van eventuele slechte fit te identificeren.
Eerst leg je vast welke verbanden je model moet bevatten tussen welke variabelen. Die structuur stop je in een object, hier mijnSEM
genaamd. Vergeet de aanhalingstekens '
helemaal vooraan en achteraan niet. De modelsyntax vormt immers een “string” (een stuk tekst) in R. Deze modelspecificatie zal in een volgende fase aan de functie sem()
worden gegeven, die schattingen van alle modelparameters zal teruggeven. Straks meer daarover.
In het model hieronder zijn verschillende soorten verbanden opgenomen, elk met een eigen syntax. Die zullen we een voor een bespreken.4
mijnSEM <- '
# Latente variabelen
worries =~ worries_relatives + worries_health
risks =~ risk_other + risk_self
motv_ident =~ motv_vaccin_ident_2 + motv_vaccin_ident_3 + motv_vaccin_ident_4
motv_exter =~ motv_vaccin_exter_1 + motv_vaccin_exter_3
motv_distr =~ motv_vaccin_distrust_1 + motv_vaccin_distrust_4 + motv_vaccin_distrust_5
motv_effrt =~ motv_vaccin_effort_1 + motv_vaccin_effort_2 + motv_vaccin_effort_3
# Factor covarianties
worries ~~ risks
motv_ident ~~ motv_exter
motv_ident ~~ motv_distr
motv_ident ~~ motv_effrt
motv_exter ~~ motv_distr
motv_exter ~~ motv_effrt
motv_distr ~~ motv_effrt
# Directe effecten
motv_ident ~ worries + risks
motv_exter ~ worries + risks
motv_distr ~ worries + risks
motv_effrt ~ worries + risks
vaccination_intention ~ worries + risks + motv_ident + motv_exter + motv_distr + motv_effrt
'
Hier worden verbanden tussen indicatoren en potentiële factoren vastgelegd (i.e. de “factorstructuur”).
Deze code weerspiegelt dus de hypothesen die de onderzoekers bij voorbaat hadden over welke indicatoren welke latente variabele meten. Het gelijkheidsteken =
in combinatie met een tilde ~
kan je lezen als “wordt gemeten door”. Dus kan je de eerste regel hierboven begrijpen als “de latente variabele worries
wordt gemeten door de indicatoren worries_relatives
en worries_health
.5
Covarianties tussen factoren kunnen ook worden geschat en weergegeven. Als je niet uitdrukkelijk de covarianties neerschrijft in de modelspecificatie, dan zullen enkel covarianties tussen de zuiver exogene variabelen6 automatisch worden opgenomen, i.c. worries
en risks
.
Covarianties tussen mediatoren (hier: motv_ident
, motv_exter
, motv_distr
en motv_effrt
) worden dus niet standaard opgenomen. Hier is het aan de onderzoeker om te bepalen of er goede inhoudelijke/theoretische redenen zijn om covarianties tussen (sommige) mediatoren toe te laten. Vaak specifiëren onderzoekers covarianties tussen alle mediatoren. Merk op dat het toelaten van zulke covarianties meteen ook een “toegeving” inhoudt dat het model en de onderliggende theorie erg onvolledig zijn: dan zijn er namelijk nog verbanden tussen de mediatoren die op geen enkele manier in dit model worden verklaard.
In de output bij dit voorbeeld zal je schattingen van covarianties zien tussen de exogene factoren worries
en risks
en ook tussen alle mediatoren. In de plot die het model visualiseert (zie eerder) zie je deze covarianties getekend als dubbele pijlen met stippellijn.
Directe effecten zijn de effecten in het structurele gedeelte van het model. Elk direct effect stemt overeen met een enkele pijl in het rode gedeelte van het diagram van hierboven. Een voorbeeld is het effect van autonome motivatie op de vaccinatie-intentie. Deze effecten codeer je met de operator ~
.
In de modelspecificatie hierboven is er een lijn code per afhankelijke variabele. Als je dat wenst kan je de code wat meer opsplitsen. Voor de effecten op bijvoorbeeld motv_ident
wordt dat dan:
'
motv_ident ~ worries
motv_ident ~ risks
'
Nadat de specificatie van het model is afgerond, laat je R alle parameters schatten met de functie sem()
. Deze functie heeft de specificatie van het model nodig (object mijnSEM
) en de data.7
fit <- sem(mijnSEM, data = vaccin)
Om de resultaten te zien gebruik je de functie summary()
. Het object fit
kan uiteraard niet ontbreken. Met de argumenten kan je verder bepalen wat je precies in de output wil zien. (De interpretatie van de output behandelen we verderop.)
summary(fit, fit.measures = TRUE, standardized = TRUE, rsquare = TRUE)
De argumenten geven enkele keuzes aan die je kan maken bij de summary()
-functie:
Indien fit.measures = FALSE
- wat standaard het geval is - wordt enkel een \(\chi^2\)-toets uitgevoerd als maat voor de fit van het model. Deze toets is niet altijd voldoende om de model fit te beoordelen, zeker bij hele grote steekproeven. In Schmitz et al. (2020) zijn er 8887 respondenten, wat veel is.
Het is aangeraden om sowieso bijkomende fitmaten te laten berekenen en te rapporteren in je publicatie, zoals CFI/TLI, RMSEA en SRMR. Al deze fitmaten zijn op een verschillende manier gevoelig voor misspecificatie van het model. Voeg deze fitmaten toe door eenvoudigweg het argument fit.measures = TRUE
te gebruiken. De lezer van je onderzoek kan zo zelf een oordeel vellen.
Meer uitgebreide uitleg over deze fitmaten (en meer) vind je in hoofdstuk 12 van
→ Kline, R. B. (2015). Principles and practice of structural equation modeling (Fourth Edition). New York: Guilford Press.
Het is niet zomaar mogelijk om de padcoëfficiënten onderling te vergelijken en om uitspraken te doen over welke groter of kleiner is. Dat komt omdat de getalwaarde ervan afhangt van de meeteenheid van de variabelen.
Met het argument standardized = TRUE
krijg je een extra kolom Std.all
met schattingen voor gestandaardiseerde padcoëfficiënten. Wanneer er nu twee pijlen in eenzelfde variabele toekomen, zoals bij motv_ident
, kan je wel de grootte van de verschillende effecten vergelijken.
Met dit argument kan je een schatting van de proportie verklaarde variantie (m.a.w. de determinatiecoëfficiënt \(R^2\)) van elke endogene variabele laten berekenen.
Wat kan je leren uit de output van summary(fit, fit.measures = TRUE, standardized = TRUE, rsquare = TRUE)
?
lavaan 0.6.16 ended normally after 70 iterations
Estimator ML
Optimization method NLMINB
Number of model parameters 52
Number of observations 8887
Model Test User Model:
Test statistic 1974.297
Degrees of freedom 84
P-value (Chi-square) 0.000
Model Test Baseline Model:
Test statistic 92690.428
Degrees of freedom 120
P-value 0.000
User Model versus Baseline Model:
Comparative Fit Index (CFI) 0.980
Tucker-Lewis Index (TLI) 0.971
Loglikelihood and Information Criteria:
Loglikelihood user model (H0) -173231.586
Loglikelihood unrestricted model (H1) -172244.438
Akaike (AIC) 346567.172
Bayesian (BIC) 346935.974
Sample-size adjusted Bayesian (SABIC) 346770.727
Root Mean Square Error of Approximation:
RMSEA 0.050
90 Percent confidence interval - lower 0.048
90 Percent confidence interval - upper 0.052
P-value H_0: RMSEA <= 0.050 0.388
P-value H_0: RMSEA >= 0.080 0.000
Standardized Root Mean Square Residual:
SRMR 0.036
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
worries =~
worries_reltvs 1.000 0.776 0.710
worries_health 1.058 0.024 43.366 0.000 0.820 0.698
risks =~
risk_other 1.000 0.620 0.725
risk_self 0.730 0.017 43.248 0.000 0.453 0.657
motv_ident =~
mtv_vccn_dnt_2 1.000 1.373 0.952
mtv_vccn_dnt_3 0.924 0.005 174.332 0.000 1.269 0.930
mtv_vccn_dnt_4 0.854 0.006 138.195 0.000 1.173 0.868
motv_exter =~
mtv_vccn_xtr_1 1.000 1.320 0.999
mtv_vccn_xtr_3 0.488 0.014 35.062 0.000 0.644 0.524
motv_distr =~
mtv_vccn_dst_1 1.000 1.031 0.805
mtv_vccn_dst_4 1.201 0.011 106.600 0.000 1.238 0.933
mtv_vccn_dst_5 1.179 0.012 98.508 0.000 1.215 0.880
motv_effrt =~
mtv_vccn_ffr_1 1.000 0.634 0.782
mtv_vccn_ffr_2 1.162 0.018 66.327 0.000 0.737 0.857
mtv_vccn_ffr_3 0.794 0.015 52.715 0.000 0.504 0.595
Regressions:
Estimate Std.Err z-value P(>|z|) Std.lv Std.all
motv_ident ~
worries 0.146 0.049 2.960 0.003 0.082 0.082
risks 0.847 0.064 13.169 0.000 0.383 0.383
motv_exter ~
worries 0.281 0.050 5.562 0.000 0.165 0.165
risks -0.869 0.066 -13.229 0.000 -0.408 -0.408
motv_distr ~
worries 0.129 0.040 3.258 0.001 0.097 0.097
risks -0.548 0.051 -10.709 0.000 -0.330 -0.330
motv_effrt ~
worries 0.097 0.026 3.756 0.000 0.118 0.118
risks -0.310 0.033 -9.389 0.000 -0.303 -0.303
vaccination_intention ~
worries 0.006 0.027 0.229 0.819 0.005 0.003
risks 0.063 0.034 1.843 0.065 0.039 0.027
motv_ident 0.661 0.015 44.354 0.000 0.908 0.619
motv_exter 0.028 0.008 3.701 0.000 0.037 0.025
motv_distr -0.451 0.019 -23.719 0.000 -0.465 -0.317
motv_effrt 0.033 0.016 2.034 0.042 0.021 0.014
Covariances:
Estimate Std.Err z-value P(>|z|) Std.lv Std.all
worries ~~
risks 0.365 0.010 35.857 0.000 0.759 0.759
.motv_ident ~~
.motv_exter -0.811 0.021 -37.766 0.000 -0.526 -0.526
.motv_distr -1.035 0.021 -49.342 0.000 -0.848 -0.848
.motv_effrt -0.364 0.011 -31.987 0.000 -0.480 -0.480
.motv_exter ~~
.motv_distr 0.696 0.018 38.303 0.000 0.557 0.557
.motv_effrt 0.276 0.011 25.499 0.000 0.356 0.356
.motv_distr ~~
.motv_effrt 0.303 0.009 32.001 0.000 0.494 0.494
Variances:
Estimate Std.Err z-value P(>|z|) Std.lv Std.all
.worries_reltvs 0.590 0.015 38.915 0.000 0.590 0.495
.worries_health 0.707 0.017 40.660 0.000 0.707 0.512
.risk_other 0.348 0.009 36.840 0.000 0.348 0.475
.risk_self 0.270 0.006 46.250 0.000 0.270 0.568
.mtv_vccn_dnt_2 0.195 0.005 38.045 0.000 0.195 0.094
.mtv_vccn_dnt_3 0.252 0.005 47.315 0.000 0.252 0.135
.mtv_vccn_dnt_4 0.450 0.008 57.848 0.000 0.450 0.247
.mtv_vccn_xtr_1 0.003 0.040 0.067 0.947 0.003 0.002
.mtv_vccn_xtr_3 1.097 0.019 57.828 0.000 1.097 0.725
.mtv_vccn_dst_1 0.579 0.010 58.340 0.000 0.579 0.353
.mtv_vccn_dst_4 0.227 0.006 35.411 0.000 0.227 0.129
.mtv_vccn_dst_5 0.429 0.009 50.347 0.000 0.429 0.225
.mtv_vccn_ffr_1 0.255 0.006 41.540 0.000 0.255 0.388
.mtv_vccn_ffr_2 0.196 0.007 27.977 0.000 0.196 0.265
.mtv_vccn_ffr_3 0.464 0.008 59.310 0.000 0.464 0.647
worries 0.601 0.020 30.347 0.000 1.000 1.000
risks 0.385 0.012 30.847 0.000 1.000 1.000
.motv_ident 1.506 0.028 54.419 0.000 0.799 0.799
.motv_exter 1.581 0.048 33.176 0.000 0.908 0.908
.motv_distr 0.989 0.023 43.488 0.000 0.930 0.930
.motv_effrt 0.382 0.010 38.021 0.000 0.948 0.948
.vaccintn_ntntn 0.442 0.008 58.239 0.000 0.442 0.205
R-Square:
Estimate
worries_reltvs 0.505
worries_health 0.488
risk_other 0.525
risk_self 0.432
mtv_vccn_dnt_2 0.906
mtv_vccn_dnt_3 0.865
mtv_vccn_dnt_4 0.753
mtv_vccn_xtr_1 0.998
mtv_vccn_xtr_3 0.275
mtv_vccn_dst_1 0.647
mtv_vccn_dst_4 0.871
mtv_vccn_dst_5 0.775
mtv_vccn_ffr_1 0.612
mtv_vccn_ffr_2 0.735
mtv_vccn_ffr_3 0.353
motv_ident 0.201
motv_exter 0.092
motv_distr 0.070
motv_effrt 0.052
vaccintn_ntntn 0.795
Of het model in zijn geheel goed fit met de data kan je achterhalen met behulp van de verschillende fitmaten.
Let op! De \(\chi^2\)-toets onder Model Test User Model
interpreteren is hier anders dan wat je misschien intuïtief zou denken. Hier stelt de nulhypothese dat er een goede fit is. Dus als je een p-waarde kleiner dan 0.05 ziet, dan is de conclusie dat je de nulhypothese van goede fit moet verwerpen. Dat is in ons model het geval.
Met het argument fit.measures = TRUE
heb je naast de \(\chi^2\)-toets nog andere fitmaten opgevraagd. Er bestaat geen consensus over de precieze waarde vanaf dewelke je zeker kan spreken van een goede fit. Vuistregels voor deze fitmaten die in de literatuur voorkomen zijn:
De waarden voor deze drie fitmaten zijn goed, in tegenstelling tot wat de \(\chi^2\)-toets ons leerde. De conclusie die we in dit geval kunnen trekken is dat de fit goed is, omdat de “slechte” uitkomst van de \(\chi^2\)-toets kan worden verklaard door de hoge steekproefgrootte. Zie ook eerder.
De aanvaardbare fit betekent dat je verder kan gaan met de analyse van de afzonderlijke parameters als je dat wil. Je kan directe en indirecte effecten en andere geschatte parameters beoordelen. Als de model fit niet aanvaardbaar was geweest, dan zou dit niet zinvol zijn. In dat geval zou je eerst op zoek moeten naar een beter fittend model!
Als de fit van je model niet goed is, kan je eventueel de functie modificationIndices()
gebruiken om suggesties te krijgen voor verbeteringen. In de kolom mi
lees je welke bijkomende parameters een substantiële impact zouden hebben op de fit.8
modificationIndices(fit,
sort=TRUE, # Sorteer van grootste naar kleinste effect op de model fit (kolom 'mi')
maximum.number=5L # Toon enkel de eerste vijf
)
lhs op rhs mi epc sepc.lv sepc.all sepc.nox
66 worries =~ motv_vaccin_distrust_1 377.179 0.262 0.204 0.159 0.159
92 motv_ident =~ motv_vaccin_distrust_4 347.659 -0.278 -0.381 -0.287 -0.287
79 risks =~ motv_vaccin_distrust_1 302.022 0.304 0.188 0.147 0.147
150 worries_health ~~ risk_self 283.791 0.124 0.124 0.284 0.284
91 motv_ident =~ motv_vaccin_distrust_1 260.014 0.235 0.323 0.252 0.252
De factorladingen geven informatie over de samenhang tussen de indicatoren. Je kan ze interpreteren als regressiecoëfficiënten in een model waarbij de indicator de afhankelijke variabele is en de factor de onafhankelijke variabele. De factorlading bij de eerste indicator van elke factor wordt door de functie sem()
automatisch gefixeerd op 1. Zo wordt ervoor gezorgd dat de factor (ruwweg) de meeteenheid overneemt van één van de indicatoren.
\[y_1 = \lambda_{11}\eta_1 + \epsilon_1\]
Onder Latent Variables
vind je in de kolom Std.all
de gestandaardiseerde factorladingen. Deze kan je met elkaar vergelijken om te bepalen welke groter en kleiner zijn.
Een goede vuistregel stelt dat een gestandaardiseerde factorlading boven 0.70 moet liggen om de indicator als een degelijke meting te beschouwen. Boven 0.80 spreken we van een echt goede meting. In ons model voldoen veel factorladingen aan deze vuistregels, maar enkele doen dat duidelijk niet.
Als je gebruik hebt gemaakt van gevestigde meetinstrumenten, die eerder al grondig zijn onderzocht, dan is een zwakkere factorlading niet erg. Heb je daarentegen bijvoorbeeld zelf een vragenlijst samengesteld, dan kan je overwegen om het item met de lage factorlading weg te laten. Besef wel dat een lagere factorlading automatisch ook een kleinere rol speelt in het bredere model. De keuze om zo’n item erin te laten of te schrappen zal dus relatief weinig impact hebben op conclusies over het structurele model.
Onder Covariances
vind je in de kolom Std.all
de gevraagde covarianties tussen de variabelen in het structurele model.
Onder Variances
vind je in de kolom Std.all
de schattingen van de residuele varianties van elke indicator en van elke variabele in het structureel model. Dit is niet iets wat je uitdrukkelijk hebt gevraagd in de modelspecificatie. De functie sem()
vult dit automatisch aan.9
De residuele variantie van een indicator is de variantie van de indicator die niet gedeeld wordt met de andere indicatoren van dezelfde factor. Het vormt het spiegelbeeld van de factorladingen: als de factorlading hoog is, dan moet de residuele variantie laag zijn.
De variabelen in het structureel model vallen uiteen in twee soorten: de exogene en de endogene. De exogene factoren (worries
en risks
) zijn te herkennen doordat er geen punt voor staat. Zij worden door geen enkele andere variabele in het model verklaard. De output geeft dan gewoon de variantie weer.
Van de endogene factoren (bv. motv_ident
) wordt de residuele variantie gegeven, dus de variantie die niet verklaard wordt in het model.
De schattingen van padcoëfficiënten (die overeenstemmen met directe effecten) zijn te vinden onder Regressions
. De waarden onder Std.all
kan je vergelijken met elkaar om te bepalen welk effect groter of kleiner is. Of een direct effect verschillend is van 0 kan je beoordelen aan de hand van de p-waarde of overschrijdingskans in de kolom P(>|z|)
.
In dit model zie je dat de meeste vooropgestelde directe effecten significant blijken te zijn op het 5%-significantieniveau. De uitzonderingen zijn de directe effecten van worries
op vaccination_intention
en van risks
op vaccination_intention
.
Het is mogelijk om indirecte en totale effecten te laten berekenen. Dit doe je best pas nadat je hebt vastgesteld dat het model in zijn geheel goed fit met de data.
Hoe ga je te werk? In de modelspecificatie voeg je labels (a11, a21,…) toe aan de verschillende directe effecten. De labels stellen schattingen van padcoëfficiënten (de directe effecten) voor. Aan de hand daarvan kan je nu de indirecte effecten laten berekenen. Daarvoor vermenigvuldig je de padcoëfficiënten op een pad tussen twee variabelen. Die berekeningen zie je in de modelspecificatie hieronder. Het effect dat je wil berekenen geef je een naam, gevolgd door de operator :=
met daarachter de gepaste formule met labels.
Totale effecten berekenen kan ook. Die zijn niets anders dan de som van alle indirecte en directe effecten die te vinden zijn tussen twee variabelen.
Een voorbeeld: de onderzoekers willen in de output het totale effect zien van worries
op vaccination_intention
. Daarvoor gaan ze na welke directe en indirecte paden allemaal lopen van worries
naar vaccination_intention
. Al die paden brengen ze samen in een totaal effect c1
.
c1 := cp1 + a11*b1 + a12*b2 + a13*b3 + a14*b4
Schattingen van indirecte en totale effecten die je op deze manier opvraagt in de modelspecificatie (met de operator :=
) komen in de output. Je vindt ze onder Defined parameters
. Ook hier kan je de schattingen in de kolom Std.all
vergelijken met elkaar. De significantie van het effect kan je beoordelen aan de hand van de p-waarde of overschrijdingskans in de kolom P(>|z|)
.
mijnSEM <- '
# Latente variabelen
worries =~ worries_relatives + worries_health
risks =~ risk_other + risk_self
motv_ident =~ motv_vaccin_ident_2 + motv_vaccin_ident_3 + motv_vaccin_ident_4
motv_exter =~ motv_vaccin_exter_1 + motv_vaccin_exter_3
motv_distr =~ motv_vaccin_distrust_1 + motv_vaccin_distrust_4 + motv_vaccin_distrust_5
motv_effrt =~ motv_vaccin_effort_1 + motv_vaccin_effort_2 + motv_vaccin_effort_3
# Factor covarianties
worries ~~ risks
motv_ident ~~ motv_exter
motv_ident ~~ motv_distr
motv_ident ~~ motv_effrt
motv_exter ~~ motv_distr
motv_exter ~~ motv_effrt
motv_distr ~~ motv_effrt
# Directe effecten
motv_ident ~ a11*worries + a21*risks
motv_exter ~ a12*worries + a22*risks
motv_distr ~ a13*worries + a23*risks
motv_effrt ~ a14*worries + a24*risks
vaccination_intention ~ cp1*worries + cp2*risks + b1*motv_ident + b2*motv_exter + b3*motv_distr + b4*motv_effrt
# Indirecte effecten
a11b1 := a11*b1
a12b2 := a12*b2
a13b3 := a13*b3
a14b4 := a14*b4
a21b1 := a21*b1
a22b2 := a22*b2
a23b3 := a23*b3
a24b4 := a24*b4
a1b := a11*b1 + a12*b2 + a13*b3 + a14*b4
a2b := a21*b1 + a22*b2 + a23*b3 + a24*b4
# Total effects
c1 := cp1 + a11*b1 + a12*b2 + a13*b3 + a14*b4
c2 := cp2 + a21*b1 + a22*b2 + a23*b3 + a24*b4
'
Als je indirecte effecten laat berekenen kan je het model fitten met de volgende code.
fit <- sem(mijnSEM, data=vaccin, se="bootstrap")
In de code zie je een extra argument se = 'bootstrap'
. Dit slaat op de methode om de standaardfouten (“standard error”) van parameters te berekenen. Wanneer je bv. een indirect effect schat, dan is de parameter een product van twee andere parameters en dus typisch niet normaal verdeeld over vele steekproeven heen. Met de “gewone” methode van standaardfouten berekenen kan dit tot verkeerde conclusies leiden over de significantie van het indirect effect. Daarom is het sterk aangeraden om hier steeds voor se = 'bootstrap'
te kiezen.
Er bestaan packages die toelaten om automatisch een diagram te maken van je model. Het is duidelijk dat een visualisatie van een model met veel variabelen al gauw ingewikkeld en onoverzichtelijk wordt, terwijl een visualisatie precies de omgekeerde bedoeling heeft! Mogelijk is het interessanter om bijvoorbeeld enkel het structurele gedeelte in een diagram te tonen.
Een package dat je kan gebruiken is bijvoorbeeld lavaanPlot
. Meer info vind je hier, voornamelijk onder “Documentation”.
install.packages("lavaanPlot") # eenmalig het package installeren
library(lavaanPlot) # package laden voor gebruik
lavaanPlot(model = fit,
node_options = list(shape = "box", fontname = "Helvetica"),
edge_options = list(color = "grey"),
coefs = TRUE,
sig=.05 # toon enkel significante coëfficiënten
)
Klik op de afbeelding om te vergroten.
Een alternatief is het package semPlot
. Meer info vind je hier, voornamelijk onder “Documentation”.
install.packages('semPlot') # eenmalig het package installeren
library(semPlot) # package laden voor gebruik
semPaths(fit, layout='circle')
Klik op de afbeelding om te vergroten.
Online en in handboeken is leermateriaal te vinden dat wat meer diepgang biedt dan deze demonstratie. Enkele bronnen waarvan we de kwaliteit kunnen verzekeren zijn:
Kline R.B. (2016). Principles and practice of structural equation modeling. Fourth edition. New York ; London: The Guilford Press.
Schmitz M., Luminet O., Klein O., Morbée S., Van den Bergh O., Van Oost P., Waterschoot J., Yzerbyt V. & Van Steenkiste M. (2022). Predicting vaccine uptake during COVID-19 crisis: A motivational approach. Vaccine 40 (2), pp.288-297. doi: 10.1016/j.vaccine.2021.11.068
In het Nederlands: Structureel vergelijkingsmodel↩︎
Schmitz et al. (2022) onderzochten ook de effecten op andere afhankelijke variabelen. Dat deel van hun studie wordt op deze pagina niet besproken.↩︎
Vaccinatie-intentie is rechtstreeks meetbaar. Het is m.a.w. een manifeste variabele, geen latente variabele. Er zijn dan ook geen indicatoren.↩︎
Alles wat rechts van een #
staat is commentaar. Het is enkel bedoeld om wat duiding te geven bij de code.↩︎
Of dat werkelijk het geval is zal natuurlijk moeten blijken uit de analyse. Op dit moment is het nog maar een hypothese!↩︎
Exogene variabelen zijn variabelen die niet door andere variabelen in het model verklaard worden. In het diagram bovenaan de pagina zijn het variabelen waar geen enkele pijl toekomt.↩︎
In plaats van de volledige dataset wordt vaak enkel de variantie-covariantiematrix aan sem()
gegeven.↩︎
Bij de verbanden die al in je model zitten, staat er logischerwijs een waarde 0 in deze kolom.↩︎
De functie sem()
is gebruiksvriendelijk in de zin dat het heel wat van je syntax automatisch aanvult. Als je dit niet wil, dan kan je de functie lavaan()
gebruiken in de plaats.↩︎