The cross-lagged panel model (CLPM) is a powerful tool for studying reciprocal relationships over time, helping researchers disentangle causality in longitudinal data. If you’re new to CLPM, check out this post, where we introduce the model and explain its core assumptions.
While CLPM accounts for temporal dependencies through autoregressive and cross-lagged paths, it does not inherently control for potential confounders that might bias these effects. To address this, we can include control variables—either time-constant (e.g., demographic factors) or time-varying (e.g., changing life circumstances)—to improve the model’s validity. This post explores ways to include control variables in a CLPM using lavaan
in R.
Preparing the data
We will use the wide version of the Understanding Society data (“usw”) to explore the different types of trajectories in satisfaction (more on how to prepare the data in this previous post). Here, mental and physical health are measured using the SF12 scale and calculated to take values between 0 and 100, with higher values indicating higher health levels. We will use the lavaan
package to run the models and tidyverse
to clean up some data.
Access the code used here.
Access the data here.
We start by loading the package (we assume they have already been installed).
library(lavaan) library(tidyverse)
We then load the cleaned data from the “data” subfolder in our working directory:
load("./data/us_clean_syn.RData")
Sometimes, centering variables (subtracting the mean from the variable) helps with interpretation and estimation. We can do this for the mental and physical health variables using the mutate_at()
function from the dplyr
package. First, we select just the variables of interest, and then we subtract the mean from each variable.
usw <- usw %>% mutate_at(vars(starts_with("sf12"), -matches("lag|dev|ind")), list("test" = ~ . - mean(., na.rm = T))) glimpse(usw)
## Rows: 51,007 ## Columns: 105 ## $ pidp <dbl> 5, 6, 7, 10, 11, 13, 14, 16, 18, 19, 21, 22, 23, 24, 25… ## $ age <hvn_lbll> 28, 80, 60, 42, 53, 51, 38, 27, 56, 31, 46, 30, 47… ## $ hiqual <hvn_lbll> 1, 9, 3, 1, 1, 9, 4, 5, 2, 9, 1, 1, 9, 4, 1, 3, 3,… ## $ gndr.fct <fct> Male, Male, Male, Male, Male, Female, Female, Male, Mal… ## $ degree.fct <fct> Degree, No degree, No degree, Degree, Degree, No degree… ## $ sati.ind <dbl> 3.000000, 5.000000, 2.000000, 5.000000, 4.666667, 4.000… ## $ sf12pcs.ind <dbl> 57.65250, 48.82667, 33.18000, 57.22500, 56.16667, 50.69… ## $ sf12mcs.ind <dbl> 52.75250, 42.96333, 46.80000, 49.26500, 52.78000, 38.30… ## $ waves <int> 4, 4, 1, 2, 4, 4, 4, 4, 4, 3, 4, 4, 4, 3, 4, 4, 3, 4, 1… ## $ single_1 <hvn_lbll> 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1,… ## $ single_2 <hvn_lbll> 1, 0, NA, NA, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, … ## $ single_3 <hvn_lbll> 1, 0, NA, 0, 0, 0, 0, 1, 1, NA, 0, 0, 1, NA, 1, 1,… ## $ single_4 <hvn_lbll> 0, 0, NA, NA, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, … ## $ fimngrs_1 <dbl> 3283.87, 896.00, 1458.33, 2916.67, 4306.27, 1121.13, 68… ## $ fimngrs_2 <dbl> 4002.50, 709.00, NA, NA, 4010.01, 280.17, 1308.33, 1549… ## $ fimngrs_3 <dbl> 3616.67, 702.00, NA, 336.58, 2751.66, 666.67, 1000.00, … ## $ fimngrs_4 <dbl> 850.50, 829.15, NA, NA, 4322.68, 423.58, 988.01, 777.28… ## $ sclfsato_1 <hvn_lbll> -9, 6, 2, -9, -7, 3, 5, 6, -9, -9, 6, 3, 5, 3, 7, … ## $ sclfsato_2 <hvn_lbll> 6, 6, NA, NA, 6, 3, 5, 6, 5, 5, 4, 6, 6, 1, 7, 6, … ## $ sclfsato_3 <hvn_lbll> 2, -8, NA, 5, 6, -8, 3, 6, 3, NA, 6, 6, 3, NA, 6, … ## $ sclfsato_4 <hvn_lbll> 1, 3, NA, NA, 2, 6, 5, 5, 5, -7, 6, 4, 6, 4, 6, 7,… ## $ sf12pcs_1 <dbl> 54.51, 53.93, 33.18, 58.30, NA, 46.66, 49.93, 30.75, 31… ## $ sf12pcs_2 <dbl> 62.98, 46.39, NA, NA, 54.80, 62.65, 56.10, 50.45, 12.41… ## $ sf12pcs_3 <dbl> 56.97, NA, NA, 56.15, 57.28, NA, 58.05, 58.57, 43.98, N… ## $ sf12pcs_4 <dbl> 56.15, 46.16, NA, NA, 56.42, 42.78, 56.40, 52.24, 42.52… ## $ sf12mcs_1 <dbl> 55.73, 46.48, 46.80, 49.41, NA, 37.17, 56.10, 45.20, 49… ## $ sf12mcs_2 <dbl> 36.22, 45.39, NA, NA, 54.37, 31.34, 57.16, 58.47, 52.93… ## $ sf12mcs_3 <dbl> 60.02, NA, NA, 49.12, 49.52, NA, 57.16, 55.17, 54.20, N… ## $ sf12mcs_4 <dbl> 59.04, 37.02, NA, NA, 54.45, 46.40, 60.02, 52.40, 36.56… ## $ istrtdaty_1 <hvn_lbll> 2009, 2010, 2009, 2009, 2010, 2009, 2010, 2010, 20… ## $ istrtdaty_2 <hvn_lbll> 2010, 2011, NA, NA, 2011, 2010, 2011, 2011, 2011, … ## $ istrtdaty_3 <hvn_lbll> 2011, 2012, NA, 2012, 2012, 2011, 2012, 2012, 2012… ## $ istrtdaty_4 <hvn_lbll> 2012, 2013, NA, NA, 2013, 2012, 2013, 2013, 2014, … ## $ sf1_1 <hvn_lbll> 2, 4, 5, 2, 3, 5, 3, 2, 3, 2, 2, 2, 4, 3, 2, 4, 2,… ## $ sf1_2 <hvn_lbll> 2, 3, NA, NA, 2, 3, 2, 3, 5, 3, 3, 2, 5, 2, 4, 3, … ## $ sf1_3 <hvn_lbll> 1, 3, NA, 2, 3, 3, 2, 3, 3, NA, 2, 2, 4, NA, 3, 3,… ## $ sf1_4 <hvn_lbll> 3, 3, NA, NA, 2, 4, 2, 3, 4, 3, 2, 2, 4, 2, 4, 3, … ## $ present_1 <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, T… ## $ present_2 <lgl> TRUE, TRUE, NA, NA, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE,… ## $ present_3 <lgl> TRUE, TRUE, NA, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, NA,… ## $ present_4 <lgl> TRUE, TRUE, NA, NA, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE,… ## $ single.fct_1 <fct> Single, In relationship, Single, In relationship, In re… ## $ single.fct_2 <fct> Single, In relationship, NA, NA, In relationship, In re… ## $ single.fct_3 <fct> Single, In relationship, NA, In relationship, In relati… ## $ single.fct_4 <fct> In relationship, In relationship, NA, NA, In relationsh… ## $ urban.fct_1 <fct> Urban, Urban, Urban, Urban, Urban, Urban, Urban, Urban,… ## $ urban.fct_2 <fct> Urban, Urban, NA, NA, Urban, Urban, Urban, Urban, Urban… ## $ urban.fct_3 <fct> Urban, Urban, NA, Urban, Urban, Urban, Urban, Urban, Ur… ## $ urban.fct_4 <fct> Urban, Urban, NA, NA, Urban, Urban, Urban, Urban, Urban… ## $ vote6.fct_1 <fct> Not very, Not at all, Fairly, Very, NA, Not very, Not v… ## $ vote6.fct_2 <fct> Not at all, Not at all, NA, NA, Fairly, Not at all, Not… ## $ vote6.fct_3 <fct> Not at all, Not at all, NA, Not very, Not very, Not at … ## $ vote6.fct_4 <fct> Not at all, Not very, NA, NA, Very, Not at all, Not at … ## $ sati_1 <dbl> NA, 6, 2, NA, NA, 3, 5, 6, NA, NA, 6, 3, 5, 3, 7, 6, NA… ## $ sati_2 <dbl> 6, 6, NA, NA, 6, 3, 5, 6, 5, 5, 4, 6, 6, 1, 7, 6, NA, 7… ## $ sati_3 <dbl> 2, NA, NA, 5, 6, NA, 3, 6, 3, NA, 6, 6, 3, NA, 6, 6, NA… ## $ sati_4 <dbl> 1, 3, NA, NA, 2, 6, 5, 5, 5, NA, 6, 4, 6, 4, 6, 7, 5, 7… ## $ sati.fct_1 <fct> NA, Mostly satisfied, Mostly dissatisfied, NA, NA, Some… ## $ sati.fct_2 <fct> Mostly satisfied, Mostly satisfied, NA, NA, Mostly sati… ## $ sati.fct_3 <fct> Mostly dissatisfied, NA, NA, Somewhat satisfied, Mostly… ## $ sati.fct_4 <fct> Completely dissatisfied, Somewhat dissatisfied, NA, NA,… ## $ fimngrs.cap_1 <dbl> 3283.87, 896.00, 1458.33, 2916.67, 4306.27, 1121.13, 68… ## $ fimngrs.cap_2 <dbl> 4002.50, 709.00, NA, NA, 4010.01, 280.17, 1308.33, 1549… ## $ fimngrs.cap_3 <dbl> 3616.67, 702.00, NA, 336.58, 2751.66, 666.67, 1000.00, … ## $ fimngrs.cap_4 <dbl> 850.50, 829.15, NA, NA, 4322.68, 423.58, 988.01, 777.28… ## $ logincome_1 <dbl> 8.099818, 6.809039, 7.291881, 7.981621, 8.370147, 7.030… ## $ logincome_2 <dbl> 8.297170, 6.577861, NA, NA, 8.299040, 5.670467, 7.18412… ## $ logincome_3 <dbl> 8.196070, 6.568078, NA, 5.848114, 7.923587, 6.517184, 6… ## $ logincome_4 <dbl> 6.757514, 6.732390, NA, NA, 8.373942, 6.072076, 6.90576… ## $ year_1 <dbl> 2009, 2010, 2009, 2009, 2010, 2009, 2010, 2010, 2010, 2… ## $ year_2 <dbl> 2010, 2011, NA, NA, 2011, 2010, 2011, 2011, 2011, 2011,… ## $ year_3 <dbl> 2011, 2012, NA, 2012, 2012, 2011, 2012, 2012, 2012, NA,… ## $ year_4 <dbl> 2012, 2013, NA, NA, 2013, 2012, 2013, 2013, 2014, 2013,… ## $ sati.dev_1 <dbl> NA, 1.0000000, 0.0000000, NA, NA, -1.0000000, 0.5000000… ## $ sati.dev_2 <dbl> 3.0000000, 1.0000000, NA, NA, 1.3333333, -1.0000000, 0.… ## $ sati.dev_3 <dbl> -1.000000, NA, NA, 0.000000, 1.333333, NA, -1.500000, 0… ## $ sati.dev_4 <dbl> -2.0000000, -2.0000000, NA, NA, -2.6666667, 2.0000000, … ## $ sf12pcs.dev_1 <dbl> -3.1425000, 5.1033333, 0.0000000, 1.0750000, NA, -4.036… ## $ sf12pcs.dev_2 <dbl> 5.327500, -2.436667, NA, NA, -1.366667, 11.953333, 0.98… ## $ sf12pcs.dev_3 <dbl> -0.682500, NA, NA, -1.075000, 1.113333, NA, 2.930000, 1… ## $ sf12pcs.dev_4 <dbl> -1.5025000, -2.6666667, NA, NA, 0.2533333, -7.9166667, … ## $ sf12mcs.dev_1 <dbl> 2.977500, 3.516667, 0.000000, 0.145000, NA, -1.133333, … ## $ sf12mcs.dev_2 <dbl> -16.532500, 2.426667, NA, NA, 1.590000, -6.963333, -0.4… ## $ sf12mcs.dev_3 <dbl> 7.2675, NA, NA, -0.1450, -3.2600, NA, -0.4500, 2.3600, … ## $ sf12mcs.dev_4 <dbl> 6.2875000, -5.9433333, NA, NA, 1.6700000, 8.0966667, 2.… ## $ sati.lag_1 <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,… ## $ sati.lag_2 <dbl> NA, 6, 2, NA, NA, 3, 5, 6, NA, NA, 6, 3, 5, 3, 7, 6, NA… ## $ sati.lag_3 <dbl> 6, 6, NA, NA, 6, 3, 5, 6, 5, 5, 4, 6, 6, 1, 7, 6, NA, 7… ## $ sati.lag_4 <dbl> 2, NA, NA, 5, 6, NA, 3, 6, 3, NA, 6, 6, 3, NA, 6, 6, NA… ## $ sf12pcs.lag_1 <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,… ## $ sf12pcs.lag_2 <dbl> 54.51, 53.93, 33.18, 58.30, NA, 46.66, 49.93, 30.75, 31… ## $ sf12pcs.lag_3 <dbl> 62.98, 46.39, NA, NA, 54.80, 62.65, 56.10, 50.45, 12.41… ## $ sf12pcs.lag_4 <dbl> 56.97, NA, NA, 56.15, 57.28, NA, 58.05, 58.57, 43.98, N… ## $ sf12mcs.lag_1 <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,… ## $ sf12mcs.lag_2 <dbl> 55.73, 46.48, 46.80, 49.41, NA, 37.17, 56.10, 45.20, 49… ## $ sf12mcs.lag_3 <dbl> 36.22, 45.39, NA, NA, 54.37, 31.34, 57.16, 58.47, 52.93… ## $ sf12mcs.lag_4 <dbl> 60.02, NA, NA, 49.12, 49.52, NA, 57.16, 55.17, 54.20, N… ## $ sf12pcs_1_test <dbl> 5.0158871, 4.4358871, -16.3141129, 8.8058871, NA, -2.83… ## $ sf12pcs_2_test <dbl> 13.2249899, -3.3650101, NA, NA, 5.0449899, 12.8949899, … ## $ sf12pcs_3_test <dbl> 7.226404, NA, NA, 6.406404, 7.536404, NA, 8.306404, 8.8… ## $ sf12pcs_4_test <dbl> 6.5042404, -3.4857596, NA, NA, 6.7742404, -6.8657596, 6… ## $ sf12mcs_1_test <dbl> 5.29055, -3.95945, -3.63945, -1.02945, NA, -13.26945, 5… ## $ sf12mcs_2_test <dbl> -13.615662, -4.445662, NA, NA, 4.534338, -18.495662, 7.… ## $ sf12mcs_3_test <dbl> 10.62854251, NA, NA, -0.27145749, 0.12854251, NA, 7.768… ## $ sf12mcs_4_test <dbl> 9.550426, -12.469574, NA, NA, 4.960426, -3.089574, 10.5…
This will create new variables with the suffix “_test” centered around the mean. We can rename the variables to make them easier to work with. This can be quickly done using the rename_at()
function from the dplyr
package. We select the variables with the suffix “_test” and remove the suffix using the str_remove_all()
function from the stringr
package.
usw <- usw %>% rename_at(vars(ends_with("test")), ~str_remove_all(., "sf12|_test")) glimpse(usw)
## Rows: 51,007 ## Columns: 105 ## $ pidp <dbl> 5, 6, 7, 10, 11, 13, 14, 16, 18, 19, 21, 22, 23, 24, 25,… ## $ age <hvn_lbll> 28, 80, 60, 42, 53, 51, 38, 27, 56, 31, 46, 30, 47,… ## $ hiqual <hvn_lbll> 1, 9, 3, 1, 1, 9, 4, 5, 2, 9, 1, 1, 9, 4, 1, 3, 3, … ## $ gndr.fct <fct> Male, Male, Male, Male, Male, Female, Female, Male, Male… ## $ degree.fct <fct> Degree, No degree, No degree, Degree, Degree, No degree,… ## $ sati.ind <dbl> 3.000000, 5.000000, 2.000000, 5.000000, 4.666667, 4.0000… ## $ sf12pcs.ind <dbl> 57.65250, 48.82667, 33.18000, 57.22500, 56.16667, 50.696… ## $ sf12mcs.ind <dbl> 52.75250, 42.96333, 46.80000, 49.26500, 52.78000, 38.303… ## $ waves <int> 4, 4, 1, 2, 4, 4, 4, 4, 4, 3, 4, 4, 4, 3, 4, 4, 3, 4, 1,… ## $ single_1 <hvn_lbll> 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, … ## $ single_2 <hvn_lbll> 1, 0, NA, NA, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, N… ## $ single_3 <hvn_lbll> 1, 0, NA, 0, 0, 0, 0, 1, 1, NA, 0, 0, 1, NA, 1, 1, … ## $ single_4 <hvn_lbll> 0, 0, NA, NA, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1… ## $ fimngrs_1 <dbl> 3283.87, 896.00, 1458.33, 2916.67, 4306.27, 1121.13, 682… ## $ fimngrs_2 <dbl> 4002.50, 709.00, NA, NA, 4010.01, 280.17, 1308.33, 1549.… ## $ fimngrs_3 <dbl> 3616.67, 702.00, NA, 336.58, 2751.66, 666.67, 1000.00, 5… ## $ fimngrs_4 <dbl> 850.50, 829.15, NA, NA, 4322.68, 423.58, 988.01, 777.28,… ## $ sclfsato_1 <hvn_lbll> -9, 6, 2, -9, -7, 3, 5, 6, -9, -9, 6, 3, 5, 3, 7, 6… ## $ sclfsato_2 <hvn_lbll> 6, 6, NA, NA, 6, 3, 5, 6, 5, 5, 4, 6, 6, 1, 7, 6, N… ## $ sclfsato_3 <hvn_lbll> 2, -8, NA, 5, 6, -8, 3, 6, 3, NA, 6, 6, 3, NA, 6, 6… ## $ sclfsato_4 <hvn_lbll> 1, 3, NA, NA, 2, 6, 5, 5, 5, -7, 6, 4, 6, 4, 6, 7, … ## $ sf12pcs_1 <dbl> 54.51, 53.93, 33.18, 58.30, NA, 46.66, 49.93, 30.75, 31.… ## $ sf12pcs_2 <dbl> 62.98, 46.39, NA, NA, 54.80, 62.65, 56.10, 50.45, 12.41,… ## $ sf12pcs_3 <dbl> 56.97, NA, NA, 56.15, 57.28, NA, 58.05, 58.57, 43.98, NA… ## $ sf12pcs_4 <dbl> 56.15, 46.16, NA, NA, 56.42, 42.78, 56.40, 52.24, 42.52,… ## $ sf12mcs_1 <dbl> 55.73, 46.48, 46.80, 49.41, NA, 37.17, 56.10, 45.20, 49.… ## $ sf12mcs_2 <dbl> 36.22, 45.39, NA, NA, 54.37, 31.34, 57.16, 58.47, 52.93,… ## $ sf12mcs_3 <dbl> 60.02, NA, NA, 49.12, 49.52, NA, 57.16, 55.17, 54.20, NA… ## $ sf12mcs_4 <dbl> 59.04, 37.02, NA, NA, 54.45, 46.40, 60.02, 52.40, 36.56,… ## $ istrtdaty_1 <hvn_lbll> 2009, 2010, 2009, 2009, 2010, 2009, 2010, 2010, 201… ## $ istrtdaty_2 <hvn_lbll> 2010, 2011, NA, NA, 2011, 2010, 2011, 2011, 2011, 2… ## $ istrtdaty_3 <hvn_lbll> 2011, 2012, NA, 2012, 2012, 2011, 2012, 2012, 2012,… ## $ istrtdaty_4 <hvn_lbll> 2012, 2013, NA, NA, 2013, 2012, 2013, 2013, 2014, 2… ## $ sf1_1 <hvn_lbll> 2, 4, 5, 2, 3, 5, 3, 2, 3, 2, 2, 2, 4, 3, 2, 4, 2, … ## $ sf1_2 <hvn_lbll> 2, 3, NA, NA, 2, 3, 2, 3, 5, 3, 3, 2, 5, 2, 4, 3, N… ## $ sf1_3 <hvn_lbll> 1, 3, NA, 2, 3, 3, 2, 3, 3, NA, 2, 2, 4, NA, 3, 3, … ## $ sf1_4 <hvn_lbll> 3, 3, NA, NA, 2, 4, 2, 3, 4, 3, 2, 2, 4, 2, 4, 3, 3… ## $ present_1 <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TR… ## $ present_2 <lgl> TRUE, TRUE, NA, NA, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, … ## $ present_3 <lgl> TRUE, TRUE, NA, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, NA, … ## $ present_4 <lgl> TRUE, TRUE, NA, NA, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, … ## $ single.fct_1 <fct> Single, In relationship, Single, In relationship, In rel… ## $ single.fct_2 <fct> Single, In relationship, NA, NA, In relationship, In rel… ## $ single.fct_3 <fct> Single, In relationship, NA, In relationship, In relatio… ## $ single.fct_4 <fct> In relationship, In relationship, NA, NA, In relationshi… ## $ urban.fct_1 <fct> Urban, Urban, Urban, Urban, Urban, Urban, Urban, Urban, … ## $ urban.fct_2 <fct> Urban, Urban, NA, NA, Urban, Urban, Urban, Urban, Urban,… ## $ urban.fct_3 <fct> Urban, Urban, NA, Urban, Urban, Urban, Urban, Urban, Urb… ## $ urban.fct_4 <fct> Urban, Urban, NA, NA, Urban, Urban, Urban, Urban, Urban,… ## $ vote6.fct_1 <fct> Not very, Not at all, Fairly, Very, NA, Not very, Not ve… ## $ vote6.fct_2 <fct> Not at all, Not at all, NA, NA, Fairly, Not at all, Not … ## $ vote6.fct_3 <fct> Not at all, Not at all, NA, Not very, Not very, Not at a… ## $ vote6.fct_4 <fct> Not at all, Not very, NA, NA, Very, Not at all, Not at a… ## $ sati_1 <dbl> NA, 6, 2, NA, NA, 3, 5, 6, NA, NA, 6, 3, 5, 3, 7, 6, NA,… ## $ sati_2 <dbl> 6, 6, NA, NA, 6, 3, 5, 6, 5, 5, 4, 6, 6, 1, 7, 6, NA, 7,… ## $ sati_3 <dbl> 2, NA, NA, 5, 6, NA, 3, 6, 3, NA, 6, 6, 3, NA, 6, 6, NA,… ## $ sati_4 <dbl> 1, 3, NA, NA, 2, 6, 5, 5, 5, NA, 6, 4, 6, 4, 6, 7, 5, 7,… ## $ sati.fct_1 <fct> NA, Mostly satisfied, Mostly dissatisfied, NA, NA, Somew… ## $ sati.fct_2 <fct> Mostly satisfied, Mostly satisfied, NA, NA, Mostly satis… ## $ sati.fct_3 <fct> Mostly dissatisfied, NA, NA, Somewhat satisfied, Mostly … ## $ sati.fct_4 <fct> Completely dissatisfied, Somewhat dissatisfied, NA, NA, … ## $ fimngrs.cap_1 <dbl> 3283.87, 896.00, 1458.33, 2916.67, 4306.27, 1121.13, 682… ## $ fimngrs.cap_2 <dbl> 4002.50, 709.00, NA, NA, 4010.01, 280.17, 1308.33, 1549.… ## $ fimngrs.cap_3 <dbl> 3616.67, 702.00, NA, 336.58, 2751.66, 666.67, 1000.00, 5… ## $ fimngrs.cap_4 <dbl> 850.50, 829.15, NA, NA, 4322.68, 423.58, 988.01, 777.28,… ## $ logincome_1 <dbl> 8.099818, 6.809039, 7.291881, 7.981621, 8.370147, 7.0309… ## $ logincome_2 <dbl> 8.297170, 6.577861, NA, NA, 8.299040, 5.670467, 7.184121… ## $ logincome_3 <dbl> 8.196070, 6.568078, NA, 5.848114, 7.923587, 6.517184, 6.… ## $ logincome_4 <dbl> 6.757514, 6.732390, NA, NA, 8.373942, 6.072076, 6.905763… ## $ year_1 <dbl> 2009, 2010, 2009, 2009, 2010, 2009, 2010, 2010, 2010, 20… ## $ year_2 <dbl> 2010, 2011, NA, NA, 2011, 2010, 2011, 2011, 2011, 2011, … ## $ year_3 <dbl> 2011, 2012, NA, 2012, 2012, 2011, 2012, 2012, 2012, NA, … ## $ year_4 <dbl> 2012, 2013, NA, NA, 2013, 2012, 2013, 2013, 2014, 2013, … ## $ sati.dev_1 <dbl> NA, 1.0000000, 0.0000000, NA, NA, -1.0000000, 0.5000000,… ## $ sati.dev_2 <dbl> 3.0000000, 1.0000000, NA, NA, 1.3333333, -1.0000000, 0.5… ## $ sati.dev_3 <dbl> -1.000000, NA, NA, 0.000000, 1.333333, NA, -1.500000, 0.… ## $ sati.dev_4 <dbl> -2.0000000, -2.0000000, NA, NA, -2.6666667, 2.0000000, 0… ## $ sf12pcs.dev_1 <dbl> -3.1425000, 5.1033333, 0.0000000, 1.0750000, NA, -4.0366… ## $ sf12pcs.dev_2 <dbl> 5.327500, -2.436667, NA, NA, -1.366667, 11.953333, 0.980… ## $ sf12pcs.dev_3 <dbl> -0.682500, NA, NA, -1.075000, 1.113333, NA, 2.930000, 10… ## $ sf12pcs.dev_4 <dbl> -1.5025000, -2.6666667, NA, NA, 0.2533333, -7.9166667, 1… ## $ sf12mcs.dev_1 <dbl> 2.977500, 3.516667, 0.000000, 0.145000, NA, -1.133333, -… ## $ sf12mcs.dev_2 <dbl> -16.532500, 2.426667, NA, NA, 1.590000, -6.963333, -0.45… ## $ sf12mcs.dev_3 <dbl> 7.2675, NA, NA, -0.1450, -3.2600, NA, -0.4500, 2.3600, 5… ## $ sf12mcs.dev_4 <dbl> 6.2875000, -5.9433333, NA, NA, 1.6700000, 8.0966667, 2.4… ## $ sati.lag_1 <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, … ## $ sati.lag_2 <dbl> NA, 6, 2, NA, NA, 3, 5, 6, NA, NA, 6, 3, 5, 3, 7, 6, NA,… ## $ sati.lag_3 <dbl> 6, 6, NA, NA, 6, 3, 5, 6, 5, 5, 4, 6, 6, 1, 7, 6, NA, 7,… ## $ sati.lag_4 <dbl> 2, NA, NA, 5, 6, NA, 3, 6, 3, NA, 6, 6, 3, NA, 6, 6, NA,… ## $ sf12pcs.lag_1 <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, … ## $ sf12pcs.lag_2 <dbl> 54.51, 53.93, 33.18, 58.30, NA, 46.66, 49.93, 30.75, 31.… ## $ sf12pcs.lag_3 <dbl> 62.98, 46.39, NA, NA, 54.80, 62.65, 56.10, 50.45, 12.41,… ## $ sf12pcs.lag_4 <dbl> 56.97, NA, NA, 56.15, 57.28, NA, 58.05, 58.57, 43.98, NA… ## $ sf12mcs.lag_1 <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, … ## $ sf12mcs.lag_2 <dbl> 55.73, 46.48, 46.80, 49.41, NA, 37.17, 56.10, 45.20, 49.… ## $ sf12mcs.lag_3 <dbl> 36.22, 45.39, NA, NA, 54.37, 31.34, 57.16, 58.47, 52.93,… ## $ sf12mcs.lag_4 <dbl> 60.02, NA, NA, 49.12, 49.52, NA, 57.16, 55.17, 54.20, NA… ## $ pcs_1 <dbl> 5.0158871, 4.4358871, -16.3141129, 8.8058871, NA, -2.834… ## $ pcs_2 <dbl> 13.2249899, -3.3650101, NA, NA, 5.0449899, 12.8949899, 6… ## $ pcs_3 <dbl> 7.226404, NA, NA, 6.406404, 7.536404, NA, 8.306404, 8.82… ## $ pcs_4 <dbl> 6.5042404, -3.4857596, NA, NA, 6.7742404, -6.8657596, 6.… ## $ mcs_1 <dbl> 5.29055, -3.95945, -3.63945, -1.02945, NA, -13.26945, 5.… ## $ mcs_2 <dbl> -13.615662, -4.445662, NA, NA, 4.534338, -18.495662, 7.3… ## $ mcs_3 <dbl> 10.62854251, NA, NA, -0.27145749, 0.12854251, NA, 7.7685… ## $ mcs_4 <dbl> 9.550426, -12.469574, NA, NA, 4.960426, -3.089574, 10.53…
Including time-constant controls in the cross-lagged panel model
Although the model partially controls for possible confounders by including the auto-regressive estimates and the time-specific correlations, it is still possible that some confounders might explain or bias the cross-lagged effects. If we believe that to be the case and have measurements of the confounders, we can include them in the model. If the confounders are time constant, they can be included in the model by explaining one or all of the variables present. Such a model, with one time constant confounder, could be presented in this way:

Note that we excluded the correlations to simplify the graph (they should still be estimated). Also, because we are now explaining the variables of interest at wave 1, they also have residuals there.
We can see how we would do this in practice by including some time constant predictors in the model discussed in our previous post that investigated the causal direction of physical and mental health. Here, we include gender and age at the start of the study as predictors.
model <- 'pcs_1 ~ 1 + gndr.fct + age pcs_2 ~ 1 + pcs_1 + mcs_1 + gndr.fct + age pcs_3 ~ 1 + pcs_2 + mcs_2 + gndr.fct + age mcs_1 ~ 1 + gndr.fct + age mcs_2 ~ 1 + mcs_1 + pcs_1 + gndr.fct + age mcs_3 ~ 1 + mcs_2 + pcs_2 + gndr.fct + age pcs_1 ~~ mcs_1 pcs_2 ~~ mcs_2 pcs_3 ~~ mcs_3' m2 <- sem(model, data = usw, missing = "ML") summary(m2)
## lavaan 0.6-19 ended normally after 149 iterations ## ## Estimator ML ## Optimization method NLMINB ## Number of model parameters 35 ## ## Number of observations 51007 ## Number of missing patterns 8 ## ## Model Test User Model: ## ## Test statistic 4554.969 ## Degrees of freedom 4 ## P-value (Chi-square) 0.000 ## ## Parameter Estimates: ## ## Standard errors Standard ## Information Observed ## Observed information based on Hessian ## ## Regressions: ## Estimate Std.Err z-value P(>|z|) ## pcs_1 ~ ## gndr.fct -0.920 0.097 -9.450 0.000 ## age -0.253 0.003 -94.255 0.000 ## pcs_2 ~ ## pcs_1 0.527 0.005 106.287 0.000 ## mcs_1 0.124 0.005 23.432 0.000 ## gndr.fct 0.120 0.103 1.173 0.241 ## age -0.131 0.003 -41.535 0.000 ## pcs_3 ~ ## pcs_2 0.496 0.006 87.409 0.000 ## mcs_2 0.095 0.006 15.181 0.000 ## gndr.fct -0.363 0.110 -3.287 0.001 ## age -0.121 0.003 -34.764 0.000 ## mcs_1 ~ ## gndr.fct -1.701 0.093 -18.248 0.000 ## age 0.030 0.003 11.775 0.000 ## mcs_2 ~ ## mcs_1 0.278 0.006 49.037 0.000 ## pcs_1 0.067 0.005 12.404 0.000 ## gndr.fct -0.598 0.111 -5.401 0.000 ## age 0.051 0.003 15.189 0.000 ## mcs_3 ~ ## mcs_2 0.438 0.006 73.566 0.000 ## pcs_2 0.117 0.006 20.634 0.000 ## gndr.fct -0.606 0.106 -5.694 0.000 ## age 0.078 0.003 23.181 0.000 ## ## Covariances: ## Estimate Std.Err z-value P(>|z|) ## .pcs_1 ~~ ## .mcs_1 2.939 0.489 6.015 0.000 ## .pcs_2 ~~ ## .mcs_2 -8.202 0.479 -17.136 0.000 ## .pcs_3 ~~ ## .mcs_3 -13.047 0.478 -27.271 0.000 ## ## Intercepts: ## Estimate Std.Err z-value P(>|z|) ## .pcs_1 12.974 0.202 64.243 0.000 ## .pcs_2 5.488 0.226 24.265 0.000 ## .pcs_3 6.082 0.247 24.671 0.000 ## .mcs_1 1.263 0.193 6.530 0.000 ## .mcs_2 -1.724 0.244 -7.072 0.000 ## .mcs_3 -2.679 0.238 -11.243 0.000 ## ## Variances: ## Estimate Std.Err z-value P(>|z|) ## .pcs_1 111.220 0.723 153.830 0.000 ## .pcs_2 73.304 0.625 117.281 0.000 ## .pcs_3 77.019 0.692 111.340 0.000 ## .mcs_1 101.596 0.660 153.972 0.000 ## .mcs_2 85.485 0.727 117.572 0.000 ## .mcs_3 71.654 0.648 110.581 0.000
The interpretation is similar to a standard regression. For example, females have lower levels of physical health in wave 1 than males (by -0.920). This is also true for older respondents. With an increase in age by 1, we expect a decrease in physical health in wave 1 of 0.253. The interpretation of the intercept is also similar to that in a regression.
Our primary interest is still in the cross-lagged effects. As such, it is interesting to investigate if the coefficients have changed with the inclusion of the new variables. We see that the impact of mental health on physical health from wave 1 to 2 is now 0.124, while the opposite cross-lagged effect is 0.067. The difference between the two appears slightly larger after including the controls compared to our original model (in this post).
Including time-varying controls in the cross-lagged panel model
We can also include time-varying predictors in the model as controls. These can be included in a few different ways, but if we expect concurrent effects, we could build a model similar to this one:

We can look at an example where we include being “single” and “logincome” as controls. As an illustration, let’s assume that the effects are concurrent (as in the graph above), and a change in each wave for these variables will lead to an immediate change in health. The model in lavaan
would look like this:
model <- 'pcs_1 ~ 1 + single_1 + logincome_1 pcs_2 ~ 1 + pcs_1 + mcs_1 + single_2 + logincome_2 pcs_3 ~ 1 + pcs_2 + mcs_2 + single_3 + logincome_3 mcs_1 ~ 1 + single_1 + logincome_1 mcs_2 ~ 1 + mcs_1 + pcs_1 + single_2 + logincome_2 mcs_3 ~ 1 + mcs_2 + pcs_2 + single_3 + logincome_3 pcs_1 ~~ mcs_1 pcs_2 ~~ mcs_2 pcs_3 ~~ mcs_3' m3 <- sem(model, data = usw, missing = "ML") summary(m3)
## lavaan 0.6-19 ended normally after 156 iterations ## ## Estimator ML ## Optimization method NLMINB ## Number of model parameters 35 ## ## Used Total ## Number of observations 30813 51007 ## Number of missing patterns 8 ## ## Model Test User Model: ## ## Test statistic 5156.575 ## Degrees of freedom 28 ## P-value (Chi-square) 0.000 ## ## Parameter Estimates: ## ## Standard errors Standard ## Information Observed ## Observed information based on Hessian ## ## Regressions: ## Estimate Std.Err z-value P(>|z|) ## pcs_1 ~ ## single_1 -0.883 0.139 -6.351 0.000 ## logincome_1 0.441 0.046 9.582 0.000 ## pcs_2 ~ ## pcs_1 0.603 0.005 116.994 0.000 ## mcs_1 0.098 0.006 16.622 0.000 ## single_2 -0.410 0.119 -3.435 0.001 ## logincome_2 0.271 0.045 6.047 0.000 ## pcs_3 ~ ## pcs_2 0.564 0.005 102.717 0.000 ## mcs_2 0.077 0.006 11.892 0.000 ## single_3 -0.362 0.122 -2.961 0.003 ## logincome_3 0.291 0.048 6.024 0.000 ## mcs_1 ~ ## single_1 -1.549 0.122 -12.744 0.000 ## logincome_1 0.229 0.040 5.692 0.000 ## mcs_2 ~ ## mcs_1 0.292 0.006 46.638 0.000 ## pcs_1 0.025 0.006 4.585 0.000 ## single_2 -0.621 0.127 -4.892 0.000 ## logincome_2 0.319 0.048 6.702 0.000 ## mcs_3 ~ ## mcs_2 0.447 0.006 73.608 0.000 ## pcs_2 0.064 0.005 11.823 0.000 ## single_3 -0.552 0.116 -4.766 0.000 ## logincome_3 0.168 0.046 3.669 0.000 ## ## Covariances: ## Estimate Std.Err z-value P(>|z|) ## .pcs_1 ~~ ## .mcs_1 0.944 0.654 1.443 0.149 ## .pcs_2 ~~ ## .mcs_2 -11.055 0.533 -20.751 0.000 ## .pcs_3 ~~ ## .mcs_3 -15.347 0.512 -29.992 0.000 ## ## Intercepts: ## Estimate Std.Err z-value P(>|z|) ## .pcs_1 -2.671 0.328 -8.151 0.000 ## .pcs_2 -2.026 0.323 -6.270 0.000 ## .pcs_3 -2.052 0.349 -5.878 0.000 ## .mcs_1 -0.759 0.287 -2.646 0.008 ## .mcs_2 -2.178 0.343 -6.342 0.000 ## .mcs_3 -0.939 0.331 -2.838 0.005 ## ## Variances: ## Estimate Std.Err z-value P(>|z|) ## .pcs_1 128.305 1.060 121.005 0.000 ## .pcs_2 75.378 0.704 107.098 0.000 ## .pcs_3 80.935 0.746 108.427 0.000 ## .mcs_1 97.618 0.806 121.155 0.000 ## .mcs_2 84.986 0.793 107.204 0.000 ## .mcs_3 73.466 0.677 108.441 0.000
The results show that being single tends to decrease mental and physical health, while income seems to have a positive effect. Again, the cross-lagged effects are slightly different to what we saw before.
When including time-varying predictors, it is important to consider if the effects are concurrent or lagged. So far, we assumed that being single and income immediately affected health. If we think there is a lag effect, or if we are concerned about reverse causality (e.g., health is impacting income and not the other way around), we can consider using lagged effects. The simplest version of this would be a lag of one (e.g., income in wave 1 impacting health in wave 2). We can easily include more prolonged lag effects if we have good reasons.
Let’s explore the previous model using a lag of 1 instead of concurrent effects.
model <- 'pcs_2 ~ 1 + pcs_1 + mcs_1 + single_1 + logincome_1 pcs_3 ~ 1 + pcs_2 + mcs_2 + single_2 + logincome_2 mcs_2 ~ 1 + mcs_1 + pcs_1 + single_1 + logincome_1 mcs_3 ~ 1 + mcs_2 + pcs_2 + single_2 + logincome_2 pcs_1 ~~ mcs_1 pcs_2 ~~ mcs_2 pcs_3 ~~ mcs_3' m4 <- sem(model, data = usw, missing = "ML") summary(m4)
## lavaan 0.6-19 ended normally after 153 iterations ## ## Estimator ML ## Optimization method NLMINB ## Number of model parameters 31 ## ## Used Total ## Number of observations 38213 51007 ## Number of missing patterns 8 ## ## Model Test User Model: ## ## Test statistic 5392.873 ## Degrees of freedom 20 ## P-value (Chi-square) 0.000 ## ## Parameter Estimates: ## ## Standard errors Standard ## Information Observed ## Observed information based on Hessian ## ## Regressions: ## Estimate Std.Err z-value P(>|z|) ## pcs_2 ~ ## pcs_1 0.600 0.005 126.199 0.000 ## mcs_1 0.099 0.005 18.260 0.000 ## single_1 -0.268 0.111 -2.407 0.016 ## logincome_1 0.280 0.037 7.656 0.000 ## pcs_3 ~ ## pcs_2 0.564 0.005 103.001 0.000 ## mcs_2 0.077 0.006 11.910 0.000 ## single_2 -0.236 0.122 -1.932 0.053 ## logincome_2 0.256 0.046 5.534 0.000 ## mcs_2 ~ ## mcs_1 0.283 0.006 49.535 0.000 ## pcs_1 0.032 0.005 6.316 0.000 ## single_1 -0.735 0.117 -6.285 0.000 ## logincome_1 0.170 0.038 4.426 0.000 ## mcs_3 ~ ## mcs_2 0.447 0.006 73.742 0.000 ## pcs_2 0.064 0.005 11.851 0.000 ## single_2 -0.685 0.116 -5.915 0.000 ## logincome_2 0.109 0.044 2.478 0.013 ## ## Covariances: ## Estimate Std.Err z-value P(>|z|) ## pcs_1 ~~ ## mcs_1 1.104 0.609 1.814 0.070 ## .pcs_2 ~~ ## .mcs_2 -10.218 0.497 -20.545 0.000 ## .pcs_3 ~~ ## .mcs_3 -15.371 0.512 -30.046 0.000 ## ## Intercepts: ## Estimate Std.Err z-value P(>|z|) ## .pcs_2 -2.188 0.262 -8.355 0.000 ## .pcs_3 -1.823 0.334 -5.466 0.000 ## .mcs_2 -1.131 0.275 -4.108 0.000 ## .mcs_3 -0.466 0.316 -1.475 0.140 ## pcs_1 -0.112 0.060 -1.854 0.064 ## mcs_1 0.129 0.053 2.430 0.015 ## ## Variances: ## Estimate Std.Err z-value P(>|z|) ## .pcs_2 77.862 0.665 117.111 0.000 ## .pcs_3 80.886 0.746 108.389 0.000 ## .mcs_2 86.147 0.734 117.360 0.000 ## .mcs_3 73.437 0.677 108.432 0.000 ## pcs_1 131.612 0.981 134.180 0.000 ## mcs_1 101.265 0.754 134.331 0.000
The results differ from those in the previous model. For example, the effect of being single on physical health in wave 2 is now -0.268 (compared to -0.410 in the previous model). There are also some small differences in the cross-lagged coefficients.
The decision to include concurrent or lagged effects should be based on theoretical considerations. If we believe the effects are immediate, we should include them concurrently. On the other hand, if we are not sure about the causal direction or think some time needs to pass to see an effect, we should include the predictors as a lagged effect.
Mixing time-constant and time-varying controls
Of course, the model can be expanded to include a combination of time-varying and time-constant predictors. As an example, we combine the previous models to include sex and age at the start of the study, the concurrent effect of single and the lagged effect of income.
model <- 'pcs_1 ~ 1 + single_1 + gndr.fct + age pcs_2 ~ 1 + pcs_1 + mcs_1 + single_2 + logincome_1 + gndr.fct + age pcs_3 ~ 1 + pcs_2 + mcs_2 + single_3 + logincome_2 + gndr.fct + age mcs_1 ~ 1 + single_1 + gndr.fct + age mcs_2 ~ 1 + mcs_1 + pcs_1 + single_2 + logincome_1 + gndr.fct + age mcs_3 ~ 1 + mcs_2 + pcs_2 + single_3 + logincome_2 + gndr.fct + age pcs_1 ~~ mcs_1 pcs_2 ~~ mcs_2 pcs_3 ~~ mcs_3' m5 <- sem(model, data = usw, missing = "ML") summary(m5)
## lavaan 0.6-19 ended normally after 213 iterations ## ## Estimator ML ## Optimization method NLMINB ## Number of model parameters 45 ## ## Used Total ## Number of observations 30829 51007 ## Number of missing patterns 8 ## ## Model Test User Model: ## ## Test statistic 4862.544 ## Degrees of freedom 24 ## P-value (Chi-square) 0.000 ## ## Parameter Estimates: ## ## Standard errors Standard ## Information Observed ## Observed information based on Hessian ## ## Regressions: ## Estimate Std.Err z-value P(>|z|) ## pcs_1 ~ ## single_1 -2.128 0.129 -16.508 0.000 ## gndr.fct -0.729 0.124 -5.900 0.000 ## age -0.247 0.004 -70.163 0.000 ## pcs_2 ~ ## pcs_1 0.515 0.005 95.490 0.000 ## mcs_1 0.120 0.006 20.870 0.000 ## single_2 -0.951 0.117 -8.113 0.000 ## logincome_1 0.546 0.040 13.826 0.000 ## gndr.fct 0.451 0.111 4.056 0.000 ## age -0.143 0.003 -41.399 0.000 ## pcs_3 ~ ## pcs_2 0.481 0.006 81.687 0.000 ## mcs_2 0.093 0.006 14.547 0.000 ## single_3 -0.882 0.120 -7.360 0.000 ## logincome_2 0.447 0.046 9.775 0.000 ## gndr.fct -0.158 0.115 -1.379 0.168 ## age -0.133 0.004 -36.534 0.000 ## mcs_1 ~ ## single_1 -1.370 0.121 -11.322 0.000 ## gndr.fct -1.584 0.116 -13.651 0.000 ## age 0.036 0.003 10.879 0.000 ## mcs_2 ~ ## mcs_1 0.282 0.006 44.897 0.000 ## pcs_1 0.058 0.006 9.829 0.000 ## single_2 -0.390 0.128 -3.043 0.002 ## logincome_1 0.075 0.043 1.745 0.081 ## gndr.fct -0.574 0.122 -4.720 0.000 ## age 0.052 0.004 13.640 0.000 ## mcs_3 ~ ## mcs_2 0.436 0.006 71.997 0.000 ## pcs_2 0.116 0.006 19.819 0.000 ## single_3 -0.197 0.116 -1.699 0.089 ## logincome_2 -0.047 0.044 -1.064 0.287 ## gndr.fct -0.607 0.111 -5.482 0.000 ## age 0.081 0.004 23.016 0.000 ## ## Covariances: ## Estimate Std.Err z-value P(>|z|) ## .pcs_1 ~~ ## .mcs_1 3.580 0.603 5.935 0.000 ## .pcs_2 ~~ ## .mcs_2 -9.061 0.511 -17.743 0.000 ## .pcs_3 ~~ ## .mcs_3 -12.949 0.489 -26.483 0.000 ## ## Intercepts: ## Estimate Std.Err z-value P(>|z|) ## .pcs_1 13.600 0.270 50.385 0.000 ## .pcs_2 2.388 0.369 6.474 0.000 ## .pcs_3 3.524 0.417 8.458 0.000 ## .mcs_1 1.498 0.254 5.906 0.000 ## .mcs_2 -2.100 0.403 -5.208 0.000 ## .mcs_3 -2.409 0.403 -5.983 0.000 ## ## Variances: ## Estimate Std.Err z-value P(>|z|) ## .pcs_1 110.139 0.910 121.012 0.000 ## .pcs_2 70.143 0.656 106.928 0.000 ## .pcs_3 77.155 0.706 109.341 0.000 ## .mcs_1 96.728 0.798 121.178 0.000 ## .mcs_2 84.291 0.786 107.234 0.000 ## .mcs_3 71.906 0.662 108.544 0.000
Our primary interest in these models is still the cross-lagged effects (now after including the controls). We see that the impact of mental health on physical health from wave 1 to 2 is 0.120, while the opposite cross-lagged effect is 0.058. From wave 2 to wave 3, the effects are 0.093 and 0.116. We see the direction is slightly different once we include the controls. As a next step, we can test if these differences are significant as we covered in this post.
Conclusion
In this post, we have seen how to include control variables in a cross-lagged panel model. We have also explored how to include time-constant and time-varying predictors and discussed the issue of concurrent and lagged effects. The decision to include control variables (and how to do so) should be based on careful theoretical considerations.
The model can be further extended to include separate within- and between-variation (also known as a Random Effect Cross-Lagged Panel Model). You can read more about it here. In a future post, we will explore how to estimate such a model.

Was the information useful?
Consider supporting the site by: