Skip to contents
library(modsem)
#> This is modsem (1.0.12). Please report any bugs!

The Problem

Interaction effects between two endogenous (i.e., dependent) variables work as you would expect for the product indicator methods ("dblcent", "rca", "ca", "uca"). However, for the lms and qml approaches, it is not as straightforward.

The lms and qml approaches can (by default) handle interaction effects between endogenous and exogenous (i.e., independent) variables, but they do not natively support interaction effects between two endogenous variables. When an interaction effect exists between two endogenous variables, the equations cannot easily be written in “reduced” form, meaning that normal estimation procedures won’t work.

The Solution

Despite these limitations, there is a workaround for both the lms and qml approaches. Essentially, the model can be split into two parts: one linear and one non-linear. You can replace the covariance matrix used in the estimation of the non-linear model with the model-implied covariance matrix from a linear model. This allows you to treat an endogenous variable as if it were exogenous—provided that it can be expressed in a linear model.

Example

Let’s consider the theory of planned behavior (TPB), where we wish to estimate the quadratic effect of INT on BEH (INT:INT). We can use the following model:

tpb <- '
# Outer Model (Based on Hagger et al., 2007)
  ATT =~ att1 + att2 + att3 + att4 + att5
  SN =~ sn1 + sn2
  PBC =~ pbc1 + pbc2 + pbc3
  INT =~ int1 + int2 + int3
  BEH =~ b1 + b2

# Inner Model (Based on Steinmetz et al., 2011)
  INT ~ ATT + SN + PBC
  BEH ~ INT + PBC
  BEH ~ INT:INT
'

Since INT is an endogenous variable, its quadratic term (i.e., an interaction effect with itself) would involve two endogenous variables. As a result, we would normally not be able to estimate this model using the lms or qml approaches. However, we can split the model into two parts: one linear and one non-linear.

While INT is an endogenous variable, it can be expressed in a linear model since it is not affected by any interaction terms:

tpb_linear <- 'INT ~ PBC + ATT + SN'

We can then remove this part from the original model, giving us:

tpb_nonlinear <- '
# Outer Model (Based on Hagger et al., 2007)
  ATT =~ att1 + att2 + att3 + att4 + att5
  SN =~ sn1 + sn2
  PBC =~ pbc1 + pbc2 + pbc3
  INT =~ int1 + int2 + int3
  BEH =~ b1 + b2

# Inner Model (Based on Steinmetz et al., 2011)
  BEH ~ INT + PBC
  BEH ~ INT:INT
'

Now, we can estimate the non-linear model since INT is treated as an exogenous variable. However, this would not incorporate the structural model for INT. To address this, we can instruct modsem to replace the covariance matrix (phi) of (INT, PBC, ATT, SN) with the model-implied covariance matrix from the linear model while estimating both models simultaneously. To achieve this, we use the cov.syntax argument in modsem:

est_lms <- modsem(tpb_nonlinear, data = TPB, cov.syntax = tpb_linear,
                  method = "lms")
#> Warning: It is recommended that you have at least 32 nodes for interaction
#> effects between endogenous variables in the lms approach 'nodes = 24'
summary(est_lms)
#> 
#> modsem (1.0.12) ended normally after 29 iterations
#> 
#>   Estimator                                        LMS
#>   Optimization method                       EMA-NLMINB
#>   Number of model parameters                        54
#>                                                       
#>   Number of observations                          2000
#>  
#> Loglikelihood and Information Criteria:
#>   Loglikelihood                              -26360.48
#>   Akaike (AIC)                                52828.96
#>   Bayesian (BIC)                              53131.41
#>  
#> Numerical Integration:
#>   Points of integration (per dim)                   24
#>   Dimensions                                         1
#>   Total points of integration                       24
#>  
#> Fit Measures for Baseline Model (H0):
#>   Loglikelihood                              -26393.22
#>   Akaike (AIC)                                52892.45
#>   Bayesian (BIC)                              53189.29
#>   Chi-square                                     66.27
#>   Degrees of Freedom (Chi-square)                   82
#>   P-value (Chi-square)                           0.897
#>   RMSEA                                          0.000
#>  
#> Comparative Fit to H0 (LRT test):
#>   Loglikelihood change                           32.74
#>   Difference test (D)                            65.49
#>   Degrees of freedom (D)                             1
#>   P-value (D)                                    0.000
#>  
#> R-Squared Interaction Model (H1):
#>   INT                                            0.369
#>   BEH                                            0.239
#> R-Squared Baseline Model (H0):
#>   INT                                            0.367
#>   BEH                                            0.210
#> R-Squared Change (H1 - H0):
#>   INT                                            0.003
#>   BEH                                            0.029
#> 
#> Parameter Estimates:
#>   Coefficients                          unstandardized
#>   Information                                 observed
#>   Standard errors                             standard
#>  
#> Latent Variables:
#>                  Estimate  Std.Error  z.value  P(>|z|)
#>   ATT =~        
#>     att1            1.000                             
#>     att2            0.878      0.012   71.582    0.000
#>     att3            0.789      0.012   66.390    0.000
#>     att4            0.695      0.011   61.010    0.000
#>     att5            0.887      0.013   70.870    0.000
#>   SN =~         
#>     sn1             1.000                             
#>     sn2             0.888      0.017   52.613    0.000
#>   PBC =~        
#>     pbc1            1.000                             
#>     pbc2            0.913      0.013   69.390    0.000
#>     pbc3            0.801      0.012   66.087    0.000
#>   INT =~        
#>     int1            1.000                             
#>     int2            0.914      0.015   58.995    0.000
#>     int3            0.807      0.015   55.616    0.000
#>   BEH =~        
#>     b1              1.000                             
#>     b2              0.960      0.032   29.910    0.000
#> 
#> Regressions:
#>                  Estimate  Std.Error  z.value  P(>|z|)
#>   INT ~         
#>     ATT             0.213      0.026    8.155    0.000
#>     SN              0.175      0.028    6.320    0.000
#>     PBC             0.222      0.030    7.482    0.000
#>   BEH ~         
#>     PBC             0.239      0.023   10.588    0.000
#>     INT             0.197      0.025    7.756    0.000
#>     INT:INT         0.128      0.016    7.897    0.000
#> 
#> Intercepts:
#>                  Estimate  Std.Error  z.value  P(>|z|)
#>    .int1            1.014      0.022   46.966    0.000
#>    .int2            1.012      0.020   50.412    0.000
#>    .int3            1.005      0.018   54.811    0.000
#>    .att1            1.014      0.024   42.012    0.000
#>    .att2            1.007      0.021   46.978    0.000
#>    .att3            1.016      0.020   51.467    0.000
#>    .att4            0.999      0.018   55.665    0.000
#>    .att5            0.992      0.022   45.682    0.000
#>    .sn1             1.006      0.024   41.678    0.000
#>    .sn2             1.011      0.022   46.728    0.000
#>    .pbc1            0.998      0.024   42.429    0.000
#>    .pbc2            0.985      0.022   44.952    0.000
#>    .pbc3            0.991      0.020   50.473    0.000
#>    .b1              0.999      0.023   42.629    0.000
#>    .b2              1.017      0.022   46.239    0.000
#> 
#> Covariances:
#>                  Estimate  Std.Error  z.value  P(>|z|)
#>   ATT ~~        
#>     SN              0.629      0.029   21.710    0.000
#>   PBC ~~        
#>     ATT             0.677      0.029   23.466    0.000
#>     SN              0.677      0.029   23.099    0.000
#> 
#> Variances:
#>                  Estimate  Std.Error  z.value  P(>|z|)
#>    .int1            0.158      0.009   18.229    0.000
#>    .int2            0.160      0.008   20.381    0.000
#>    .int3            0.168      0.007   23.640    0.000
#>    .att1            0.167      0.007   23.534    0.000
#>    .att2            0.150      0.006   24.719    0.000
#>    .att3            0.159      0.006   26.388    0.000
#>    .att4            0.162      0.006   27.650    0.000
#>    .att5            0.159      0.006   24.931    0.000
#>    .sn1             0.178      0.015   12.084    0.000
#>    .sn2             0.157      0.012   13.261    0.000
#>    .pbc1            0.145      0.008   18.445    0.000
#>    .pbc2            0.160      0.007   21.429    0.000
#>    .pbc3            0.154      0.006   23.804    0.000
#>    .b1              0.186      0.020    9.423    0.000
#>    .b2              0.135      0.018    7.594    0.000
#>     ATT             0.998      0.037   26.957    0.000
#>     SN              0.987      0.039   25.241    0.000
#>     PBC             0.961      0.036   27.064    0.000
#>    .INT             0.488      0.020   24.577    0.000
#>    .BEH             0.475      0.024   19.740    0.000

est_qml <- modsem(tpb_nonlinear, data = TPB, cov.syntax = tpb_linear,
                  method = "qml")
summary(est_qml)
#> 
#> modsem (1.0.12) ended normally after 61 iterations
#> 
#>   Estimator                                        QML
#>   Optimization method                           NLMINB
#>   Number of model parameters                        54
#>                                                       
#>   Number of observations                          2000
#>  
#> Loglikelihood and Information Criteria:
#>   Loglikelihood                              -26360.52
#>   Akaike (AIC)                                52829.04
#>   Bayesian (BIC)                              53131.49
#>  
#> Fit Measures for Baseline Model (H0):
#>   Loglikelihood                              -26393.22
#>   Akaike (AIC)                                52892.45
#>   Bayesian (BIC)                              53189.29
#>   Chi-square                                     66.27
#>   Degrees of Freedom (Chi-square)                   82
#>   P-value (Chi-square)                           0.897
#>   RMSEA                                          0.000
#>  
#> Comparative Fit to H0 (LRT test):
#>   Loglikelihood change                           32.70
#>   Difference test (D)                            65.41
#>   Degrees of freedom (D)                             1
#>   P-value (D)                                    0.000
#>  
#> R-Squared Interaction Model (H1):
#>   INT                                            0.370
#>   BEH                                            0.239
#> R-Squared Baseline Model (H0):
#>   INT                                            0.367
#>   BEH                                            0.210
#> R-Squared Change (H1 - H0):
#>   INT                                            0.003
#>   BEH                                            0.029
#> 
#> Parameter Estimates:
#>   Coefficients                          unstandardized
#>   Information                                 observed
#>   Standard errors                             standard
#>  
#> Latent Variables:
#>                  Estimate  Std.Error  z.value  P(>|z|)
#>   ATT =~        
#>     att1            1.000                             
#>     att2            0.878      0.012   71.563    0.000
#>     att3            0.789      0.012   66.373    0.000
#>     att4            0.695      0.011   60.999    0.000
#>     att5            0.887      0.013   70.851    0.000
#>   SN =~         
#>     sn1             1.000                             
#>     sn2             0.888      0.017   52.623    0.000
#>   PBC =~        
#>     pbc1            1.000                             
#>     pbc2            0.913      0.013   69.384    0.000
#>     pbc3            0.801      0.012   66.079    0.000
#>   INT =~        
#>     int1            1.000                             
#>     int2            0.914      0.015   59.036    0.000
#>     int3            0.807      0.015   55.653    0.000
#>   BEH =~        
#>     b1              1.000                             
#>     b2              0.960      0.032   29.902    0.000
#> 
#> Regressions:
#>                  Estimate  Std.Error  z.value  P(>|z|)
#>   INT ~         
#>     ATT             0.213      0.026    8.173    0.000
#>     SN              0.175      0.028    6.333    0.000
#>     PBC             0.222      0.030    7.507    0.000
#>   BEH ~         
#>     PBC             0.239      0.023   10.586    0.000
#>     INT             0.197      0.025    7.758    0.000
#>     INT:INT         0.128      0.016    7.879    0.000
#> 
#> Intercepts:
#>                  Estimate  Std.Error  z.value  P(>|z|)
#>    .int1            1.014      0.022   46.963    0.000
#>    .int2            1.012      0.020   50.404    0.000
#>    .int3            1.005      0.018   54.804    0.000
#>    .att1            1.014      0.024   42.007    0.000
#>    .att2            1.007      0.021   46.967    0.000
#>    .att3            1.016      0.020   51.455    0.000
#>    .att4            0.999      0.018   55.654    0.000
#>    .att5            0.992      0.022   45.672    0.000
#>    .sn1             1.006      0.024   41.658    0.000
#>    .sn2             1.010      0.022   46.707    0.000
#>    .pbc1            0.998      0.024   42.414    0.000
#>    .pbc2            0.985      0.022   44.934    0.000
#>    .pbc3            0.991      0.020   50.454    0.000
#>    .b1              0.999      0.023   42.638    0.000
#>    .b2              1.017      0.022   46.249    0.000
#> 
#> Covariances:
#>                  Estimate  Std.Error  z.value  P(>|z|)
#>   ATT ~~        
#>     SN              0.629      0.029   21.696    0.000
#>   PBC ~~        
#>     ATT             0.678      0.029   23.447    0.000
#>     SN              0.678      0.029   23.081    0.000
#> 
#> Variances:
#>                  Estimate  Std.Error  z.value  P(>|z|)
#>    .int1            0.158      0.009   18.219    0.000
#>    .int2            0.160      0.008   20.377    0.000
#>    .int3            0.168      0.007   23.630    0.000
#>    .att1            0.167      0.007   23.530    0.000
#>    .att2            0.150      0.006   24.712    0.000
#>    .att3            0.160      0.006   26.379    0.000
#>    .att4            0.162      0.006   27.646    0.000
#>    .att5            0.159      0.006   24.925    0.000
#>    .sn1             0.178      0.015   12.086    0.000
#>    .sn2             0.157      0.012   13.263    0.000
#>    .pbc1            0.145      0.008   18.444    0.000
#>    .pbc2            0.160      0.007   21.425    0.000
#>    .pbc3            0.154      0.006   23.799    0.000
#>    .b1              0.185      0.020    9.417    0.000
#>    .b2              0.135      0.018    7.596    0.000
#>     ATT             0.998      0.037   26.933    0.000
#>     SN              0.988      0.039   25.225    0.000
#>     PBC             0.962      0.036   27.041    0.000
#>    .INT             0.488      0.020   24.585    0.000
#>    .BEH             0.475      0.024   19.736    0.000

It is also possible to make modsem attempt to split up the model syntax automatically using the auto.split.syntax argument:

est_lms <- modsem(tpb, data = TPB, method = "lms", auto.split.syntax = TRUE)
#> Warning: It is recommended that you have at least 32 nodes for interaction
#> effects between endogenous variables in the lms approach 'nodes = 24'
summary(est_lms)
#> 
#> modsem (1.0.12) ended normally after 29 iterations
#> 
#>   Estimator                                        LMS
#>   Optimization method                       EMA-NLMINB
#>   Number of model parameters                        54
#>                                                       
#>   Number of observations                          2000
#>  
#> Loglikelihood and Information Criteria:
#>   Loglikelihood                              -26360.48
#>   Akaike (AIC)                                52828.96
#>   Bayesian (BIC)                              53131.41
#>  
#> Numerical Integration:
#>   Points of integration (per dim)                   24
#>   Dimensions                                         1
#>   Total points of integration                       24
#>  
#> Fit Measures for Baseline Model (H0):
#>   Loglikelihood                              -26393.22
#>   Akaike (AIC)                                52892.45
#>   Bayesian (BIC)                              53189.29
#>   Chi-square                                     66.27
#>   Degrees of Freedom (Chi-square)                   82
#>   P-value (Chi-square)                           0.897
#>   RMSEA                                          0.000
#>  
#> Comparative Fit to H0 (LRT test):
#>   Loglikelihood change                           32.74
#>   Difference test (D)                            65.49
#>   Degrees of freedom (D)                             1
#>   P-value (D)                                    0.000
#>  
#> R-Squared Interaction Model (H1):
#>   INT                                            0.369
#>   BEH                                            0.239
#> R-Squared Baseline Model (H0):
#>   INT                                            0.367
#>   BEH                                            0.210
#> R-Squared Change (H1 - H0):
#>   INT                                            0.003
#>   BEH                                            0.029
#> 
#> Parameter Estimates:
#>   Coefficients                          unstandardized
#>   Information                                 observed
#>   Standard errors                             standard
#>  
#> Latent Variables:
#>                  Estimate  Std.Error  z.value  P(>|z|)
#>   ATT =~        
#>     att1            1.000                             
#>     att2            0.878      0.012   71.582    0.000
#>     att3            0.789      0.012   66.390    0.000
#>     att4            0.695      0.011   61.010    0.000
#>     att5            0.887      0.013   70.870    0.000
#>   SN =~         
#>     sn1             1.000                             
#>     sn2             0.888      0.017   52.613    0.000
#>   PBC =~        
#>     pbc1            1.000                             
#>     pbc2            0.913      0.013   69.390    0.000
#>     pbc3            0.801      0.012   66.087    0.000
#>   INT =~        
#>     int1            1.000                             
#>     int2            0.914      0.015   58.995    0.000
#>     int3            0.807      0.015   55.616    0.000
#>   BEH =~        
#>     b1              1.000                             
#>     b2              0.960      0.032   29.910    0.000
#> 
#> Regressions:
#>                  Estimate  Std.Error  z.value  P(>|z|)
#>   INT ~         
#>     ATT             0.213      0.026    8.155    0.000
#>     SN              0.175      0.028    6.320    0.000
#>     PBC             0.222      0.030    7.482    0.000
#>   BEH ~         
#>     PBC             0.239      0.023   10.588    0.000
#>     INT             0.197      0.025    7.756    0.000
#>     INT:INT         0.128      0.016    7.897    0.000
#> 
#> Intercepts:
#>                  Estimate  Std.Error  z.value  P(>|z|)
#>    .int1            1.014      0.022   46.966    0.000
#>    .int2            1.012      0.020   50.412    0.000
#>    .int3            1.005      0.018   54.811    0.000
#>    .att1            1.014      0.024   42.012    0.000
#>    .att2            1.007      0.021   46.978    0.000
#>    .att3            1.016      0.020   51.467    0.000
#>    .att4            0.999      0.018   55.665    0.000
#>    .att5            0.992      0.022   45.682    0.000
#>    .sn1             1.006      0.024   41.678    0.000
#>    .sn2             1.011      0.022   46.728    0.000
#>    .pbc1            0.998      0.024   42.429    0.000
#>    .pbc2            0.985      0.022   44.952    0.000
#>    .pbc3            0.991      0.020   50.473    0.000
#>    .b1              0.999      0.023   42.629    0.000
#>    .b2              1.017      0.022   46.239    0.000
#> 
#> Covariances:
#>                  Estimate  Std.Error  z.value  P(>|z|)
#>   ATT ~~        
#>     SN              0.629      0.029   21.710    0.000
#>     PBC             0.677      0.029   23.466    0.000
#>   SN ~~         
#>     PBC             0.677      0.029   23.099    0.000
#> 
#> Variances:
#>                  Estimate  Std.Error  z.value  P(>|z|)
#>    .int1            0.158      0.009   18.229    0.000
#>    .int2            0.160      0.008   20.381    0.000
#>    .int3            0.168      0.007   23.640    0.000
#>    .att1            0.167      0.007   23.534    0.000
#>    .att2            0.150      0.006   24.719    0.000
#>    .att3            0.159      0.006   26.388    0.000
#>    .att4            0.162      0.006   27.650    0.000
#>    .att5            0.159      0.006   24.931    0.000
#>    .sn1             0.178      0.015   12.084    0.000
#>    .sn2             0.157      0.012   13.261    0.000
#>    .pbc1            0.145      0.008   18.445    0.000
#>    .pbc2            0.160      0.007   21.429    0.000
#>    .pbc3            0.154      0.006   23.804    0.000
#>    .b1              0.186      0.020    9.423    0.000
#>    .b2              0.135      0.018    7.594    0.000
#>     ATT             0.998      0.037   26.957    0.000
#>     SN              0.987      0.039   25.241    0.000
#>     PBC             0.961      0.036   27.064    0.000
#>    .INT             0.488      0.020   24.577    0.000
#>    .BEH             0.475      0.024   19.740    0.000