Skip to content

Instantly share code, notes, and snippets.

@grantmcdermott
Last active December 24, 2021 08:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save grantmcdermott/db8501b0281813792bf78393fef98481 to your computer and use it in GitHub Desktop.
Save grantmcdermott/db8501b0281813792bf78393fef98481 to your computer and use it in GitHub Desktop.
A ggplot2 implementation fixest::iplot()
## Examples, borrowing from the intro vignette
library(fixest)
library(ggplot2)
## Source the function
source('https://gist.githubusercontent.com/grantmcdermott/db8501b0281813792bf78393fef98481/raw/ggiplot.R')
## Vanilla TWFE
est_did = feols(y ~ x1 + i(period, treat, 5) | id + period, base_did)
ggiplot(est_did)
ggiplot(est_did, geom_style = 'errorbar', pt.join = TRUE) ## Another option
ggiplot(est_did, geom_style = 'ribbon') ## And another
## Staggered rollout
res_twfe = feols(y ~ x1 + i(time_to_treatment, treated, ref = c(-1, -1000)) | id + year, base_stagg)
res_sa20 = feols(y ~ x1 + sunab(year_treated, year) | id + year, base_stagg)
ggiplot(list('TWFE' = res_twfe, 'Sun & Abraham (2020)' = res_sa20), ref.line = -1)
ggiplot(list('TWFE' = res_twfe, 'Sun & Abraham (2020)' = res_sa20), ref.line = -1, multi_style = 'facet')
## A fancier example showing off some of ggiplot's extra features (grouped facetting, etc.)
base_stagg_grp = base_stagg
base_stagg_grp$grp = ifelse(base_stagg_grp$id %% 2 == 0, 'Evens', 'Odds')
res_twfe_grp = feols(y ~ x1 + i(time_to_treatment, treated, ref = c(-1, -1000)) | id + year, base_stagg_grp, split = ~ grp)
res_sa20_grp = feols(y ~ x1 + sunab(year_treated, year) | id + year, base_stagg_grp, split = ~ grp)
ggiplot(list('TWFE' = res_twfe_grp, 'Sun & Abraham (2020)' = res_sa20_grp),
ref.line = -1,
main = 'Super duper plot',
xlab = 'Time to treatment',
multi_style = 'facet',
geom_style = 'ribbon',
theme = theme_minimal() +
theme(text = element_text(family = 'HersheySans'),
plot.title = element_text(hjust = 0.5),
legend.position = 'none'))
## Note: requires that both fixest and ggplot2 are installed and loaded
# library(fixest)
# library(ggplot2
ggiplot =
function(models,
geom_style = c('pointrange', 'errorbar', 'ribbon'),
multi_style = c('dodge', 'facet'),
dict = getFixest_dict(),
pt.col = NULL,
pt.join = FALSE,
facet_args = NULL,
zero = TRUE,
zero.par = list(col = 'black', lty = 1, lwd = 0.3),
ref.line = 'auto',
ref.line.par = list(col = 'black', lty = 2, lwd = 0.3),
ci_level = 0.95,
xlab = NULL,
ylab = NULL,
main = NULL,
theme = NULL) {
geom_style = match.arg(geom_style)
multi_style = match.arg(multi_style)
iplot_data = function(model) {
p = iplot(model, only.params = TRUE, ci_level = ci_level, dict = dict)
d = p$prms
if (class(model)=='fixest_multi') {
d$x = rep(p$labels, each = length(model))
d$id = factor(d$id, labels = names(model))
d$dep_var = unique(as.character(lapply(model, function(m) paste(m$call$fml[[2]]))))
} else {
if (class(p$labels)=='integer') p$labels = as.numeric(p$labels) ## catch
if (!identical(d$x, p$labels)) d$x = factor(d$x, labels = p$labels)
d$dep_var = paste(model$call$fml[[2]])
}
return(d)
}
if (class(models) %in% c('fixest', 'fixest_multi')) {
data = iplot_data(models)
data$group = data$id
if (class(models)=='fixest_multi') {
fct_vars = ~ id
} else {
multi_style = 'none'
}
}
if (class(models)=='list') {
data = lapply(models, iplot_data)
nms = names(models)
if (is.null(nms)) {
if ('fixest' %in% unlist(lapply(models, class))) {
nms = paste('Model', seq_along(models))
} else {
nms = paste('Group', seq_along(models))
}
}
nms = as.character(mapply(rep, nms, sapply(data, nrow)))
data = do.call('rbind', data)
data$group = nms
rownames(data) = NULL
if (length(unique(data$id))==1) {
fct_vars = ~ group
} else {
fct_vars = ~ id + group
}
if (is.null(facet_args$ncol)) facet_args$ncol = length(unique(data$group))
}
if (is.null(xlab)) xlab = sub('::.*', '', data$estimate_names_raw[1])
if (!is.null(ref.line)) {
if (ref.line=='auto') ref.line = data$x[which(data$is_ref)[1]]
}
if (is.null(ylab)) ylab = paste0('Estimate and ', ci_level*100, '% Conf. Int.')
if (is.null(main)) main = paste0('Effect on ', unique(data$dep_var))
if (multi_style=='facet') {
facet_defaults = formals(facet_wrap)
facet_defaults$facets = fct_vars
if (!is.null(facet_args)) {
facet_rep_ind = match(names(facet_args), names(facet_defaults))
facet_defaults = replace(facet_defaults, facet_rep_ind, facet_args)
}
facet_args = facet_defaults
}
if (multi_style=='none') {
if (is.null(pt.col)) {
gg = ggplot(data, aes(x, estimate, ymin=ci_low, ymax=ci_high))
} else {
gg = ggplot(data, aes(x, estimate, ymin=ci_low, ymax=ci_high,
col=pt.col, fill=pt.col))
}
} else {
gg = ggplot(data, aes(x, estimate, ymin=ci_low, ymax=ci_high,
fill=group, col=group, shape = group))
}
gg =
gg +
geom_vline(xintercept=ref.line, col=ref.line.par$col, lwd=ref.line.par$lwd, lty=ref.line.par$lty) +
{
if (zero) {
geom_hline(yintercept=0, col=zero.par$col, lwd=zero.par$lwd, lty=zero.par$lty)
}
} +
{
if (geom_style %in% c('pointrange', 'errorbar') & multi_style %in% c('none', 'facet')) {
if (geom_style=='pointrange') {
geom_pointrange()
} else {
list(geom_point(size = 2.5),
geom_errorbar(width = 0.1))
}
}
} +
{
if (geom_style %in% c('pointrange', 'errorbar') & multi_style=='dodge') {
if (geom_style=='pointrange') {
geom_pointrange(position = position_dodge2(width = 0.5, padding = 0.5))
} else {
list(geom_point(size = 2.5, position = position_dodge2(width = 0.5, padding = 0.5)),
geom_errorbar(width = 0.2, position = position_dodge(width = 0.5)))
}
}
} +
{
if (geom_style=='ribbon') {
list(geom_ribbon(alpha = 0.5, col = NA), geom_point())
}
} +
{
if (geom_style=='ribbon' || (pt.join && multi_style!='dodge')) {
geom_line()
}
} +
{
if (geom_style=='ribbon' || (pt.join && multi_style=='dodge')) {
geom_line(position = position_dodge2(width = 0.5, padding = 0.5))
}
} +
scale_color_brewer(palette = 'Set2', aesthetics = c('colour', 'fill')) +
{
if (!is.null(pt.col)) {
if (multi_style=='none') {
list(scale_colour_manual(values = pt.col, aesthetics = c('colour', 'fill')),
guides(col = 'none', fill = 'none'))
} else {
scale_colour_manual(values = pt.col, aesthetics = c('colour', 'fill'))
}
}
} +
labs(x = xlab, y = ylab, title = main) +
{
if (multi_style=='facet') facet_wrap(facets = facet_args$facets,
nrow = facet_args$nrow,
ncol = facet_args$ncol,
scales = facet_args$scales,
shrink = facet_args$shrink,
labeller = facet_args$labeller,
as.table = facet_args$as.table,
switch = facet_args$switch,
drop = facet_args$drop,
dir = facet_args$dir,
strip.position = facet_args$strip.position)
}
if (!is.null(theme)) {
gg =
gg +
theme
} else {
gg =
gg +
theme_linedraw() +
theme(#panel.grid.minor = element_blank(),
plot.title = element_text(hjust = 0.5),
legend.position = 'bottom', legend.title = element_blank())
}
return(gg)
}
@mattysimonson
Copy link

Just stumbled upon this page. This is great! Do you know if anyone has created a ggplot2 version of fixest::coefplot()?

@grantmcdermott
Copy link
Author

Glad you like it. Fwiw I've now bundled it into an actual package. http://grantmcdermott.com/ggiplot/

I haven't added coefplot functionality yet, though. In part because I haven't had time. And in other part because I think modelsummary::modelplot has this pretty well covered already. Check it out: https://vincentarelbundock.github.io/modelsummary/articles/modelplot.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment