Search code examples
rplotboxplot

Add boxplots to a line graph in base R using a seperate data frame


I want to add boxplots to a line graph in base R.

I have a rather big data set, and I want to create many different plots, which is why I created a subset for every plot I wanted to create.

Here is a very stripped-down version of my problem:

cat <- rep(1:10,10)
#create 10 categories

x1 <- rnorm(cat)
x2 <- rnorm(cat)
x3 <- rnorm(cat)
#create 3 X variables


test_data<-data.frame(cat,x1,x2,x3)
#create data frame

cat <- 1:10

plot_data <- data.frame(
  y = cat,
  x1_mean = tapply(test_data$x1,test_data$cat,mean),
  x2_mean = tapply(test_data$x2, test_data$cat,mean),
  x3_mean = tapply(test_data$x3, test_data$cat,mean)
)
#create data frame for plotting
#mean for every x variable per category



plot(plot_data$y,plot_data$x1_mean,type = "b", xlab = "x", ylab = "y", col = "blue", ylim = c((-1),1))  
lines(plot_data$y,plot_data$x2_mean, type = "b",col = "green")
lines(plot_data$y,plot_data$x3_mean, type = "b",col = "red")
#plot means per category

I use the data frame data_plot to plot my line graph. And it looks like this: enter image description here

Now I would like to add a boxplot of the distribution of every x-variable per category (cat/y).

For this, I probably have to capture the information needed in the boxplot in the data frame data_plot and then use it to plot the boxplot. Do you have any idea on how to do this?

Thanks!


Solution

  • Here's how I would approach it.

    While the means of the three x variables for one category can be placed at the same x coordinate (1 to 10), the boxplots should be drawn at different x coordinates not to cover one another. This could be done using a loop over the three x variables or by transforming the data into long format with a single x variable and another variable indicating to which cat/x the value belongs. We will go with the latter approach.

    # put all x variables in one column
    test_data_long <- reshape(test_data, direction="long", varying=list(2:4), 
                              v.names="x", timevar="xn")
    # category and variant of x (xn) together for placing boxplots along the x axis
    test_data_long$cat2 <- with(test_data_long, cat + (xn - 2)/5)
    # reorder so that boxplots are drawn in the correct order
    test_data_long <- test_data_long[order(test_data_long$cat2), ]
    

    The first command just transforms the data into long format with a single x variable and an xn variable which indicates the original index of x (x1, x2, x3).

    Then we create the cat2 variable which distinguishes between xn within cat and is used as the x coordinate for the boxplots. We want to have x2 values exactly at cat (1, 2, ..., 10), x1 values to the left (0.8, 1.8, ..., 9.8) and x2 values to the right (1.2, 2.2, ..., 10.2), which is achieved with cat + (xn - 2) / 5. This spacing is specifically for three x variables (max(xn) = 3) and generally it could be something like cat + (xn - (max(xn) + 1)/2) / (max(xn) + 2).

    Next we just need to reorder the data according to cat2 so that the boxplots are drawn in the same order the x values we will provide are in.

    And now the plotting itself. With different layers of information in one plot I like to suppress the less important ones with transparency.

    xn_col <- palette()[4:2]
    box_col <- xn_col[unique(test_data_long$xn)]
    # draw boxplots in semi-transparent colors
    par(mar=c(4.5, 4.5, .5, .5))
    boxplot(x ~ cat2, test_data_long, at=unique(test_data_long$cat2), 
            boxwex=.15, xaxt='n', xlab='cat',
            border=paste0(box_col, '66'), col=paste0(box_col, '11'))
    axis(1, at=unique(test_data$cat))
    
    # compute mean values
    data_mean <- aggregate(. ~ cat, test_data, mean)
    # draw semi-transparent points under the means to make them pop out more
    sapply(data_mean[, -1], points, x=data_mean$cat, 
           pch=16, col='#ffffffcc', cex=2)
    # draw mean values
    for (cat in 1:3) {
      lines(data_mean$cat, data_mean[[paste0('x', cat)]], type='b', 
            col=xn_col[cat], lwd=1.7, cex=1.2)
    }
    

    boxplots