Yongfu's Blog

A Diary of Charts

Some code sketches for Base R plotting

Oct 27, 2023

My love for the base R plotting system has been growing since I started dealing with complicated charts. Complexity in these charts can arise for many reasons. For one, it might simply result from the data structure when working with complex statistical models (e.g., multilevel models). Conventions in the workplace could be another source of complexity. For instance, tweaking a chart in R to replicate the one originally created in Excel could be extremely difficult, and it is nearly impossible to do so with higher-level plotting packages such as ggplot2. To approach the look of a chart created outside of R, life would be much easier if lower-level frameworks such as the base R plotting system were utilized.

Below is just a cumulation of charts I’ve created. It is intended to help me search and locate the code for plotting certain features in base R. The remainder of the post is divided into sections by charts, each of which consists of (1) the code, (2) the output chart, and (3) a list of features presented in the chart.

Scatter plots

Features

  • Text on plot margins: mtext()
 1#### Data ####
 2set.seed(2023)
 3subj_idx = 1:30
 4gender = rep(1:2, each=15)
 5heights = c(160, 175)[gender] + rnorm(30, sd=15)
 6
 7#### Plot ####
 8ylim = c( min(heights)-5, max(heights)+5 )
 9plot( 1, type="n", xlim=c(.5,30.5), ylim=ylim,
10      xlab="Subject Index", ylab="Height (cm)" )
11points(  1:15, heights[ 1:15], col=2, pch=19 )
12points( 16:30, heights[16:30], col=4, pch=19 )
13abline( v=15.5, col="grey", lty="dashed" )
14mtext( c("Girls","Boys"), at=c(7,23), col=c(2,4), padj=-.5 )

Bar Charts

Features

  • custom axis: axis()
  • axis label rotation (horizontal): las = 1
  • axis label padding adjustment: hadj, padj
  • number of digits: spintf("%.2f", yseq)

Code & Plot

 1yseq = seq(0, 1, .25)
 2plot(1, type="n", xaxt='n', yaxt='n',
 3     ylab = "Probability", xlab="",
 4     xlim=c(-.7, 1.7), ylim=c(0, 1) )
 5lines( c(0, 0), c(0, 0.25), lwd=12, col=2 )
 6lines( c(1, 1), c(0, 0.75), lwd=12, col=2 )
 7axis( 2, at=yseq, labels=sprintf("%.2f", yseq), las=1, hadj = .85 )
 8axis( 1, at=0:1, padj = .5,
 9      labels = c("0\n(tail)", "1\n(head)"), 
10)

Interaction Plots

Features

  • Legend outside of plotting region: par(), legend(), mar, xpd, inset
  • Shaded region: polygon()
  • Custom axis (categorical axis): axis()
  • text / label: text()

Code & Plot

 1library(stom)
 2library(dplyr)
 3
 4#### Data ####
 5d = iris |> 
 6    group_by(Species) |> 
 7    summarise(
 8        S.L = mean(Sepal.Length),
 9        S.W = mean(Sepal.Width),
10        P.L = mean(Petal.Length),
11        P.W = mean(Petal.Width)
12    )
13d
14
15#### Annotations ####
16(LABELS = colnames(d)[-1])
# A tibble: 3 x 5
  Species      S.L   S.W   P.L   P.W
  <fct>      <dbl> <dbl> <dbl> <dbl>
1 setosa      5.01  3.43  1.46 0.246
2 versicolor  5.94  2.77  4.26 1.33 
3 virginica   6.59  2.97  5.55 2.03 
[1] "S.L" "S.W" "P.L" "P.W"
 1#### Plot ####
 2#       c( b,   l,   t,   r )       
 3par( mar=c(5.1, 4.1, 4.1, 8.1), xpd=F )  # Larger right margin for legend
 4plot( 1, type="n", xlim=c(.5, 4.5), ylim=c(0,7),
 5      xlab="", ylab="Mean",
 6      xaxt="n", yaxt="n")  # disable x/y-axis
 7# Shaded region (coordinates are specified (counter-)clockwise)
 8polygon( c(0,4.9,4.9,0),c(2,2,5,5), col=col.alpha("grey",.15), lty=2, border="grey" )
 9# Auxiliary lines
10for ( h in 0:7 )
11    abline( h=h, col=col.alpha("grey") )
12# Lines & Points
13for ( i in 1:nrow(d) ) {
14    lines ( 1:4, d[i,-1], col=i, lwd=4 )
15    points( 1:4, d[i,-1], col=i, lwd=4, pch=2+i )
16}
17# Labels
18for ( i in 1:nrow(d) ) {
19    if ( i != 2 ) next
20    text( 1:4-.03, d[i,-1]-.45, labels = d[i,-1], cex=.9, col=i )
21}
22# Axis
23axis( 1, at=1:4, tck=-.02, labels=LABELS )
24axis( 2, at=0:7, labels=sprintf("%.1f", 0:7), las=1 )  # rotated y-axis labels
25# Legend (outside of plotting region)
26legend("right", legend=d$Species, inset=c(-.17,0), xpd=TRUE, 
27       col=1:nrow(d), pch=1:nrow(d) + 2, lwd=4, cex=.9, 
28       y.intersp=1.8, box.col="transparent", bg="transparent" )

Density Plots

Features

  • Line segments: segments()
  • Density: density()
  • text / label: text()

Code & Plot

 1library(stom)
 2library(dplyr)
 3
 4#### Data ####
 5set.seed(2020)
 6d = data.frame(
 7    x = rnorm(2000)
 8)
 9
10#### Annotations ####
11X  = d$x
12AVG = mean(X)
13SD  = sd(X)
14
15#### Plot ####
16plot( density(X), ylim=c(0,.46),
17      main = "A Standard Normal", xlab = "X" )
18# Mean line segment
19segments( x0=AVG, y0=0, y1=.43, col=2, lwd=3 )
20text( AVG, .46, labels = paste("AVG:",round(AVG,3)), col=2, cex=.8 )
21# SD line segment
22segments( x0=AVG+.06, x1=AVG+SD, y0=.02, lwd=2, col=4 )
23segments( x0=AVG+.06, y0=.01, y1=.03,    lwd=2, col=4 )
24segments( x0=AVG+SD,  y0=.01, y1=.03,    lwd=2, col=4 )
25text( .5*(AVG+SD+.06), .035, 
26      labels = paste("SD =",round(SD,3)), col=4, cex=.8 )

Combining Plots

Features

  • Plot panels: par() + mfrow()

Code & Plot

1inner = c(4.1, 4.1, 1.8, .5)
2par( mfrow=c(2,2), oma=c(0,0,0,0), mar=inner )
3
4for ( i in 1:4 ) {
5    plot( 1, type="n", xlim=0:1, ylim=0:1, xlab="", ylab="" )
6    text( .5, .5, labels=paste("Plot",i), cex=2 )
7}