--- title: "Comparing ggiplot with iplot" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Comparing ggiplot with iplot} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` 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`.^[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.] 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. ```{r setup} library(ggfixest) ``` In the examples that follow, I'll be drawing on the **fixest** [introductory vignette](https://lrberge.github.io/fixest/articles/fixest_walkthrough.html), as well as the `iplot` help documentation. ### Example 1: Vanilla TWFE ```{r est_did} data(base_did) est_did = feols(y ~ x1 + i(period, treat, 5) | id + period, base_did) ``` Let's compare the (base) `iplot` and `ggiplot` default plots. ```{r est_did_plot_defaults} 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). ```{r est_did_ebar} 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. ```{r est_did_pt_join} 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, ```{r est_did_ribbon} # iplot(est_did, pt.join = TRUE, ci.lty = 0, ci.width = 0, ci.fill = TRUE) iplot( est_did, pt.join = TRUE, ci.lty = 0, ci.width = 0, ci.fill = TRUE, 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') ``` Unlike base `iplot`, multiple confidence interval levels are supported. This works for ribbons too. ```{r est_did_ci_multi} 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. ```{r est_did_aggr_eff} ggiplot( est_did, ci_level = c(.8, .95), aggr_eff = "post", aggr_eff.par = list(col = "orange") # default col is grey ) ``` ### Example 2: Multiple estimation (i) We'll demonstrate multiple estimation functionality using the staggered treatment example (comparing vanilla TWFE with the Sun-Abraham estimator) from the **fixest** introductory vignette. ```{r base_stagg} data(base_stagg) est_twfe = feols( y ~ x1 + i(time_to_treatment, treated, ref = c(-1, -1000)) | id + year, data = base_stagg ) est_sa20 = feols( y ~ x1 + sunab(year_treated, year) | id + year, data = base_stagg ) ``` Again, for comparison, here the base `iplot` original. Note that we add the legend manually. ```{r stagg_iplot} 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. ```{r stagg_ggiplot} 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. ```{r stagg_ggiplot_noname} 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. ```{r stagg_ggiplot_facet} ggiplot( list('TWFE' = est_twfe, 'Sun & Abraham (2020)' = est_sa20), main = 'Staggered treatment', ref.line = -1, pt.join = TRUE, multi_style = 'facet' ) ``` ### Example 3: Multiple estimation (ii) 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. ```{r base_stagg_grp} base_stagg_grp = base_stagg base_stagg_grp$grp = ifelse(base_stagg_grp$id %% 2 == 0, 'Evens', 'Odds') ``` Now re-run our two regressions from earlier, but splitting the sample to generate `fixest_multi` objects. ```{r stagg_grp} est_twfe_grp = feols( y ~ x1 + i(time_to_treatment, treated, ref = c(-1, -1000)) | id + year, data = base_stagg_grp, split = ~ grp ) est_sa20_grp = feols( y ~ x1 + sunab(year_treated, year) | id + year, base_stagg_grp, split = ~ 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) ```{r stagg_grp_single} 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. ```{r stagg_grp_multi_iplot, error = TRUE} iplot( list('TWFE' = est_twfe_grp, 'Sun & Abraham (2020)' = est_sa20_grp), ref.line = -1, main = 'Staggered treatment: Split mutli-sample' ) ``` In contrast, `ggiplot` works... ```{r stagg_grp_multi_ggiplot} 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. ```{r stagg_grp_multi_ggiplot_fancy} 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" ) ) ``` ### Asides #### On theming and scale adjustments 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. ```{r theme_update} 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')) ``` etc. #### On dictionaries Dictionaries work similarly to `iplot`. Simple example: ```{r dict} base_did$letter = letters[base_did$period] est_letters = feols(y ~ x1 + i(letter, treat, 'e') | id+letter, base_did) # Dictionary for capitalising the letters dict = LETTERS[1:10]; names(dict) = letters[1:10] ggiplot(est_letters) # No dictionary ``` You can either set the dictionary directly in the plot call... ```{r dict_direct} ggiplot(est_letters, dict = dict) ``` ... Or, set it globally using the `setFixest_dict` macro. ```{r dict_global} setFixest_dict(dict) ggiplot(est_letters) setFixest_dict() # reset ```