Introduction

The package itsadug (http://github.com/vr-vr/itsadug) includes several plot functions to visualize the estimates of Generalized Additive (Mixed) Models (GAMM) implemented using the package mgcv (Wood 2006; 2011). This paper presents a short overview of:

Example GAMM model

The code below was used to fit a GAMM model m1 to the data set simdat from the package itsadug. The data set simdat is simulated time series data with arbitrary predictors. We use the interaction between the predictors Time and Trial to illustrate the various functions that are available for visualizing nonlinear interactions.

library(itsadug)
## Loading required package: mgcv
## Loading required package: nlme
## This is mgcv 1.8-3. For overview type 'help("mgcv-package")'.
## Loaded package itsadug 0.8 (see 'help("itsadug")' ).
data(simdat)

# For illustration purposes, we build a GAMM model
# with a nonlinear interaction, two groups, and
# random wiggly smooths for Subjects:
m1 <- bam(Y ~ Group + te(Time, Trial, by=Group)
  + s(Time, Subject, bs='fs', m=1),
  data=simdat)

The function gamtabs converts the summary quickly in a Latex table or in a HTML table (specify type="html"), which could be included in a knitr file.

gamtabs(m1, caption="Summaty of m1", comment=FALSE, type='html')
Summaty of m1
A. parametric coefficients Estimate Std. Error t-value p-value
(Intercept) 1.7930 0.6466 2.7730 0.0056
GroupAdults 2.4421 0.9197 2.6552 0.0079
B. smooth terms edf Ref.df F-value p-value
te(Time,Trial):GroupChildren 23.0273 23.1209 1579.9964 < 0.0001
te(Time,Trial):GroupAdults 23.3166 23.3935 1302.5649 < 0.0001
s(Time,Subject) 317.5683 322.0000 3291.1419 < 0.0001

Nonlinear interactions

1. plot.gam() for partial effects

The default way to plot interactions is to use mgcv’s plot.gam. This function visualizes the partial effects, see Figure 1.

par(mfrow=c(1,2))
plot(m1, select=1, rug=FALSE,
     main='Group=Children', cex.axis=1.5, cex.lab=1.5)
plot(m1, select=2, rug=FALSE,
     main='Group=Adults', cex.axis=1.5, cex.lab=1.5)
Figure 1. Partial effects surfaces te(Time,Trial):GroupChildren and te(Time,Trial):GroupAdults plotted with plot.gam.

Figure 1. Partial effects surfaces te(Time,Trial):GroupChildren and te(Time,Trial):GroupAdults plotted with plot.gam.

Advantage and disadvantages of plot.gam:

  • Confidence bands are plotted, which are useful for seeing whether effects are significant.

  • Without colored background the contour plots are difficult to read. It is possible to plot a colored background using the argument scheme=2, but only heat.colors are possible.

  • The use of plot.gam is limited to interactions with two (or less) continuous variables.

2. pvisgam() for partial effects

Alternatively one could use the function pvisgam from the package itsadug to visualize the partial effects. The function plots exactly the same surfaces as plot.gam, but visualizes the surface slightly different.

par(mfrow=c(1,2))
# Note: specify zlim when comparing two plots
pvisgam(m1, view=c("Time", "Trial"), select=1, 
     main='Group=Children', labcex=.8,
     zlim=c(-15,15), print.summary=FALSE)
pvisgam(m1, view=c("Time", "Trial"), select=2, 
     main='Group=Adults', labcex=.8,
     zlim=c(-15,15), print.summary=FALSE)
Figure 2. Partial effects surfaces te(Time,Trial):GroupChildren and te(Time,Trial):GroupAdults plotted with pvisgam.

Figure 2. Partial effects surfaces te(Time,Trial):GroupChildren and te(Time,Trial):GroupAdults plotted with pvisgam.

Advantage and disadvantages of pvisgam:

  • pvisgam plots are easier to interpret than plot.gam plots, because the background is colored.

  • This function can be used to plot more complex interactions, including more than two continuous predictors.

  • The surfaces might look different when being plot with different z-range (zlim), which changes the colors and contour lines being plot.

  • Highly similar in appearance to the function vis.gam (summarized in next section). Therefore, it is good practice to report which function and settings were used to generate the plot.

3. vis.gam() for summed effects

For visualizing the summed effects, rather than partial effects, mgcv’s function vis.gam could be used. The functions pvisgam and fvisgam are derived from vis.gam, and therefore look very similar in style.

par(mfrow=c(1,2))
# Note: specify zlim when comparing two plots
vis.gam(m1, view=c("Time", "Trial"), 
        cond=list(Group='Children', Subject='a01'),
        plot.type='contour', color='topo', main='Group=Children',
        zlim=c(-8,10))
vis.gam(m1, view=c("Time", "Trial"), 
        cond=list(Group='Adults', Subject='a01'),
        plot.type='contour', color='topo', main='Group=Adults',
        zlim=c(-8,10))
Figure 3. Summed effects surfaces for Time and Trial plotted with vis.gam.

Figure 3. Summed effects surfaces for Time and Trial plotted with vis.gam.

Advantages and disadvantages of vis.gam:

  • It shows the additive effect of the different nonlinear components, which facilitates interpretation;

  • This function can be used to plot more complex interactions, including more than two continuous predictors;

  • The surfaces might look different when being plot with different z-range (zlim), which changes the colors and contour lines being plot;

  • vis.gam requires a value for each predictor in the model, and also includes the random effects (e.g., effects for participants and items). This makes it difficult to generalize over the random effects.

Different settings for not viewed predictors may change the surface. Therefore, it is good practice to report which settings were used to generate the plot.

The function gradientLegend in the package itsadug can be used to add a color legend to the vis.gam plot (automatically added by pvisgam and fvisgam).

4. fvisgam() for summed effects

The function fvisgam in itsadug is a variant of vis.gam that allows to exclude random effects. Figure 4 shows similar patterns as the partial effects in Figures 1 and 2, but the values on the contour lines are different. This is caused by the intercept and other terms in the model that are added as constants to the surface. When the random effects would not be excluded (i.e., setting rm.ranef to NULL) the surfaces in Figure 4 would be the same as with vis.gam (Figure 3).

## Summary:
##  * Group : factor; set to the value(s): Adults. 
##  * Time : numeric predictor; with 30 values ranging from 0.000000 to 2000.000000. 
##  * Trial : numeric predictor; with 30 values ranging from -10.000000 to 10.000000. 
##  * Subject : factor; set to the value(s): a01.
Figure 4. Summed effects surfaces for Time and Trial plotted with fvisgam. Random effects are zeroed out.

Figure 4. Summed effects surfaces for Time and Trial plotted with fvisgam. Random effects are zeroed out.

Unless the argument is set to , the function will print the values of all model terms that are being used to generate the predictions.

Advantages and disadvantages of fvisgam:

  • It shows the additive effect of the different nonlinear components, which facilitates interpretation;

  • This function can be used to plot more complex interactions, including more than two continuous predictors;

  • The surfaces might look different when being plot with different z-range (zlim), which changes the colors and contour lines being plot;

  • It allows to generalize over random effects.

  • It reports the values of the other model terms that are not in view, but might influence the plot.

Different settings for not viewed predictors may change the surface. Therefore, it is good practice to report which settings were used to generate the plot.

Other plots

1. plot.gam() for partial effects

The default function for plotting a one dimensional smooth is plot.gam. This function plots the partial effect of a particular one dimensional smooth in the model. The argument shift could be used to raise or lower the smooth with the intercept or intercept adjustments, as shown in Figure 5.

Figure 5. Using plot.gam for plotting a one dimensional smooth.

Figure 5. Using plot.gam for plotting a one dimensional smooth.

Advantages and disadvantages of plot.gam:

  • Useful for inspection of the partial effects of nonlinear smooths.

  • The use of plot.gam is limited to interactions with two (or less) continuous variables.

2. plot_smooth() for summed effects

The function plot_smooth from the package itsadug does not plot the partial effects, but the summed effects. Thus, when plotting the smooth for one particular group, the intercept for that group is also included in the smooth. Optionally the random effects could be excluded by setting the argument rm.ranef to FALSE.

par(mfrow=c(1,2), cex=1.1)

# First plot the smooth for Adult participants in gray...
plot_smooth(m2, view="Time", cond=list(Group="Adults"), rug=FALSE, 
  ylim=c(-10,15), print.summary=FALSE,
  main='m2: Time, Group=Adults')
# ... then add the smooth from which the random effects are excluded
plot_smooth(m2, view="Time", cond=list(Group="Adults"), 
  rug=FALSE, add=TRUE, col='red', rm.ranef=TRUE, 
  ylim=c(-10,15), print.summary=FALSE, xpd=TRUE)
# Add legend:
legend('bottomleft', 
  legend=c("incl. random","excl. random"),
  col=c("black", "red"), lwd=2,
  bty='n')

# Secondly, a smooth based on the tensor in m1:
plot_smooth(m1, view="Time", cond=list(Group="Adults"), 
  rug=FALSE, ylim=c(-10,15), print.summary=FALSE,
  main='m1: Time, Group=Adults')
plot_smooth(m1, view="Time", cond=list(Group="Adults"), 
  rug=FALSE, add=TRUE, col='red', rm.ranef=TRUE, 
  ylim=c(-10,15), print.summary=FALSE, xpd=TRUE)
Figure 6. Left: Using plot_smooth for plotting a one dimensional smooth from model m2. Right: Using plot_smooth for extracting a one dimensional smooth from an interaction surface in model m1.

Figure 6. Left: Using plot_smooth for plotting a one dimensional smooth from model m2. Right: Using plot_smooth for extracting a one dimensional smooth from an interaction surface in model m1.

Figure 6 Left and Right shows the same plot, Left is based on model m2, whereas Right is based on model m1. Remember that model m1 included only interaction surfaces, but no one dimensional smooths. The function plot_smooth can derive the estimates of a one dimensional smooth on the basis of the complex interactions. Although very similar, deriving the estimate from an interaction surface results in a different smooth, because it is based on a different model.

Advantages and disadvantages of plot_smooth:

  • Useful for inspection of the summed effects of nonlinear smooths;

  • Allows to overlay the smooths of different conditions;

  • The function plot_smooth is able to plot interactions with two (or less) continuous variables.

3. plot_parametric() for plotting group averages

The function plot_parametric plots the estimates for one or more grouping predictors as a dotplot, see Figure 7.

par(cex=1.1)

# First plot the Group estimates by setting the smooth terms on zero:
plot_parametric(m1, pred=list(Group=c("Adults", "Children")),  
  cond=list(Time=0, Trial=0), rm.ranef=TRUE, 
  print.summary=FALSE)
Figure 7. Estimates for each group when all other predictors are set to zero.

Figure 7. Estimates for each group when all other predictors are set to zero.

4. plot_diff() and plot_diff2() for differences

The differences in the Time by Trial interaction between the adult participants and the children could be modeled using a binary predictor resulting in a difference surface. Alternatively, one could use the function plot_diff2 of package itsadug to calculate and plot the difference surface. The function plot_diff is the one dimensional version of plot_diff2.

par(mfrow=c(1,2))
plot_diff2(m1, view=c("Time","Trial"), 
        comp=list(Group=c("Adults", "Children")),
        zlim=c(-5,7.5), 
        main='Difference Adults-Children',
        print.summary=FALSE)
plot_diff(m1, view="Time",         
        comp=list(Group=c("Adults", "Children")),
        main='Time difference Adults-Children')
Figure 8. Difference surface for Group plotted with plot_diff2.

Figure 8. Difference surface for Group plotted with plot_diff2.

Note that the random effects do not need to removed, because these are canceled out when the difference between the conditions is calculated.

Using predictions to customize plots

Rather than using the functions plot.gam, pvisgam, vis.gam, fvisgam, or plot_smooth the model predictions provide a way to make your own plots. The functions get_predictions, get_random, and get_difference return predictions for given conditions. Using the same GAMM model, we provide an example of how to make a contour plot and smooths on the basis of predictions.

1. image(), contour() for visualizing surfaces

For a contour plot, one need to generate all combinations of x- and y-values. These can be specified in the argument cond of the function get_predictions of itsadug. A matrix is created out of these values, and consequently plotted using the functions image and contour, as shown in Figure 9.

# Extract prediction from model
# Note that the random effects are canceled out by setting rm.ranef to TRUE
xval <- seq(0,2000, length=100)
yval <- seq(-10,10, length=50)
g1 <- get_predictions(m1, cond=list(Time=xval, Trial=yval, Group='Adults'), 
                      rm.ranef=TRUE, print.summary=FALSE)
# Create plot matrix
g1 <- g1[order(g1$Time, g1$Trial),]
zval <- matrix(g1$fit, byrow=TRUE, nrow=100,ncol=50)
image(xval, yval, zval, col=topo.colors(100),
      main='Group=Adults', xlab='Time', ylab='Trial')
contour(xval, yval, zval, labcex=.8, add=TRUE, col='red')
Figure 9. Using image() and contour() to plot the interaction surface.

Figure 9. Using image() and contour() to plot the interaction surface.

2. get_predictions() for visualizing smooths

To explain how the reader should interpret this surface, one could use two smooths that illustrate how Time influences the dependent variable at Trial 5 and Trial -5. I generated two data frames, one for Trial==5 and one for Trial==-5:

# Extract prediction from model
# Note that the random effects are canceled out by setting rm.ranef to TRUE
xval <- seq(0,2000, length=100)
g2 <- get_predictions(m1, cond=list(Time=xval, Trial=5, Group='Adults'), 
                      rm.ranef=TRUE, print.summary=FALSE)
g3 <- get_predictions(m1, cond=list(Time=xval, Trial=-5, Group='Adults'), 
                      rm.ranef=TRUE, print.summary=FALSE)
head(g2)
##    Group      Time Trial Subject          fit       CI        rm.ranef
## 1 Adults   0.00000     5     a01 -1.308769111 1.966704 s(Time,Subject)
## 2 Adults  20.20202     5     a01 -0.873584687 1.956358 s(Time,Subject)
## 3 Adults  40.40404     5     a01 -0.439061543 1.946922 s(Time,Subject)
## 4 Adults  60.60606     5     a01 -0.005860961 1.938340 s(Time,Subject)
## 5 Adults  80.80808     5     a01  0.425355779 1.930549 s(Time,Subject)
## 6 Adults 101.01010     5     a01  0.853927396 1.923474 s(Time,Subject)

In the leftmost of the following plots, the interaction surface is plotted again. Now two arrows are added to indicate which smooths are being plot in the rightmost panel:

par(mfrow=c(1,2))

image(xval, yval, zval, col=topo.colors(100),
      main='Group=Adults', xlab='Time', ylab='Trial')
contour(xval, yval, zval, labcex=.8, add=TRUE, col='red')
# Add arrows for comparing two conditions
arrows(x0=0, x1=2200, y0=5, y1=5, code=2, 
  length=.1, angle=30, lwd=2, xpd=TRUE)
arrows(x0=0, x1=2200, y0=-5, y1=-5, code=2, 
  length=.1, angle=30, lwd=2, col='magenta', xpd=TRUE)
text(2100, c(5,-5), labels=c('A', 'B'), 
  col=c('black', 'magenta'), pos=3, cex=1.1, xpd=TRUE)

# Setup the plot region for the two smooths:
emptyPlot(range(g2$Time), 
  range(c(g2$fit+g2$CI, g2$fit-g2$CI,g3$fit+g3$CI, g3$fit-g3$CI)),
  main='Trials 5 and -5', xlab='Time', ylab='Est. value of Y',
  h0=0)

# add two smooths:
# Note: f is set to 1, because the SE are already multiplied by 1.96 to get 95%CI
plot_error(g2$Time, g2$fit, g2$CI, 
  shade=TRUE, f=1, xpd=TRUE)
plot_error(g3$Time, g3$fit, g3$CI, 
  col='magenta', shade=TRUE, f=1, xpd=TRUE)

# add text as legend:
text(1500,c(15,10), labels=c("A", "B"), 
  col=c('black', 'magenta'), font=2, adj=0)
Figure 10. Left: Interaction between Time and Trial for adult participants. Right: The effect of Time for Trial 5 (A) and Trial -5 (B) with 95% CI.

Figure 10. Left: Interaction between Time and Trial for adult participants. Right: The effect of Time for Trial 5 (A) and Trial -5 (B) with 95% CI.

3. get_random() to visualize random effects

It might be useful to inspect the random effects estimates. The random smooths do not necessarily sum to zero, and might show drifts. By default, random effects are plotted by the function plot.gam. To calculate the mean or median of the random effects, the function get_random may be helpful.

# Extract the mean of the random smooths from model m1
g4 <- get_random(m1, fun='mean')
# Extract the median of the random smooths from model m1
g5 <- get_random(m1, fun='median')
par(mfrow=c(1,2))

plot(m1, select=3, ylim=c(-20,20))
title(main="Random smooths")
abline(h=0)

# Plot the mean random smooth:
itsadug::emptyPlot(range(g4[[1]]$Time), c(-10,10),
    main='Mean and median of random smooths', 
    xlab='Time', ylab='Est. value of Y',
    h0=0)
lines(g4[[1]]$Time, g4[[1]]$x, lwd=2, xpd=TRUE)
lines(g5[[1]]$Time, g5[[1]]$x, lwd=2, col='blue', xpd=TRUE)
Figure 11. Left: Random effects smooths. Right: The mean (black) and median (blue) of the random smooths.

Figure 11. Left: Random effects smooths. Right: The mean (black) and median (blue) of the random smooths.

Inspection of residuals

1. check_resid() and resid_gam()

The function check_resid can be used to inspect the residual error of the model. It checks for the distribution of the model and the the autocorrelation in the residuals. For GAMM models without AR1 model, only the standard residuals are presented. However, it will reflect both the standard and the corrected residuals in models with an AR1 model included, because the function makes use of resid_gam. resid_gam can be used to retrieve the corrected residuals rather than the normal residuals.

# Note: as no AR1 model was included, resid() is used instead of resid_gam()

check_resid(m1, split_by=list(Subject=simdat$Subject, Trial=simdat$Trial))
## No AR1 model included.
Figure 12. Top row: Test the distribution of the model residuals for normality. The residuals seem to follow a t-distribution rather than normal distribution. Bottom row: The autocorrelation in the residuals. The left panel shows the acf of all the residuals, treating them as a single time series. The right panel is based on the function acf_plot, which averages over the time series.

Figure 12. Top row: Test the distribution of the model residuals for normality. The residuals seem to follow a t-distribution rather than normal distribution. Bottom row: The autocorrelation in the residuals. The left panel shows the acf of all the residuals, treating them as a single time series. The right panel is based on the function acf_plot, which averages over the time series.

2. acf_plot() and acf_n_plots()

To test the structure of the autocorrelation, the function acf_n_plots presents \(N\) acf plots of individual participants or time series. These time series can be randomly selected, or provide an overview of the differences by selecting time series on the basis of the quantiles, as illustrated in Figure 13.

acf_n_plots(resid(m1), 
  split_by=list(Subject=simdat$Subject, Trial=simdat$Trial), 
  n=6,cex.lab=1.5, cex.axis=1.5, cex.main=2)
## Quantiles to be plotted:
##          0%         20%         40%         60%         80%        100% 
## -0.31881507 -0.01289427  0.08513155  0.21909939  0.53231119  0.96714554
Figure 13. 6 ACF plots, averaged over time series.

Figure 13. 6 ACF plots, averaged over time series.

3. acf_resid()

The function acf_resid() is a shortcut for plotting the ACF of model residuals. This function is also available for other regression models and lmer() / glmer() models. Note that the split_pred argument can take a vector with names of model terms that define how to split the data. If the argument n is being used, acf_n_plots is being called, otherwise acf_plot.

acf_resid(m1, split_pred=c("Subject", "Trial"))
Figure 14. ACF plot of model residuals.

Figure 14. ACF plot of model residuals.

Package info

This summary was created on .

packageVersion("mgcv")
## [1] '1.8.3'
packageVersion("itsadug")
## [1] '0.8'

Use the following command for the citation information in BibTex format:

citation("itsadug")
## 
## van Rij J, Wieling M, Baayen R and van Rijn H (2015). "itsadug:
## Interpreting Time Series, Autocorrelated Data Using GAMMs." R
## package version 0.8.
## 
## A BibTeX entry for LaTeX users is
## 
##   @Misc{,
##     title = {{itsadug}: Interpreting Time Series, Autocorrelated Data Using GAMMs},
##     author = {Jacolien {van Rij} and Martijn Wieling and R. Harald Baayen and Hedderik {van Rijn}},
##     year = {2015},
##     note = {R package version 0.8},
##   }

References

Wood, Simon N. 2006. Generalized Additive Models: An Introduction with R. Chapman; Hall/CRC.

———. 2011. “Fast Stable Restricted Maximum Likelihood and Marginal Likelihood Estimation of Semiparametric Generalized Linear Models.” Journal of the Royal Statistical Society (B) 73 (1): 3–36.