Search code examples
rr-lavaan

Plot interaction effect in sem model with observed variables in R


I am estimating an SEM model that has observed variables. I am using SEM to handle missing data using FIML. My model has an interaction term to test for moderation. Here is a toy example that illustrates the issue.

library(lavaan)
library(car)
library(dplyr)

data(starwars)

sw2 <- starwars %>% mutate(
  male = Recode(sex, "'male' = 1; NA=NA; else = 0"),
  human = Recode(species, "'Human' = 1; NA=NA; else = 0"),
  maleXby = male * birth_year,
)

mod <- 'mass ~ height + human + male + birth_year + maleXby'
fit <- sem(mod, data = sw2, missing="fiml.x")
summary(fit)

What I want to do is plot the interaction term like a margin plot, to visualize the interaction effect. But package like library(interactions) does not work with an object of class lavaan. How could I visualize this? Is there a package (like interactions) that makes this easier.


Solution

  • You could fit this model using lm(), but I think you want to be able to use FIML estimates, yes? In that case, you could use the emmeans package, which can work on lavaan-class objects if you have the semTools package loaded.

    You didn't say which predictor was focal vs. moderator, but I assume you want to treat male as moderator because it is a grouping variable. The example below can be adapted by switching their roles in the pairs() function, as well as by selecting different birth_year levels at= which to probe the effect of male. When birth_year is the focal predictor, its linear effect will be the same regardless of which levels are chosen, so I chose the full range() below.

    library(emmeans)
    library(semTools)
    
    ## for ease of use, fit model using colon operator
    mod <- 'mass ~ height + human + male + birth_year + male:birth_year'
    fit <- sem(mod, data = sw2, missing = "fiml.x")
    
    ## calculate expected marginal means for multiple 
    ## levels of male (1:0) and birth_year
    BYrange <- range(sw2$birth_year, na.rm = TRUE)
    em.mass <- emmeans(fit, specs = ~ birth_year | male, 
                       at = list(male = 1:0, birth_year = BYrange),
                       # because SEMs can have multiple DVs:
                       lavaan.DV = "mass")
    em.mass
    ## probe effect of year across sex
    rbind(pairs(em.mass))
    ## plot effect of year across sex
    emmip(em.mass, male ~ birth_year)   # 2 lines in same plot
    emmip(em.mass, ~ birth_year | male) # in separate panels