How to graph a function of 4 variables using a grid

This came up in response to a student’s question.

I wrote that, in general, you can plot a function y(x) on a simple graph. You can plot y(x,x2) by plotting y vs x and then having several lines showing different values of x2 (for example, x2=0, x2=0.5, x2=1, x2=1.5, x2=2, etc). You can plot y(x,x2,x3,x4) by making a two-dimensional grid of plots, where the rows show different values of x3 and the columns show different values of x4.

Then I thought I should illustrate with a graph:

It took me about an hour to make this in R (or maybe half an hour, as I was doing other things at the same time). The code is really ugly; see below. Among other things, I had difficulty with the expression() function in R. I expect it should be much easier and more effective to do this in ggplot2.

[Check out the comments, which include several excellent implementations of this idea in ggplot2. If this doesn’t induce me to switch to ggplot2, I don’t know what will! — ed.]

Anyway, below is my code, which I include not out of pride but out of honesty. I could clean it up a bit but I might as well show you what I did. In any case, the grid of graphs illustrates the general point of how to plot a function of many variables, a point which I don’t think is as well known as it should be.

pdf("2waygrid.pdf", height=6, width=8)
par(mfrow=c(5,6))
par(mar=c(3,3,0,0), tck=-.01, mgp=c(1.5,.5,0))
par(oma=c(0,0,3,0))
x2_grid <- seq(0, 1, 0.5)
x3_grid <- seq(0, 6, 2)
x4_grid <- seq(0, 2, 0.5)
empty_plot <- function(x=0, y=0, a="") {
  plot(c(-1, 1), c(-1, 1), xlab="", ylab="", xaxt="n", yaxt="n", bty="n", type="n")
  text(x, y, a, cex=1.2)
}
curve_to_plot <- function(x, x2, x3, x4){
  return(x2*sin(x3*x + x4))
}
x_min <- 0
x_max <- 5
empty_plot()
empty_plot(0, -0.8, expression(paste(x[4], "=", 0)))
empty_plot(0, -0.8, expression(paste(x[4], "=", 0.5)))
empty_plot(0, -0.8, expression(paste(x[4], "=", 1)))
empty_plot(0, -0.8, expression(paste(x[4], "=", 1.5)))
empty_plot(0, -0.8, expression(paste(x[4], "=", 2)))
for (i in 1:4){
  if (i==1)
    empty_plot(0, 0, expression(paste(x[3], "=", 0)))
  else if (i==2)
    empty_plot(0, 0, expression(paste(x[3], "=", 2)))
  else if (i==3)
    empty_plot(0, 0, expression(paste(x[3], "=", 4)))
  else if (i==4)
    empty_plot(0, 0, expression(paste(x[3], "=", 6)))
  x3 <- x3_grid[i]
  for (j in 1:5){
    x4 <- x4_grid[j]
    plot(c(x_min, x_max), c(-1, 1), xlab=if (i==4) "x" else "", ylab=if (j==1) "y" else "", xaxt="n", yaxt="n", bty="l", type="n")
    if (i==4) axis(1, c(0,4,2))
    if (j==1) axis(2, c(-1,0,1))
    for (k in 1:3){
      x2 <- x2_grid[k]
      curve(curve_to_plot(x, x2, x3, x4), from=x_min, to=x_max, add=TRUE, col=(k+1))
    }
  }
}
mtext("How to graph a function of 4 variables using a grid:", side=3, line=1.5, outer=TRUE)
mtext(expression(paste("Graphing ", x[2]*sin(x[3]*x + x[4]), ", as a function of x, for different values of ", x[2], ", ", x[3], ", and ", x[4], ".")), side=3, line=-0.5, outer=TRUE, cex=0.9)
mtext(expression(paste("(In each graph, red: ", x[2], "=0, green: ", x[2], "=0.5, blue: ", x[2], "=1.  Rows and columns show different values of ", x[3], " and ", x[4], ".)")), side=3, line=-2, outer=TRUE, cex=0.8)
dev.off()

28 thoughts on “How to graph a function of 4 variables using a grid

  1. I know this is no place for the whole ggplot vs base R debate, but, nevertheless:

    https://imgur.com/a/pCzGyOi

    library(ggplot2)
    library(cowplot) # Nice minimal theme
    df = expand.grid(
    x = seq(0, 5, .1),
    x2 = seq(0, 1, 0.5),
    x3 = seq(0, 6, 2),
    x4 = seq(0, 2, 0.5))
    df$y = with(df, x2*sin(x3*x + x4))

    ggplot(df, aes(x, y, color=factor(x2))) +
    facet_grid(x3~x4,
    labeller = label_bquote(rows=x[3] == .(x3),
    cols=x[4] == .(x4))) +
    scale_color_manual(values=c(‘red’, ‘green’, ‘blue’)) +
    geom_path() +
    theme_cowplot(font_size=14) +
    theme(strip.placement = “outside”, legend.position=’none’)
    ggsave(‘4xPlot.png’, width = 8, height = 6)

  2. the tidyverse makes it somewhat simpler:

    library(dplyr)
    library(ggplot2)

    x1_grid <- seq(0,5, 0.01)
    x2_grid <- seq(0, 1, 0.5)
    x3_grid <- seq(0, 6, 2)
    x4_grid <- seq(0, 2, 0.5)

    df %
    rename( x1 = Var1, x2 = Var2, x3 = Var3, x4=Var4) %>%
    mutate( z = x2*sin(x3*x1+x4) )

    df %>%
    ggplot(aes( x=x1, z, colour = factor(x2)))+
    geom_line()+
    facet_grid(x3~x4)

    • Looks like it got munged, here is what it should have been:

      expand.grid(
      x1 = seq(0, 5, 0.01),
      x2 = seq(0, 1, 0.5),
      x3 = seq(0, 6, 2),
      x4 = seq(0, 2, 0.5)
      ) %>%
      mutate(z = x2 * sin(x3 * x1 + x4)) %>%
      ggplot(aes(x = x1, y = z, colour = factor(x2))) +
      geom_line() +
      facet_grid(x3 ~ x4)

  3. This is a one-liner in ggplot2:

    library(ggplot2)
    f = function(x1, x2, x3, x4) x2 * sin(x1 * x3 + x4)
    df = expand.grid(x1=seq(0,5,.1), x2=seq(0,1,.5),
    x3=seq(0,6,2), x4=seq(0,2,.5))
    df = within(df, y <- f(x1, x2, x3, x4))
    ggplot(df, aes(x=x1, y=y)) + geom_line(aes(group=x2, colour=x2)) + facet_grid(x3~x4)

  4. I wrote a function (called flexplot) recently that makes this easier. It’s just a wrapper for ggplot. Here’s the basic notation:

    flexplot(y~x1+x2|x3 + x4, data=data, se=F)

    That will put x1 on the x axis, x2 as different lines, x3 in columns, and x4 in rows. If anyone’s interested, it’s on github: dustinfife/fifer

  5. It’s not that much less code, but would scale to more levels without new code for each new level.

    Unfortunately GoDaddy seem to think I’m trying to hack their server with my R code.

  6. This is great – thanks for posting this. This plot is beautifully simple and I think will make an excellent example to share with colleagues.

    (I struggle with `expression()` every $&#^ time.)

  7. To revive an old debate. I created the graph using JMP – no programming required. It took me a half hour – but that’s because I was too stupid to realize how to randomize the rows for the x2, x3, and x4 variables. If I had thought of using the Random Integer function at first it actually took about 5 minutes. I can’t paste the picture into the comment (although I’ll try, but I don’t think it works), but it looks the same as what Andrew generated. So, the old debate is: is programming important? Clearly some mathematical reasoning was required for me to enter the right formulas, but Graph Builder in JMP made it easy to display the 5 variables in one graph. I’m not saying that programming does not have beneficial effects, but these exchanges about R or ggplot make me wonder once again why programming is so great.
    (can’t paste images and I can’t get it onto my website at the moment but it does look the same)

    • > I created the graph using JMP – no programming required.

      I know what you mean here, but at a fundamental level, I disagree, clicking the appropriate series of buttons to get a piece of software to automatically perform a certain graphing task is programming whether the buttons are on-screen menu items or just the individual keys on your keyboard. The fundamental concepts behind programming: describing to a computer how a task should be carried out, still remains.

      I don’t really want to revive the old debate, suffice it to say that there are lifetime positive payouts to learning a general purpose programming language, and if you’re in the position where those payouts are meaningful (ie. you’re going to do more than say 2 data analysis projects in your lifetime) then a semester long course on R programming or Python programming or the like will pay for itself about a thousand times over.

    • I love JMP. It’s what I always use if I need to do something fairly simple. And it is so much easier to teach to non-statisticians than R.

      The big case for coding, besides having far more flexibility in what you can do, is reproducible research. I suppose I could save JMP scripts if I really wanted. But if I’m working with a team, or working on a project that I might need to come back to in a year, I want to have code.

  8. expand.grid(x2 = c(0, 0.5, 1),
    x3 = c(0, 2, 4, 6),
    x4 = c(0, 0.5, 1, 1.5, 2),
    x = seq(0, 4, length.out = 100)) %>%
    mutate(y = x2* sin((x3 * x) + x4)) %>%
    ggplot(aes(x, y, color = factor(x2))) +
    geom_line() +
    facet_grid(x3 ~ x4)

  9. Red and green are hard to distinguish between for color-blind people or when you print out stuff in B&W. I would use different line types (and perhaps different colors in addition, just to emphasize the difference).

Leave a Reply

Your email address will not be published. Required fields are marked *