# Comparing ggiplot with iplot

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.

### Example 1: Vanilla TWFE

    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.

    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(
        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')
    #> 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(
        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.

    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.

    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'
        )

### 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.

    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.

    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)

    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'
        )

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"
            )
    )

### 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.

    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.

#### On dictionaries

Dictionaries work similarly to `iplot`. Simple example:

    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…

    ggiplot(est_letters, dict = dict)

… Or, set it globally using the `setFixest_dict` macro.

    setFixest_dict(dict)
    ggiplot(est_letters)

    setFixest_dict() # reset

1. 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.↩︎