Distinguishable dyads: SEM in wide format

The three-model sequence and the k-pattern tests

The wide-format SEM is the classical specification for the distinguishable APIM. The three nested models — unconstrained, slopes-equal, fully constrained — give you three likelihood ratio tests, each of which has a clean substantive interpretation.

This tutorial reproduces the Olsen & Kenny (2006) specification and the Kenny & Ledermann (2010) k-pattern tests.

Setup

Code
library(lavaan)
library(dplyr)

load("../../../data/dyad_data.RData")

cat("Wide format:", nrow(ddw), "rows\n")
Wide format: 100 rows

Model 1: unconstrained (all paths free)

The unconstrained model estimates a separate slope for every path. Actor (male) and partner (female) intercepts, slopes, and residual variances are all free to differ.

Code
model_unconstrained <- '
  satisfaction_a ~ a_h*wnc_a + p_h*wnc_p + ar_h*recovery_a +
                   pr_h*recovery_p + c_h*has_children + d_h*dual_earner
  satisfaction_p ~ a_w*wnc_p + p_w*wnc_a + ar_w*recovery_p +
                   pr_w*recovery_a + c_w*has_children + d_w*dual_earner
  satisfaction_a ~ int_a*1
  satisfaction_p ~ int_p*1
  satisfaction_a ~~ var_a*satisfaction_a
  satisfaction_p ~~ var_p*satisfaction_p
  wnc_a ~~ wnc_p
  recovery_a ~~ recovery_p
  satisfaction_a ~~ res_cov*satisfaction_p
'

fit_unconstrained <- sem(model_unconstrained, data = ddw)
summary(fit_unconstrained, standardized = TRUE, fit.measures = TRUE)
lavaan 0.6-21 ended normally after 37 iterations

  Estimator                                         ML
  Optimization method                           NLMINB
  Number of model parameters                        27

  Number of observations                           100

Model Test User Model:
                                                      
  Test statistic                                43.676
  Degrees of freedom                                12
  P-value (Chi-square)                           0.000

Model Test Baseline Model:

  Test statistic                               284.840
  Degrees of freedom                                27
  P-value                                        0.000

User Model versus Baseline Model:

  Comparative Fit Index (CFI)                    0.877
  Tucker-Lewis Index (TLI)                       0.724

Loglikelihood and Information Criteria:

  Loglikelihood user model (H0)               -646.240
  Loglikelihood unrestricted model (H1)       -624.402
                                                      
  Akaike (AIC)                                1346.481
  Bayesian (BIC)                              1416.820
  Sample-size adjusted Bayesian (SABIC)       1331.547

Root Mean Square Error of Approximation:

  RMSEA                                          0.162
  90 Percent confidence interval - lower         0.112
  90 Percent confidence interval - upper         0.216
  P-value H_0: RMSEA <= 0.050                    0.000
  P-value H_0: RMSEA >= 0.080                    0.995

Standardized Root Mean Square Residual:

  SRMR                                           0.145

Parameter Estimates:

  Standard errors                             Standard
  Information                                 Expected
  Information saturated (h1) model          Structured

Regressions:
                   Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
  satisfaction_a ~                                                      
    wnc_a    (a_h)   -0.296    0.051   -5.794    0.000   -0.296   -0.470
    wnc_p    (p_h)   -0.116    0.056   -2.093    0.036   -0.116   -0.170
    recvry_ (ar_h)    0.240    0.052    4.633    0.000    0.240    0.330
    rcvry_p (pr_h)    0.076    0.049    1.568    0.117    0.076    0.112
    hs_chld  (c_h)    0.089    0.092    0.962    0.336    0.089    0.066
    dul_rnr  (d_h)    0.318    0.097    3.266    0.001    0.318    0.225
  satisfaction_p ~                                                      
    wnc_p    (a_w)   -0.276    0.055   -5.040    0.000   -0.276   -0.390
    wnc_a    (p_w)   -0.170    0.050   -3.369    0.001   -0.170   -0.261
    rcvry_p (ar_w)    0.254    0.048    5.303    0.000    0.254    0.360
    recvry_ (pr_w)    0.145    0.051    2.837    0.005    0.145    0.193
    hs_chld  (c_w)   -0.023    0.091   -0.251    0.802   -0.023   -0.016
    dul_rnr  (d_w)    0.287    0.096    2.995    0.003    0.287    0.197

Covariances:
                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
  wnc_a ~~                                                               
    wnc_p              0.516    0.110    4.682    0.000    0.516    0.530
  recovery_a ~~                                                          
    rcvry_p            0.218    0.087    2.502    0.012    0.218    0.258
 .satisfaction_a ~~                                                      
   .stsfct_ (rs_c)     0.045    0.020    2.233    0.026    0.045    0.229

Intercepts:
                   Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
   .stsfc_ (int_a)    5.040    0.211   23.875    0.000    5.040    7.765
   .stsfc_ (int_p)    4.580    0.208   22.040    0.000    4.580    6.842
    wnc_a             0.247    0.103    2.397    0.017    0.247    0.240
    wnc_p            -0.033    0.095   -0.353    0.724   -0.033   -0.035
    rcvry_            3.005    0.089   33.684    0.000    3.005    3.368
    rcvry_            3.235    0.095   34.131    0.000    3.235    3.413

Variances:
                   Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
   .stsfct_ (var_)    0.199    0.028    7.071    0.000    0.199    0.472
   .stsfct_ (vr_p)    0.193    0.027    7.071    0.000    0.193    0.430
    wnc_a             1.058    0.150    7.071    0.000    1.058    1.000
    wnc_p             0.896    0.127    7.071    0.000    0.896    1.000
    recvry_           0.796    0.113    7.071    0.000    0.796    1.000
    rcvry_p           0.898    0.127    7.071    0.000    0.898    1.000

Model 2: slopes equal, intercepts free

The slopes-equal model constrains actor and partner slopes to be the same. Intercepts and residual variances are free to differ.

Code
model_slopes_equal <- '
  satisfaction_a ~ a*wnc_a + p*wnc_p + ar*recovery_a +
                   pr*recovery_p + c*has_children + d*dual_earner
  satisfaction_p ~ a*wnc_p + p*wnc_a + ar*recovery_p +
                   pr*recovery_a + c*has_children + d*dual_earner
  satisfaction_a ~ int_a*1
  satisfaction_p ~ int_p*1
  satisfaction_a ~~ var_h*satisfaction_a
  satisfaction_p ~~ var_w*satisfaction_p
  wnc_a ~~ wnc_p
  recovery_a ~~ recovery_p
  satisfaction_a ~~ res_cov*satisfaction_p
'

fit_slopes_equal <- sem(model_slopes_equal, data = ddw)
summary(fit_slopes_equal, standardized = TRUE, fit.measures = TRUE)
lavaan 0.6-21 ended normally after 33 iterations

  Estimator                                         ML
  Optimization method                           NLMINB
  Number of model parameters                        27
  Number of equality constraints                     6

  Number of observations                           100

Model Test User Model:
                                                      
  Test statistic                                47.242
  Degrees of freedom                                18
  P-value (Chi-square)                           0.000

Model Test Baseline Model:

  Test statistic                               284.840
  Degrees of freedom                                27
  P-value                                        0.000

User Model versus Baseline Model:

  Comparative Fit Index (CFI)                    0.887
  Tucker-Lewis Index (TLI)                       0.830

Loglikelihood and Information Criteria:

  Loglikelihood user model (H0)               -648.023
  Loglikelihood unrestricted model (H1)       -624.402
                                                      
  Akaike (AIC)                                1338.046
  Bayesian (BIC)                              1392.755
  Sample-size adjusted Bayesian (SABIC)       1326.432

Root Mean Square Error of Approximation:

  RMSEA                                          0.127
  90 Percent confidence interval - lower         0.084
  90 Percent confidence interval - upper         0.172
  P-value H_0: RMSEA <= 0.050                    0.003
  P-value H_0: RMSEA >= 0.080                    0.962

Standardized Root Mean Square Residual:

  SRMR                                           0.146

Parameter Estimates:

  Standard errors                             Standard
  Information                                 Expected
  Information saturated (h1) model          Structured

Regressions:
                   Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
  satisfaction_a ~                                                      
    wnc_a      (a)   -0.288    0.035   -8.141    0.000   -0.288   -0.446
    wnc_p      (p)   -0.146    0.035   -4.141    0.000   -0.146   -0.208
    recovery_ (ar)    0.241    0.034    6.998    0.000    0.241    0.324
    recovry_p (pr)    0.111    0.034    3.214    0.001    0.111    0.158
    hs_chldrn  (c)    0.025    0.072    0.347    0.728    0.025    0.018
    dual_ernr  (d)    0.304    0.076    4.006    0.000    0.304    0.210
  satisfaction_p ~                                                      
    wnc_p      (a)   -0.288    0.035   -8.141    0.000   -0.288   -0.416
    wnc_a      (p)   -0.146    0.035   -4.141    0.000   -0.146   -0.230
    recovry_p (ar)    0.241    0.034    6.998    0.000    0.241    0.349
    recovery_ (pr)    0.111    0.034    3.214    0.001    0.111    0.151
    hs_chldrn  (c)    0.025    0.072    0.347    0.728    0.025    0.018
    dual_ernr  (d)    0.304    0.076    4.006    0.000    0.304    0.213

Covariances:
                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
  wnc_a ~~                                                               
    wnc_p              0.516    0.110    4.682    0.000    0.516    0.530
  recovery_a ~~                                                          
    rcvry_p            0.218    0.087    2.502    0.012    0.218    0.258
 .satisfaction_a ~~                                                      
   .stsfct_ (rs_c)     0.043    0.020    2.122    0.034    0.043    0.217

Intercepts:
                   Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
   .stsfc_ (int_a)    4.955    0.167   29.664    0.000    4.955    7.462
   .stsfc_ (int_p)    4.688    0.167   28.103    0.000    4.688    7.158
    wnc_a             0.247    0.103    2.397    0.017    0.247    0.240
    wnc_p            -0.033    0.095   -0.353    0.724   -0.033   -0.035
    rcvry_            3.005    0.089   33.684    0.000    3.005    3.368
    rcvry_            3.235    0.095   34.131    0.000    3.235    3.413

Variances:
                   Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
   .stsfct_ (vr_h)    0.202    0.029    7.071    0.000    0.202    0.459
   .stsfct_ (vr_w)    0.196    0.028    7.071    0.000    0.196    0.456
    wnc_a             1.058    0.150    7.071    0.000    1.058    1.000
    wnc_p             0.896    0.127    7.071    0.000    0.896    1.000
    recvry_           0.796    0.113    7.071    0.000    0.796    1.000
    rcvry_p           0.898    0.127    7.071    0.000    0.898    1.000

Model 3: fully constrained (slopes, intercepts, variances equal)

The fully-constrained model forces every parameter to be equal across the two roles. This is the indistinguishable model.

Code
model_full_equal <- '
  satisfaction_a ~ a*wnc_a + p*wnc_p + ar*recovery_a +
                   pr*recovery_p + c*has_children + d*dual_earner
  satisfaction_p ~ a*wnc_p + p*wnc_a + ar*recovery_p +
                   pr*recovery_a + c*has_children + d*dual_earner
  satisfaction_a ~ alpha*1
  satisfaction_p ~ alpha*1
  satisfaction_a ~~ var_res*satisfaction_a
  satisfaction_p ~~ var_res*satisfaction_p
  wnc_a ~~ wnc_p
  recovery_a ~~ recovery_p
  satisfaction_a ~~ res_cov*satisfaction_p
'

fit_full_equal <- sem(model_full_equal, data = ddw)
summary(fit_full_equal, standardized = TRUE, fit.measures = TRUE)
lavaan 0.6-21 ended normally after 27 iterations

  Estimator                                         ML
  Optimization method                           NLMINB
  Number of model parameters                        27
  Number of equality constraints                     8

  Number of observations                           100

Model Test User Model:
                                                      
  Test statistic                                66.150
  Degrees of freedom                                20
  P-value (Chi-square)                           0.000

Model Test Baseline Model:

  Test statistic                               284.840
  Degrees of freedom                                27
  P-value                                        0.000

User Model versus Baseline Model:

  Comparative Fit Index (CFI)                    0.821
  Tucker-Lewis Index (TLI)                       0.758

Loglikelihood and Information Criteria:

  Loglikelihood user model (H0)               -657.477
  Loglikelihood unrestricted model (H1)       -624.402
                                                      
  Akaike (AIC)                                1352.955
  Bayesian (BIC)                              1402.453
  Sample-size adjusted Bayesian (SABIC)       1342.446

Root Mean Square Error of Approximation:

  RMSEA                                          0.152
  90 Percent confidence interval - lower         0.112
  90 Percent confidence interval - upper         0.193
  P-value H_0: RMSEA <= 0.050                    0.000
  P-value H_0: RMSEA >= 0.080                    0.998

Standardized Root Mean Square Residual:

  SRMR                                           0.150

Parameter Estimates:

  Standard errors                             Standard
  Information                                 Expected
  Information saturated (h1) model          Structured

Regressions:
                   Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
  satisfaction_a ~                                                      
    wnc_a      (a)   -0.256    0.037   -6.971    0.000   -0.256   -0.394
    wnc_p      (p)   -0.178    0.037   -4.846    0.000   -0.178   -0.252
    recovery_ (ar)    0.226    0.036    6.300    0.000    0.226    0.302
    recovry_p (pr)    0.125    0.036    3.476    0.001    0.125    0.177
    hs_chldrn  (c)    0.026    0.072    0.364    0.716    0.026    0.019
    dual_ernr  (d)    0.305    0.076    4.010    0.000    0.305    0.209
  satisfaction_p ~                                                      
    wnc_p      (a)   -0.256    0.037   -6.971    0.000   -0.256   -0.363
    wnc_a      (p)   -0.178    0.037   -4.846    0.000   -0.178   -0.275
    recovry_p (ar)    0.226    0.036    6.300    0.000    0.226    0.321
    recovery_ (pr)    0.125    0.036    3.476    0.001    0.125    0.167
    hs_chldrn  (c)    0.026    0.072    0.364    0.716    0.026    0.019
    dual_ernr  (d)    0.305    0.076    4.010    0.000    0.305    0.209

Covariances:
                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
  wnc_a ~~                                                               
    wnc_p              0.516    0.110    4.682    0.000    0.516    0.530
  recovery_a ~~                                                          
    rcvry_p            0.218    0.087    2.502    0.012    0.218    0.258
 .satisfaction_a ~~                                                      
   .stsfct_ (rs_c)     0.027    0.022    1.246    0.213    0.027    0.126

Intercepts:
                   Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
   .stsfct_ (alph)    4.824    0.164   29.359    0.000    4.824    7.221
   .stsfct_ (alph)    4.824    0.164   29.359    0.000    4.824    7.236
    wnc_a             0.247    0.103    2.397    0.017    0.247    0.240
    wnc_p            -0.033    0.095   -0.353    0.724   -0.033   -0.035
    recvry_           3.005    0.089   33.684    0.000    3.005    3.368
    rcvry_p           3.235    0.095   34.131    0.000    3.235    3.413

Variances:
                   Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
   .stsfct_ (vr_r)    0.215    0.022    9.922    0.000    0.215    0.482
   .stsfct_ (vr_r)    0.215    0.022    9.922    0.000    0.215    0.484
    wnc_a             1.058    0.150    7.071    0.000    1.058    1.000
    wnc_p             0.896    0.127    7.071    0.000    0.896    1.000
    recvry_           0.796    0.113    7.071    0.000    0.796    1.000
    rcvry_p           0.898    0.127    7.071    0.000    0.898    1.000

The three nested LRTs

Code
cat("LRT 1: Unconstrained vs Slopes Equal\n")
LRT 1: Unconstrained vs Slopes Equal
Code
cat("(Do slopes differ by gender?)\n\n")
(Do slopes differ by gender?)
Code
lrt1 <- lavTestLRT(fit_unconstrained, fit_slopes_equal)
print(lrt1)

Chi-Squared Difference Test

                  Df    AIC    BIC  Chisq Chisq diff RMSEA Df diff Pr(>Chisq)
fit_unconstrained 12 1346.5 1416.8 43.676                                    
fit_slopes_equal  18 1338.0 1392.8 47.242     3.5659     0       6     0.7352
Code
cat("\nLRT 2: Slopes Equal vs Fully Constrained\n")

LRT 2: Slopes Equal vs Fully Constrained
Code
cat("(Do intercepts differ by gender?)\n\n")
(Do intercepts differ by gender?)
Code
lrt2 <- lavTestLRT(fit_slopes_equal, fit_full_equal)
print(lrt2)

Chi-Squared Difference Test

                 Df  AIC    BIC  Chisq Chisq diff   RMSEA Df diff Pr(>Chisq)
fit_slopes_equal 18 1338 1392.8 47.242                                      
fit_full_equal   20 1353 1402.5 66.150     18.909 0.29076       2  7.836e-05
                    
fit_slopes_equal    
fit_full_equal   ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Code
cat("\nLRT 3: Unconstrained vs Fully Constrained\n")

LRT 3: Unconstrained vs Fully Constrained
Code
cat("(Overall indistinguishability)\n\n")
(Overall indistinguishability)
Code
lrt3 <- lavTestLRT(fit_unconstrained, fit_full_equal)
print(lrt3)

Chi-Squared Difference Test

                  Df    AIC    BIC  Chisq Chisq diff   RMSEA Df diff Pr(>Chisq)
fit_unconstrained 12 1346.5 1416.8 43.676                                      
fit_full_equal    20 1353.0 1402.5 66.150     22.474 0.13451       8   0.004109
                    
fit_unconstrained   
fit_full_equal    **
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
TipReading the three tests
  • LRT 1 tests whether actor and partner slopes are equal. A non-significant p-value supports the couple pattern (equal slopes). Significant p-value means the dyads are distinguishable by effects, not just by means.
  • LRT 2 tests whether the intercepts and residual variances are equal, given equal slopes. A significant p-value means the dyads are distinguishable by means.
  • LRT 3 tests overall indistinguishability (slopes, intercepts, residual variances all equal). A non-significant p-value means the dyads are indistinguishable on every parameter.

In our data, LRT 1 should be non-significant (equal slopes) and LRT 2 should be significant (different intercepts). This is the standard empirical pattern: distinguishable by means, not by effects.

Fit indices comparison

Build a side-by-side table of fit measures for the three models.

Code
fits <- c("chisq", "df", "pvalue", "cfi", "tli", "rmsea", "srmr")

fit_indices <- data.frame(
  unconstrained   = fitMeasures(fit_unconstrained,   fits),
  slopes_equal    = fitMeasures(fit_slopes_equal,    fits),
  full_equal      = fitMeasures(fit_full_equal,      fits)
)

print(round(fit_indices, 4))
       unconstrained slopes_equal full_equal
chisq        43.6759      47.2417    66.1502
df           12.0000      18.0000    20.0000
pvalue        0.0000       0.0002     0.0000
cfi           0.8771       0.8866     0.8210
tli           0.7236       0.8299     0.7584
rmsea         0.1625       0.1275     0.1519
srmr          0.1451       0.1459     0.1503

What to take away

Key takeaways

  • The wide-format SEM gives you the three classical tests of distinguishability: slopes, intercepts, overall.
  • LRT 1 (slopes) is the most important test for the APIM: it tells you whether the effects of predictors differ by role.
  • LRT 2 (intercepts) tells you whether the dyads are distinguishable by means (often they are, even when slopes are equal).
  • LRT 3 (overall) is the strict test; it is rarely used in practice because LRT 1 and LRT 2 are more informative.