Bijlage D — Assumptieschendingen oplossen

#Packages
library(ggResidpanel)
library(rio)
library(tidyverse)
library(modelsummary)
library(marginaleffects)
library(broom)

# Gebruikte datasets
demdata <- import("demdata.rds")
ess_demsatis <- import("ess_demsatis.dta")
normal_residual_data <- import("normal_residual_data.rda")
serial_data <- import("./data/serial_autocorrelation.rda")
1
Assumpties testen met plots
2
Data importeren
3
Datamanipulatie/plots
4
Regressietabellen maken
5
Voorspelde waarden en marginale effecten
6
Gegevens samenvatten
Voor wie is deze Appendix?

Je hoeft niet te weten hoe je deze analyses moet uitvoeren voor de opdrachten in Statistiek II. Deze gids is bedoeld voor studenten die hun eindpaper schrijven voor Academische Vaardigheden: Kwantitatieve Data-Analyse of een BAP-scriptieproject en graag assumptieschendingen willen vermijden.

Regressiemodellen zijn gebaseerd op een aantal assumpties of aannames. Een belangrijk onderdeel van de analyse is nagaan of aan die assumpties voldaan is. Zoniet, dan moeten assumptieschendingen op een passende manier behandeld worden. Statistiek II richt zich meer op het eerste deel van het proces: wanneer is niet voldaan aan een assumptie? In deze Appendix geven we een kort overzicht van enkele mogelijke oplossingen voor assumptieschendingen waar je mee geconfronteerd kan worden.

D.1 Assumpties over de fouten (residuals) in OLS modellen

\[ y_{i} = b_{0} + b_{1} * x_{1} + b_{2} * x_{2} ... b_{k} * x_{k} + e_{i} \]

De \(e_{i}\) term in bovenstaande vergelijking staat voor de ‘error’, fout, of residual in een linear regressiemodel. Er zijn drie assumpties over deze fouten:1

  • De variantie van de residuals is constant over het hele bereik van de voorspellingen van het model (homoskedasticiteit)
  • De residuals zijn onafhankelijk van elkaar
  • De residuals volgen een normaalverdeling met een gemiddelde van 0

Schendingen van deze assumpties hebben belangrijke gevolgen voor statistische significantietests. Ernstige schendingen leiden tot onbetrouwbare schattingen van de standaardfout van een coëfficiënt en, als gevolg daarvan, tot onjuiste oordelen over statistische significantie.

D.2 Omgaan met Heteroskedasticiteit

D.2.1 Wat was het probleem ook al weer?

Onderstaand voorbeeld komt uit Hoofdstuk 7 en voorspelt de mate van politieke stabiliteit in een land op basis van democratieniveaus. In het voorbeeld hebben we ook een kwadratische term voor democratiescore toegevoegd om non-lineaire verbanden te kunnen vatten. Hier is het model en de resultaten:

#gekwadrateerde variabele maken
demdata <- demdata |> 
  mutate(v2x_polyarchy_sq = v2x_polyarchy^2)

# Model schatten
violence_sqmodel <- lm(pve ~ v2x_polyarchy + v2x_polyarchy_sq, 
                      data=demdata)

# coëfficiënten bekijken
tidy(violence_sqmodel, conf.int = TRUE)
# A tibble: 3 × 7
  term             estimate std.error statistic  p.value conf.low conf.high
  <chr>               <dbl>     <dbl>     <dbl>    <dbl>    <dbl>     <dbl>
1 (Intercept)        -0.589     0.243     -2.42 0.0165      -1.07    -0.109
2 v2x_polyarchy      -1.73      1.10      -1.58 0.117       -3.90     0.438
3 v2x_polyarchy_sq    3.83      1.05       3.64 0.000361     1.75     5.90 

We kunnen kijken of de assumptie van homoskedasticiteit geschonden is via resid_panel() zoals besproken in Hoofdstuk 7.

# assumptie bekijken
resid_panel(violence_sqmodel, plots = c("resid"))

Aan de assumptie is voldaan als de fouten een even grote spreiding kennen op lage, gemiddelde, en hoge waarden van de voorspelde waarden. Ze moeten zich ongeveer in een evenwijdige band bevinden. Hier zien we dat de assumptie geschonden is aan de trechtervorm: we vinden een grote spreiding van residuals bij lage voorspele waarden (de schatting is hier onnauwkeurig) en weinig spreiding bij hoge voorspelde waarden (de schatting is hier veel nauwkeuriger).

D.2.2 Mogelijke oplossing: robuste standaardfouten

Opmerking

De vcov functie die we hieronder gebruiken is afkomstig uit hetsandwichpackage en helpt bij de berekening van robuuste standaardfouten. Je zult dit package misschien eerst moeten installeren.

Heteroskedasticiteit heeft verschillende mogelijke oorzaken. Zo kan het onstaan door het ontbrekenen van belangrijke predictoren in het model. Het is een goed idee om na te denken over waarom de residuals zo verschillen en welke bijkomende onafhankelijke variabele deze spreiding kan verklaren. We kunnen dit echter niet altijd weten en als we al een idee hebben, kan het zijn dat de relevante onafhankelijke variabele niet in de dataset voorkomt.

Een andere mogelijke oorzaak is dat we een lineaire relatie schatten waar eigenlijk een niet-lineaire relatie geschat moet worden. Dit is hier echter niet het geval.

Wat kunnen we dan nog doen?

Een gangbare manier is om de berekenmethode voor de standaardfouten van de coëfficiënten in het model aan te passen. De standaardfouten die normaal door R worden berekend gaan uit van homoskedasticiteit, maar we kunnen ‘heteroskedasticiteit-robuuste’ standaardfouten berekenen als alternatief. Belangrijk is om te onthouden dat deze oplossing een mathematisch ‘truukje’ is, ons model past nog altijd slecht en het is belangrijk theoretisch hierover na te denken. De eerste stap blijft altijd om na te denken over mogelijk ontbrekende predictoren.

De eenvoudigste manier om robuuste standaardfouten te verkrijgen is via het modelsummary package. We bekijken eerst nog even de ‘normale’ regressie-output voor het model:

modelsummary(violence_sqmodel, 
             stars = T, 
             coef_rename = c(
               "(Intercept)" = "Intercept", 
               "v2x_polyarchy" = "Democratiescore", 
               "v2x_polyarchy_sq" = "Democratiescore gekwadrateerd"), 
             gof_map = c("nobs", "r.squared", "adj.r.squared"))
(1)
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
Intercept -0.589*
(0.243)
Democratiescore -1.731
(1.098)
Democratiescore gekwadrateerd 3.829***
(1.051)
Num.Obs. 164
R2 0.401
R2 Adj. 0.394

De tabel hierboven maakt gebruik van de normale standaardfouten. We kunnen de robuuste opvragen via de vcov optie:

modelsummary(violence_sqmodel, 
             stars = T, 
             vcov = "HC3",
             coef_rename = c(
               "(Intercept)" = "Intercept", 
               "v2x_polyarchy" = "Democratiescore", 
               "v2x_polyarchy_sq" = "Democratiescore gekwadrateerd"), 
             gof_map = c("nobs", "r.squared", "adj.r.squared"))
(1)
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
Intercept -0.589+
(0.303)
Democratiescore -1.731
(1.249)
Democratiescore gekwadrateerd 3.829***
(1.107)
Num.Obs. 164
R2 0.401
R2 Adj. 0.394
vcov = "HC3"

De vcov = optie vraagt om robuuste standaardfouten te berekenen en te gebruiken in de tabel. “HC3” staat voor ‘heteroskedasticiteit-robuuste standardfouten’. Er zijn verschillende heteroskedasticiteit-robuuste standaardfouten (bv., “HC0”, “HC1”, etc.). We raden “HC3” aan als standaard, ook omdat deze goed werkt bij kleine steekproefgroottes.

Laten we de ‘normale’ en ‘robuuste’ standaardfouten vergelijken:

modelsummary(violence_sqmodel, 
             stars = T, 
             vcov = c("classical", "HC3"), 
             coef_rename = c(
               "(Intercept)" = "Intercept", 
               "v2x_polyarchy" = "Democratiescore", 
               "v2x_polyarchy_sq" = "Democratiescore gekwadrateerd"),
             gof_map = c("nobs", "r.squared", "adj.r.squared"))
(1) (2)
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
Intercept -0.589* -0.589+
(0.243) (0.303)
Democratiescore -1.731 -1.731
(1.098) (1.249)
Democratiescore gekwadrateerd 3.829*** 3.829***
(1.051) (1.107)
Num.Obs. 164 164
R2 0.401 0.401
R2 Adj. 0.394 0.394

De eerste kolom toont de resultaten voor de ‘normale’ standaardfouten, de tweede kolom deze voor de ‘robuuste’ fouten. We kunnen het volgende opmerken:

  • De coëfficiënten veranderen niet, enkel de standaardfouten.
  • De robuuste standaardfouten zijn typisch groter. Heteroskedasticiteit zorgt immers doorgaans voor een neerwaarste bias: standaarfouten worden te klein geschat als we de normale methode zouden gebruiken.
  • De interpretatie van de resultaten verandert hier niet. De significantietoetsen voor de predictoren komen uit op dezelfde conclusies. Di is echter lang niet altijd het geval!

Het voorbeeld hierboven maakt gebruik van modelsummary() en geeft de resultaten weer in een tabel. Ook voor voorspelde waarden en coëfficiëntenplots kunnen we robuuste standaardfouten gebruiken zodanig dat betrouwbaarheidsintervallen aangepast worden.

We blijven de bovenstaande vcov optie gebruiken, maar binnen de predictions() functie uit het marginaleffects package (bv., predictions(model, ..., vcov = "HC3")) voor voorspelde waarden.

Voor coëfficiëntenplots kunnen we de syntax echter niet netjes combineren met tidy() zoals we deden in Hoofdstuk 8. In de plaats daarvan gebruiken we de avg_slopes() functie uit het marginaleffects package. avg_slopes() schat de marginale effecten van elke predictor in het model. Bij OLS is dit gelijk aan de coëfficiënten.

# Resultaten via tidy
tidy(violence_sqmodel)
# A tibble: 3 × 5
  term             estimate std.error statistic  p.value
  <chr>               <dbl>     <dbl>     <dbl>    <dbl>
1 (Intercept)        -0.589     0.243     -2.42 0.0165  
2 v2x_polyarchy      -1.73      1.10      -1.58 0.117   
3 v2x_polyarchy_sq    3.83      1.05       3.64 0.000361
# Resultaten  via avg_slopes
avg_slopes(violence_sqmodel)

             Term Estimate Std. Error     z Pr(>|z|)    S 2.5 % 97.5 %
 v2x_polyarchy       -1.73       1.10 -1.58    0.115  3.1 -3.88  0.421
 v2x_polyarchy_sq     3.83       1.05  3.64   <0.001 11.9  1.77  5.888

Type:  response 
Comparison: dY/dX

We voegen de vcov = optie toe aan de avg_slopes() functie en plotten de resultaten:

# robuuste SEs en plot
avg_slopes(violence_sqmodel, vcov = "HC3") |>
  ggplot(aes(x = estimate, y = term)) + 
  geom_pointrange(aes(xmin = conf.low, xmax = conf.high)) + 
  geom_text(aes(label = round(estimate, 2)), vjust = -0.5) + 
  geom_vline(xintercept = 0, linetype = 'dashed', color = 'red') + 
  theme_bw() + 
  labs(x = "Coëfficiënt", 
       y = "Onafhankelijke variabele", 
       title = "Coëfficiënten met Heteroskedasticiteit-robuuste betrouwbaarheidsintervallen")
1
Opvragen van marginale effecten van de predictoren samen met de robuuste standaardfouten.

D.3 Omgaan met afhankelijke fouten

Een tweede belangrijke assumptie in OLS (en logistische) modellen is deze van onafhankelijkheid. We gaan ervan uit dat de fouten in de populatie en in ons model niet gecorreleerd zijn met elkaar. Deze assumptie kan geschonden zijn wanneer data ‘geclusterd’ is.

D.3.1 Probleem 1: geclusterde data

D.3.1.1 Voorbeeld van het probleem

Stel dat we meten in welke mate burgers tevreden zijn met democratie in verschillende Europese landen (bv. met links-rechtspositie als predictor). We kunnen de European Social Survey (ESS) gebruike, waarbij respondenten uit verschillende landen werden ondervraagd. Wanneer de analyse meerdere landen beschouwt, zijn respondenten geclustert in hun land.

We bekijken de resultaten van een dergelijk model. De afhankelijke variabele meet tevredenheid met democratie op een schaal van 0 (“zeer ontevreden”) tot 10 (“zeer tevreden”). De onafhankelijke variabele meet links-rechtspositie met een schaal van 0 (“links”) tot 10 (“rechts”).

# Model 
demsatis_model <- lm(stfdem ~ lrscale, data = ess_demsatis)

# Coefficients
tidy(demsatis_model, conf.int = TRUE)
# A tibble: 2 × 7
  term        estimate std.error statistic   p.value conf.low conf.high
  <chr>          <dbl>     <dbl>     <dbl>     <dbl>    <dbl>     <dbl>
1 (Intercept)    4.53    0.0308      147.  0            4.47      4.59 
2 lrscale        0.128   0.00556      23.1 7.97e-117    0.117     0.139

De coëfficiënt voor links-rechtspositie (lrscale) is positief: mensen die zich meer aan de rechterkant van het politieke spectrum plaatsen zijn meer tevreden met democratie. De coëfficiënt is ook statistisch significant met wel een heel kleine p-waarde (7.97e-117). Laten we echter kijken hoe clustering hier bij komt kijken.

Show the code
# Extra package for plotting
library(patchwork)

# Distribution plot
distribution_plot <- ess_demsatis |> 
  group_by(country_name, stfdem) |>
  tally() |> 
  ungroup() |> 
  group_by(country_name) |>
  mutate(prop = n / sum(n)) |>
  ggplot(aes(x = stfdem, y = prop)) + 
  geom_col() +
  facet_wrap(~ country_name) + 
  theme_bw() + 
  labs(title = "Variatie volgens Land", 
       y = "Proportie", 
       x = "Tevredenheidsscore") + 
  scale_x_continuous(breaks = c(0,5,10))

# Plot of means
mean_plot <- ess_demsatis |> 
  group_by(country_name) |> 
  summarize(dem_satis = mean(stfdem, na.rm = T)) |> 
  ggplot(aes(x = dem_satis, y = reorder(country_name, dem_satis))) +
  geom_col() + 
  theme_bw() + 
  labs(title = "Gemiddelde per land", 
       y = "Naam land", 
       x = "Gemiddelde tevredenheid met democratie")

# Combine together using patchwork
distribution_plot + mean_plot
1
De patchwork library wordt hier gebruikt om meerdere grafieken te combineren met elkaar.
2
Deze regels berekenen het aantal antwoorden per antwoordcategorie per land.
3
We berekenen hier de proportie van observaties met een specifieke respons per land.
4
De reorder() optie herordent de y-as zodat die loopt van hogere tot lagere gemiddelde scores.

Het linkse plot in de figuur hierboven toont de proportie observaties per antwoordcategorie (x-as) per land (aparte ‘facets’). De plot toont variatie tussen individuen binnen een land: sommige zijn tevreden, andere niet. De verdeling van observaties is echter niet dezelfde per land. Sommige landen zien meer tevreden respondenten (bv., Finland, Norwegen, en Zwitserland) terwijl in andere landen mensen zich meer onderaan de schaal bevinden (bv., Bulgarije, Servië, en Griekenland).

Er is dus niet alleen variatie tussen individuen, maar ook variatie tussen landen. Dit zien we ook op het rechtse plot, dat de gemiddelde tevedenheidsscore per land weergeeft.2

Democratische tevredenheid is hoger in sommige landen dan andere. Daar kunnen verschillende redenen voor bestaan: andere politieke instituties, economische welvaart, corruptieniveaus enz. Mensen binnen een land leven in een context waarin die factoren gelijk zijn, maar mensen buiten een bepaald land leven in andere omstandigheden. We kunne dan ook verwachten dat errors van mensen binnen een bepaald land gecorreleerd zijn met elkaar in een studie waarbij meerdere landen zijn opgenomen.De standaardfouten in het model zijn dan wellicht ook niet correct.3

We kunnen 2 methoden gebruiken om dit te corrigeren:4

We kunnen gebruik maken van “geclusterde standaardfouten” en “fixed effects”. Net zoals bij heteroskedasticiteit herberekenen we de standaardfouten (Let op: de berekening is wel anders). Deze corrigeren voor het feit dat de errors van observaties binnen een cluster (hier: land) gecorreleerd zijn. Fixed effects toevoegen houdt in dat we een dummy-variabele toevoegen per cluster (hier:land) om te controleren op variaties in de afhankelijke variabele afkomstig uit kenmerken van de cluster.

Sommige onderzoekers maken ook gebruik van “multilevel modellen”. Met deze modellen kunnen predictoren op het niveau van individuele respondenten alsook predictoren op het cluster-niveau worden toegevoegd (bv. BBP) Deze methode is te gevorderd voor dit handboek en wordt verder niet besproken.

D.3.2 Mogelijke oplossing: Clustered standard errors & fixed effects

We schatten ons model met geclusterde standaardfouten met de code uitgelegd hieronder. Om “fixed effects” toe te voegen transformeren we gewoon de clustervariabele in een factor-variabele. Wanneer we deze factor toevoegen aan het model neemt R dummies op voor elke cluster, behoudens de referentiecategorie. 5

# factor maken
ess_demsatis <- ess_demsatis |> 
  mutate(
    country_name_F = factor(country_name))

# Controleren of alles juist is gegaan: Austria is hier de referentiecategorie
levels(ess_demsatis$country_name_F)
 [1] "Austria"        "Belgium"        "Bulgaria"       "Croatia"       
 [5] "Cyprus"         "Finland"        "France"         "Germany"       
 [9] "Greece"         "Hungary"        "Iceland"        "Ireland"       
[13] "Israel"         "Italy"          "Latvia"         "Lithuania"     
[17] "Montenegro"     "Netherlands"    "Norway"         "Poland"        
[21] "Portugal"       "Serbia"         "Slovakia"       "Slovenia"      
[25] "Spain"          "Sweden"         "Switzerland"    "United Kingdom"
# Factor toevoegen aan model
demsatis_model2 <- lm(stfdem ~ lrscale + country_name_F, data = ess_demsatis)

# Coëfficiënten opvragen
tidy(demsatis_model2, conf.int = TRUE)
# A tibble: 29 × 7
   term                estimate std.error statistic   p.value conf.low conf.high
   <chr>                  <dbl>     <dbl>     <dbl>     <dbl>    <dbl>     <dbl>
 1 (Intercept)           5.15     0.0568     90.7   0            5.03     5.26  
 2 lrscale               0.136    0.00521    26.2   4.59e-150    0.126    0.147 
 3 country_name_FBelg…  -0.550    0.0790     -6.96  3.45e- 12   -0.704   -0.395 
 4 country_name_FBulg…  -2.42     0.0751    -32.1   7.25e-224   -2.56    -2.27  
 5 country_name_FCroa…  -1.35     0.0819    -16.5   8.89e- 61   -1.51    -1.19  
 6 country_name_FCypr…  -1.27     0.108     -11.7   8.62e- 32   -1.49    -1.06  
 7 country_name_FFinl…   0.885    0.0791     11.2   4.70e- 29    0.730    1.04  
 8 country_name_FFran…  -1.53     0.0782    -19.6   4.41e- 85   -1.68    -1.38  
 9 country_name_FGerm…  -0.0598   0.0705     -0.848 3.96e-  1   -0.198    0.0784
10 country_name_FGree…  -1.62     0.0703    -23.0   1.57e-116   -1.76    -1.48  
# ℹ 19 more rows

Het model bevat nu een coëfficiënt voor links-rechtspositie (lrscale) en een reeks dummy-variabelen voor elk land (country_name_FBelgium, country_name_FBulgaria, etc.). Schrik niet van deze output, dit is effectief de bedoeling!

De coëfficient voor lrscale toont de associatie tussen links-rechtspositie en tevredenheid met democratie, gecontroleerd voor het land waar de respondent zich bevindt. De dummy-coëfficiënten tonen het verschil tussen elk land en de referentiecategorie (Oostenrijk), gecontroleerd voor lrscale. We vinden bijvoorbeeld dat als we Bulgaarse en Oostenrijkse burgers met dezelfde ideologiescore zouden vergelijken, dan zouden we verwachten dat tevredenheid van Bulgaarse burgers gemiddeld genomen 2.42 eenheden lager is dan die van Oostenrijkse burgers.

De resultaten hieboven maken nog gebruik van de ‘normale’ standaardfouten. We kunnen de geclusterde standaardfouten toevoegen via modelsummary() met de vcov = optie, zoals hieronder (coef_rename hebben we hier even weggelaten).

modelsummary(
  demsatis_model2, 
  stars = T, 
  vcov = ~country_name_F, 
  gof_map = c("nobs", "r.squared", "adj.r.squared"))
(1)
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
(Intercept) 5.146***
(0.230)
lrscale 0.136**
(0.048)
country_name_FBelgium -0.550***
(0.012)
country_name_FBulgaria -2.415***
(0.022)
country_name_FCroatia -1.349***
(0.018)
country_name_FCyprus -1.273***
(0.021)
country_name_FFinland 0.885***
(0.035)
country_name_FFrance -1.531***
(0.010)
country_name_FGermany -0.060***
(0.013)
country_name_FGreece -1.620***
(0.023)
country_name_FHungary -1.353***
(0.040)
country_name_FIceland 0.121***
(0.001)
country_name_FIreland 0.098***
(0.005)
country_name_FIsrael -1.733***
(0.055)
country_name_FItaly -0.736***
(0.016)
country_name_FLatvia -1.313***
(0.052)
country_name_FLithuania -1.003***
(0.019)
country_name_FMontenegro -1.307***
(0.029)
country_name_FNetherlands 0.172***
(0.010)
country_name_FNorway 1.343***
(0.015)
country_name_FPoland -0.672***
(0.034)
country_name_FPortugal -0.946***
(0.005)
country_name_FSerbia -1.741***
(0.008)
country_name_FSlovakia -1.230***
(0.014)
country_name_FSlovenia -0.811***
(0.003)
country_name_FSpain -1.019***
(0.008)
country_name_FSweden 0.799***
(0.002)
country_name_FSwitzerland 1.710***
(0.011)
country_name_FUnited Kingdom -1.327***
(0.008)
Num.Obs. 39522
R2 0.160
R2 Adj. 0.160
vcov = \~country_name_F

Met deze optie vragen we om geclusterde standaardfouten te berekenen voor ons model. De clustervariabele moet aangeduid worden in de syntax.

We kunnen de vcov optie ook gebruiken voor voorspellingen en marginal effects binnen de avg_slopes en predictions functies:

predictions(demsatis_model2, 
            newdata = datagrid(lrscale = c(0:10)), 
            vcov = ~country_name_F) |> 
  ggplot(aes(x = lrscale, y = estimate)) + 
  geom_line() + 
  geom_ribbon(aes(ymin = conf.low, ymax = conf.high), alpha = 0.2) + 
  theme_bw() + 
  labs(title = "Voorspelde waarden met geclusterde standaardfouten", 
       x = "Links-rechtspositionering", 
       y = "Voorspelde tevredenheid met democratie") + 
  scale_x_continuous(breaks = c(0:10)) + 
  scale_y_continuous(limits = c(0,10))

Presentatie

Met de modelsummary functie hierboven creëerden we een regressietabel met de coëfficiënten voor alle `country_name_F’- categorieën. Als er echter heel veel dummies zijn (en die dummies zijn ook minder relevant voor de interpetatie), dan worden ze vaak weggelaten in de output. Wel moet je in de notitie aan de lezer verduidelijken dat ‘fixed effects’ en geclusterde standaardfouten werden gebruikt.

We gebruiken coef_map om bepaalde coëfficiënten weg te kunnen laten in de output (zie Paragraaf 15.2).

modelsummary(
  demsatis_model2, 
  stars = T, 
  vcov = ~country_name_F, 
  coef_map = c("(Intercept)" = "Constante", 
               "lrscale" = "Links-Rechtspositie"),
  gof_map = c("nobs", "r.squared", "adj.r.squared"),
  title = "Democratische tevredenheid en links-rechtspositie", 
  notes = "Lineaire regressiecoëfficiënten met geclusterde standaardfouten tussen haakjes. Model geschat met country fixed effects.")
Democratische tevredenheid en links-rechtspositie
(1)
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
Lineaire regressiecoëfficiënten met geclusterde standaardfouten tussen haakjes. Model geschat met country fixed effects.
Constante 5.146***
(0.230)
Links-Rechtspositie 0.136**
(0.048)
Num.Obs. 39522
R2 0.160
R2 Adj. 0.160

D.3.3 Probleem 2: Data over de tijd heen en seriële autocorrelatie

De assumptie van onafhankelijke fouten kan ook geschonden worden als we dezelfde eenheid (bv. een persoon, land, bedrijf) observeren over meerder punten in de tijd. Dit wordt ‘time-series’ data genoemd.

Stel bijvoorbeeld dat we geïnteresseerd zijn in de relatie tussen welvaart en democratie in een land. We maken hier gebruik van V-Dem data en kijken naar de relatie tussen welvaart van een land (e_gdp) en democratiescore (v2x_polyarchy). We richten ons eerst op Nederland. Hieronder tonen we hoe de data eruit ziet.

#Filteren op Nederland
netherlands <- serial_data |> 
  filter(country_name == "Netherlands")

#eerste 15 rijen in de dataset bekijken
head(netherlands, n = 15L)
   country_name year v2x_polyarchy    e_gdp
1   Netherlands 1789         0.076 2309.141
2   Netherlands 1790         0.076 2362.091
3   Netherlands 1791         0.076 2411.786
4   Netherlands 1792         0.076 2454.337
5   Netherlands 1793         0.076 2481.157
6   Netherlands 1794         0.076 2485.290
7   Netherlands 1795         0.097 2463.161
8   Netherlands 1796         0.133 2486.460
9   Netherlands 1797         0.181 2491.454
10  Netherlands 1798         0.179 2447.313
11  Netherlands 1799         0.127 2358.512
12  Netherlands 1800         0.127 2249.312
13  Netherlands 1801         0.127 2283.304
14  Netherlands 1802         0.127 2304.492
15  Netherlands 1803         0.127 2283.129

De dataset meet de variabelen vanaf 1789. Maar vinden we een relatie tussen de twee variabelen? Gaat hogere welvaart gepaard met meer democratie? We kunnen een lineaire regressie schatten om dit te proberen nagaan. We beperken ons hier tot een bivariate analyse. In de praktijk zou je ook op zoek willen gaan naar controlevariabelen.6

# Het model
neth_model1 <- lm(v2x_polyarchy ~ e_gdp, data = netherlands)

# Coëfficiënt bekijken
tidy(neth_model1, conf.int = TRUE)
# A tibble: 2 × 7
  term          estimate   std.error statistic  p.value   conf.low  conf.high
  <chr>            <dbl>       <dbl>     <dbl>    <dbl>      <dbl>      <dbl>
1 (Intercept) 0.355      0.0166           21.4 2.14e-56 0.323      0.388     
2 e_gdp       0.00000384 0.000000255      15.0 5.82e-36 0.00000334 0.00000434

De coëfficiënt voor e_gdp is positief. Het getal is erg klein, maar dit betekent niet noodzakelijk een klein effect gezien de predictor hier een sterke spreiding heeft. De coëfficiënt is ook statistisch significant.

Echter moeten we opletten: onze observaties zijn allemaal gelinkt aan hetzelfde land en zijn dus niet onafhankelijk. Dit betekent vaak dat we de standaardfouten onderschatten en we kunnen zo onterecht significate effecten vinden.

Dit probleem is gekend als seriële autocorrelatie: de errors van het model zijn gecorreleerd met elkaar over de tijd heen. Een error op tijdstip t is systematisch gerelateerd aan de error van het jaar voordien, t-1. Dit is logisch gezien de welvaart en het democratisch gehalte van een land vrij stabiel zijn en afhangen van onderliggende condities zoals economische instituties, natuurlijke rijkdommen enz. Deze factoren fluctueren niet zo sterk over de tijd heen waardoor opeenvolgende errors vaak lijken op elkaar.

We kunnen formeel nagaan of er seriële autocorrelatie aanwezig is met behulp van de Durbin-Watson statistiek zoals besproken in Hoofdstuk 7.

car::durbinWatsonTest(neth_model1)
1
Het car:: prefix staat ons hier toe om het carpackage te gebruiken zonder het te moeten laden. Dit heeft voordelen gezien carook funties heeft die conflicteren met andere functies die we gebruiken, in het bijzonder de recode functie uit het dplyr/tidyverse package.
 lag Autocorrelation D-W Statistic p-value
   1       0.9667039    0.05304265       0
 Alternative hypothesis: rho != 0

De Autocorrelation kolom geeft de correlatie weer tussen errors van het ene jaar op het andere. De correlatie is 0.97. Dit is hoog, gezien het maximum +1 is. De D-W Statistic test formeel of de autocorrelatie te hoog is. Dit is het geval als D-W hoger is dan 3 of kleiner dan 1. Hier is het duidelijk lager dan 1 (0.053). De p-waarde is extreem klein en dus verwerpen we de nulhypothese dat er geen autocorrelatie is. De standaardfouten zijn niet correct.

We kunnen dit probleem oplossen door een ‘vertraagde afhankelijke variabele’ (‘lagged dependent variable’) toe te voegen als predictor in ons model. Deze vertraagde afhankelijke variabele bevat voor elke observatie de score voor de afhankelijke variabele in het onmiddellijk voorgaande tijdspunt. Door deze variabele op te nemen wordt het probleem van stabiliteit/inertie in de afhankelijke variabele (en gecorreleerde errors) verholpen.7

D.3.3.1 Lagged Dependent Variables maken

We kunnen de lag() functie gebruiken om onze afhankelijke variabele te ‘vertragen’ voor een nieuw aangemaakte variabele.8

netherlands <- netherlands |> 
  mutate(dem_lag = lag(v2x_polyarchy, 1))
lag(v2x_polyarchy, 1)

We gebruiken de functie lag op de relevante afhankelijke variabele (hier, v2x_polyarchy). Het nummer aan het einde van de functie geeft weer hoeveel eenheden vertraging we willen. Met het cijfer 1 zeggen we 1 tijdseenheid (hier: 1 jaar). Dit is het meest gebruikelijk. Indien we de v2x_polyarchy score van 2 jaar terug zouden willen, dan schrijven we ‘2’ enzovoort.

Laten we even kijken wat dat doet:

head(netherlands, n = 15L)
   country_name year v2x_polyarchy    e_gdp dem_lag
1   Netherlands 1789         0.076 2309.141      NA
2   Netherlands 1790         0.076 2362.091   0.076
3   Netherlands 1791         0.076 2411.786   0.076
4   Netherlands 1792         0.076 2454.337   0.076
5   Netherlands 1793         0.076 2481.157   0.076
6   Netherlands 1794         0.076 2485.290   0.076
7   Netherlands 1795         0.097 2463.161   0.076
8   Netherlands 1796         0.133 2486.460   0.097
9   Netherlands 1797         0.181 2491.454   0.133
10  Netherlands 1798         0.179 2447.313   0.181
11  Netherlands 1799         0.127 2358.512   0.179
12  Netherlands 1800         0.127 2249.312   0.127
13  Netherlands 1801         0.127 2283.304   0.127
14  Netherlands 1802         0.127 2304.492   0.127
15  Netherlands 1803         0.127 2283.129   0.127

Elke rij in de dataset bevat waarden per uniek jaar (1789, 1790, …). v2x_polyarchy geeft de democratiescore van het land in het betreffende jaar. De nieuwe kolom (dem_lag) geeft de score in het voorgaande jaar. De waarde voor dem_lag in rij 9 (year = 1797) is 0.133; dis was de waarde voor democratie in 1796 (rij 8).We zien NA in de eerste rij gezien we geen voorgaande data hebben.

Waarschuwing!

Het spreekt voor zich dat de dataset eerst netjes geordend moet zijn op jaar vooraleer we functies zoals car::durbinWatsonTesten lag kunnen gebruiken. Hoe je een dataset kan ordenen (indien nodig) wordt besproken in Hoofdstuk 7.

We voegen nu de lagged dependent variabele toe als predictor aan ons origineel model

# Het nieuwe model
neth_model2 <- lm(v2x_polyarchy ~ e_gdp + dem_lag, data = netherlands)

# Coëfficiënten opvragen
tidy(neth_model2, conf.int = TRUE)
# A tibble: 3 × 7
  term            estimate    std.error statistic   p.value   conf.low conf.high
  <chr>              <dbl>        <dbl>     <dbl>     <dbl>      <dbl>     <dbl>
1 (Intercept) 0.0121       0.00667          1.81  7.20e-  2   -1.09e-3   2.52e-2
2 e_gdp       0.0000000561 0.0000000838     0.669 5.04e-  1   -1.09e-7   2.21e-7
3 dem_lag     0.978        0.0155          63.3   2.53e-145    9.48e-1   1.01e+0
#Opvragen model fit statistieken via broom::glance()
glance(neth_model2)
# A tibble: 1 × 12
  r.squared adj.r.squared  sigma statistic   p.value    df logLik   AIC   BIC
      <dbl>         <dbl>  <dbl>     <dbl>     <dbl> <dbl>  <dbl> <dbl> <dbl>
1     0.973         0.973 0.0486     4110. 7.47e-178     2   367. -727. -713.
# ℹ 3 more variables: deviance <dbl>, df.residual <int>, nobs <int>

De coëfficiënt voor dem_lag is 0.978. Dit is hoog, onze afhankelijke variabele heeft een bereik van 0 tot 1. De coëfficiënt toont dus dat er sterke stabiliteit is tussen democratiescores in opeenvolgende jaren. 9 We vinden dan ook dat de R2 statistieken dichtbij hun maxima van 1 zitten: toevoeging van de lagged dependent variable verklaart bijna alle variatie in democratiescore.

De coëfficiënt voor e_gdp toont de associate tussen BBP en democratiescores gecontroleerd voor democratiescore in het voorgaande jaar. Dit vertelt ons eigenlijk of BBP gerelataard is aan verandering in democratiescore van jaar tot jaar. De coëfficiënt is positief, maar niet langer significant.

Hebben we daarmee het probleem van autocorrelatie opgelost? We gaan het na:

car::durbinWatsonTest(neth_model2) 
 lag Autocorrelation D-W Statistic p-value
   1       0.2865016      1.425975   0.004
 Alternative hypothesis: rho != 0

Autocorrelatie bedraagt nu slechts 0.29, wat veel lager is dan de 0.97 in het voorgaande model. De D-W statistiek bevindt zich nu tussen 1 en 3. Er is nog autocorrelatie (zie de ook de p-waarde), maar we hebben al veel in rekening gebracht.

Voorgaand voorbeeld was gericht op 1 land, namelijk Nederland. In het geval we meerdere landen hebben kunnen we ook de lag functie gebruiken, maar er zal wel een probleem optreden waar we ons van bewust moeten zijn. We tonen eerst wat het probleem is en bieden dan een oplossing:

serial_data |> 
  mutate(dem_lag = lag(v2x_polyarchy)) |> 
  slice(225:235)
1
Deze syntax vraagt R om de data te ‘snijden’ en data te tonen voor rij 225 tot en met 235.
   country_name year v2x_polyarchy      e_gdp dem_lag
1        Mexico 2013         0.623 423815.579   0.649
2        Mexico 2014         0.620 429987.309   0.623
3        Mexico 2015         0.639 432220.100   0.620
4        Mexico 2016         0.640 440041.959   0.639
5        Mexico 2017         0.635 449152.118   0.640
6        Mexico 2018         0.675 455569.106   0.635
7        Mexico 2019         0.685 456743.982   0.675
8      Suriname 1960         0.526    237.768   0.685
9      Suriname 1961         0.526    244.437   0.526
10     Suriname 1962         0.526    255.809   0.526
11     Suriname 1963         0.527    272.408   0.526

We tonen hier de transitie in de dataset van Mexico naar Suriname. In rij 7 bevind zich de data voor Mexico voor het laats beschikbare jaar 2019. Dan springt de dataset naar het eerste jaar voor Suriname, namelijk 1960. Als we kijken naar de dem_lag variabele zien we dat Suriname in 1960 de democratiescore van Mexico in 2019 heeft meegekregen. Dat mag natuurlijk niet. Zonder correctie doet R dit voor alle punten in de dataset waar we van 1 land naar een ander land gaan.

We vermijden dit door de group_by() functie te gebruiken:

serial_data <- serial_data |> 
  group_by(country_name) |> 
  mutate(dem_lag = lag(v2x_polyarchy)) |> 
  ungroup() 
group_by(country_name)

De group_by() functie vraagt R om de dataset eerst te groeperen volgens de variabele tussen haakjes (hier: country_name). Daarna pas wordt de lagfunctie, via mutate(), toegepast. Wat er precies gebeurt kun je zien met deze gif van Andrew Heiss (2024):

Heiss, Andrew. 2024. ‘Visualizing {Dplyr}’s Mutate(), Summarize(), Group_by(), and Ungroup() with Animations’. 4 april 2024. https://doi.org/10.59350/d2sz4-w4e25.
ungroup()

Deze functie is nodig om mutate te vertellen dat we niet langer willen groeperen voor toekomstige toepassingen van de functie. Anders blijft mutate verdere bewerkingen per groep uitvoeren.

Laten we bekijken wat er gebeurt is

serial_data |> 
  slice(225:235)
# A tibble: 11 × 5
   country_name  year v2x_polyarchy   e_gdp dem_lag
   <chr>        <dbl>         <dbl>   <dbl>   <dbl>
 1 Mexico        2013         0.623 423816.   0.649
 2 Mexico        2014         0.62  429987.   0.623
 3 Mexico        2015         0.639 432220.   0.62 
 4 Mexico        2016         0.64  440042.   0.639
 5 Mexico        2017         0.635 449152.   0.64 
 6 Mexico        2018         0.675 455569.   0.635
 7 Mexico        2019         0.685 456744.   0.675
 8 Suriname      1960         0.526    238.  NA    
 9 Suriname      1961         0.526    244.   0.526
10 Suriname      1962         0.526    256.   0.526
11 Suriname      1963         0.527    272.   0.526

We zien nu een NA waarde in rij 8, het eerste beschikbare jaar voor Suriname. Dit is wat we willen.

We zouden nu de regressie opnieuw kunnen uitvoeren. Onze dataset heeft nu echter niet alleen een time series element, maar bevat ook geclusterde data want meerdere landen zijn opgenomen. We zullen de lagged dependent variable-techniek dus moeten combineren met bovenstaande technieken voor clustering (fixed effects en geclusterde standaardfouten).

D.4 Omgaan met niet-normaal verdeelde residuals

D.4.1 Wat was het probleem ook al weer?

De laatste assumptie die we hier bekijken stelt dat de errors/residuals/fouten in een OLS model normaal verdeeld zijn. Of aan deze assumptie voldaan is kunnen we nagaan met plots gemaakt via resid_panel(): een histogram van de residuals en een Q-Q plot van de residuals.

# Model schatten
norm_model <- lm(v2x_polyarchy ~ gini_disp + pr_fct + region1, data = normal_residual_data)

# Coëfficiënten opvragen
tidy(norm_model, conf.int = TRUE)
# A tibble: 6 × 7
  term            estimate std.error statistic p.value conf.low conf.high
  <chr>              <dbl>     <dbl>     <dbl>   <dbl>    <dbl>     <dbl>
1 (Intercept)      0.641     0.195       3.29  0.00154   0.253    1.03   
2 gini_disp       -0.00492   0.00531    -0.928 0.357    -0.0155   0.00566
3 pr_fctPR System  0.0831    0.0619      1.34  0.184    -0.0404   0.207  
4 region1Africa   -0.0940    0.114      -0.824 0.413    -0.321    0.133  
5 region1Europe    0.176     0.0665      2.65  0.00997   0.0435   0.309  
6 region1Americas  0.168     0.0710      2.37  0.0207    0.0265   0.310  
# Assumptie checken
resid_panel(norm_model, plots = c("hist", "qq"))

Beide grafieken leiden tot dezelfde vaststelling: de assumptie is geschonden (zie Hoofdstuk 7). Met een relatief grote steekproef is een schending van deze assumptie doorgaans geen probleem. In kleine steekproeven kan dit wel problematisch zijn. Hier zien we met de glance() functie uit het broom package dat we met een kleine steekproef te maken hebben:

glance(norm_model)$nobs
[1] 77

We hebben 77 observaties in het model. Is dit klein of groot genoeg? Jammergenoeg bestaat hier geen eenduidig antwoord voor. Een vuistregel is dat we met een kleine steekproef te maken hebben bij minder dan 15 observaties per onafhankelijke variabele. Hier hebben we 5 predictors. We vinden dan dat we met 77 observaties net 2 meer hebben dan de vuistregel voorschrijft (5 * 15 = 75). Dit is echter dicht bij de grens, dus misschien is het toch veiliger om rekening te houden met een assumptieschending.

D.4.2 Mogelijke oplossing: bootstrapping

Ook voor een schending van deze assumptie (met een kleine steekproef) moeten we de standaardfouten herberekenen. We gebruiken in dit geval weer een andere herberekening dan hierboven, namelijk ‘gebootstrapte’ standaardfouten. Met bootstrapping worden standaardfouten herschat met de volgende procedure:

We nemen een steekproef van de oorspronkelijke steekproef met dezelfde grootte. Dit gebeurt met teruglegging van de observaties, dat wil zeggen 1 observatie kan meerdere malen opgenomen worden in de nieuwe steekproef. Dat moet natuurlijk, anders kom je gewoon telkens dezelfde steekproef uit. Op basis van de nieuwe steekproef schatten we coëfficiënt en standaardfout opnieuw en slagen de resultaten op. Dit doen we een aantal keren. Ten slotte kijken we naar alle verschillende coëfficiënten die we uitgekomen zijn en in het bijzonder naar de standaardafwijking van de verzameling coëfficiënten. Deze standaardafwijking wordt de ’gebootstrapte’standaardfout.

Ook deze standaardfout kunnen we bekomen met de vcov= optie in de modelsummary() syntax. Wat nieuw is hier is dat we een element van toeval toevoegen in de berekening: R gaat ‘random’ nieuwe steekproeven trekken. Op zich is dit goed -moeten wij het niet doen!- maar juist door dit toevalselement kan de uitkomst telkens licht anders zijn. Om resultaten reproduceerbaar te houden zetten we hier dan ook een ‘seed’: een seed is een zelfgekozen startgeval (je kan 1 gebruiken maar ook je verjaardag enz.). Met de seed houdt R bij welke steekproeven precies getrokken werden en worden telkens dezelfde resultaten bereikt. Voor je je seed vastlegt wil je wel eerst nagaan of je met hetzelfde aantal steekproeven sterk verschillende resultaten uitkomt, want in dat geval vraag je R best meer steekproeven te trekken voor betere schattingen, zie onder).

# seed vastleggen
set.seed(1)

# Model schatten 
modelsummary(norm_model, 
             stars = T, 
             vcov = "bootstrap", 
             gof_map = c("nobs", "r.squared", "adj.r.squared"))
(1)
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
(Intercept) 0.641**
(0.230)
gini_disp -0.005
(0.006)
pr_fctPR System 0.083
(0.082)
region1Africa -0.094
(0.100)
region1Europe 0.176*
(0.079)
region1Americas 0.168*
(0.080)
Num.Obs. 77
R2 0.305
R2 Adj. 0.256
set.seed(1)

We zetten het toevallig trekken van steekproeven hier vast met een zelfgekozen getal zodat resultaten dezelfde blijven.

vcov = "boostrap"

Deze optie vraagt om de ‘gebootstrapte’ standaarfouten.

We kunnen de standaardfouten vergelijken:

# Seed: zelfde als hierboven want dan krijgen we zelfde resultaten
set.seed(1)

# Modellen vergelijken
modelsummary(norm_model, 
             stars = T, 
             vcov = c("classical", "bootstrap"),
             gof_map = c("nobs", "r.squared", "adj.r.squared"))
(1) (2)
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
(Intercept) 0.641** 0.641**
(0.195) (0.230)
gini_disp -0.005 -0.005
(0.005) (0.006)
pr_fctPR System 0.083 0.083
(0.062) (0.082)
region1Africa -0.094 -0.094
(0.114) (0.100)
region1Europe 0.176** 0.176*
(0.067) (0.079)
region1Americas 0.168* 0.168*
(0.071) (0.080)
Num.Obs. 77 77
R2 0.305 0.305
R2 Adj. 0.256 0.256

Wat merken we op:

  • De coëfficiënten veranderen niet, enkel de standaardfouten. Dat zagen we ook bij heteroskedasticiteit-robuuste standaardfouten en geclusterde standaardfouten
  • De standaardfouten zijn verschillend. Doorgaans, maar niet altijd, zijn gebootstrapte standaardfouten groter.
  • Conclusies over de significantie van resultaten blijven hier dezelfde, hoewel we zien dat het significantieniveau voor de coëfficiënt van region1Europe veranderd is van p < 0.01 naar p < 0.05. De gevolgen kunnen veel groter zijn in andere analyses.

Zoals gezegd neemt R zelf nieuwe toevalssteekproeven. Standaard worden 250 steekproeven genomen. Zoals eerder gezegd zet je een ‘seed’ om resultaten gelijk te houden, maar natuurlijk is het niet echt betrouwbaar als resultaten bij elke trekking sterk veranderen. Vooraleer te beslissen om je seed vast te leggen, wil je nagaan of resultaten sterk verschillen zonder seed. Als dit het geval is, wil je R vragen meer steekproeven te trekken om betere schattingen te maken van de standaardfouten. Dit doen we in onderstaande syntax door R om 1000 steekproeven te vragen. We zetten een seed in de syntax, maar ga ervanuit dat we eerst goed gekeken hebben naar verschillende uitkomsten vooraleer een seed te zetten.

set.seed(1) 

modelsummary(norm_model, 
             stars = T, 
             vcov = "bootstrap", 
             R = 1000, 
             gof_map = c("nobs", "r.squared", "adj.r.squared"))
(1)
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
(Intercept) 0.641**
(0.216)
gini_disp -0.005
(0.006)
pr_fctPR System 0.083
(0.079)
region1Africa -0.094
(0.101)
region1Europe 0.176*
(0.076)
region1Americas 0.168*
(0.081)
Num.Obs. 77
R2 0.305
R2 Adj. 0.256
R = 1000

Deze optie laat toe het aantal steekproeven dat R trekt aan te passen. Hier vragen we er 1000.

Onze resultaten hier zijn gelijkaardig, maar niet identiek aan deze gevonden bij 250 steekproeven. Het heeft echter geen gevolgen voor de algemene conclusies over significantie.

Bootstrapping kan ook gebruikt worden binnen het marginaleffects package, bv. wanneer we voorspelde waarden en betrouwbaarheidsintervallen nodig hebben. De functie om bootstrapping te gebruiken (inferences()) is echter nog steeds in ontwikkeling en je moet er ook bijkomende packages voor installeren. Indien je dit nodig zou hebben, bekijk dan het “Bootstrap” hoofdstuk op de marginaleffects website.


  1. Slechts een van deze assumpties is ook van toepassing op logistische modellen: de assumptie van onafhankelijke fouten. De oplossingen hiervoor zijn dezelfde bij logistische modellen, vandaar dat we ons in deze voorbeelden richten op OLS modellen.↩︎

  2. Wel zien we dat er meer variatie is tussen individuen dan tussen landen. dat is wel vaker zo in dergelijke cross-nationale surveydatasets.↩︎

  3. We moeten er ook op letten dat er geen ‘ommitted variable bias’ optreedt. Hier gebruiken we bijvoorbeeld slechts 1 predictor en belangrijke controlevariabelen worden wellicht over het hoofd gezien.↩︎

  4. Er bestaan eigenlijk ook twee andere strategieën. Het probleem hier wordt veroorzaakt door het samenbrengen (“poolen”) van gegevens uit verschillende landen (“clusters”). Het kan echter zijn dat we ons zorgen maken over één cluster in het bijzonder, bijvoorbeeld het doel van ons artikel kan zijn om de relatie tussen ideologie en democratische tevredenheid specifiek in Duitsland of in Nederland te onderzoeken, enz. Een “oplossing” is dan om waarnemingen uit de andere landen weg te filteren en enkel een model voor 1 land te schatten. Het kan ook zijn dat we niet echt geïnteresseerd zijn in het lagere niveau van de dataset (bijv. de individuen in dit voorbeeld) en eigenlijk meer geven om het verklaren van de variatie tussen clusters (bijv. waarom landen als Zwitserland een hogere democratische tevredenheid hebben dan landen als Bulgarije). Een optie hier is om het clustergemiddelde te gebruiken als onze afhankelijke variabele in een regressiemodel en dit te voorspellen met predictoren op landniveau (bv. BBP). Zoals altijd is de eerste stap in elke data-analyse uitzoeken wat onze vraag is, omdat dit een grote invloed heeft op het type analyse dat geschikt is om te leren wat we willen leren.↩︎

  5. Deze strategie werkt ongeacht de hoeveelheid clusters die je hebt. De berekening kan wel langer duren bij een groter aantal clsuters. Stel dat we met longitudinale, panel-data werken van 500 respondenten die elke maand van het jaar bevraagd worden. Voor een ‘fixed effects’analyse betekent dit dat je 499 dummies toevoegd. Dit is best veel. In dit geval kun je ook specifieke packages gebruiken die speciaal ontworpen ziin voor ’fixed effects’ en vlugger werken, bijvoorbeeld het fixest package met bijhorende feols functie. Deze functie gebruikt ook automatisch geclusterde standaardfouten.↩︎

  6. Als we ons richten op controlevariabelen in het geval van 1 land dan moeten deze variëren over de tijd in Nederland (time-variant) en niet vaststaan (time-invariant).↩︎

  7. Er zijn nog andere manieren om dergelijke time series te modelleren, maar deze zijn te gevorderd voor dit handboek.↩︎

  8. Hier maken we een vertraagde afhankelijke variable maar hetzelfde proces kan gebruikt worden voor onafhankelijke variabelen, bijvoorbeeld als er zorgen zijn over omgekeerde causale verbanden (‘reverse causality’).↩︎

  9. De bivariate pearson correlatie tussen de twee variabelen is 0.99. Democratiescores in Nederland blijven erg stabiel van jaar tot jaar.↩︎