This document will outline the code used to define a cross-lagged panel model which will allow for more fine grained analysis of the effect of social resources on eating behaviours.

library(lavaan)
library(MASS)
library(semPlot)
library(dagitty)
library(tidyverse)
library(haven)

wave1 <- read_sav("covid-19_wave1_survey_cls.sav") |> mutate(wave = 1)
wave2 <- read_sav("covid-19_wave2_survey_cls.sav") |> mutate(wave = 2)
wave3 <- read_sav("covid-19_wave3_survey_cls.sav") |> mutate(wave = 3)

long_data1 <- bind_rows(wave1, wave2, wave3)

#Reverse coding SOCPROV_3 
long_data1$CW1_SOCPROV_3 <- 4 - long_data1$CW1_SOCPROV_3
long_data1$CW2_SOCPROV_3 <- 4 - long_data1$CW2_SOCPROV_3
long_data1$CW3_SOCPROV_3 <- 4 - long_data1$CW3_SOCPROV_3

#converting labelled variables to numeric 
long_data1 <- long_data1 |> mutate(across(where(is.labelled), as.numeric))

#converting CW1_EXCISESP to a consistent scale
long_data1 <- long_data1 |>
  mutate(CW1_EXCISESP_scaled = scales::rescale(CW1_EXCISESP, to = c(0, 7)))

#removing unit errors or misunderstandings from CW1_FRTVEGSP
long_data1 <- long_data1 |>
  mutate(
    CW1_FRTVEGSP_trim = ifelse(CW1_FRTVEGSP > 20, NA, CW1_FRTVEGSP)
  )

#reverse coding CW1_FOODAFFORD
long_data1 <- long_data1 |>
  mutate(
    ses_proxy = case_when(
      CW1_FOODAFFORD == 1 ~ 4,
      CW1_FOODAFFORD == 2 ~ 3,
      CW1_FOODAFFORD == 3 ~ 2,
      CW1_FOODAFFORD == 4 ~ 1,
      TRUE ~ NA_real_
    )
  )
model_clpm <- '

  # Measurement model: latent social provisions at each wave
  socialprov_t1 =~ CW1_SOCPROV_1 + CW1_SOCPROV_2 + CW1_SOCPROV_3
  socialprov_t2 =~ CW2_SOCPROV_1 + CW2_SOCPROV_2 + CW2_SOCPROV_3
  socialprov_t3 =~ CW3_SOCPROV_1 + CW3_SOCPROV_2 + CW3_SOCPROV_3

  # Autoregressive effects
  socialprov_t2 ~ socialprov_t1
  socialprov_t3 ~ socialprov_t2

  eating_t2 ~ eating_t1
  eating_t3 ~ eating_t2

  exercise_t2 ~ exercise_t1
  exercise_t3 ~ exercise_t2

  # Cross-lagged effects
  eating_t2 ~ socialprov_t1 + ses_proxy
  eating_t3 ~ socialprov_t2 + ses_proxy

  exercise_t2 ~ socialprov_t1 + ses_proxy
  exercise_t3 ~ socialprov_t2 + ses_proxy

  # Covariances at each wave
  eating_t1 ~~ exercise_t1
  eating_t2 ~~ exercise_t2
  eating_t3 ~~ exercise_t3

  # SES covariances
  ses_proxy ~~ socialprov_t1

  # Define observed variables (rename for clarity)
  eating_t1 =~ 1*CW1_FRTVEGSP_trim
  eating_t2 =~ 1*CW2_FRTVEGSP
  eating_t3 =~ 1*CW3_FRTVEGSP

  exercise_t1 =~ 1*CW1_EXCISESP_scaled
  exercise_t2 =~ 1*CW2_EXCISESP
  exercise_t3 =~ 1*CW3_EXCISESP

  # Variances (optional, helps with identifiability)
  socialprov_t1 ~~ socialprov_t1
  socialprov_t2 ~~ socialprov_t2
  socialprov_t3 ~~ socialprov_t3

  ses_proxy ~~ ses_proxy
  eating_t1 ~~ eating_t1
  eating_t2 ~~ eating_t2
  eating_t3 ~~ eating_t3
  exercise_t1 ~~ exercise_t1
  exercise_t2 ~~ exercise_t2
  exercise_t3 ~~ exercise_t3
'
fit_clpm <- lavaan::sem(model_clpm, data = long_data1, missing = "ML",   fixed.x = FALSE)
summary(fit_clpm, fit.measures = TRUE, standardized = TRUE)
## lavaan 0.6-19 ended normally after 166 iterations
## 
##   Estimator                                         ML
##   Optimization method                           NLMINB
##   Number of model parameters                        63
## 
##                                                   Used       Total
##   Number of observations                         58862       67518
##   Number of missing patterns                        59            
## 
## Model Test User Model:
##                                                       
##   Test statistic                               392.752
##   Degrees of freedom                                89
##   P-value (Chi-square)                           0.000
## 
## Model Test Baseline Model:
## 
##   Test statistic                             18226.076
##   Degrees of freedom                               120
##   P-value                                        0.000
## 
## User Model versus Baseline Model:
## 
##   Comparative Fit Index (CFI)                    0.983
##   Tucker-Lewis Index (TLI)                       0.977
##                                                       
##   Robust Comparative Fit Index (CFI)                NA
##   Robust Tucker-Lewis Index (TLI)                   NA
## 
## Loglikelihood and Information Criteria:
## 
##   Loglikelihood user model (H0)            -294186.697
##   Loglikelihood unrestricted model (H1)    -293990.321
##                                                       
##   Akaike (AIC)                              588499.394
##   Bayesian (BIC)                            589065.320
##   Sample-size adjusted Bayesian (SABIC)     588865.105
## 
## Root Mean Square Error of Approximation:
## 
##   RMSEA                                          0.008
##   90 Percent confidence interval - lower         0.007
##   90 Percent confidence interval - upper         0.008
##   P-value H_0: RMSEA <= 0.050                    1.000
##   P-value H_0: RMSEA >= 0.080                    0.000
##                                                       
##   Robust RMSEA                                      NA
##   90 Percent confidence interval - lower            NA
##   90 Percent confidence interval - upper            NA
##   P-value H_0: Robust RMSEA <= 0.050                NA
##   P-value H_0: Robust RMSEA >= 0.080                NA
## 
## Standardized Root Mean Square Residual:
## 
##   SRMR                                           0.107
## 
## Parameter Estimates:
## 
##   Standard errors                             Standard
##   Information                                 Observed
##   Observed information based on                Hessian
## 
## Latent Variables:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##   socialprov_t1 =~                                                      
##     CW1_SOCPROV_1     1.000                               0.263    0.628
##     CW1_SOCPROV_2     1.276    0.040   31.912    0.000    0.335    0.745
##     CW1_SOCPROV_3     0.906    0.028   32.111    0.000    0.238    0.571
##   socialprov_t2 =~                                                      
##     CW2_SOCPROV_1     1.000                               0.308    0.649
##     CW2_SOCPROV_2     1.270    0.029   43.851    0.000    0.392    0.802
##     CW2_SOCPROV_3     0.889    0.020   45.525    0.000    0.274    0.555
##   socialprov_t3 =~                                                      
##     CW3_SOCPROV_1     1.000                               0.319    0.682
##     CW3_SOCPROV_2     1.204    0.027   44.362    0.000    0.384    0.790
##     CW3_SOCPROV_3     0.920    0.021   44.527    0.000    0.293    0.598
##   eating_t1 =~                                                          
##     CW1_FRTVEGSP_t    1.000                               1.881    1.000
##   eating_t2 =~                                                          
##     CW2_FRTVEGSP      1.000                               2.246    1.000
##   eating_t3 =~                                                          
##     CW3_FRTVEGSP      1.000                               2.390    1.000
##   exercise_t1 =~                                                        
##     CW1_EXCISESP_s    1.000                               0.862    1.000
##   exercise_t2 =~                                                        
##     CW2_EXCISESP      1.000                               2.181    1.000
##   exercise_t3 =~                                                        
##     CW3_EXCISESP      1.000                               2.231    1.000
## 
## Regressions:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##   socialprov_t2 ~                                                       
##     socialprov_t1    -1.047       NA                     -0.892   -0.892
##   socialprov_t3 ~                                                       
##     socialprov_t2    -0.000       NA                     -0.000   -0.000
##   eating_t2 ~                                                           
##     eating_t1        -0.425   68.027   -0.006    0.995   -0.356   -0.356
##   eating_t3 ~                                                           
##     eating_t2        -0.075       NA                     -0.070   -0.070
##   exercise_t2 ~                                                         
##     exercise_t1      -0.139  379.725   -0.000    1.000   -0.055   -0.055
##   exercise_t3 ~                                                         
##     exercise_t2      -0.309    1.675   -0.184    0.854   -0.302   -0.302
##   eating_t2 ~                                                           
##     socialprov_t1     0.670   20.650    0.032    0.974    0.078    0.078
##     ses_proxy        -0.098   85.717   -0.001    0.999   -0.044   -0.019
##   eating_t3 ~                                                           
##     socialprov_t2     0.000       NA                      0.000    0.000
##     ses_proxy        -0.004       NA                     -0.001   -0.001
##   exercise_t2 ~                                                         
##     socialprov_t1     0.666   71.703    0.009    0.993    0.080    0.080
##     ses_proxy        -0.071       NA                     -0.033   -0.015
##   exercise_t3 ~                                                         
##     socialprov_t2    -0.000       NA                     -0.000   -0.000
##     ses_proxy        -0.013  134.910   -0.000    1.000   -0.006   -0.003
## 
## Covariances:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##   eating_t1 ~~                                                          
##     exercise_t1       0.325    0.014   24.048    0.000    0.200    0.200
##  .eating_t2 ~~                                                          
##    .exercise_t2       0.707   52.846    0.013    0.989    0.156    0.156
##  .eating_t3 ~~                                                          
##    .exercise_t3       0.727       NA                      0.143    0.143
##   socialprov_t1 ~~                                                      
##     ses_proxy        -0.020    0.002  -11.785    0.000   -0.074   -0.167
##     eating_t1        -0.046    0.008   -5.941    0.000   -0.093   -0.093
##     exercise_t1      -0.014    0.003   -4.175    0.000   -0.064   -0.064
##  .socialprov_t3 ~~                                                      
##    .eating_t3        -0.059       NA                     -0.077   -0.077
##    .exercise_t3      -0.075       NA                     -0.111   -0.111
## 
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##    .CW1_SOCPROV_1     1.177    0.005  230.795    0.000    1.177    2.815
##    .CW1_SOCPROV_2     1.182    0.005  215.031    0.000    1.182    2.627
##    .CW1_SOCPROV_3     1.139    0.005  222.488    0.000    1.139    2.731
##    .CW2_SOCPROV_1     1.247    0.004  284.387    0.000    1.247    2.627
##    .CW2_SOCPROV_2     1.221    0.005  270.602    0.000    1.221    2.500
##    .CW2_SOCPROV_3     1.208    0.005  264.725    0.000    1.208    2.447
##    .CW3_SOCPROV_1     1.242    0.005  257.082    0.000    1.242    2.654
##    .CW3_SOCPROV_2     1.221    0.005  243.069    0.000    1.221    2.510
##    .CW3_SOCPROV_3     1.212    0.005  239.110    0.000    1.212    2.469
##    .CW1_FRTVEGSP_t    3.841    0.015  251.263    0.000    3.841    2.042
##    .CW2_FRTVEGSP      4.226  323.156    0.013    0.990    4.226    1.881
##    .CW3_FRTVEGSP      3.924       NA                      3.924    1.642
##    .CW1_EXCISESP_s    1.199    0.007  173.236    0.000    1.199    1.391
##    .CW2_EXCISESP      3.260       NA                      3.260    1.495
##    .CW3_EXCISESP      3.023  509.136    0.006    0.995    3.023    1.355
##     ses_proxy         3.770    0.003 1077.682    0.000    3.770    8.497
## 
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##     socialprov_t1     0.069    0.003   22.233    0.000    1.000    1.000
##    .socialprov_t2     0.019       NA                      0.204    0.204
##    .socialprov_t3     0.102       NA                      1.000    1.000
##     ses_proxy         0.197    0.002   89.681    0.000    0.197    1.000
##     eating_t1         3.536    0.041   86.920    0.000    1.000    1.000
##    .eating_t2         4.345  202.661    0.021    0.983    0.861    0.861
##    .eating_t3         5.686       NA                      0.995    0.995
##     exercise_t1       0.743    0.008   88.031    0.000    1.000    1.000
##    .exercise_t2       4.707   78.008    0.060    0.952    0.989    0.989
##    .exercise_t3       4.525       NA                      0.909    0.909
##    .CW1_SOCPROV_1     0.106    0.003   39.090    0.000    0.106    0.606
##    .CW1_SOCPROV_2     0.090    0.004   25.227    0.000    0.090    0.446
##    .CW1_SOCPROV_3     0.117    0.003   44.570    0.000    0.117    0.674
##    .CW2_SOCPROV_1     0.130    0.003   50.356    0.000    0.130    0.578
##    .CW2_SOCPROV_2     0.085    0.003   25.535    0.000    0.085    0.357
##    .CW2_SOCPROV_3     0.169    0.003   62.552    0.000    0.169    0.692
##    .CW3_SOCPROV_1     0.117    0.003   44.150    0.000    0.117    0.535
##    .CW3_SOCPROV_2     0.089    0.003   27.679    0.000    0.089    0.376
##    .CW3_SOCPROV_3     0.155    0.003   54.327    0.000    0.155    0.642
##    .CW1_FRTVEGSP_t    0.000                               0.000    0.000
##    .CW2_FRTVEGSP      0.000                               0.000    0.000
##    .CW3_FRTVEGSP      0.000                               0.000    0.000
##    .CW1_EXCISESP_s    0.000                               0.000    0.000
##    .CW2_EXCISESP      0.000                               0.000    0.000
##    .CW3_EXCISESP      0.000                               0.000    0.000
library(semPlot)
library(semPlot)

# Custom label names for clarity (optional)
node_labels <- c(
  "SocialProv_T1", "SocialProv_T2", "SocialProv_T3",
  "Eating_T1", "Eating_T2", "Eating_T3",
  "Exercise_T1", "Exercise_T2", "Exercise_T3",
  "SES"
)

# Plot with modifications
semPaths(
  object = fit_clpm,
  what = "std",              # show standardized estimates
  layout = "tree",           # vertical ordering by time
  style = "lisrel",          # cleaner SEM look
  edge.label.cex = 0.8,
  fade = FALSE,
  curve = 1.5,               # less curvature for readability
  sizeMan = 6,
  sizeLat = 8,
  nCharNodes = 0,
  residuals = FALSE,         # HIDE residual variances
  intercepts = FALSE,        # HIDE means/intercepts
  nodeLabels = node_labels,  # custom labels
  reorder = TRUE,            # arrange left to right
  exoCov = TRUE              # show covariances with SES
)