Author: Daniel Lüdecke
Date: 2018-07-03

# Tidy Data Frames of Marginal Effects

#### Daniel Lüdecke

#### 2018-07-03

# Aim of the ggeffects-package

The aim of this package is similar to the broom-package: transforming “untidy” input into a tidy data frame, especially for further use with ggplot. However, ggeffects does not return model-summaries; rather, this package computes marginal effects at the mean or average marginal effects from statistical models and returns the result as tidy data frame (as tibbles, to be more precisely).

Since the focus lies on plotting the data (the marginal effects), at least one model term needs to be specified for which the effects are computed. It is also possible to compute marginal effects for model terms, grouped by the levels of another model’s predictor. The package also allows plotting marginal effects for two- or three-way-interactions, or for specific values of a model term only. Examples are shown below.

# Consistent and tidy structure

The returned data frames always have the same, consistent structure and column names, so it’s easy to create ggplot-plots without the need to re-write the arguments to be mapped in each ggplot-call. `x` and `predicted` are the values for the x- and y-axis. `conf.low` and `conf.high` could be used as `ymin` and `ymax` aesthetics for ribbons to add confidence bands to the plot. `group` can be used as grouping-aesthetics, or for faceting.

# Marginal effects at the mean

`ggpredict()` computes predicted values for all possible levels and values from a model’s predictors. In the simplest case, a fitted model is passed as first argument, and the term in question as second argument:

    library(ggeffects)
    data(efc)
    fit <- lm(barthtot ~ c12hour + neg_c_7 + c161sex + c172code, data = efc)

    ggpredict(fit, terms = "c12hour")
    #> # A tibble: 83 x 5
    #>        x predicted conf.low conf.high group
    #>    <int>     <dbl>    <dbl>     <dbl> <fct>
    #>  1     4      74.4     72.3      76.5 1
    #>  2     6      73.9     71.9      76.0 1
    #>  3     8      73.4     71.4      75.4 1
    #>  4    10      72.9     70.9      74.9 1
    #>  5    12      72.4     70.5      74.3 1
    #>  6    14      71.9     70.0      73.8 1
    #>  7    16      71.4     69.5      73.3 1
    #>  8    18      70.9     69.0      72.7 1
    #>  9    20      70.4     68.6      72.2 1
    #> 10    22      69.9     68.1      71.7 1
    #> # ... with 73 more rows

The output shows the predicted values for the response at each value from the term c12hour. The data is already in shape for ggplot:

    library(ggplot2)
    theme_set(theme_bw())

    mydf <- ggpredict(fit, terms = "c12hour")
    ggplot(mydf, aes(x, predicted)) + geom_line()

# Marginal effects at the mean for different groups

The `terms`-argument accepts up to three model terms, where the second and third term indicate grouping levels. This allows predictions for the term in question at different levels for other model terms:

    ggpredict(fit, terms = c("c12hour", "c172code"))
    #> # A tibble: 249 x 5
    #>        x predicted conf.low conf.high group
    #>    <int>     <dbl>    <dbl>     <dbl> <fct>
    #>  1     4      73.7     70.3      77.1 low level of education
    #>  2     4      74.5     72.4      76.5 intermediate level of education
    #>  3     4      75.2     71.8      78.5 high level of education
    #>  4     6      73.2     69.8      76.6 low level of education
    #>  5     6      73.9     71.9      76.0 intermediate level of education
    #>  6     6      74.7     71.4      78.0 high level of education
    #>  7     8      72.7     69.4      76.1 low level of education
    #>  8     8      73.4     71.4      75.5 intermediate level of education
    #>  9     8      74.2     70.9      77.4 high level of education
    #> 10    10      72.2     68.9      75.5 low level of education
    #> # ... with 239 more rows

Creating a ggplot is pretty straightforward: the colour-aesthetics is mapped with the `group`-column:

    mydf <- ggpredict(fit, terms = c("c12hour", "c172code"))
    ggplot(mydf, aes(x, predicted, colour = group)) + geom_line()

Finally, a second grouping structure can be defined, which will create another column named `facet`, which - as the name implies - might be used to create a facted plot:

    mydf <- ggpredict(fit, terms = c("c12hour", "c172code", "c161sex"))
    mydf
    #> # A tibble: 498 x 6
    #>        x predicted conf.low conf.high group                           facet
    #>    <int>     <dbl>    <dbl>     <dbl> <fct>                           <fct>
    #>  1     4      72.9     68.4      77.5 low level of education          [1] Male
    #>  2     4      74.0     70.5      77.5 low level of education          [2] Female
    #>  3     4      73.7     70.1      77.2 intermediate level of education [1] Male
    #>  4     4      74.7     72.4      77.0 intermediate level of education [2] Female
    #>  5     4      74.4     70.1      78.7 high level of education         [1] Male
    #>  6     4      75.4     71.9      78.9 high level of education         [2] Female
    #>  7     6      72.4     67.9      77.0 low level of education          [1] Male
    #>  8     6      73.5     70.0      77.0 low level of education          [2] Female
    #>  9     6      73.2     69.6      76.7 intermediate level of education [1] Male
    #> 10     6      74.2     71.9      76.5 intermediate level of education [2] Female
    #> # ... with 488 more rows
    ggplot(mydf, aes(x, predicted, colour = group)) +
      geom_line() +
      facet_wrap(~facet)

# Marginal effects for each model term

If the `term` argument is either missing or `NULL`, marginal effects for each model term are calculated. The result is returned as a list, which can be plotted manually (or using the `plot()` function).

    mydf <- ggpredict(fit)
    mydf
    #> $c12hour
    #> # A tibble: 83 x 5
    #>        x predicted conf.low conf.high group
    #>    <int>     <dbl>    <dbl>     <dbl> <chr>
    #>  1     4      74.4     72.3      76.5 c12hour
    #>  2     6      73.9     71.9      76.0 c12hour
    #>  3     8      73.4     71.4      75.4 c12hour
    #>  4    10      72.9     70.9      74.9 c12hour
    #>  5    12      72.4     70.5      74.3 c12hour
    #>  6    14      71.9     70.0      73.8 c12hour
    #>  7    16      71.4     69.5      73.3 c12hour
    #>  8    18      70.9     69.0      72.7 c12hour
    #>  9    20      70.4     68.6      72.2 c12hour
    #> 10    22      69.9     68.1      71.7 c12hour
    #> # ... with 73 more rows
    #>
    #> $neg_c_7
    #> # A tibble: 21 x 5
    #>        x predicted conf.low conf.high group
    #>    <dbl>     <dbl>    <dbl>     <dbl> <chr>
    #>  1     7      75.9     73.2      78.6 neg_c_7
    #>  2     8      73.6     71.2      75.9 neg_c_7
    #>  3     9      71.3     69.2      73.3 neg_c_7
    #>  4    10      69.0     67.1      70.8 neg_c_7
    #>  5    11      66.7     65.0      68.4 neg_c_7
    #>  6    12      64.4     62.7      66.0 neg_c_7
    #>  7    13      62.1     60.4      63.8 neg_c_7
    #>  8    14      59.8     57.9      61.7 neg_c_7
    #>  9    15      57.5     55.3      59.7 neg_c_7
    #> 10    16      55.2     52.7      57.7 neg_c_7
    #> # ... with 11 more rows
    #>
    #> $c161sex
    #> # A tibble: 2 x 5
    #>       x predicted conf.low conf.high group
    #>   <dbl>     <dbl>    <dbl>     <dbl> <chr>
    #> 1     1      64.0     60.6      67.3 c161sex
    #> 2     2      65.0     63.1      66.9 c161sex
    #>
    #> $c172code
    #> # A tibble: 3 x 5
    #>       x predicted conf.low conf.high group
    #>   <dbl>     <dbl>    <dbl>     <dbl> <chr>
    #> 1     1      64.1     61.0      67.1 c172code
    #> 2     2      64.8     63.1      66.4 c172code
    #> 3     3      65.5     62.3      68.7 c172code
    #>
    #> attr(,"class")
    #> [1] "ggalleffects" "list"

# Average marginal effects

`ggaverage()` compute average marginal effects. While `ggpredict()` creates a data-grid (using `expand.grid()`) for all possible combinations of values (even if some combinations are not present in the data), `ggaverage()` computes predicted values based on the given data. This means that different predicted values for the outcome may occure at the same value or level for the term in question. The predicted values are then averaged for each value of the term in question and the linear trend is smoothed accross the averaged predicted values. This means that the line representing the marginal effects may cross or diverge, and are not necessarily in paralell to each other.

    mydf <- ggaverage(fit, terms = c("c12hour", "c172code"))
    ggplot(mydf, aes(x, predicted, colour = group)) + geom_line()

# Two- and Three-Way-Interactions

To plot the marginal effects of interaction terms, simply specify these terms in the `terms`-argument.

    library(sjmisc)
    data(efc)

    # make categorical
    efc$c161sex <- to_factor(efc$c161sex)

    # fit model with interaction
    fit <- lm(neg_c_7 ~ c12hour + barthtot * c161sex, data = efc)

    # select only levels 30, 50 and 70 from continuous variable Barthel-Index
    mydf <- ggpredict(fit, terms = c("barthtot [30,50,70]", "c161sex"))
    ggplot(mydf, aes(x, predicted, colour = group)) + geom_line()

Since the `terms`-argument accepts up to three model terms, you can also compute marginal effects for a 3-way-interaction.

To plot the marginal effects of interaction terms, simply specify these terms in the `terms`-argument.

    # fit model with 3-way-interaction
    fit <- lm(neg_c_7 ~ c12hour * barthtot * c161sex, data = efc)

    # select only levels 30, 50 and 70 from continuous variable Barthel-Index
    mydf <- ggpredict(fit, terms = c("c12hour", "barthtot [30,50,70]", "c161sex"))

    ggplot(mydf, aes(x, predicted, colour = group)) +
      geom_line() +
      facet_wrap(~facet)

# Polynomial terms and splines

`ggpredict()` also works for models with polynomial terms or splines. Following code reproduces the plot from `?splines::bs`:

    library(splines)
    data(women)

    fm1 <- lm(weight ~ bs(height, df = 5), data = women)
    dat <- ggpredict(fm1, "height")

    ggplot(dat, aes(x, predicted)) +
      geom_line() +
      geom_point()

# Labelling the data

ggeffects makes use of the sjlabelled-package and supports labelled data. If the data from the fitted models is labelled, the value and variable label attributes are usually copied to the model frame stored in the model object. ggeffects provides various getter-functions to access these labels, which are returned as character vector and can be used in ggplot’s `lab()`- or `scale_*()`-functions.

- `get_title()` - a generic title for the plot, based on the model family, like “predicted values” or “predicted probabilities”
- `get_x_title()` - the variable label of the first model term in `terms`.
- `get_y_title()` - the variable label of the response.
- `get_legend_title()` - the variable label of the second model term in `terms`.
- `get_x_labels()` - value labels of the first model term in `terms`.
- `get_legend_labels()` - value labels of the second model term in `terms`.

The data frame returned by `ggpredict()` or `ggaverage()` must be used as argument to one of the above function calls.

    get_x_title(mydf)
    #> [1] "average number of hours of care per week"
    get_y_title(mydf)
    #> [1] "Negative impact with 7 items"

    ggplot(mydf, aes(x, predicted, colour = group)) +
      geom_line() +
      facet_wrap(~facet) +
      labs(
        x = get_x_title(mydf),
        y = get_y_title(mydf),
        colour = get_legend_title(mydf)
      )