Skip to contents

By default, modsem() creates product indicators for you, based on the interaction specified in your model. Behind the scenes we can see that modsem() creates in total 9 variables (product indicators) 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

Whilst this often is sufficient, you might want some control over how these indicators are created. In general, modsem() has two mechanisms for giving control over the creating of indicator products: 1. By specifying the measurement model of your latent product your self, and 2. By using the mean() and sum() function, collectively known as parceling operations.

Specifying The Measurement Model

By default, modsem() creates all possible combinations of different product indicators. However, another common approach is to match the indicators by order. For example, let’s say you have an interaction between the laten variables X and Z: ‘X =~ x1 + x2’ and ‘Z =~ z1 + z2’. By default you would get ‘XZ =~ x1z1 + x1z2 + x2z1 + x2z2’. If you wanted to use the matching approach you would want to get ‘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: 
#> Method = dblcent
#> lavaan 0.6-18 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

I you want even more control you can use the get_pi_syntax() and get_pi_data() functions, such that you can extract the modified syntax and data from modsem, and alter them accordingly. This can be particularly useful in cases where you want to estimate a model using a feature in lavaan, which isn’t available in modsem. For example, (as of yet) the syntax for both ordered- and multigroup models isn’t as flexible as in lavaan. Thus you can modify the auto-generated syntax (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