jtools: vignettes/effect_plot.Rmd (2024)

In jtools: Analysis and Presentation of Social Scientific Data

required <- c("MASS")if (!all(sapply(required, requireNamespace, quietly = TRUE))) knitr::opts_chunk$set(eval = FALSE)knitr::opts_chunk$set(message = F, warning = F, fig.width = 6, fig.height = 4, dpi = 125, render = knitr::normal_print)library(jtools)

One great way to understand what your regression model is telling you is tolook at what kinds of predictions it generates. The most straightforward way to do so is to pick a predictor in the model and calculate predicted valuesacross values of that predictor, holding everything else in the model equal.This is what Fox and Weisberg (2018) call a "predictor effect display."

To illustrate, let's create a model using the mpg data from the ggplot2 package. These data comprise information about 234 cars over several years.We will be predicting the gas mileage in cities (cty) using several variables,including engine displacement (displ), model year (year), # of engine cylinders (cyl), class of car (class), and fuel type (fl).

Here's a model summary courtesy of summ:

library(ggplot2)data(mpg)fit <- lm(cty ~ displ + year + cyl + class + fl, data = mpg[mpg$fl != "c",])summ(fit)

Let's explore the effect of engine displacement on gas mileage:

effect_plot(fit, pred = displ)

To be clear, these predictions set all the continuous variables other than displ to their mean value. This can be tweaked via the centered argument ("none" or a vector of variables to center are options). Factor variables areset to their base level and logical variables are set to FALSE.

So this plot, in this case, is not super illuminating. Let's see the uncertainty around this line.

effect_plot(fit, pred = displ, interval = TRUE)

Now we're getting somewhere.

If you want to get a feel for how the data are distributed,you can add what is known as a rug plot.

effect_plot(fit, pred = displ, interval = TRUE, rug = TRUE)

For a more direct look at how the model relates to the observed data, you canuse the plot.points = TRUE argument.

effect_plot(fit, pred = displ, interval = TRUE, plot.points = TRUE)

Now we're really learning something about our model---and things aren't lookinggreat. It seems like a simple linear model may not even be appropriate. Let'stry fitting a polynomial for the displ term to capture that curvature.

fit_poly <- lm(cty ~ poly(displ, 2) + year + cyl + class + fl, data = mpg)effect_plot(fit_poly, pred = displ, interval = TRUE, plot.points = TRUE)

Okay, now we're getting closer even though the predicted line curiously grazes over the top of most of the observed data. Before we panic, let'sintroduce another feature that might clear things up.

In complex regressions like the one in this running example, plotting theobserved data can sometimes be relatively uninformative because the points seemto be all over the place. While the typical effects plot shows predicted valuesof cty across different values of displ, I includedincluded a lot of predictors besides displ in this model andthey may be accounting for some of this variation. This is what partial residual plots are designed to help with. Using the argumentpartial.residuals = TRUE, what is plotted instead is theobserved data with the effects of all the control variables accounted for.In other words, the value cty for the observed data is based only on the values of displ and the model error. Let's take a look.

effect_plot(fit_poly, pred = displ, interval = TRUE, partial.residuals = TRUE)

There we go! Our polynomial term for displ is looking much better now. You could tell in the previous plot without partial residuals that the shapeof the predictions were about right, but the predicted line was just too high.The partial residuals set all those controls to the same value which shifted the observations up (in this case) to where the predictions are. That meansthe model does a good job of explaining away that discrepancy and we can seemore clearly the polynomial term for displ works better than a linear maineffect.

You can learn more about the technique and theory in Fox and Weisberg (2018).Another place to generate partial residual plots is in Fox's effects package.

Plotting can be even more essential to understands models like GLMs (e.g., logit, probit, poisson).

Logit and probit

We'll use the bacteria data from the MASS package to explore binarydependent variable models. These data come from a study in which childrenwith a bacterial illness were provided with either an active drug or placeboand some were given extra encouragement to take the medicine by the doctor. These conditions are represented by the trt variable. Patientswere checked for presence or absence of the bacteria (y) every few weeks(week).

library(MASS)data(bacteria)l_mod <- glm(y ~ trt + week, data = bacteria, family = binomial)summ(l_mod)

Let's check out the effect of time.

effect_plot(l_mod, pred = week, interval = TRUE, y.label = "% testing positive")

As time goes on, fewer patients test positive for the bacteria.

Poisson

For a poisson example, we'll use the Insurance data from the MASS package.We're predicting the number of car insurance claims for people with differentcombinations of car type, region, and age. While Age is an ordered factor,I'll convert it to a continuous variable for the sake of demonstration.Claims is a count variable, so the poisson distribution is an appropriatemodeling approach.

library(MASS)data(Insurance)Insurance$age_n <- as.numeric(Insurance$Age)p_mod <- glm(Claims ~ District + Group + age_n, data = Insurance, offset = log(Holders), family = poisson)summ(p_mod)

Okay, age is a significant predictor of the number of claims. Note that we have an offset term, so the count we're predicting is more like a rate.That is, we are modeling how many claims there are adjusting for the amountof policyholders.

effect_plot(p_mod, pred = age_n, interval = TRUE)

So what does this mean? Critical is understanding the scale of the outcomevariable. Because of the offset, we must pick a value of the offset to generate predictions at. effect_plot, by default, sets the offset at 1.That means the predictions you see can be interpreted as a percentage; for every policyholder, there are between 0.16 and 0.10 claims. We can also seethat as age goes up, the proportion of policyholders with claims goes down.

Now let's take a look at the observed data...

effect_plot(p_mod, pred = age_n, interval = TRUE, plot.points = TRUE)

Oops! That doesn't look right, does it? The problem here is the offset. Someage groups have many more policyholders than others and they all have more than 1, which is what we set the offset to for the predictions. This is a moreextreme version of the problem we had the with the linear model previously, solet's use the same solution: partial residuals.

effect_plot(p_mod, pred = age_n, interval = TRUE, partial.residuals = TRUE)

Now we're getting somewhere. The only difficulty in interpreting this is the overlapping points. Let's use the jitter argument to add a random bit of noise to each observation so we can see just how many points there are more clearly.

effect_plot(p_mod, pred = age_n, interval = TRUE, partial.residuals = TRUE, jitter = c(0.1, 0))

Because I didn't want to alter the height of the points, I provided a vectorwith both 0.1 (referring to the horizontal position) and 0 (referring to thevertical position) to the jitter argument.

These methods don't work as clearly when the predictor isn't continuous.Luckily, effect_plot automatically handles such cases and offers a number ofoptions for visualizing effects of categorical predictors.

Using our first example, predicting gas mileage, let's focus on the class ofcar as predictor.

effect_plot(fit, pred = fl, interval = TRUE)

We can clearly see how diesel ("d") is associated with the best mileage by farand ethanol ("e") the worst by a little bit.

You can plot the observed data in these types of plots as well:

effect_plot(fit, pred = fl, interval = TRUE, plot.points = TRUE, jitter = .2)

These seem a bit far off from the predictions.Let's see if the partial residuals are a little more in line with expectations.

effect_plot(fit, pred = fl, interval = TRUE, partial.residuals = TRUE, jitter = .2)

Now things make a little more sense and you can see the range of possibilitieswithin each category after accounting for model year and so on. Diesel in particular seems have too few and too spaced out observations to take overlyseriously.

Let's also look at the bacteria example, using treatment type as the predictor of interest.

effect_plot(l_mod, pred = trt, interval = TRUE, y.label = "% testing positive")

Now we can see that receiving the drug is clearly superior to placebo, butthe drug plus encouragement is not only no better than the drug alone, it'shardly better than placebo. Of course, we can also tell that the confidenceintervals are fairly wide, so I won't say that these data tell us anything definitively besides the superiority of the drug over placebo.

You may also want to use lines to convey the ordered nature of this predictor.

effect_plot(l_mod, pred = trt, interval = TRUE, y.label = "% testing positive", cat.geom = "line")

Try the jtools package in your browser

Any scripts or data that you put into this service are public.

Nothing

 

jtools documentation built on Sept. 11, 2024, 7 p.m.

jtools: vignettes/effect_plot.Rmd (2024)
Top Articles
Latest Posts
Recommended Articles
Article information

Author: Nicola Considine CPA

Last Updated:

Views: 6243

Rating: 4.9 / 5 (69 voted)

Reviews: 92% of readers found this page helpful

Author information

Name: Nicola Considine CPA

Birthday: 1993-02-26

Address: 3809 Clinton Inlet, East Aleisha, UT 46318-2392

Phone: +2681424145499

Job: Government Technician

Hobby: Calligraphy, Lego building, Worldbuilding, Shooting, Bird watching, Shopping, Cooking

Introduction: My name is Nicola Considine CPA, I am a determined, witty, powerful, brainy, open, smiling, proud person who loves writing and wants to share my knowledge and understanding with you.