TITLE: Random effects plots DATE: 2020-10-25 AUTHOR: John L. Godlee ==================================================================== I was forwarded a question about how to customise sjPlot::plot_model(). The colleague wanted to plot slopes of fixed effects at different levels of a random effect, in a random slope linear mixed effects model. [sjPlot::plot_model()]: https://cran.r-project.org/web/packages/sjPlot/index.html Load some packages: library(ggplot2) library(lmerTest) library(sjPlot) library(tibble) library(purrr) The example model was: model <- lmerTest::lmer(mpg ~ cyl + disp + hp + drat + wt + qsec + carb + (1|am) + (cyl + disp + hp + drat + wt + qsec + carb|gear), data = mtcars, REML = FALSE) This model doesn't even converge properly, but resembles the structure of their actual model. The random effects are plotted like this: sjPlot::plot_model(model, type="re", vline.color="#A9A9A9", dot.size=1.5, show.values=F, value.offset=.2)[[1]] This produces a pretty ugly plot, which compresses the x axis and makes it difficult to compare slopes. ![Random effects plotted using plot_model()](https://johngodlee.xyz/img_full/sjplot/plot_model.png) Specifically, they wanted to plot the random effects for gear only, rearrange the figure to have 3 rows and 3 columns, and order the facets with intercept at the start. The code below basically simplifies the source code of sjPlot:::plot_type_ranef() for our specific situation. First, extract random effects for gear: rand_ef <- ranef(model)[[1]] Then, extract the standard errors: vars.m <- attr(rand_ef, "postVar") K <- dim(vars.m)[1] J <- dim(vars.m)[3] names.full <- dimnames(rand_ef) rand_se <- array(NA, c(J, K)) for (j in 1:J) { rand_se[j, ] <- sqrt(diag(as.matrix(vars.m[, , j]))) } dimnames(rand_se) <- list(names.full[[1]], names.full[[2]]) Define some parameters for confidence intervals: ci.lvl <- 0.95 ci <- 1 - ((1 - ci.lvl) / 2) Make rownames a column for both effects and SEs: rand_ef <- rownames_to_column(rand_ef) rand_se <- rownames_to_column(as.data.frame(rand_se)) Define group names: grp.names <- colnames(rand_ef) alabels <- rand_ef[["rowname"]] For each effect, calculate values per level of gear: mydf <- map_df(2:ncol(rand_ef), function(i) { out <- data_frame(estimate = rand_ef[[i]]) # Calculate confidence intervals out$conf.low = rand_ef[[i]] - (stats::qnorm(ci) * rand_se[[i]]) out$conf.high = rand_ef[[i]] + (stats::qnorm(ci) * rand_se[[i]]) # set column names (variable / coefficient name) # as group indicator, and save axis labels and title in variable out$facet <- grp.names[i] out$term <- factor(alabels) # create default grouping, depending on the effect: # split positive and negative associations with outcome # into different groups out$group <- dplyr::if_else(out$estimate > 0, "pos", "neg") return(out) }) Make facet a factor to control plotting order: mydf$facet <- factor(mydf$facet, levels = c("(Intercept)", "wt", "cyl", "carb", "disp", "drat", "hp", "qsec")) Create plot: ggplot() + geom_point(data = mydf, aes(x = estimate, y = term, colour = group)) + geom_errorbarh(data = mydf, aes(xmin = conf.low, xmax = conf.high, y = term, colour = group), height = 0) + scale_colour_manual(values = c("red", "blue")) + facet_wrap(~facet) ![New plot](https://johngodlee.xyz/img_full/sjplot/new_plot.png)