library(tidyverse) ## graphics theme_set(theme_bw()+theme(panel.spacing=grid::unit(0,"lines"))) ## modeling/coef plots library(lme4) library(broom) library(broom.mixed) library(dotwhisker) library(stargazer) ## coef tables ## predictions/effects plots library(emmeans) library(effects) library(ggeffects) ## also: margins, marginaleffects, sjPlot, ... library(remef) ## remotes::install_github("hohenstein/remef") library(faux)
arm::coefplot
, coefplot2
broom[.mixed]
+ ggplot2
dotwhisker
data(Contraception,package="mlmRev") Contraception <- Contraception %>% mutate(ch = factor(livch != 0, labels = c("N", "Y"))) m3 <- glmer(use ~ age * ch + I(age^2) + urban + (1 | urban:district), data=Contraception, family=binomial)
stargazer(m3,type="html")
Dependent variable: | |
use | |
age | -0.046** |
(0.022) | |
chY | 1.213*** |
(0.210) | |
I(age2) | -0.006*** |
(0.001) | |
urbanY | 0.787*** |
(0.172) | |
age:chY | 0.066*** |
(0.026) | |
Constant | -1.341*** |
(0.222) | |
Observations | 1,934 |
Log Likelihood | -1,177.237 |
Akaike Inf. Crit. | 2,368.475 |
Bayesian Inf. Crit. | 2,407.446 |
Note: | p<0.1; p<0.05; p<0.01 |
Also see broom
/broom.mixed
+ huxtable
packages
gg0 <- dotwhisker::dwplot(m3, by_2sd=TRUE) print(gg0)
gg1 <- gg0 + geom_vline(xintercept=0,lty=2) print(gg1)
arm::standardize
, arm::rescale
(handy but inefficient)binary.inputs: options for standardizing binary variables, default is ‘center’; ‘0/1’ keeps original scale; ‘-0.5,0.5’ rescales 0 as -0.5 and 1 as 0.5; ‘center’ subtracts the mean; and ‘full’ subtracts the mean and divides by 2 sd.
Look at results if we turn off auto-scaling (default)
dotwhisker::dwplot(m3, by_2sd = FALSE)
Contraception <- Contraception %>% mutate(sc_age = drop(scale(age))) m3_sc <- update(m3, . ~ sc_age * ch + I(sc_age^2) + urban + (1 | urban:district))
dotwhisker::dwplot(m3_sc, effects="fixed", by_2sd = FALSE) + geom_vline(xintercept=0,lty=2)
cc <- broom.mixed::tidy(m3_sc,effects="fixed") print(cc,digits=3)
## # A tibble: 6 × 6 ## effect term estimate std.error statistic p.value ## <chr> <chr> <dbl> <dbl> <dbl> <dbl> ## 1 fixed (Intercept) -1.34 0.222 -6.04 1.56e- 9 ## 2 fixed sc_age -0.416 0.198 -2.10 3.57e- 2 ## 3 fixed chY 1.21 0.210 5.79 7.18e- 9 ## 4 fixed I(sc_age^2) -0.457 0.0690 -6.62 3.51e-11 ## 5 fixed urbanY 0.787 0.172 4.57 4.88e- 6 ## 6 fixed sc_age:chY 0.599 0.231 2.59 9.57e- 3
m3_fixed <- glm( use ~ sc_age * ch + I(sc_age^2) + urban, data=Contraception, family=binomial) dotwhisker::dwplot(list(sc_GLMM=m3_sc, GLM=m3_fixed))+ geom_vline(xintercept=0,lty=2)+ scale_colour_brewer(palette="Dark2")
tidy()
(edit variable/term names, etc.)purrr::map_dfr(list(mod1=, mod2=, .id = "model")
to process a list of models and collapse to a tibblem3_res <- (purrr::map_dfr(list(with_re = m3_sc, no_re=m3_fixed), tidy, conf.int = TRUE, .id = "model") %>% mutate(term=fct_inorder(term)) %>% filter(term!="(Intercept)") )
(purrr::map_dfr
== “run function on elements of a list, bind results into a single data frame, add .id
column based on names of the list elements”)
pd <- position_dodge(width=0.5) (gg5 <- ggplot(m3_res,aes(x=estimate,y=term,colour=model)) + geom_pointrange(aes(xmin=conf.low,xmax=conf.high), position=pd) + labs(y="") + scale_colour_brewer(palette="Dark2") + geom_vline(xintercept=0,lty=2))
m3_res_order <- mutate(m3_res,term=reorder(term,estimate)) gg5 %+% m3_res_order
Reorders levels, allowing for multiple terms per level
caterpillar_levels <- function(x, order_term = "(Intercept)") { lev_order <- (x %>% filter(term==order_term) %>% group_by(level) %>% summarise(across(estimate, mean, na.rm = TRUE)) %>% arrange(estimate) %>% pull(level) ) x <- dplyr::mutate(x, across(level, factor, levels = lev_order)) return(x) }
cat_data <- (m3 %>% broom.mixed::tidy(m3, effects="ran_vals") %>% select(level, term, estimate, std.error) %>% caterpillar_levels() ) ggplot(cat_data, aes(level, estimate, ymin = estimate-2*std.error, ymax = estimate+2*std.error)) + geom_pointrange() + guides(x = guide_axis(n.dodge = 3))
faux
package?)e1 <- emmeans(m3, ~age*ch, at=list(age=-14:20), type="response") plot(e1)
e1d <- as.data.frame(e1) print(gg1 <- ggplot(e1d, aes(age, prob, colour=ch, fill=ch, ymin=asymp.LCL, ymax=asymp.UCL)) + geom_line() + geom_ribbon(colour=NA, alpha=0.2))
plot(ae <- allEffects(m3, xlevels = list(age=51)))
aed <- as.data.frame(ae)[["age:ch"]] gg1 %+% aed + aes(y=fit, ymin=lower, ymax=upper)
ag <- ggeffect(m3, terms=c("age [all]", "ch")) plot(ag)
‘Correct’ data for non-focal terms:
\[ \beta_0 + \sum_{i \in \substack{\textrm{focal} \\ \textrm{params}}} \beta_i x_i + \textrm{resid}_i \]
or
\[ Y_i - \sum_{i \in \substack{\textrm{non-focal} \\ \textrm{params}}} \beta_i x_i \]
fm1 <- lmer(Reaction ~ 1 + Days + (1 + Days | Subject), sleepstudy) ag <- ggeffect(fm1, terms="Days [all]") gg2 <- plot(ag) + geom_point(data=sleepstudy, aes(x=Days, y = Reaction, colour=Subject)) + colorspace::scale_colour_discrete_qualitative(guide="none")
sleepstudy$re <- remef(fm1, ran = list(Subject = "Days")) gg3 <- plot(ag) + geom_point(data=sleepstudy, aes(x=Days, y = re, colour=Subject)) + colorspace::scale_colour_discrete_qualitative(guide="none") cowplot::plot_grid(gg2, gg3)
mm <- (mtcars %>% mutate(across(cyl, contr_code_treatment)) ) m1 <- lm(mpg ~ disp*cyl, mm) dwplot(m1, by_2sd=TRUE) + geom_vline(xintercept = 0, lty=2)
Gelman, Andrew. 2008. “Scaling Regression Inputs by Dividing by Two Standard Deviations.” Statistics in Medicine 27 (15): 2865–73. https://doi.org/10.1002/sim.3107.
Schielzeth, Holger. 2010. “Simple Means to Improve the Interpretability of Regression Coefficients.” Methods in Ecology and Evolution 1: 103–13. https://doi.org/10.1111/j.2041-210X.2010.00012.x.