I begin with an intercept-only model in a latent change framework and then build to a full dual change model. SEM images in this post are taken from a lecture by Amy Nuttall. Two notes about the models and code below. First, the initial models will not fit well because I use a DGP with both dual change components, the initial models purposefully exclude either constant or proportion change. Second, I use the sem command throughout rather than growth in the lavaan package because it forces me to specify the entire model. I do not like using commands that make automatic constraints for me – if you do, you are much more likely to make a mistake or not know what your model is doing.

### DGP

The underlying DGP will be the same throughout this exercise. Consistent with Ghisletta and McArdle, 2012, we have:

$$$y_t = \alpha*b_1 + (1 + b_2)*y_{t-1}$$$

where $$b_1$$ is the constant change (similar to the “slope” term in a basic growth model, in latent change frameworks it is called the “change factor”) and $$b_2$$ is the proportion change, or the change from point to point. The values specified in the DGP are

$$$y_t = 1*0.3 + (1 + -0.4)*y_{t-1}$$$

where $$b_1$$ is equal to 0.3 and $$b_2$$ is equal to -0.4. Let’s generate data for 500 people across six time points.

constant <- 0.3
proportion <- -0.4

people <- 500
time <- 6

df <- matrix(, nrow = people*time, ncol = 3)
count <- 0

for(i in 1:people){

y_het <- rnorm(1, 0, 2)

for(j in 1:time){
count <- count + 1

if(j == 1){
df[count, 1] <- i
df[count, 2] <- j
df[count, 3] <- y_het + rnorm(1,0,1)
}else{
df[count, 1] <- i
df[count, 2] <- j
df[count, 3] <- 1*constant + (1+proportion)*df[count - 1, 3] + y_het + rnorm(1,0,1)
}

}

}

df <- data.frame(df)
names(df) <- c('id', 'time', 'y')
random_ids <- sample(1:people, 5)
sample_df <- df %>%
filter(id %in% random_ids)

ggplot(df, aes(x = time, y = y, group = id)) +
geom_point(color = 'grey85') +
geom_line(color = 'grey85') +
geom_point(data = sample_df, aes(x = time, y = y, group = id)) +
geom_line(data = sample_df, aes(x = time, y = y, group = id))

Change the data to wide and load lavaan before we start modeling.

df_wide <- reshape(df, idvar = 'id', timevar = 'time', direction = 'wide')
library(lavaan)

# Intercept Only Model

Similar to the intercept-only model in a “non-latent change” framework (i.e., a simple growth model), the intercept-only model here contains a latent variable over the first observation.

There are six observations of $$y$$ and each is predicted by its latent “true score.” The first true score term is regressed on a latent intercept. The other true scores are regressed on additional latent variables that represent latent change. We don’t have anything relating to those latent change score terms yet so they don’t do much in this model. The autoregressive paths from true score to true score are constrained to 1. Here is how we estimate it.

int_only_string <- '

# latent true scores over the observed y points
l_y1 =~ 1*y.1
l_y2 =~ 1*y.2
l_y3 =~ 1*y.3
l_y4 =~ 1*y.4
l_y5 =~ 1*y.5
l_y6 =~ 1*y.6

# latent change scores over the latent true scores
# y1 does not get one because it is the first time point
lc_y2 =~ 1*l_y2
lc_y3 =~ 1*l_y3
lc_y4 =~ 1*l_y4
lc_y5 =~ 1*l_y5
lc_y6 =~ 1*l_y6

# autoregression of the latent true scores
l_y2 ~ 1*l_y1
l_y3 ~ 1*l_y2
l_y4 ~ 1*l_y3
l_y5 ~ 1*l_y4
l_y6 ~ 1*l_y5

# latent intercept over the first true score of y
latent_intercept =~ 1*l_y1

# estimate mean and variance of latent intercept
latent_intercept ~~ latent_intercept
latent_intercept ~ 1

# means and variances of latent factors set to zero

l_y1 ~ 0
l_y2 ~ 0
l_y3 ~ 0
l_y4 ~ 0
l_y5 ~ 0
l_y6 ~ 0

l_y1 ~~ 0*l_y1
l_y2 ~~ 0*l_y2
l_y3 ~~ 0*l_y3
l_y4 ~~ 0*l_y4
l_y5 ~~ 0*l_y5
l_y6 ~~ 0*l_y6

lc_y2 ~ 0
lc_y3 ~ 0
lc_y4 ~ 0
lc_y5 ~ 0
lc_y6 ~ 0

lc_y2 ~~ 0*lc_y2
lc_y3 ~~ 0*lc_y3
lc_y4 ~~ 0*lc_y4
lc_y5 ~~ 0*lc_y5
lc_y6 ~~ 0*lc_y6

# means of indicators set to zero

y.1 ~ 0
y.2 ~ 0
y.3 ~ 0
y.4 ~ 0
y.5 ~ 0
y.6 ~ 0

# residual variances constrained to be equal across time

y.1 ~~ res_var*y.1
y.2 ~~ res_var*y.2
y.3 ~~ res_var*y.3
y.4 ~~ res_var*y.4
y.5 ~~ res_var*y.5
y.6 ~~ res_var*y.6

# Constrain latent change factors to not correlate with each other

lc_y2 ~~ 0*lc_y3 + 0*lc_y4 + 0*lc_y5 + 0*lc_y6
lc_y3 ~~ 0*lc_y4 + 0*lc_y5 + 0*lc_y6
lc_y4 ~~ 0*lc_y5 + 0*lc_y6
lc_y5 ~~ 0*lc_y6

# constrain latent intercept not to correlate with the change factors
latent_intercept ~~ 0*lc_y2 + 0*lc_y3 + 0*lc_y4 + 0*lc_y5 + 0*lc_y6

'

int_only_model <- sem(int_only_string, data = df_wide)
summary(int_only_model, fit.measures = T)
## lavaan 0.6-3 ended normally after 17 iterations
##
##   Optimization method                           NLMINB
##   Number of free parameters                          8
##   Number of equality constraints                     5
##
##   Number of observations                           500
##
##   Estimator                                         ML
##   Model Fit Test Statistic                    2488.894
##   Degrees of freedom                                24
##   P-value (Chi-square)                           0.000
##
## Model test baseline model:
##
##   Minimum Function Test Statistic             6352.195
##   Degrees of freedom                                15
##   P-value                                        0.000
##
## User model versus baseline model:
##
##   Comparative Fit Index (CFI)                    0.611
##   Tucker-Lewis Index (TLI)                       0.757
##
## Loglikelihood and Information Criteria:
##
##   Loglikelihood user model (H0)              -6300.904
##   Loglikelihood unrestricted model (H1)      -5056.457
##
##   Number of free parameters                          3
##   Akaike (AIC)                               12607.808
##   Bayesian (BIC)                             12620.452
##   Sample-size adjusted Bayesian (BIC)        12610.930
##
## Root Mean Square Error of Approximation:
##
##   RMSEA                                          0.453
##   90 Percent Confidence Interval          0.438  0.468
##   P-value RMSEA <= 0.05                          0.000
##
## Standardized Root Mean Square Residual:
##
##   SRMR                                           0.572
##
## Parameter Estimates:
##
##   Information                                 Expected
##   Information saturated (h1) model          Structured
##   Standard Errors                             Standard
##
## Latent Variables:
##                       Estimate  Std.Err  z-value  P(>|z|)
##   l_y1 =~
##     y.1                  1.000
##   l_y2 =~
##     y.2                  1.000
##   l_y3 =~
##     y.3                  1.000
##   l_y4 =~
##     y.4                  1.000
##   l_y5 =~
##     y.5                  1.000
##   l_y6 =~
##     y.6                  1.000
##   lc_y2 =~
##     l_y2                 1.000
##   lc_y3 =~
##     l_y3                 1.000
##   lc_y4 =~
##     l_y4                 1.000
##   lc_y5 =~
##     l_y5                 1.000
##   lc_y6 =~
##     l_y6                 1.000
##   latent_intercept =~
##     l_y1                 1.000
##
## Regressions:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   l_y2 ~
##     l_y1              1.000
##   l_y3 ~
##     l_y2              1.000
##   l_y4 ~
##     l_y3              1.000
##   l_y5 ~
##     l_y4              1.000
##   l_y6 ~
##     l_y5              1.000
##
## Covariances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   lc_y2 ~~
##     lc_y3             0.000
##     lc_y4             0.000
##     lc_y5             0.000
##     lc_y6             0.000
##   lc_y3 ~~
##     lc_y4             0.000
##     lc_y5             0.000
##     lc_y6             0.000
##   lc_y4 ~~
##     lc_y5             0.000
##     lc_y6             0.000
##   lc_y5 ~~
##     lc_y6             0.000
##   lc_y2 ~~
##     latent_intrcpt    0.000
##   lc_y3 ~~
##     latent_intrcpt    0.000
##   lc_y4 ~~
##     latent_intrcpt    0.000
##   lc_y5 ~~
##     latent_intrcpt    0.000
##   lc_y6 ~~
##     latent_intrcpt    0.000
##
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)
##     latent_intrcpt    0.580    0.169    3.427    0.001
##     l_y1              0.000
##    .l_y2              0.000
##    .l_y3              0.000
##    .l_y4              0.000
##    .l_y5              0.000
##    .l_y6              0.000
##     lc_y2             0.000
##     lc_y3             0.000
##     lc_y4             0.000
##     lc_y5             0.000
##     lc_y6             0.000
##    .y.1               0.000
##    .y.2               0.000
##    .y.3               0.000
##    .y.4               0.000
##    .y.5               0.000
##    .y.6               0.000
##
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##     ltnt_nt          13.970    0.906   15.423    0.000
##     l_y1              0.000
##    .l_y2              0.000
##    .l_y3              0.000
##    .l_y4              0.000
##    .l_y5              0.000
##    .l_y6              0.000
##     lc_y2             0.000
##     lc_y3             0.000
##     lc_y4             0.000
##     lc_y5             0.000
##     lc_y6             0.000
##    .y.1     (rs_v)    2.106    0.060   35.355    0.000
##    .y.2     (rs_v)    2.106    0.060   35.355    0.000
##    .y.3     (rs_v)    2.106    0.060   35.355    0.000
##    .y.4     (rs_v)    2.106    0.060   35.355    0.000
##    .y.5     (rs_v)    2.106    0.060   35.355    0.000
##    .y.6     (rs_v)    2.106    0.060   35.355    0.000

# Proportion Change Model

Now we include the proportion change along with the latent intercept.

proportion_string <- '

# latent true scores over the observed y points
l_y1 =~ 1*y.1
l_y2 =~ 1*y.2
l_y3 =~ 1*y.3
l_y4 =~ 1*y.4
l_y5 =~ 1*y.5
l_y6 =~ 1*y.6

# latent change scores over the latent true scores
# y1 does not get one because it is the first time point
lc_y2 =~ 1*l_y2
lc_y3 =~ 1*l_y3
lc_y4 =~ 1*l_y4
lc_y5 =~ 1*l_y5
lc_y6 =~ 1*l_y6

# autoregression of the latent true scores
l_y2 ~ 1*l_y1
l_y3 ~ 1*l_y2
l_y4 ~ 1*l_y3
l_y5 ~ 1*l_y4
l_y6 ~ 1*l_y5

# latent intercept over the first true score of y
latent_intercept =~ 1*l_y1

# estimate mean and variance of latent intercept
latent_intercept ~~ latent_intercept
latent_intercept ~ 1

# HERE IS THE CHANGE
# proportion parameter estimate (estimate of b2)
# regress latent change on latent true score from the last time point
lc_y2 ~ b2*l_y1
lc_y3 ~ b2*l_y2
lc_y4 ~ b2*l_y3
lc_y5 ~ b2*l_y4
lc_y6 ~ b2*l_y5

# means and variances of latent factors set to zero

l_y1 ~ 0
l_y2 ~ 0
l_y3 ~ 0
l_y4 ~ 0
l_y5 ~ 0
l_y6 ~ 0

l_y1 ~~ 0*l_y1
l_y2 ~~ 0*l_y2
l_y3 ~~ 0*l_y3
l_y4 ~~ 0*l_y4
l_y5 ~~ 0*l_y5
l_y6 ~~ 0*l_y6

lc_y2 ~ 0
lc_y3 ~ 0
lc_y4 ~ 0
lc_y5 ~ 0
lc_y6 ~ 0

lc_y2 ~~ 0*lc_y2
lc_y3 ~~ 0*lc_y3
lc_y4 ~~ 0*lc_y4
lc_y5 ~~ 0*lc_y5
lc_y6 ~~ 0*lc_y6

# means of indicators set to zero

y.1 ~ 0
y.2 ~ 0
y.3 ~ 0
y.4 ~ 0
y.5 ~ 0
y.6 ~ 0

# residual variances constrained to be equal across time

y.1 ~~ res_var*y.1
y.2 ~~ res_var*y.2
y.3 ~~ res_var*y.3
y.4 ~~ res_var*y.4
y.5 ~~ res_var*y.5
y.6 ~~ res_var*y.6

# Constrain latent change factors to not correlate with each other

lc_y2 ~~ 0*lc_y3 + 0*lc_y4 + 0*lc_y5 + 0*lc_y6
lc_y3 ~~ 0*lc_y4 + 0*lc_y5 + 0*lc_y6
lc_y4 ~~ 0*lc_y5 + 0*lc_y6
lc_y5 ~~ 0*lc_y6

# constrain latent intercept not to correlate with the change factors
latent_intercept ~~ 0*lc_y2 + 0*lc_y3 + 0*lc_y4 + 0*lc_y5 + 0*lc_y6

'

proportion_model <- sem(proportion_string, data = df_wide)
summary(proportion_model, fit.measures = T)
## lavaan 0.6-3 ended normally after 22 iterations
##
##   Optimization method                           NLMINB
##   Number of free parameters                         13
##   Number of equality constraints                     9
##
##   Number of observations                           500
##
##   Estimator                                         ML
##   Model Fit Test Statistic                    1007.678
##   Degrees of freedom                                23
##   P-value (Chi-square)                           0.000
##
## Model test baseline model:
##
##   Minimum Function Test Statistic             6352.195
##   Degrees of freedom                                15
##   P-value                                        0.000
##
## User model versus baseline model:
##
##   Comparative Fit Index (CFI)                    0.845
##   Tucker-Lewis Index (TLI)                       0.899
##
## Loglikelihood and Information Criteria:
##
##   Loglikelihood user model (H0)              -5560.296
##   Loglikelihood unrestricted model (H1)      -5056.457
##
##   Number of free parameters                          4
##   Akaike (AIC)                               11128.592
##   Bayesian (BIC)                             11145.450
##   Sample-size adjusted Bayesian (BIC)        11132.754
##
## Root Mean Square Error of Approximation:
##
##   RMSEA                                          0.293
##   90 Percent Confidence Interval          0.277  0.308
##   P-value RMSEA <= 0.05                          0.000
##
## Standardized Root Mean Square Residual:
##
##   SRMR                                           0.197
##
## Parameter Estimates:
##
##   Information                                 Expected
##   Information saturated (h1) model          Structured
##   Standard Errors                             Standard
##
## Latent Variables:
##                       Estimate  Std.Err  z-value  P(>|z|)
##   l_y1 =~
##     y.1                  1.000
##   l_y2 =~
##     y.2                  1.000
##   l_y3 =~
##     y.3                  1.000
##   l_y4 =~
##     y.4                  1.000
##   l_y5 =~
##     y.5                  1.000
##   l_y6 =~
##     y.6                  1.000
##   lc_y2 =~
##     l_y2                 1.000
##   lc_y3 =~
##     l_y3                 1.000
##   lc_y4 =~
##     l_y4                 1.000
##   lc_y5 =~
##     l_y5                 1.000
##   lc_y6 =~
##     l_y6                 1.000
##   latent_intercept =~
##     l_y1                 1.000
##
## Regressions:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   l_y2 ~
##     l_y1              1.000
##   l_y3 ~
##     l_y2              1.000
##   l_y4 ~
##     l_y3              1.000
##   l_y5 ~
##     l_y4              1.000
##   l_y6 ~
##     l_y5              1.000
##   lc_y2 ~
##     l_y1      (b2)    0.141    0.004   39.369    0.000
##   lc_y3 ~
##     l_y2      (b2)    0.141    0.004   39.369    0.000
##   lc_y4 ~
##     l_y3      (b2)    0.141    0.004   39.369    0.000
##   lc_y5 ~
##     l_y4      (b2)    0.141    0.004   39.369    0.000
##   lc_y6 ~
##     l_y5      (b2)    0.141    0.004   39.369    0.000
##
## Covariances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##  .lc_y2 ~~
##    .lc_y3             0.000
##    .lc_y4             0.000
##    .lc_y5             0.000
##    .lc_y6             0.000
##  .lc_y3 ~~
##    .lc_y4             0.000
##    .lc_y5             0.000
##    .lc_y6             0.000
##  .lc_y4 ~~
##    .lc_y5             0.000
##    .lc_y6             0.000
##  .lc_y5 ~~
##    .lc_y6             0.000
##  .lc_y2 ~~
##     latent_intrcpt    0.000
##  .lc_y3 ~~
##     latent_intrcpt    0.000
##  .lc_y4 ~~
##     latent_intrcpt    0.000
##  .lc_y5 ~~
##     latent_intrcpt    0.000
##  .lc_y6 ~~
##     latent_intrcpt    0.000
##
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)
##     latent_intrcpt    0.423    0.119    3.565    0.000
##     l_y1              0.000
##    .l_y2              0.000
##    .l_y3              0.000
##    .l_y4              0.000
##    .l_y5              0.000
##    .l_y6              0.000
##    .lc_y2             0.000
##    .lc_y3             0.000
##    .lc_y4             0.000
##    .lc_y5             0.000
##    .lc_y6             0.000
##    .y.1               0.000
##    .y.2               0.000
##    .y.3               0.000
##    .y.4               0.000
##    .y.5               0.000
##    .y.6               0.000
##
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##     ltnt_nt           6.948    0.467   14.874    0.000
##     l_y1              0.000
##    .l_y2              0.000
##    .l_y3              0.000
##    .l_y4              0.000
##    .l_y5              0.000
##    .l_y6              0.000
##    .lc_y2             0.000
##    .lc_y3             0.000
##    .lc_y4             0.000
##    .lc_y5             0.000
##    .lc_y6             0.000
##    .y.1     (rs_v)    1.152    0.033   35.355    0.000
##    .y.2     (rs_v)    1.152    0.033   35.355    0.000
##    .y.3     (rs_v)    1.152    0.033   35.355    0.000
##    .y.4     (rs_v)    1.152    0.033   35.355    0.000
##    .y.5     (rs_v)    1.152    0.033   35.355    0.000
##    .y.6     (rs_v)    1.152    0.033   35.355    0.000

# Latent Constant Change

This model is nearly identical to the basic linear growth curve model, it simply embodies it in the latent change framework. The basis coefficients from the constant change term to the latent change scores are constrained to one, then we estimate the mean of the constant change.

constant_change_string <- '

# latent true scores over the observed y points
l_y1 =~ 1*y.1
l_y2 =~ 1*y.2
l_y3 =~ 1*y.3
l_y4 =~ 1*y.4
l_y5 =~ 1*y.5
l_y6 =~ 1*y.6

# latent change scores over the latent true scores
# y1 does not get one because it is the first time point
lc_y2 =~ 1*l_y2
lc_y3 =~ 1*l_y3
lc_y4 =~ 1*l_y4
lc_y5 =~ 1*l_y5
lc_y6 =~ 1*l_y6

# autoregression of the latent true scores (the first level latent variables)
l_y2 ~ 1*l_y1
l_y3 ~ 1*l_y2
l_y4 ~ 1*l_y3
l_y5 ~ 1*l_y4
l_y6 ~ 1*l_y5

# latent intercept over the first true score of y
latent_intercept =~ 1*l_y1

# HERE IS THE CHANGE

# latent slope over the change scores
# this is called the change factor in dual change terminology...it is not really a slope term. It is the constant change factor
latent_slope =~ 1*lc_y2 + 1*lc_y3 + 1*lc_y4 + 1*lc_y5 + 1*lc_y6

# estimate covariance between latent intercept and slope (change factor)

latent_intercept ~~ latent_slope

# estimate mean and variance of intercept and slope (change factor)

latent_intercept ~~ latent_intercept
latent_slope ~~ latent_slope

latent_intercept ~ 1
latent_slope ~ 1

# means and variances of latent factors set to zero

l_y1 ~ 0
l_y2 ~ 0
l_y3 ~ 0
l_y4 ~ 0
l_y5 ~ 0
l_y6 ~ 0

l_y1 ~~ 0*l_y1
l_y2 ~~ 0*l_y2
l_y3 ~~ 0*l_y3
l_y4 ~~ 0*l_y4
l_y5 ~~ 0*l_y5
l_y6 ~~ 0*l_y6

lc_y2 ~ 0
lc_y3 ~ 0
lc_y4 ~ 0
lc_y5 ~ 0
lc_y6 ~ 0

lc_y2 ~~ 0*lc_y2
lc_y3 ~~ 0*lc_y3
lc_y4 ~~ 0*lc_y4
lc_y5 ~~ 0*lc_y5
lc_y6 ~~ 0*lc_y6

# means of indicators set to zero

y.1 ~ 0
y.2 ~ 0
y.3 ~ 0
y.4 ~ 0
y.5 ~ 0
y.6 ~ 0

# residual variances constrained to be equal across time

y.1 ~~ res_var*y.1
y.2 ~~ res_var*y.2
y.3 ~~ res_var*y.3
y.4 ~~ res_var*y.4
y.5 ~~ res_var*y.5
y.6 ~~ res_var*y.6

# Constrain latent change factors to not correlate with each other

lc_y2 ~~ 0*lc_y3 + 0*lc_y4 + 0*lc_y5 + 0*lc_y6
lc_y3 ~~ 0*lc_y4 + 0*lc_y5 + 0*lc_y6
lc_y4 ~~ 0*lc_y5 + 0*lc_y6
lc_y5 ~~ 0*lc_y6

# constrain latent intercept not to correlate with the change factors
latent_intercept ~~ 0*lc_y2 + 0*lc_y3 + 0*lc_y4 + 0*lc_y5 + 0*lc_y6

'

constant_change_model <- sem(constant_change_string, data = df_wide)
summary(constant_change_model, fit.measures = T)
## lavaan 0.6-3 ended normally after 37 iterations
##
##   Optimization method                           NLMINB
##   Number of free parameters                         11
##   Number of equality constraints                     5
##
##   Number of observations                           500
##
##   Estimator                                         ML
##   Model Fit Test Statistic                     682.182
##   Degrees of freedom                                21
##   P-value (Chi-square)                           0.000
##
## Model test baseline model:
##
##   Minimum Function Test Statistic             6352.195
##   Degrees of freedom                                15
##   P-value                                        0.000
##
## User model versus baseline model:
##
##   Comparative Fit Index (CFI)                    0.896
##   Tucker-Lewis Index (TLI)                       0.925
##
## Loglikelihood and Information Criteria:
##
##   Loglikelihood user model (H0)              -5397.548
##   Loglikelihood unrestricted model (H1)      -5056.457
##
##   Number of free parameters                          6
##   Akaike (AIC)                               10807.096
##   Bayesian (BIC)                             10832.384
##   Sample-size adjusted Bayesian (BIC)        10813.339
##
## Root Mean Square Error of Approximation:
##
##   RMSEA                                          0.251
##   90 Percent Confidence Interval          0.235  0.267
##   P-value RMSEA <= 0.05                          0.000
##
## Standardized Root Mean Square Residual:
##
##   SRMR                                           0.148
##
## Parameter Estimates:
##
##   Information                                 Expected
##   Information saturated (h1) model          Structured
##   Standard Errors                             Standard
##
## Latent Variables:
##                       Estimate  Std.Err  z-value  P(>|z|)
##   l_y1 =~
##     y.1                  1.000
##   l_y2 =~
##     y.2                  1.000
##   l_y3 =~
##     y.3                  1.000
##   l_y4 =~
##     y.4                  1.000
##   l_y5 =~
##     y.5                  1.000
##   l_y6 =~
##     y.6                  1.000
##   lc_y2 =~
##     l_y2                 1.000
##   lc_y3 =~
##     l_y3                 1.000
##   lc_y4 =~
##     l_y4                 1.000
##   lc_y5 =~
##     l_y5                 1.000
##   lc_y6 =~
##     l_y6                 1.000
##   latent_intercept =~
##     l_y1                 1.000
##   latent_slope =~
##     lc_y2                1.000
##     lc_y3                1.000
##     lc_y4                1.000
##     lc_y5                1.000
##     lc_y6                1.000
##
## Regressions:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   l_y2 ~
##     l_y1              1.000
##   l_y3 ~
##     l_y2              1.000
##   l_y4 ~
##     l_y3              1.000
##   l_y5 ~
##     l_y4              1.000
##   l_y6 ~
##     l_y5              1.000
##
## Covariances:
##                       Estimate  Std.Err  z-value  P(>|z|)
##   latent_intercept ~~
##     latent_slope         1.151    0.085   13.481    0.000
##   lc_y2 ~~
##     lc_y3                0.000
##     lc_y4                0.000
##     lc_y5                0.000
##     lc_y6                0.000
##   lc_y3 ~~
##     lc_y4                0.000
##     lc_y5                0.000
##     lc_y6                0.000
##   lc_y4 ~~
##     lc_y5                0.000
##     lc_y6                0.000
##   lc_y5 ~~
##     lc_y6                0.000
##   lc_y2 ~~
##     latent_intrcpt       0.000
##   lc_y3 ~~
##     latent_intrcpt       0.000
##   lc_y4 ~~
##     latent_intrcpt       0.000
##   lc_y5 ~~
##     latent_intrcpt       0.000
##   lc_y6 ~~
##     latent_intrcpt       0.000
##
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)
##     latent_intrcpt    0.210    0.117    1.800    0.072
##     latent_slope      0.148    0.028    5.371    0.000
##     l_y1              0.000
##    .l_y2              0.000
##    .l_y3              0.000
##    .l_y4              0.000
##    .l_y5              0.000
##    .l_y6              0.000
##     lc_y2             0.000
##     lc_y3             0.000
##     lc_y4             0.000
##     lc_y5             0.000
##     lc_y6             0.000
##    .y.1               0.000
##    .y.2               0.000
##    .y.3               0.000
##    .y.4               0.000
##    .y.5               0.000
##    .y.6               0.000
##
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##     ltnt_nt           6.364    0.432   14.737    0.000
##     ltnt_sl           0.329    0.024   13.687    0.000
##     l_y1              0.000
##    .l_y2              0.000
##    .l_y3              0.000
##    .l_y4              0.000
##    .l_y5              0.000
##    .l_y6              0.000
##     lc_y2             0.000
##     lc_y3             0.000
##     lc_y4             0.000
##     lc_y5             0.000
##     lc_y6             0.000
##    .y.1     (rs_v)    0.878    0.028   31.623    0.000
##    .y.2     (rs_v)    0.878    0.028   31.623    0.000
##    .y.3     (rs_v)    0.878    0.028   31.623    0.000
##    .y.4     (rs_v)    0.878    0.028   31.623    0.000
##    .y.5     (rs_v)    0.878    0.028   31.623    0.000
##    .y.6     (rs_v)    0.878    0.028   31.623    0.000

# Dual Change Model

Now a full dual change model with both constant and proportion change parameters.

dual_c_string <- '

# latent true scores over the observed y points
l_y1 =~ 1*y.1
l_y2 =~ 1*y.2
l_y3 =~ 1*y.3
l_y4 =~ 1*y.4
l_y5 =~ 1*y.5
l_y6 =~ 1*y.6

# latent change scores over the latent true scores
# y1 does not get one because it is the first time point
lc_y2 =~ 1*l_y2
lc_y3 =~ 1*l_y3
lc_y4 =~ 1*l_y4
lc_y5 =~ 1*l_y5
lc_y6 =~ 1*l_y6

# autoregression of the latent true scores (the first level latent variables)
l_y2 ~ 1*l_y1
l_y3 ~ 1*l_y2
l_y4 ~ 1*l_y3
l_y5 ~ 1*l_y4
l_y6 ~ 1*l_y5

# latent intercept over the first true score of y
latent_intercept =~ 1*l_y1

# CHANGE 1 OF THE DUAL CHANGE MODEL

# latent slope over the change scores
# this is called the change factor in dual change terminology...it is not really a slope term. It is the constant change factor
latent_slope =~ 1*lc_y2 + 1*lc_y3 + 1*lc_y4 + 1*lc_y5 + 1*lc_y6

# estimate covariance between latent intercept and slope (change factor)

latent_intercept ~~ latent_slope

# estimate mean and variance of intercept and slope (change factor)

latent_intercept ~~ latent_intercept
latent_slope ~~ latent_slope

latent_intercept ~ 1
latent_slope ~ 1

# CHANGE 2 OF THE DUAL CHANGE MODEL

# autoproportion change. Relationship between true score and latent change score at next time point
# these are estimated
lc_y2 ~ b*l_y1
lc_y3 ~ b*l_y2
lc_y4 ~ b*l_y3
lc_y5 ~ b*l_y4
lc_y6 ~ b*l_y5

# means and variances of latent factors set to zero

l_y1 ~ 0
l_y2 ~ 0
l_y3 ~ 0
l_y4 ~ 0
l_y5 ~ 0
l_y6 ~ 0

l_y1 ~~ 0*l_y1
l_y2 ~~ 0*l_y2
l_y3 ~~ 0*l_y3
l_y4 ~~ 0*l_y4
l_y5 ~~ 0*l_y5
l_y6 ~~ 0*l_y6

lc_y2 ~ 0
lc_y3 ~ 0
lc_y4 ~ 0
lc_y5 ~ 0
lc_y6 ~ 0

lc_y2 ~~ 0*lc_y2
lc_y3 ~~ 0*lc_y3
lc_y4 ~~ 0*lc_y4
lc_y5 ~~ 0*lc_y5
lc_y6 ~~ 0*lc_y6

# means of indicators set to zero

y.1 ~ 0
y.2 ~ 0
y.3 ~ 0
y.4 ~ 0
y.5 ~ 0
y.6 ~ 0

# residual variances constrained to be equal across time

y.1 ~~ res_var*y.1
y.2 ~~ res_var*y.2
y.3 ~~ res_var*y.3
y.4 ~~ res_var*y.4
y.5 ~~ res_var*y.5
y.6 ~~ res_var*y.6

# Constrain latent change factors to not correlate with each other

lc_y2 ~~ 0*lc_y3 + 0*lc_y4 + 0*lc_y5 + 0*lc_y6
lc_y3 ~~ 0*lc_y4 + 0*lc_y5 + 0*lc_y6
lc_y4 ~~ 0*lc_y5 + 0*lc_y6
lc_y5 ~~ 0*lc_y6

'

dual_change_model <- sem(dual_c_string, data = df_wide)
summary(dual_change_model, fit.measures = T)
## lavaan 0.6-3 ended normally after 58 iterations
##
##   Optimization method                           NLMINB
##   Number of free parameters                         16
##   Number of equality constraints                     9
##
##   Number of observations                           500
##
##   Estimator                                         ML
##   Model Fit Test Statistic                     367.182
##   Degrees of freedom                                20
##   P-value (Chi-square)                           0.000
##
## Model test baseline model:
##
##   Minimum Function Test Statistic             6352.195
##   Degrees of freedom                                15
##   P-value                                        0.000
##
## User model versus baseline model:
##
##   Comparative Fit Index (CFI)                    0.945
##   Tucker-Lewis Index (TLI)                       0.959
##
## Loglikelihood and Information Criteria:
##
##   Loglikelihood user model (H0)              -5240.048
##   Loglikelihood unrestricted model (H1)      -5056.457
##
##   Number of free parameters                          7
##   Akaike (AIC)                               10494.096
##   Bayesian (BIC)                             10523.598
##   Sample-size adjusted Bayesian (BIC)        10501.380
##
## Root Mean Square Error of Approximation:
##
##   RMSEA                                          0.186
##   90 Percent Confidence Interval          0.170  0.203
##   P-value RMSEA <= 0.05                          0.000
##
## Standardized Root Mean Square Residual:
##
##   SRMR                                           0.029
##
## Parameter Estimates:
##
##   Information                                 Expected
##   Information saturated (h1) model          Structured
##   Standard Errors                             Standard
##
## Latent Variables:
##                       Estimate  Std.Err  z-value  P(>|z|)
##   l_y1 =~
##     y.1                  1.000
##   l_y2 =~
##     y.2                  1.000
##   l_y3 =~
##     y.3                  1.000
##   l_y4 =~
##     y.4                  1.000
##   l_y5 =~
##     y.5                  1.000
##   l_y6 =~
##     y.6                  1.000
##   lc_y2 =~
##     l_y2                 1.000
##   lc_y3 =~
##     l_y3                 1.000
##   lc_y4 =~
##     l_y4                 1.000
##   lc_y5 =~
##     l_y5                 1.000
##   lc_y6 =~
##     l_y6                 1.000
##   latent_intercept =~
##     l_y1                 1.000
##   latent_slope =~
##     lc_y2                1.000
##     lc_y3                1.000
##     lc_y4                1.000
##     lc_y5                1.000
##     lc_y6                1.000
##
## Regressions:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   l_y2 ~
##     l_y1              1.000
##   l_y3 ~
##     l_y2              1.000
##   l_y4 ~
##     l_y3              1.000
##   l_y5 ~
##     l_y4              1.000
##   l_y6 ~
##     l_y5              1.000
##   lc_y2 ~
##     l_y1       (b)   -0.347    0.017  -20.850    0.000
##   lc_y3 ~
##     l_y2       (b)   -0.347    0.017  -20.850    0.000
##   lc_y4 ~
##     l_y3       (b)   -0.347    0.017  -20.850    0.000
##   lc_y5 ~
##     l_y4       (b)   -0.347    0.017  -20.850    0.000
##   lc_y6 ~
##     l_y5       (b)   -0.347    0.017  -20.850    0.000
##
## Covariances:
##                       Estimate  Std.Err  z-value  P(>|z|)
##   latent_intercept ~~
##     latent_slope         3.504    0.256   13.712    0.000
##  .lc_y2 ~~
##    .lc_y3                0.000
##    .lc_y4                0.000
##    .lc_y5                0.000
##    .lc_y6                0.000
##  .lc_y3 ~~
##    .lc_y4                0.000
##    .lc_y5                0.000
##    .lc_y6                0.000
##  .lc_y4 ~~
##    .lc_y5                0.000
##    .lc_y6                0.000
##  .lc_y5 ~~
##    .lc_y6                0.000
##
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)
##     latent_intrcpt    0.094    0.101    0.927    0.354
##     latent_slope      0.336    0.082    4.117    0.000
##     l_y1              0.000
##    .l_y2              0.000
##    .l_y3              0.000
##    .l_y4              0.000
##    .l_y5              0.000
##    .l_y6              0.000
##    .lc_y2             0.000
##    .lc_y3             0.000
##    .lc_y4             0.000
##    .lc_y5             0.000
##    .lc_y6             0.000
##    .y.1               0.000
##    .y.2               0.000
##    .y.3               0.000
##    .y.4               0.000
##    .y.5               0.000
##    .y.6               0.000
##
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##     ltnt_nt           4.532    0.328   13.797    0.000
##     ltnt_sl           3.236    0.299   10.821    0.000
##     l_y1              0.000
##    .l_y2              0.000
##    .l_y3              0.000
##    .l_y4              0.000
##    .l_y5              0.000
##    .l_y6              0.000
##    .lc_y2             0.000
##    .lc_y3             0.000
##    .lc_y4             0.000
##    .lc_y5             0.000
##    .lc_y6             0.000
##    .y.1     (rs_v)    0.780    0.025   31.623    0.000
##    .y.2     (rs_v)    0.780    0.025   31.623    0.000
##    .y.3     (rs_v)    0.780    0.025   31.623    0.000
##    .y.4     (rs_v)    0.780    0.025   31.623    0.000
##    .y.5     (rs_v)    0.780    0.025   31.623    0.000
##    .y.6     (rs_v)    0.780    0.025   31.623    0.000

The estimate of the constant change (called “latent slope” in my string syntax; $$b_1$$) is close to 0.3 and the estimate of the proportion change ($$b_2$$) is close to -0.4. Not bad.

## A Note On Interpreting

These models predict complex change patterns. It is difficult to know the expected curvilinear pattern that the models expect without computing expected scores and plotting them. I did not do that here.

Bo$$^2$$m =)