Skip to contents

The following resource was made in advance of the 2023 SPSP Close Relationships Preconference, and is part of a working paper. Please cite the following if you are using these materials:

tl;dr:

Latent Actor-Partner Interdependence Models (APIMs) are a way to specify the common APIM model while commandeering the benefits of latent variables. Namely:

  • accounting for measurement error in the assessments of X and Y for both partners, and;
  • having a statistical framework (SEM) that allows you to interrogate other auxiliary assumptions that underlie your model and any comparisons within it that you wish to make

The specifics of the benefits (i.e., to what degree they help/hinder, and under what methodological circumstances) of using a Latent APIM vs. other approaches are not well known, and are the current target of study for Eric Tu’s comps paper. While this research is ongoing, this vignette should serve as a reasonable starting tutorial for those wishing to experiment with applying latent APIMs.

The Observed APIM vs. Latent APIM in SEM

As the informal poll in the RRIG facebook group illustrates, SEM is clearly not the go-to analytic framework for dyadic data analysis (and we hope to encourage more usage of SEM). Furthermore, even when people describe using SEM for dyadic data analysis, they usually do not mean that they specify their models with latent variables (i.e., in a way that would derive most of the available benefits of the SEM framework).

Specifically, most SEM-users of dyadic data analysis would–for a model like the APIM–first create average or sum scores for their given measures of interest (e.g., X and Y), and then use these composite scores in structural equation modeling software to specify a model that looks something like this:

Although it is technically true this is a structural equation model, it’s a fairly basic one that is essentially a multivariate path analysis model. This can be fine, for certain purposes, but it is not a structural equation model that has been specified with full measurement models of each of the latent variables under evaluation. We therefore call this model an “observed APIM” as it is modeled with fully observed (i.e., non-latent) composite variables.

A latent APIM, with a fully specified measurement model of each measure being analyzed, would look something like this:

Fitting The Latent APIM with dySEM

Example Data and Scraping Variable Names

library(dySEM)
library(lavaan)
library(semPlot)

dat <- commitmentQ
names(dat)
#>  [1] "sat.g.1_1" "sat.g.1_2" "sat.g.1_3" "sat.g.1_4" "sat.g.1_5" "com.1_1"  
#>  [7] "com.1_2"   "com.1_3"   "com.1_4"   "com.1_5"   "sat.g.2_1" "sat.g.2_2"
#> [13] "sat.g.2_3" "sat.g.2_4" "sat.g.2_5" "com.2_1"   "com.2_2"   "com.2_3"  
#> [19] "com.2_4"   "com.2_5"

The example dataset we are using contains items assessing relationship satisfaction and commitment (five items each, for both partners). As with any use of dySEM, we begin by scraping the variables which we are attempting to model. We first need to identify the repetitious “naming pattern” that is applied to the satisfaction items (see (here)[https://jsakaluk.github.io/dySEM/articles/varnames.html] if you need a refresher on these). We see the items correspond to a “Stem” (e.g., sat.g), “Partner” (“1” or “2”), “Item number” (1-5) or “spi” ordering, in which “.” is used to separate stem from partner, and “_” is used to separate partner from item number. We assign this to an object (arbitrarily) called “dvn” (as I think of this list as capturing information about (d)yad (v)ariable (n)ames):

dvn <- scrapeVarCross(dat = commitmentQ, #data set to scrape from
                        #var name patterns for X indicators
                        x_order = "spi", x_stem = "sat.g",  x_delim1 = ".", x_delim2="_",
                        #var name patterns for Y indicators
                        y_order="spi", y_stem="com", y_delim1 = ".", y_delim2="_",
                        #character used to distinguish between names for P1 and P2
                        distinguish_1="1", distinguish_2="2")
dvn
#> $p1xvarnames
#> [1] "sat.g.1_1" "sat.g.1_2" "sat.g.1_3" "sat.g.1_4" "sat.g.1_5"
#> 
#> $p2xvarnames
#> [1] "sat.g.2_1" "sat.g.2_2" "sat.g.2_3" "sat.g.2_4" "sat.g.2_5"
#> 
#> $xindper
#> [1] 5
#> 
#> $dist1
#> [1] "1"
#> 
#> $dist2
#> [1] "2"
#> 
#> $p1yvarnames
#> [1] "com.1_1" "com.1_2" "com.1_3" "com.1_4" "com.1_5"
#> 
#> $p2yvarnames
#> [1] "com.2_1" "com.2_2" "com.2_3" "com.2_4" "com.2_5"
#> 
#> $yindper
#> [1] 5
#> 
#> $indnum
#> [1] 20

We can visually confirm that the list contains:

  • $p1xvarnames: the five variable names for Partner 1’s satisfaction item responses
  • $p2xvarnames: the five variable names for Partner 2’s satisfaction item responses
  • $xindper: the number of items for Latent X (in this case, Satisfaction) for each partner
  • $dist1: the distinguishing character for the first partner
  • $dist2: the distinguishing character for the second partner
  • $p1yvarnames: the five variable names for Partner 1’s commitment item responses
  • $p2yvarnames: the five variable names for Partner 2’s commitment item responses
  • $yindper: the number of items for Latent Y (in this case, Commitment) for each partner
  • $indnum: the total number of items in the SEM model to be scripted

These pieces of information are all that is needed for dySEM to automate scripting latent APIMs (as well as other latent dyadic models like the CFM and MIM) with a variety of specification options.

Example Analysis

dySEM makes the rest of the process of fitting latent APIMs straightforward. We first need to use dySEM scripter functions to generate the correct code for lavaan to fit our latent APIM.

Model Scripting


apim.script.config <-  scriptAPIM(dvn, #the list we just created from scrapeVarCross
                                  lvxname = "Sat", #arbitrary name for LV X
                                  lvyname = "Com", #arbitrary name for LV Y
                                  constr_dy_x_meas = "none", #configurally invariant latent x
                                  constr_dy_y_meas = "none",#configurally invariant latent y
                                  constr_dy_x_struct =  "none", #no structural constraints for latent x
                                  constr_dy_y_struct = "none", #no structural constraints for latent y
                                  constr_dy_xy_struct = "none", #no constrained actor and/or partner effects
                                  est_k = TRUE,#want k-parameter? (optional, but nice)
                                  writeTo = tempdir(), fileName = "APIM_script_config") #want script saved to directory? (e.g., for OSF?)

If you return the output of scriptAPIM(), it doesn’t look particularly nice:

#> [1] "#Measurement Model\n\n#Loadings\nSat1=~NA*sat.g.1_1+sat.g.1_2+sat.g.1_3+sat.g.1_4+sat.g.1_5\nSat2=~NA*sat.g.2_1+sat.g.2_2+sat.g.2_3+sat.g.2_4+sat.g.2_5\n\nCom1=~NA*com.1_1+com.1_2+com.1_3+com.1_4+com.1_5\nCom2=~NA*com.2_1+com.2_2+com.2_3+com.2_4+com.2_5\n\n#Intercepts\nsat.g.1_1 ~ 1\nsat.g.1_2 ~ 1\nsat.g.1_3 ~ 1\nsat.g.1_4 ~ 1\nsat.g.1_5 ~ 1\n\nsat.g.2_1 ~ 1\nsat.g.2_2 ~ 1\nsat.g.2_3 ~ 1\nsat.g.2_4 ~ 1\nsat.g.2_5 ~ 1\n\ncom.1_1 ~ 1\ncom.1_2 ~ 1\ncom.1_3 ~ 1\ncom.1_4 ~ 1\ncom.1_5 ~ 1\n\ncom.2_1 ~ 1\ncom.2_2 ~ 1\ncom.2_3 ~ 1\ncom.2_4 ~ 1\ncom.2_5 ~ 1\n\n#Residual Variances\nsat.g.1_1 ~~ sat.g.1_1\nsat.g.1_2 ~~ sat.g.1_2\nsat.g.1_3 ~~ sat.g.1_3\nsat.g.1_4 ~~ sat.g.1_4\nsat.g.1_5 ~~ sat.g.1_5\n\nsat.g.2_1 ~~ sat.g.2_1\nsat.g.2_2 ~~ sat.g.2_2\nsat.g.2_3 ~~ sat.g.2_3\nsat.g.2_4 ~~ sat.g.2_4\nsat.g.2_5 ~~ sat.g.2_5\n\ncom.1_1 ~~ com.1_1\ncom.1_2 ~~ com.1_2\ncom.1_3 ~~ com.1_3\ncom.1_4 ~~ com.1_4\ncom.1_5 ~~ com.1_5\n\ncom.2_1 ~~ com.2_1\ncom.2_2 ~~ com.2_2\ncom.2_3 ~~ com.2_3\ncom.2_4 ~~ com.2_4\ncom.2_5 ~~ com.2_5\n\n#Residual Covariances\nsat.g.1_1 ~~ sat.g.2_1\nsat.g.1_2 ~~ sat.g.2_2\nsat.g.1_3 ~~ sat.g.2_3\nsat.g.1_4 ~~ sat.g.2_4\nsat.g.1_5 ~~ sat.g.2_5\n\ncom.1_1 ~~ com.2_1\ncom.1_2 ~~ com.2_2\ncom.1_3 ~~ com.2_3\ncom.1_4 ~~ com.2_4\ncom.1_5 ~~ com.2_5\n\n#Structural Model\n\n#Latent (Co)Variances\nSat1 ~~ 1*Sat1\nSat2 ~~ 1*Sat2\nSat1 ~~ Sat2\n\nCom1 ~~ 1*Com1\nCom2 ~~ 1*Com2\nCom1 ~~ Com2\n\n#Latent Means\nSat1 ~ 0*1\nSat2 ~ 0*1\n\nCom1 ~ 0*1\nCom2 ~ 0*1\n\n#Latent Actor Effects\nCom1 ~ a1*Sat1\nCom2 ~ a2*Sat2\n\n#Latent Partner Effects\nCom1 ~ p1*Sat2\nCom2 ~ p2*Sat1\n\n#k Parameter\nk1 := p1/a1\nk2 := p2/a2"

Rest assured, lavaan can make sense of this applesauce; all the required text is there, and with a light touch of the concatenate function (which will parse the line-breaks in the text of the script), you can see a friendly human-readable version of what scriptAPIM() generated:

cat(apim.script.config)
#> #Measurement Model
#> 
#> #Loadings
#> Sat1=~NA*sat.g.1_1+sat.g.1_2+sat.g.1_3+sat.g.1_4+sat.g.1_5
#> Sat2=~NA*sat.g.2_1+sat.g.2_2+sat.g.2_3+sat.g.2_4+sat.g.2_5
#> 
#> Com1=~NA*com.1_1+com.1_2+com.1_3+com.1_4+com.1_5
#> Com2=~NA*com.2_1+com.2_2+com.2_3+com.2_4+com.2_5
#> 
#> #Intercepts
#> sat.g.1_1 ~ 1
#> sat.g.1_2 ~ 1
#> sat.g.1_3 ~ 1
#> sat.g.1_4 ~ 1
#> sat.g.1_5 ~ 1
#> 
#> sat.g.2_1 ~ 1
#> sat.g.2_2 ~ 1
#> sat.g.2_3 ~ 1
#> sat.g.2_4 ~ 1
#> sat.g.2_5 ~ 1
#> 
#> com.1_1 ~ 1
#> com.1_2 ~ 1
#> com.1_3 ~ 1
#> com.1_4 ~ 1
#> com.1_5 ~ 1
#> 
#> com.2_1 ~ 1
#> com.2_2 ~ 1
#> com.2_3 ~ 1
#> com.2_4 ~ 1
#> com.2_5 ~ 1
#> 
#> #Residual Variances
#> sat.g.1_1 ~~ sat.g.1_1
#> sat.g.1_2 ~~ sat.g.1_2
#> sat.g.1_3 ~~ sat.g.1_3
#> sat.g.1_4 ~~ sat.g.1_4
#> sat.g.1_5 ~~ sat.g.1_5
#> 
#> sat.g.2_1 ~~ sat.g.2_1
#> sat.g.2_2 ~~ sat.g.2_2
#> sat.g.2_3 ~~ sat.g.2_3
#> sat.g.2_4 ~~ sat.g.2_4
#> sat.g.2_5 ~~ sat.g.2_5
#> 
#> com.1_1 ~~ com.1_1
#> com.1_2 ~~ com.1_2
#> com.1_3 ~~ com.1_3
#> com.1_4 ~~ com.1_4
#> com.1_5 ~~ com.1_5
#> 
#> com.2_1 ~~ com.2_1
#> com.2_2 ~~ com.2_2
#> com.2_3 ~~ com.2_3
#> com.2_4 ~~ com.2_4
#> com.2_5 ~~ com.2_5
#> 
#> #Residual Covariances
#> sat.g.1_1 ~~ sat.g.2_1
#> sat.g.1_2 ~~ sat.g.2_2
#> sat.g.1_3 ~~ sat.g.2_3
#> sat.g.1_4 ~~ sat.g.2_4
#> sat.g.1_5 ~~ sat.g.2_5
#> 
#> com.1_1 ~~ com.2_1
#> com.1_2 ~~ com.2_2
#> com.1_3 ~~ com.2_3
#> com.1_4 ~~ com.2_4
#> com.1_5 ~~ com.2_5
#> 
#> #Structural Model
#> 
#> #Latent (Co)Variances
#> Sat1 ~~ 1*Sat1
#> Sat2 ~~ 1*Sat2
#> Sat1 ~~ Sat2
#> 
#> Com1 ~~ 1*Com1
#> Com2 ~~ 1*Com2
#> Com1 ~~ Com2
#> 
#> #Latent Means
#> Sat1 ~ 0*1
#> Sat2 ~ 0*1
#> 
#> Com1 ~ 0*1
#> Com2 ~ 0*1
#> 
#> #Latent Actor Effects
#> Com1 ~ a1*Sat1
#> Com2 ~ a2*Sat2
#> 
#> #Latent Partner Effects
#> Com1 ~ p1*Sat2
#> Com2 ~ p2*Sat1
#> 
#> #k Parameter
#> k1 := p1/a1
#> k2 := p2/a2

We can now immediately pass all of these models to lavaan for fitting.

Model Fitting

apim.fit.config <- cfa(apim.script.config, 
                               data = commitmentQ,
                               std.lv = FALSE, 
                               auto.fix.first= FALSE, 
                               meanstructure = TRUE)

Inspecting Output

And we evaluate focal lavaan output


summary(apim.fit.config, 
        standardized = TRUE,
        fit.measures = TRUE, 
        rsquare = TRUE)
#> lavaan 0.6.17 ended normally after 63 iterations
#> 
#>   Estimator                                         ML
#>   Optimization method                           NLMINB
#>   Number of model parameters                        76
#> 
#>                                                   Used       Total
#>   Number of observations                           110         118
#> 
#> Model Test User Model:
#>                                                       
#>   Test statistic                               339.833
#>   Degrees of freedom                               154
#>   P-value (Chi-square)                           0.000
#> 
#> Model Test Baseline Model:
#> 
#>   Test statistic                              2207.988
#>   Degrees of freedom                               190
#>   P-value                                        0.000
#> 
#> User Model versus Baseline Model:
#> 
#>   Comparative Fit Index (CFI)                    0.908
#>   Tucker-Lewis Index (TLI)                       0.886
#> 
#> Loglikelihood and Information Criteria:
#> 
#>   Loglikelihood user model (H0)              -3992.007
#>   Loglikelihood unrestricted model (H1)      -3822.090
#>                                                       
#>   Akaike (AIC)                                8136.013
#>   Bayesian (BIC)                              8341.250
#>   Sample-size adjusted Bayesian (SABIC)       8101.087
#> 
#> Root Mean Square Error of Approximation:
#> 
#>   RMSEA                                          0.105
#>   90 Percent confidence interval - lower         0.090
#>   90 Percent confidence interval - upper         0.120
#>   P-value H_0: RMSEA <= 0.050                    0.000
#>   P-value H_0: RMSEA >= 0.080                    0.996
#> 
#> Standardized Root Mean Square Residual:
#> 
#>   SRMR                                           0.076
#> 
#> Parameter Estimates:
#> 
#>   Standard errors                             Standard
#>   Information                                 Expected
#>   Information saturated (h1) model          Structured
#> 
#> Latent Variables:
#>                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
#>   Sat1 =~                                                               
#>     sat.g.1_1         2.109    0.161   13.059    0.000    2.109    0.938
#>     sat.g.1_2         1.879    0.169   11.095    0.000    1.879    0.849
#>     sat.g.1_3         2.105    0.165   12.739    0.000    2.105    0.925
#>     sat.g.1_4         1.947    0.166   11.707    0.000    1.947    0.881
#>     sat.g.1_5         1.863    0.184   10.104    0.000    1.863    0.801
#>   Sat2 =~                                                               
#>     sat.g.2_1         1.847    0.149   12.412    0.000    1.847    0.911
#>     sat.g.2_2         1.850    0.149   12.457    0.000    1.850    0.911
#>     sat.g.2_3         1.858    0.149   12.470    0.000    1.858    0.913
#>     sat.g.2_4         1.604    0.154   10.430    0.000    1.604    0.819
#>     sat.g.2_5         1.918    0.161   11.934    0.000    1.918    0.890
#>   Com1 =~                                                               
#>     com.1_1           1.258    0.112   11.207    0.000    1.770    0.923
#>     com.1_2           1.113    0.132    8.429    0.000    1.566    0.737
#>     com.1_3          -0.084    0.208   -0.402    0.688   -0.118   -0.039
#>     com.1_4          -0.191    0.205   -0.932    0.351   -0.268   -0.086
#>     com.1_5           1.258    0.115   10.912    0.000    1.770    0.898
#>   Com2 =~                                                               
#>     com.2_1           1.541    0.130   11.842    0.000    2.017    0.919
#>     com.2_2           1.460    0.120   12.200    0.000    1.910    0.938
#>     com.2_3           0.422    0.221    1.912    0.056    0.552    0.180
#>     com.2_4          -0.016    0.212   -0.077    0.939   -0.021   -0.007
#>     com.2_5           1.477    0.125   11.833    0.000    1.932    0.920
#> 
#> Regressions:
#>                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
#>   Com1 ~                                                                
#>     Sat1      (a1)    0.227    0.178    1.276    0.202    0.161    0.161
#>   Com2 ~                                                                
#>     Sat2      (a2)    0.921    0.200    4.598    0.000    0.704    0.704
#>   Com1 ~                                                                
#>     Sat2      (p1)    0.805    0.199    4.044    0.000    0.572    0.572
#>   Com2 ~                                                                
#>     Sat1      (p2)   -0.104    0.173   -0.603    0.547   -0.080   -0.080
#> 
#> Covariances:
#>                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
#>  .sat.g.1_1 ~~                                                          
#>    .sat.g.2_1        -0.201    0.086   -2.343    0.019   -0.201   -0.308
#>  .sat.g.1_2 ~~                                                          
#>    .sat.g.2_2         0.344    0.115    2.994    0.003    0.344    0.351
#>  .sat.g.1_3 ~~                                                          
#>    .sat.g.2_3        -0.181    0.090   -2.015    0.044   -0.181   -0.254
#>  .sat.g.1_4 ~~                                                          
#>    .sat.g.2_4         0.134    0.127    1.059    0.290    0.134    0.114
#>  .sat.g.1_5 ~~                                                          
#>    .sat.g.2_5         0.267    0.149    1.790    0.073    0.267    0.195
#>  .com.1_1 ~~                                                            
#>    .com.2_1          -0.183    0.098   -1.862    0.063   -0.183   -0.287
#>  .com.1_2 ~~                                                            
#>    .com.2_2          -0.181    0.129   -1.404    0.160   -0.181   -0.178
#>  .com.1_3 ~~                                                            
#>    .com.2_3           3.152    0.932    3.380    0.001    3.152    0.341
#>  .com.1_4 ~~                                                            
#>    .com.2_4           4.386    0.989    4.434    0.000    4.386    0.467
#>  .com.1_5 ~~                                                            
#>    .com.2_5          -0.029    0.098   -0.298    0.766   -0.029   -0.041
#>   Sat1 ~~                                                               
#>     Sat2              0.766    0.044   17.320    0.000    0.766    0.766
#>  .Com1 ~~                                                               
#>    .Com2              0.385    0.098    3.947    0.000    0.385    0.385
#> 
#> Intercepts:
#>                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
#>    .sat.g.1_1         6.609    0.214   30.830    0.000    6.609    2.939
#>    .sat.g.1_2         6.591    0.211   31.229    0.000    6.591    2.978
#>    .sat.g.1_3         6.391    0.217   29.452    0.000    6.391    2.808
#>    .sat.g.1_4         6.673    0.211   31.674    0.000    6.673    3.020
#>    .sat.g.1_5         6.445    0.222   29.082    0.000    6.445    2.773
#>    .sat.g.2_1         6.918    0.193   35.775    0.000    6.918    3.411
#>    .sat.g.2_2         6.927    0.194   35.780    0.000    6.927    3.411
#>    .sat.g.2_3         6.727    0.194   34.679    0.000    6.727    3.307
#>    .sat.g.2_4         7.155    0.187   38.325    0.000    7.155    3.654
#>    .sat.g.2_5         6.864    0.205   33.410    0.000    6.864    3.186
#>    .com.1_1           7.527    0.183   41.154    0.000    7.527    3.924
#>    .com.1_2           7.236    0.203   35.699    0.000    7.236    3.404
#>    .com.1_3           4.809    0.292   16.478    0.000    4.809    1.571
#>    .com.1_4           4.300    0.297   14.489    0.000    4.300    1.381
#>    .com.1_5           7.282    0.188   38.760    0.000    7.282    3.696
#>    .com.2_1           7.327    0.209   35.025    0.000    7.327    3.339
#>    .com.2_2           7.345    0.194   37.831    0.000    7.345    3.607
#>    .com.2_3           5.118    0.293   17.461    0.000    5.118    1.665
#>    .com.2_4           4.136    0.289   14.315    0.000    4.136    1.365
#>    .com.2_5           7.191    0.200   35.895    0.000    7.191    3.422
#>     Sat1              0.000                               0.000    0.000
#>     Sat2              0.000                               0.000    0.000
#>    .Com1              0.000                               0.000    0.000
#>    .Com2              0.000                               0.000    0.000
#> 
#> Variances:
#>                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
#>    .sat.g.1_1         0.609    0.123    4.954    0.000    0.609    0.120
#>    .sat.g.1_2         1.368    0.207    6.596    0.000    1.368    0.279
#>    .sat.g.1_3         0.747    0.137    5.435    0.000    0.747    0.144
#>    .sat.g.1_4         1.092    0.173    6.320    0.000    1.092    0.224
#>    .sat.g.1_5         1.934    0.282    6.858    0.000    1.934    0.358
#>    .sat.g.2_1         0.702    0.120    5.865    0.000    0.702    0.171
#>    .sat.g.2_2         0.701    0.119    5.875    0.000    0.701    0.170
#>    .sat.g.2_3         0.686    0.118    5.827    0.000    0.686    0.166
#>    .sat.g.2_4         1.261    0.186    6.798    0.000    1.261    0.329
#>    .sat.g.2_5         0.963    0.154    6.241    0.000    0.963    0.207
#>    .com.1_1           0.547    0.152    3.587    0.000    0.547    0.149
#>    .com.1_2           2.067    0.309    6.701    0.000    2.067    0.457
#>    .com.1_3           9.355    1.262    7.415    0.000    9.355    0.999
#>    .com.1_4           9.617    1.297    7.412    0.000    9.617    0.993
#>    .com.1_5           0.750    0.167    4.482    0.000    0.750    0.193
#>    .com.2_1           0.748    0.145    5.159    0.000    0.748    0.155
#>    .com.2_2           0.499    0.113    4.410    0.000    0.499    0.120
#>    .com.2_3           9.146    1.235    7.404    0.000    9.146    0.968
#>    .com.2_4           9.184    1.238    7.416    0.000    9.184    1.000
#>    .com.2_5           0.681    0.131    5.185    0.000    0.681    0.154
#>     Sat1              1.000                               1.000    1.000
#>     Sat2              1.000                               1.000    1.000
#>    .Com1              1.000                               0.505    0.505
#>    .Com2              1.000                               0.584    0.584
#> 
#> R-Square:
#>                    Estimate
#>     sat.g.1_1         0.880
#>     sat.g.1_2         0.721
#>     sat.g.1_3         0.856
#>     sat.g.1_4         0.776
#>     sat.g.1_5         0.642
#>     sat.g.2_1         0.829
#>     sat.g.2_2         0.830
#>     sat.g.2_3         0.834
#>     sat.g.2_4         0.671
#>     sat.g.2_5         0.793
#>     com.1_1           0.851
#>     com.1_2           0.543
#>     com.1_3           0.001
#>     com.1_4           0.007
#>     com.1_5           0.807
#>     com.2_1           0.845
#>     com.2_2           0.880
#>     com.2_3           0.032
#>     com.2_4           0.000
#>     com.2_5           0.846
#>     Com1              0.495
#>     Com2              0.416
#> 
#> Defined Parameters:
#>                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
#>     k1                3.545    3.423    1.036    0.300    3.545    3.545
#>     k2               -0.113    0.170   -0.664    0.507   -0.113   -0.113

semPaths(apim.fit.config, "std")

Example of More Specialized Models (and Cautions)

scriptAPIM() also enables users to impose constraints of “structural indistinguishability” (e.g., estimating one actor and/or partner effect for both partners). However, the statistical conclusion validity of comparing models with these kinds of constraints to models in which actor and/or partner effects are freely estimated (as in the previous version) depends, in part, on having ensured dyadic measurement invariance, particularly for the loadings (other comparisons require other forms of invariance to be met).

scriptAPIM() will allow you to script models imposing structural constraints (via the equate = argument), regardless of what level of measurement invariance is imposed (if any) (via the constr_dy_xy_struct = argument).


apim.script.config.actpart <-  scriptAPIM(dvn, #the list we just created from scrapeVarCross
                                  lvxname = "Sat", #arbitrary name for LV X
                                  lvyname = "Com", #arbitrary name for LV Y
                                  constr_dy_x_meas = "none", #configurally invariant latent x
                                  constr_dy_y_meas = "none",#configurally invariant latent y
                                  constr_dy_x_struct =  "none", #no structural constraints for latent x
                                  constr_dy_y_struct = "none", #no structural constraints for latent y
                                  constr_dy_xy_struct = c("actors", "partners"), # constrained actor and/or partner effects
                                  est_k = TRUE,#want k-parameter? (optional, but nice)
                                  writeTo = tempdir(), fileName = "APIM_script_config") #want script saved to directory? (e.g., for OSF?)

One can therefore impose the corresponding level of invariance via the constr_dy_x_meas = and constr_dy_y_meas = arguments (at minimum "loadings" is required, but any models that are even more constrained, e.g., c("loadings", "intercepts", "residuals") would also be acceptable).


apim.script.loads.actpart <-  scriptAPIM(dvn, #the list we just created from scrapeVarCross
                                  lvxname = "Sat", #arbitrary name for LV X
                                  lvyname = "Com", #arbitrary name for LV Y
                                  constr_dy_x_meas = "loadings", #configurally invariant latent x
                                  constr_dy_y_meas = "loadings",#configurally invariant latent y
                                  constr_dy_x_struct =  "none", #no structural constraints for latent x
                                  constr_dy_y_struct = "none", #no structural constraints for latent y
                                  constr_dy_xy_struct = c("actors", "partners"), # constrained actor and/or partner effects
                                  est_k = TRUE,#want k-parameter? (optional, but nice)
                                  writeTo = tempdir(), fileName = "APIM_script_loading") #want script saved to directory? (e.g., for OSF?)

apim.fit.loads.eq.all <- cfa(apim.script.loads.actpart, 
                               data = commitmentQ,
                               std.lv = FALSE, 
                               auto.fix.first= FALSE, 
                               meanstructure = TRUE)

summary(apim.fit.loads.eq.all, 
        standardized = TRUE,
        fit.measures = TRUE, 
        rsquare = TRUE)
#> lavaan 0.6.17 ended normally after 63 iterations
#> 
#>   Estimator                                         ML
#>   Optimization method                           NLMINB
#>   Number of model parameters                        78
#>   Number of equality constraints                    12
#> 
#>                                                   Used       Total
#>   Number of observations                           110         118
#> 
#> Model Test User Model:
#>                                                       
#>   Test statistic                               359.838
#>   Degrees of freedom                               164
#>   P-value (Chi-square)                           0.000
#> 
#> Model Test Baseline Model:
#> 
#>   Test statistic                              2207.988
#>   Degrees of freedom                               190
#>   P-value                                        0.000
#> 
#> User Model versus Baseline Model:
#> 
#>   Comparative Fit Index (CFI)                    0.903
#>   Tucker-Lewis Index (TLI)                       0.888
#> 
#> Loglikelihood and Information Criteria:
#> 
#>   Loglikelihood user model (H0)              -4002.009
#>   Loglikelihood unrestricted model (H1)      -3822.090
#>                                                       
#>   Akaike (AIC)                                8136.018
#>   Bayesian (BIC)                              8314.250
#>   Sample-size adjusted Bayesian (SABIC)       8105.687
#> 
#> Root Mean Square Error of Approximation:
#> 
#>   RMSEA                                          0.104
#>   90 Percent confidence interval - lower         0.090
#>   90 Percent confidence interval - upper         0.119
#>   P-value H_0: RMSEA <= 0.050                    0.000
#>   P-value H_0: RMSEA >= 0.080                    0.996
#> 
#> Standardized Root Mean Square Residual:
#> 
#>   SRMR                                           0.089
#> 
#> Parameter Estimates:
#> 
#>   Standard errors                             Standard
#>   Information                                 Expected
#>   Information saturated (h1) model          Structured
#> 
#> Latent Variables:
#>                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
#>   Sat1 =~                                                               
#>     st.g.1_1 (lx1)    2.061    0.151   13.683    0.000    2.061    0.935
#>     st.g.1_2 (lx2)    1.964    0.159   12.375    0.000    1.964    0.862
#>     st.g.1_3 (lx3)    2.060    0.152   13.529    0.000    2.060    0.918
#>     st.g.1_4 (lx4)    1.854    0.151   12.295    0.000    1.854    0.868
#>     st.g.1_5 (lx5)    2.002    0.166   12.051    0.000    2.002    0.821
#>   Sat2 =~                                                               
#>     st.g.2_1 (lx1)    2.061    0.151   13.683    0.000    1.900    0.917
#>     st.g.2_2 (lx2)    1.964    0.159   12.375    0.000    1.811    0.905
#>     st.g.2_3 (lx3)    2.060    0.152   13.529    0.000    1.899    0.919
#>     st.g.2_4 (lx4)    1.854    0.151   12.295    0.000    1.709    0.834
#>     st.g.2_5 (lx5)    2.002    0.166   12.051    0.000    1.845    0.880
#>   Com1 =~                                                               
#>     com.1_1  (ly1)    1.313    0.109   12.083    0.000    1.786    0.926
#>     com.1_2  (ly2)    1.232    0.108   11.356    0.000    1.676    0.761
#>     com.1_3  (ly3)    0.185    0.159    1.164    0.244    0.252    0.081
#>     com.1_4  (ly4)   -0.075    0.159   -0.475    0.635   -0.103   -0.033
#>     com.1_5  (ly5)    1.280    0.109   11.796    0.000    1.742    0.892
#>   Com2 =~                                                               
#>     com.2_1  (ly1)    1.313    0.109   12.083    0.000    1.998    0.916
#>     com.2_2  (ly2)    1.232    0.108   11.356    0.000    1.876    0.935
#>     com.2_3  (ly3)    0.185    0.159    1.164    0.244    0.282    0.093
#>     com.2_4  (ly4)   -0.075    0.159   -0.475    0.635   -0.115   -0.038
#>     com.2_5  (ly5)    1.280    0.109   11.796    0.000    1.950    0.923
#> 
#> Regressions:
#>                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
#>   Com1 ~                                                                
#>     Sat1       (a)    0.623    0.134    4.656    0.000    0.458    0.458
#>   Com2 ~                                                                
#>     Sat2       (a)    0.623    0.134    4.656    0.000    0.377    0.377
#>   Com1 ~                                                                
#>     Sat2       (p)    0.383    0.125    3.074    0.002    0.259    0.259
#>   Com2 ~                                                                
#>     Sat1       (p)    0.383    0.125    3.074    0.002    0.251    0.251
#> 
#> Covariances:
#>                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
#>  .sat.g.1_1 ~~                                                          
#>    .sat.g.2_1        -0.170    0.085   -2.005    0.045   -0.170   -0.264
#>  .sat.g.1_2 ~~                                                          
#>    .sat.g.2_2         0.344    0.115    2.977    0.003    0.344    0.350
#>  .sat.g.1_3 ~~                                                          
#>    .sat.g.2_3        -0.195    0.091   -2.134    0.033   -0.195   -0.270
#>  .sat.g.1_4 ~~                                                          
#>    .sat.g.2_4         0.110    0.129    0.858    0.391    0.110    0.092
#>  .sat.g.1_5 ~~                                                          
#>    .sat.g.2_5         0.257    0.151    1.704    0.088    0.257    0.186
#>  .com.1_1 ~~                                                            
#>    .com.2_1          -0.154    0.099   -1.556    0.120   -0.154   -0.241
#>  .com.1_2 ~~                                                            
#>    .com.2_2          -0.221    0.130   -1.700    0.089   -0.221   -0.217
#>  .com.1_3 ~~                                                            
#>    .com.2_3           3.145    0.942    3.337    0.001    3.145    0.336
#>  .com.1_4 ~~                                                            
#>    .com.2_4           4.428    0.994    4.453    0.000    4.428    0.469
#>  .com.1_5 ~~                                                            
#>    .com.2_5          -0.040    0.098   -0.407    0.684   -0.040   -0.056
#>   Sat1 ~~                                                               
#>     Sat2              0.709    0.063   11.322    0.000    0.770    0.770
#>  .Com1 ~~                                                               
#>    .Com2              0.534    0.125    4.257    0.000    0.436    0.436
#> 
#> Intercepts:
#>                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
#>    .sat.g.1_1         6.609    0.210   31.448    0.000    6.609    2.998
#>    .sat.g.1_2         6.591    0.217   30.325    0.000    6.591    2.891
#>    .sat.g.1_3         6.391    0.214   29.884    0.000    6.391    2.849
#>    .sat.g.1_4         6.673    0.204   32.781    0.000    6.673    3.126
#>    .sat.g.1_5         6.445    0.232   27.736    0.000    6.445    2.644
#>    .sat.g.2_1         6.918    0.198   35.025    0.000    6.918    3.340
#>    .sat.g.2_2         6.927    0.191   36.334    0.000    6.927    3.464
#>    .sat.g.2_3         6.727    0.197   34.149    0.000    6.727    3.256
#>    .sat.g.2_4         7.155    0.195   36.611    0.000    7.155    3.491
#>    .sat.g.2_5         6.864    0.200   34.350    0.000    6.864    3.275
#>    .com.1_1           7.527    0.184   40.935    0.000    7.527    3.903
#>    .com.1_2           7.236    0.210   34.476    0.000    7.236    3.287
#>    .com.1_3           4.809    0.295   16.277    0.000    4.809    1.552
#>    .com.1_4           4.300    0.298   14.453    0.000    4.300    1.378
#>    .com.1_5           7.282    0.186   39.101    0.000    7.282    3.728
#>    .com.2_1           7.327    0.208   35.231    0.000    7.327    3.359
#>    .com.2_2           7.345    0.191   38.391    0.000    7.345    3.660
#>    .com.2_3           5.118    0.290   17.623    0.000    5.118    1.680
#>    .com.2_4           4.136    0.289   14.319    0.000    4.136    1.365
#>    .com.2_5           7.191    0.201   35.724    0.000    7.191    3.406
#>     Sat1              0.000                               0.000    0.000
#>     Sat2              0.000                               0.000    0.000
#>    .Com1              0.000                               0.000    0.000
#>    .Com2              0.000                               0.000    0.000
#> 
#> Variances:
#>                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
#>    .sat.g.1_1         0.610    0.119    5.111    0.000    0.610    0.126
#>    .sat.g.1_2         1.338    0.206    6.494    0.000    1.338    0.258
#>    .sat.g.1_3         0.789    0.140    5.639    0.000    0.789    0.157
#>    .sat.g.1_4         1.120    0.174    6.444    0.000    1.120    0.246
#>    .sat.g.1_5         1.934    0.286    6.769    0.000    1.934    0.326
#>    .sat.g.2_1         0.682    0.119    5.738    0.000    0.682    0.159
#>    .sat.g.2_2         0.720    0.121    5.968    0.000    0.720    0.180
#>    .sat.g.2_3         0.664    0.117    5.695    0.000    0.664    0.156
#>    .sat.g.2_4         1.280    0.190    6.724    0.000    1.280    0.305
#>    .sat.g.2_5         0.987    0.155    6.349    0.000    0.987    0.225
#>    .com.1_1           0.531    0.142    3.736    0.000    0.531    0.143
#>    .com.1_2           2.037    0.307    6.634    0.000    2.037    0.420
#>    .com.1_3           9.539    1.287    7.413    0.000    9.539    0.993
#>    .com.1_4           9.727    1.312    7.416    0.000    9.727    0.999
#>    .com.1_5           0.780    0.159    4.920    0.000    0.780    0.205
#>    .com.2_1           0.765    0.145    5.285    0.000    0.765    0.161
#>    .com.2_2           0.509    0.112    4.556    0.000    0.509    0.126
#>    .com.2_3           9.199    1.241    7.413    0.000    9.199    0.991
#>    .com.2_4           9.167    1.236    7.416    0.000    9.167    0.999
#>    .com.2_5           0.656    0.130    5.055    0.000    0.656    0.147
#>     Sat1              1.000                               1.000    1.000
#>     Sat2              0.850    0.115    7.366    0.000    1.000    1.000
#>    .Com1              1.000                               0.540    0.540
#>    .Com2              1.504    0.307    4.896    0.000    0.649    0.649
#> 
#> R-Square:
#>                    Estimate
#>     sat.g.1_1         0.874
#>     sat.g.1_2         0.742
#>     sat.g.1_3         0.843
#>     sat.g.1_4         0.754
#>     sat.g.1_5         0.674
#>     sat.g.2_1         0.841
#>     sat.g.2_2         0.820
#>     sat.g.2_3         0.844
#>     sat.g.2_4         0.695
#>     sat.g.2_5         0.775
#>     com.1_1           0.857
#>     com.1_2           0.580
#>     com.1_3           0.007
#>     com.1_4           0.001
#>     com.1_5           0.795
#>     com.2_1           0.839
#>     com.2_2           0.874
#>     com.2_3           0.009
#>     com.2_4           0.001
#>     com.2_5           0.853
#>     Com1              0.460
#>     Com2              0.351
#> 
#> Defined Parameters:
#>                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
#>     k                 0.614    0.274    2.245    0.025    0.566    0.566

Users should note that structural constraints will have downstream impacts on the computation of parameter k if it is requested (k = TRUE). That is, two k’s will be returned if either actor and/or partner effects are uniquely estimated, but only one k will be returned if both actor and partner effects are constrained to equivalency (as in the example above).