cross lagged model with time varying predictor

How to include control variables in a cross-lagged panel model

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:

cross-lagged panel model with time constant predictor

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:

cross-lagged panel model with time varying predictor

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:

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.