Skip to contents

By default, modsem() creates product indicators for you based on the interaction specified in your model. Behind the scenes, modsem() generates a total of 9 variables (product indicators) that are used as the indicators for your latent product.

m1 <- '
# Outer Model
X =~ x1 + x2 + x3
Y =~ y1 + y2 + y3
Z =~ z1 + z2 + z3

# Inner model
Y ~ X + Z + X:Z 
'

est1 <- modsem(m1, oneInt)
cat(est1$syntax)
#> X =~ x1
#> X =~ x2
#> X =~ x3
#> Y =~ y1
#> Y =~ y2
#> Y =~ y3
#> Z =~ z1
#> Z =~ z2
#> Z =~ z3
#> Y ~ X
#> Y ~ Z
#> Y ~ XZ
#> XZ =~ 1*x1z1
#> XZ =~ x2z1
#> XZ =~ x3z1
#> XZ =~ x1z2
#> XZ =~ x2z2
#> XZ =~ x3z2
#> XZ =~ x1z3
#> XZ =~ x2z3
#> XZ =~ x3z3
#> x1z1 ~~ 0*x2z2
#> x1z1 ~~ 0*x2z3
#> x1z1 ~~ 0*x3z2
#> x1z1 ~~ 0*x3z3
#> x1z2 ~~ 0*x2z1
#> x1z2 ~~ 0*x2z3
#> x1z2 ~~ 0*x3z1
#> x1z2 ~~ 0*x3z3
#> x1z3 ~~ 0*x2z1
#> x1z3 ~~ 0*x2z2
#> x1z3 ~~ 0*x3z1
#> x1z3 ~~ 0*x3z2
#> x2z1 ~~ 0*x3z2
#> x2z1 ~~ 0*x3z3
#> x2z2 ~~ 0*x3z1
#> x2z2 ~~ 0*x3z3
#> x2z3 ~~ 0*x3z1
#> x2z3 ~~ 0*x3z2
#> x1z1 ~~ x1z2
#> x1z1 ~~ x1z3
#> x1z1 ~~ x2z1
#> x1z1 ~~ x3z1
#> x1z2 ~~ x1z3
#> x1z2 ~~ x2z2
#> x1z2 ~~ x3z2
#> x1z3 ~~ x2z3
#> x1z3 ~~ x3z3
#> x2z1 ~~ x2z2
#> x2z1 ~~ x2z3
#> x2z1 ~~ x3z1
#> x2z2 ~~ x2z3
#> x2z2 ~~ x3z2
#> x2z3 ~~ x3z3
#> x3z1 ~~ x3z2
#> x3z1 ~~ x3z3
#> x3z2 ~~ x3z3

While this is often sufficient, you might want more control over how these indicators are created. In general, modsem() offers two mechanisms for controlling the creation of product indicators: 1. By specifying the measurement model for your latent product yourself. 2. By using the mean() and sum() functions, collectively known as parceling operations.

Specifying the Measurement Model

By default, modsem() creates all possible combinations of product indicators. However, another common approach is to match the indicators by order. For example, let’s say you have an interaction between the latent variables X and Z: X =~ x1 + x2 and Z =~ z1 + z2. By default, you would get XZ =~ x1z1 + x1z2 + x2z1 + x2z2. If you prefer to use the matching approach, you would expect XZ =~ x1z1 + x2z2 instead. To achieve this, you can use the match = TRUE argument.

m2 <- '
# Outer Model
X =~ x1 + x2
Y =~ y1 + y2
Z =~ z1 + z2

# Inner model
Y ~ X + Z + X:Z 
'

est2 <- modsem(m2, oneInt, match = TRUE)
summary(est2)
#> modsem (version 1.0.4, approach = dblcent):
#> lavaan 0.6-19 ended normally after 41 iterations
#> 
#>   Estimator                                         ML
#>   Optimization method                           NLMINB
#>   Number of model parameters                        22
#> 
#>   Number of observations                          2000
#> 
#> Model Test User Model:
#>                                                       
#>   Test statistic                                11.355
#>   Degrees of freedom                                14
#>   P-value (Chi-square)                           0.658
#> 
#> Parameter Estimates:
#> 
#>   Standard errors                             Standard
#>   Information                                 Expected
#>   Information saturated (h1) model          Structured
#> 
#> Latent Variables:
#>                    Estimate  Std.Err  z-value  P(>|z|)
#>   X =~                                                
#>     x1                1.000                           
#>     x2                0.819    0.021   38.127    0.000
#>   Y =~                                                
#>     y1                1.000                           
#>     y2                0.807    0.010   82.495    0.000
#>   Z =~                                                
#>     z1                1.000                           
#>     z2                0.836    0.024   35.392    0.000
#>   XZ =~                                               
#>     x1z1              1.000                           
#>     x2z2              0.645    0.024   26.904    0.000
#> 
#> Regressions:
#>                    Estimate  Std.Err  z-value  P(>|z|)
#>   Y ~                                                 
#>     X                 0.688    0.029   23.366    0.000
#>     Z                 0.576    0.029   20.173    0.000
#>     XZ                0.706    0.032   22.405    0.000
#> 
#> Covariances:
#>                    Estimate  Std.Err  z-value  P(>|z|)
#>  .x1z1 ~~                                             
#>    .x2z2              0.000                           
#>   X ~~                                                
#>     Z                 0.202    0.025    8.182    0.000
#>     XZ                0.003    0.026    0.119    0.905
#>   Z ~~                                                
#>     XZ                0.042    0.026    1.621    0.105
#> 
#> Variances:
#>                    Estimate  Std.Err  z-value  P(>|z|)
#>    .x1                0.179    0.022    8.029    0.000
#>    .x2                0.151    0.015    9.956    0.000
#>    .y1                0.184    0.021    8.577    0.000
#>    .y2                0.136    0.014    9.663    0.000
#>    .z1                0.197    0.025    7.802    0.000
#>    .z2                0.138    0.018    7.831    0.000
#>    .x1z1              0.319    0.035    9.141    0.000
#>    .x2z2              0.244    0.016   15.369    0.000
#>     X                 0.962    0.042   23.120    0.000
#>    .Y                 0.964    0.042   23.110    0.000
#>     Z                 0.987    0.044   22.260    0.000
#>     XZ                1.041    0.054   19.441    0.000

More Complicated Models

If you want even more control, you can use the get_pi_syntax() and get_pi_data() functions to extract the modified syntax and data from modsem(), allowing you to modify them as needed. This can be particularly useful in cases where you want to estimate a model using a feature in lavaan that isn’t available in modsem().

For example, the syntax for ordered and multigroup models (as of now) isn’t as flexible in modsem() as it is in lavaan. You can modify the auto-generated syntax (along with the altered dataset) from modsem() to suit your needs.

m3 <- '
# Outer Model
X =~ x1 + x2
Y =~ y1 + y2
Z =~ z1 + z2

# Inner model
Y ~ X + Z + X:Z 
'
syntax <- get_pi_syntax(m3)
cat(syntax)
#> X =~ x1
#> X =~ x2
#> Y =~ y1
#> Y =~ y2
#> Z =~ z1
#> Z =~ z2
#> Y ~ X
#> Y ~ Z
#> Y ~ XZ
#> XZ =~ 1*x1z1
#> XZ =~ x2z1
#> XZ =~ x1z2
#> XZ =~ x2z2
#> x1z1 ~~ 0*x2z2
#> x1z2 ~~ 0*x2z1
#> x1z1 ~~ x1z2
#> x1z1 ~~ x2z1
#> x1z2 ~~ x2z2
#> x2z1 ~~ x2z2
data <- get_pi_data(m3, oneInt)
head(data)
#>           x1         x2         y1         y2         z1         z2       x1z1
#> 1  2.4345722  1.3578655  1.4526897  0.9560888  0.8184825 1.60708140 -0.4823019
#> 2  0.2472734  0.2723201  0.5496756  0.7115311  3.6649148 2.60983102 -2.2680403
#> 3 -1.3647759 -0.5628205 -0.9835467 -0.6697747  1.7249386 2.10981827 -1.9137416
#> 4  3.0432836  2.2153763  6.4641465  4.7805981  2.5697116 3.26335379  2.9385205
#> 5  2.8148841  2.7029616  2.2860280  2.1457643  0.3467850 0.07164577 -1.4009548
#> 6 -0.5453450 -0.7530642  1.1294876  1.1998472 -0.2362958 0.60252657  1.7465860
#>         x2z1       x1z2       x2z2
#> 1 -0.1884837  0.3929380 -0.0730934
#> 2 -2.6637694 -1.2630544 -1.4547433
#> 3 -1.4299711 -2.3329864 -1.7383407
#> 4  1.3971422  3.9837389  1.9273102
#> 5 -1.1495704 -2.2058995 -1.8169042
#> 6  2.2950753  0.7717365  1.0568143