As far as possible, the ggfixest plotting functions
try to mimic the behaviour of their base compatriots. However, they also
offer additional functionality thanks to the ggplot2
API. This vignette will walk you through the key differences and
correspondences, specifically with regards to ggiplot
versus the original iplot
.1
Start by loading ggfixest. This will automatically load ggplot2 and and fixest too, as both of these packages are required for this one to work.
library(ggfixest)
#> Loading required package: ggplot2
#> Loading required package: fixest
In the examples that follow, I’ll be drawing on the
fixest introductory
vignette, as well as the iplot
help documentation.
data(base_did)
= feols(y ~ x1 + i(period, treat, 5) | id + period, base_did) est_did
Let’s compare the (base) iplot
and ggiplot
default plots.
iplot(est_did)
ggiplot(est_did)
There are some small differences, but they are certainly producing
the same basic plot. To get even closer to the original, we could
specify the use of errorbar(s) rather than (ggiplot
’s
default of) pointrange(s).
ggiplot(est_did, geom = 'errorbar')
Many of the arguments for iplot
carry over to
ggiplot
too. This is deliberate, since we want to reduce
the cognitive overhead of switching between the two plotting methods.
For example, we can join points using the same
pt.join = TRUE
argument.
iplot(est_did, pt.join = TRUE)
ggiplot(est_did, pt.join = TRUE, geom_style = 'errorbar')
The ggiplot
defaults are slightly different in some
cases, but may require less arguments depending on what you want to do.
For example,
# iplot(est_did, pt.join = TRUE, ci.lty = 0, ci.width = 0, ci.fill = TRUE)
iplot(
pt.join = TRUE, ci.lty = 0, ci.width = 0, ci.fill = TRUE,
est_did, ci.fill.par = list(col = 'black', alpha = 0.3)
)
ggiplot(est_did, geom_style = 'ribbon')
ggiplot(est_did, geom_style = 'ribbon', pt.pch = NA, col = 'orange')
#> Scale for colour is already present.
#> Adding another scale for colour, which will replace the existing scale.
Unlike base iplot
, multiple confidence interval levels
are supported. This works for ribbons too.
ggiplot(est_did, ci_level = c(.8, .95))
Another new feature (i.e. unsupported in base iplot
) is
adding aggregated post- and/or pre-treatment effects to your plots.
Here’s an example that builds on the previous plot, by adding the mean
post-treatment effect.
ggiplot(
ci_level = c(.8, .95),
est_did, aggr_eff = "post", aggr_eff.par = list(col = "orange") # default col is grey
)
We’ll demonstrate multiple estimation functionality using the staggered treatment example (comparing vanilla TWFE with the Sun-Abraham estimator) from the fixest introductory vignette.
data(base_stagg)
= feols(
est_twfe ~ x1 + i(time_to_treatment, treated, ref = c(-1, -1000)) | id + year,
y data = base_stagg
)
= feols(
est_sa20 ~ x1 + sunab(year_treated, year) | id + year,
y data = base_stagg
)
Again, for comparison, here the base iplot
original.
Note that we add the legend manually.
iplot(
list('TWFE' = est_twfe, 'Sun & Abraham (2020)' = est_sa20),
main = 'Staggered treatment', ref.line = -1, pt.join = TRUE
)legend(
'topleft', col = c(1, 2), pch = c(20, 17),
legend = c('TWFE', 'Sun & Abraham (2020)')
)
Here’s the ggiplot
version.
ggiplot(
list('TWFE' = est_twfe, 'Sun & Abraham (2020)' = est_sa20),
main = 'Staggered treatment', ref.line = -1, pt.join = TRUE
)
If we don’t name out list of models then it defaults to something sensible.
ggiplot(
list(est_twfe, est_sa20),
main = 'Staggered treatment', ref.line = -1, pt.join = TRUE
)
One nice thing about the ggplot2 API is that it
makes changing multiplot figures simple. For example, if you don’t like
the presentation of “dodged” models in a single frame, then it’s easy to
facet them instead using the multi_style = 'facet'
argument.
ggiplot(
list('TWFE' = est_twfe, 'Sun & Abraham (2020)' = est_sa20),
main = 'Staggered treatment', ref.line = -1, pt.join = TRUE,
multi_style = 'facet'
)
An area where ggiplot
shines is in complex multiple
estimation cases, such as lists of fixest_multi
objects. To
illustrate, let’s add a split variable (group) to our staggered
dataset.
= base_stagg
base_stagg_grp $grp = ifelse(base_stagg_grp$id %% 2 == 0, 'Evens', 'Odds') base_stagg_grp
Now re-run our two regressions from earlier, but splitting the sample
to generate fixest_multi
objects.
= feols(
est_twfe_grp ~ x1 + i(time_to_treatment, treated, ref = c(-1, -1000)) | id + year,
y data = base_stagg_grp, split = ~ grp
)= feols(
est_sa20_grp ~ x1 + sunab(year_treated, year) | id + year,
y split = ~ grp
base_stagg_grp, )
Both iplot
and ggiplot
do fine with a
single fixest_multi
object (although remember that we have
to manually add a legend for the former)
iplot(est_twfe_grp, ref.line = -1, main = 'Staggered treatment: TWFE')
legend('topleft', col = c(1, 2), pch = c(20, 17), legend = c('Evens', 'Odds'))
ggiplot(est_twfe_grp, ref.line = -1, main = 'Staggered treatment: TWFE')
However, iplot
complains if we combine a list of
several fixest_multi
objects.
iplot(
list('TWFE' = est_twfe_grp, 'Sun & Abraham (2020)' = est_sa20_grp),
ref.line = -1, main = 'Staggered treatment: Split mutli-sample'
)#> The degrees of freedom for the t distribution could not be deduced. Using a Normal distribution instead.
#> Note that you can provide the argument `df.t` directly.
#> Error in coefplot_prms(object = object, ..., sd = sd, ci_low = ci_low, : The first element of 'object' raises and error:
#> Error in nb * sd : non-numeric argument to binary operator
In contrast, ggiplot
works…
ggiplot(
list('TWFE' = est_twfe_grp, 'Sun & Abraham (2020)' = est_sa20_grp),
ref.line = -1, main = 'Staggered treatment: Split mutli-sample'
)
… but is even better when we use faceting instead of dodged errorbars. Let’s use this as an opportunity to construct a fancy plot that invokes some additional arguments and ggplot theming.
ggiplot(
list("TWFE" = est_twfe_grp, "Sun & Abraham (2020)" = est_sa20_grp),
ref.line = -1,
main = "Staggered treatment: Split mutli-sample",
xlab = "Time to treatment",
multi_style = "facet",
geom_style = "ribbon",
facet_args = list(labeller = labeller(id = \(x) gsub(".*: ", "", x))),
theme = theme_minimal() +
theme(
text = element_text(family = "HersheySans"),
plot.title = element_text(hjust = 0.5),
legend.position = "none"
) )
Setting the theme inside the ggiplot
call is optional
and not strictly necessary, since the ggplot2 API allows programmatic
updating of existing plots. E.g.
last_plot() +
labs(caption = 'Note: Super fancy plot brought to you by ggiplot')
last_plot() +
theme_grey() +
theme(legend.position = 'none') +
scale_fill_brewer(palette = 'Set1', aesthetics = c('colour', 'fill'))
#> Scale for colour is already present.
#> Adding another scale for colour, which will replace the existing scale.
etc.
Dictionaries work similarly to iplot
. Simple
example:
$letter = letters[base_did$period]
base_did= feols(y ~ x1 + i(letter, treat, 'e') | id+letter, base_did)
est_letters
# Dictionary for capitalising the letters
= LETTERS[1:10]; names(dict) = letters[1:10]
dict
ggiplot(est_letters) # No dictionary
You can either set the dictionary directly in the plot call…
ggiplot(est_letters, dict = dict)
… Or, set it globally using the setFixest_dict
macro.
setFixest_dict(dict)
ggiplot(est_letters)
setFixest_dict() # reset
The ggcoefplot
and coefplot
functions share a subset of the differences presented here, so you
should be aware of those too once you’ve read this vignette.↩︎