Search code examples
rplotr-micer-lavaansemplot

Plotting a SEM Actor-Partner Interdependence Model after runMI()


I am using multiply-imputed data to run an Actor-Partner Interdependence Model (APIM; Cook & Kenny, 2005). I am using mice to impute my data and for reference, I am using this resource guide for constructing my APIM with lavaan.

As far as I can tell, there is only one other StackOverflow question that is related my issue. However, I was unable to directly apply that answer to my APIM. There is also a three-year-old Github issue with semPaths that doesn't seem to have made any progress since the other StackOverflow user posted about it.

I tried adjusting the other StackOverflow answer for my APIM by filtering out everything except the two actor effects and two partner effects. I think I'm almost there; if you compare the summary output with my third attempt at plotting, the summary estimates are similar to what's plotted by semPaths() but not exact.

What's causing the plotted estimates to be off? Is there a better way of plotting lavaan.mi objects that feels less hacky?

library(mice)
library(lavaan)
library(tidyverse)
library(faux)
library(missMethods)
library(semTools)
library(semPlot)
set.seed(1234)

## prepwork
# generate data
df <- faux::rnorm_multi(n = 100, vars = 4, mu = 3, sd = 1, varnames = c("x1", "x2", "y1", "y2")) %>%
  mutate(id = rep(1:100)) %>% select(id, everything()) %>%
  missMethods::delete_MCAR(cols = c("x1", "x2", "y1", "y2"), p = .15)
  
# make lavaan object
myapim <- "x1  ~ a1*y1; x2  ~ a2*y2; x1  ~ p12*y2; x2  ~ p21*y1; y1 ~ mx1*1; y2 ~ mx2*1; x1 ~ my1*1; x2 ~ my2*1
           y1 ~~ vx1*y1; y2 ~~ vx2*y2; x1 ~~ vy1*x1; x2 ~~ vy2*x2; y2 ~~ cx*y1; x2 ~~ cy*x1 
           a_diff := a1 - a2; p_diff := p12 - p21; k1 := p12/a1; k2 := p21/a2
           k_diff := k1 - k2; i_diff := my1 - my2; a_ave := (a1 + a2)/2; p_ave := (p12 + p21)/2
           i_ave := (my1 +my2)/2; sum1 := (p12 + a1)/2; sum2 := (p21 + a2)/2; cont1 := a1 - p12; cont2 := a2 - p21
"
apimd <- lavaan::sem(myapim, data = df, fixed.x = FALSE, missing = "default")

# impute data
imp <- mice::mice(df, m = 5, maxit = 10, seed = 1234, printFlag = FALSE)

# conduct SEM
sem_model <- runMI(model = myapim, data = imp, fun = "sem", miPackage = "mice", seed = 1234)

## plot
# try to plot lavaan.mi
semPaths(sem_model, what = "est", sizeMan = 25, sizeMan2 = 10, 
         label.cex = 1.2, edge.label.position = 0.45, edge.label.cex = 1.5) #  error
#> Error in (function (classes, fdef, mtable) : unable to find an inherited method for function 'semPlotModel_S4' for signature '"lavaan.mi"'

# try to use other StackOverflow answer directly
other_model <- sem(myapim,  data = df)
SEMPLOT <- semPlot::semPlotModel(other_model)
desired_output <- data.frame(standardizedsolution(sem_model))
SEMPLOT@Pars$std <- desired_output$est.std # error
#> Error in `$<-.data.frame`(`*tmp*`, std, value = structure(c(0.164373878998654, : replacement has 27 rows, data has 14

# try to adjust the other StackOverflow answer for my APIM
baseplot <- semPlot::semPlotModel(other_model)
desired_output <- data.frame(standardizedsolution(sem_model))
desired_output <- desired_output %>% filter(op != ":=")
baseplot@Pars$est <- desired_output$est.std
semPaths(baseplot, 
         nCharNodes = 0, what = "est",
         fade = FALSE, layout = "tree2", 
         rotation = 2, style = "ram",
         intercepts = FALSE, residuals = FALSE, 
         optimizeLatRes = TRUE, curve = 3.1, nDigits = 3,
         sizeMan = 12, sizeMan2 = 10, 
         label.cex = 1.2, edge.label.position = 0.45, edge.label.cex = 1.5)

enter image description here

# I only pasted part of summary() to keep it short
> summary(sem_model)
lavaan.mi object based on 5 imputed data sets. 
See class?lavaan.mi help page for available methods. 

Convergence information:
The model converged on 5 imputed data sets 

Rubin's (1987) rules were used to pool point and SE estimates across 5 imputed data sets, and to calculate degrees of freedom for each parameter's t test and CI.

Parameter Estimates:

  Standard errors                             Standard
  Information                                 Expected
  Information saturated (h1) model          Structured

Regressions:
                   Estimate  Std.Err  t-value       df  P(>|t|)
  x1 ~                                                         
    y1        (a1)    0.162    0.111    1.456   12.345    0.170
  x2 ~                                                         
    y2        (a2)   -0.005    0.112   -0.046   75.147    0.963
  x1 ~                                                         
    y2       (p12)    0.209    0.122    1.714   37.386    0.095
  x2 ~                                                         
    y1       (p21)    0.049    0.103    0.478   47.391    0.635

Solution

  • What's causing the plotted estimates to be off?

    I just left a comment on the earlier post you linked to, informing them that standardizedSolution() is not for lavaan.mi objects. You should use an actual class?lavaan.mi method, something like:

    desired_output <- summary(sem_model, standardized = "std.all",
                              ## so you can check it more easily:
                              output = "data.frame")
    

    And carefully compare the rows and columns of desired_output to those of baseplot@Pars to make sure your replacement makes sense.

    Also, you only need to set the miPackage= when you want to impute within the runMI() call, which is not recommended. You did the right thing by imputing first, but that is when setting your seed counts.

    # impute data
    imp <- mice::mice(df, m = 5, maxit = 10,
                      seed = 1234, printFlag = FALSE)
    
    # conduct SEM
    sem_model <- sem.mi(model = myapim, data = imp)
    

    Is there a better way of plotting lavaan.mi objects that feels less hacky?

    Not yet. Of course, the easiest and safest way to make this work would be if semPaths() checked for the lavaan.mi class and correctly extracted the necessary information itself. I'll make a note to send Sacha a pull request for semPlot when I have time to work on semTools again.