Plotting simplexes using R

crumb /

In evolutionary game theory and related fields, one often needs to visualize the dynamics of three-dimensional systems, e.g. competition between three strategies \(x_1\), \(x_2\) and \(x_3\) for which \(x_1 + x_2 + x_3 = 1\). This is most conveniently done on a 2-simplex (ternary plot, de Finetti diagram), and the following code snippet defines a minimal way of visualizing data on a 2-simplex using R base graphics.

The function takes a minimum of four arguments: x and y are vectors holding the \(x_1\) and \(x_2\) values (it is not necessary to input the remaining, third value, as \(x_3 = 1 - x_1 - x_2\)); label is a vector of length 3 giving labels for the vertices of the simplex. Additional arguments are passed to the base plot function.

simplex <- function(x,
                    y,
                    label = expression(italic(x)[1], italic(x)[2], italic(x)[3]),
                    ...) {
  # empty plot
  op <- par(mar=c(1,0,0,0) + 0.1, pty="s")
  plot(x=c(-0.2,1.2), y=c(-0.2,1.2), type="n", axes=FALSE, xlab="", ylab="")

  # triangle (borders)
  points(x=c(0,0.5,1,0), y=c(0,0.5*sqrt(3),0,0), type="l")

  # transform the points
  xx <- 0.5*(1-x+y)
  yy <- 0.5*sqrt(3)*(1-x-y)

  # plot points
  points(x=xx, y=yy, ...)

  # labels
  if (!is.null(label)) {
    text(x=0.5, y=0.5*sqrt(3), pos=3, labels=label[3])
    text(x=0.0, y=0.0, pos=2, labels=label[1])
    text(x=1.0, y=0.0, pos=4, labels=label[2])
  }
  
  # restore plotting parameters
  par(op)
}

Here’s a demo:

x <- seq(from=0, to=1, length.out=100)^2
y <- 0.5*(1-x) + rnorm(length(x), 0, 0.05*(1-x))
simplex(x=x, y=y, type="l", lwd=2, col="red")

This minimal procedure can easily be extended in the obvious ways, as in the following state diagram with vector field: