Plotting Microtiter Plate Maps

[This article was first published on Brian Connelly » R | Brian Connelly, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

I recently wrote about my workflow for Analyzing Microbial Growth with R. Perhaps the most important part of that process is the plate map, which describes the different experimental variables and where they occur. In the example case, the plate map described which strain was growing and in which environment for each of the wells used in a 96-well microtiter plate. Until recently, I’ve always created two plate maps. The first one is hand-drawn using pens and markers and sat on the bench with me when I started an experiment. By marking the wells with different colors, line types, and whatever other hieroglyphics I decide on, I can keep track of where everything is and how to inoculate the wells.

A Plate Map

The second is a CSV file that contains a row for each well that I use and columns describing the values of each of my experimental variables. This file contains all of the information that I had on my hand-drawn plate map, but in a format that I can later merge with my result data to produce a fully-annotated data set. The fully-annotated data set is the perfect format for plotting with tools like ggplot2 or for sharing with others.

    Well Strain Environment
 1    B2      A           1
 2    B3      B           1
 3    B4      C           1
 4    B5     <NA>         1
 5    B6      A           2
 6    B7      B           2
 7    B8      C           2
 8    B9     <NA>         2
 9   B10      A           3
 10  B11      B           3

But when talking with Carrie Glenney, whom I’ve been convincing of the awesomeness of the CSV/dplyr/ggplot workflow, I realized that there’s really no need to have two separate plate maps. Since all the information is in the CSV plate map, why bother drawing one out on paper? This post describes how I’ve started using ggplot2 to create a nice plate map image that I can print and take with me to the bench or paste in my lab notebook.

Reading in the Plate Map

First, load load your plate map file into R. You may need to first change your working directory with setwd or give read.csv the full path of the plate map file.

platemap <- read.csv("platemap.csv")

If you don’t yet have a plate map of your own, you can use this sample plate map.

Extracting Row and Column Numbers

In my plate maps, I refer to each well by its row-column pair, like “C6″. To make things easier to draw, we’re going to be splitting those well IDs into their row and column numbers. So for “C6″, we’ll get row 3 and column 6. This process is easy with dplyr’s mutate function. If you haven’t installed dplyr, you can get it by running install.packages('dplyr').

library(dplyr)

platemap <- mutate(platemap,
                   Row=as.numeric(match(toupper(substr(Well, 1, 1)), LETTERS)),
                   Column=as.numeric(substr(Well, 2, 5)))

Once this is done, the platemap data frame will now have two additional columns, Row and Column, which contain the row and column numbers associated with the well in the Well column, respectively.

Drawing the Plate

Microtiter plates are arranged in a grid, so it’s not a big leap to think about a plate as a plot containing the row values along the Y axis and the column values along the X axis. So let’s use ggplot2 to create a scatter plot of all of the wells in the plate map. We’ll also give it a title.

library(ggplot2)

ggplot(data=platemap, aes(x=Column, y=Row)) +
    geom_point(size=10) +
    labs(title="Plate Layout for My Experiment")

First Plot

As you can see, this plot doesn’t tell us anything about our experiment other than the wells it uses and their location.

Showing Empty Wells

I often don’t use all 96 wells in my experiments. It is useful, however, to show all of them. This makes it obvious which wells are used and helps orient your eyes when shifting between the plate map and the plate. Because of this, we’ll create some white circles with a light grey border for all 96 wells below the points that we’ve already created. We’ll also change the aspect ratio of the plot so that it better matches the proportions of a 96-well plate.

ggplot(data=platemap, aes(x=Column, y=Row)) +
    geom_point(data=expand.grid(seq(1, 12), seq(1, 8)), aes(x=Var1, y=Var2),
               color="grey90", fill="white", shape=21, size=6) +
    geom_point(size=10) +
    coord_fixed(ratio=(13/12)/(9/8), xlim = c(0.5, 12.5), ylim=c(0.5, 8.5)) +
    labs(title="Plate Layout for My Experiment")

plot of chunk plot2 blank wells and aspect ratio

Flipping the Axis

Now that we are showing all 96 wells, one thing becomes clear—the plot arranges the rows from 1 on the bottom to 8 at the top, which is opposite of how microtiter plates are labeled. Fortunately, we can easily flip the Y axis. While we’re at it, we’ll also tell the Y axis to use letters instead of numbers and to draw these labels for each value. Similarly, we’ll label each column value along the X axis.

ggplot(data=platemap, aes(x=Column, y=Row)) +
    geom_point(data=expand.grid(seq(1, 12), seq(1, 8)), aes(x=Var1, y=Var2),
               color="grey90", fill="white", shape=21, size=6) +
    geom_point(size=10) +
    coord_fixed(ratio=(13/12)/(9/8), xlim=c(0.5, 12.5), ylim=c(0.5, 8.5)) +
    scale_y_reverse(breaks=seq(1, 8), labels=LETTERS[1:8]) +
    scale_x_continuous(breaks=seq(1, 12)) +
    labs(title="Plate Layout for My Experiment")

Axes flipped

For those who would like to mimic the look of a microtiter plate even more closely, I have some bad news. It’s not possible to place the X axis labels above the plot. Not without some complicated tricks, at least.

Removing Grids and other Plot Elements

Although the plot is starting to look a lot like a microtiter plate, there’s still some unnecessary “chart junk”, such as grids and tick marks along the axes. To create a more straightforward plate map, we can apply a theme that will strip these elements out. My theme for doing this (theme_bdc_microtiter) is available as part of the ggplot2bdc package. Follow that link for installation instructions. Once installed, we can now apply the theme:

library(ggplot2bdc)

ggplot(data=platemap, aes(x=Column, y=Row)) +
    geom_point(data=expand.grid(seq(1, 12), seq(1, 8)), aes(x=Var1, y=Var2),
               color="grey90", fill="white", shape=21, size=6) +
    geom_point(size=10) +
    coord_fixed(ratio=(13/12)/(9/8), xlim=c(0.5, 12.5), ylim=c(0.5, 8.5)) +
    scale_y_reverse(breaks=seq(1, 8), labels=LETTERS[1:8]) +
    scale_x_continuous(breaks=seq(1, 12)) +
    labs(title="Plate Layout for My Experiment") +
    theme_bdc_microtiter()

plot of chunk theme

Highlighting Experimental Variables

Now that our plot is nicely formatted, it’s time to get back to the main point of all of this—displaying the values of the different experimental variables.

You’ll first need to think about how to best encode each of these values. For this, ggplot provides a number of aesthetics, such as color, shape, size, and opacity. There are no one-size-fits-all rules for this. If you’re interested in this topic, Jacques Bertin’s classic Semiology of Graphics has some great information, and Jeff Heer and Mike Bostock‘s Crowdsourcing Graphical Perception: Using Mechanical Turk to Assess Visualization Design is very interesting. After a little experimentation you should be able to figure out which encodings best represent your data.

You’ll also need to consider the data types of the experimental variables, because it’s not possible to map a shape or some other discrete property to continuous values.

Here, we’ll show the different environments using shapes, and the different strains using color. When R imported the plate map, it interpreted the Environment variable as continuous (not a crazy assumption, since it has values 1, 2, and 3). We’re first going to be transforming it to a categorical variable (factor in R speak) so that we can map it to a shape. We’ll then pass our encodings to ggplot as the aes argument to geom_point.

platemap$Environment <- as.factor(platemap$Environment)

ggplot(data=platemap, aes(x=Column, y=Row)) +
    geom_point(data=expand.grid(seq(1, 12), seq(1, 8)), aes(x=Var1, y=Var2),
               color="grey90", fill="white", shape=21, size=6) +
    geom_point(aes(shape=Environment, colour=Strain), size=10) +
    coord_fixed(ratio=(13/12)/(9/8), xlim=c(0.5, 12.5), ylim=c(0.5, 8.5)) +
    scale_y_reverse(breaks=seq(1, 8), labels=LETTERS[1:8]) +
    scale_x_continuous(breaks=seq(1, 12)) +
    labs(title="Plate Layout for My Experiment") +
    theme_bdc_microtiter()

plot of chunk full plot

Changing Colors, Shapes, Etc.

By default, ggplot will use a default ordering of shapes and colors. If you’d prefer to use a different set, either because they make the data more easy to interpret (see Sharon Lin and Jeffrey Heer‘s fascinating The Right Colors Make Data Easier To Read) or for some other reason, we can adjust them. I’ll change the colors used to blue, red, and black, which I normally associate with these strains. Although these colors aren’t quite as aesthetically pleasing as ggplot2′s defaults, I use them because they are the colors of markers I have at my bench.

ggplot(data=platemap, aes(x=Column, y=Row)) +
    geom_point(data=expand.grid(seq(1, 12), seq(1, 8)), aes(x=Var1, y=Var2),
               color="grey90", fill="white", shape=21, size=6) +
    geom_point(aes(shape=Environment, colour=Strain), size=10) +
    scale_color_manual(values=c("A"="blue", "B"="red", "C"="black")) +
    coord_fixed(ratio=(13/12)/(9/8), xlim=c(0.5, 12.5), ylim=c(0.5, 8.5)) +
    scale_y_reverse(breaks=seq(1, 8), labels=LETTERS[1:8]) +
    scale_x_continuous(breaks=seq(1, 12)) +
    labs(title="Plate Layout for My Experiment") +
    theme_bdc_microtiter()

plot of chunk scale colors

Wrap-Up

And that’s all it takes! You can now save the plot using ggsave, print it, add it to some slides, or anything else. In the future, I’ll describe a similar visualizations that can be made that allow exploration of the annotated data set, which contains the plate map information along with the actual data.

Many thanks to Sarah Hammarlund for her comments on a draft of this post!

To leave a comment for the author, please follow the link and comment on their blog: Brian Connelly » R | Brian Connelly.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)