Now that I know how to use annotate(), I can see uses for it everywhere!


Hey folks,

I’d love to hear your experiences trying to recreate the figures I’ve been discussing in recent newsletters. Does a “verbal” description of my thought process for each figure help? Can you pick a figure and do it yourself? What are the biggest obstacles to translating between the verbal description and actual code? Feel free to reply to this email to let me know how you like this approach. Also, if you have a figure you’d like me to walk through, I’d love that too!


This week I want you to look at Figures 3E and 3F from “Preexisting cell state rather than stochastic noise confers high or low infection susceptibility of human lung epithelial cells to adenovirus” by Anthony Petkidis and colleagues, which was recently published in mSphere.

There isn’t anything super special about this set of figures. I’m more interested in the general style of the figures that I want to draw your attention to. You’ll notice that the x-axis in both figures is broken. As I go looking for papers, I find broken axes like these in a lot of papers. Often they are broken y-axes, but as this case shows, people break the x-axis too.

Why do people break an axis? In this case, we see that there was a jump in the data between weeks 4 and 8. Instead of having 3 empty positions in their figures, the cut that out. When people break the y-axis, they often have a big difference between the values for different treatments. Perhaps A is super high and B and C are much lower, but C is greater than B. The author wants to call attention to the relationship between all three treatments.

Why shouldn’t you break an axis? The honest truth is that breaking axes is generally considered a poor data visualization practice. That’s because although the axis is clearly indicated as broken, the human eye and mind will quickly forget that and make comparisons between points based on their distance to each other.

How would you get around the need for a break? One thing these authors could have done would have been to make facets of the early and later time points and drawn boxes around both groups. They’d share a y-axis, but there’d be a stronger indication that there’s a jump in the data. For a break in the y-axis, you might consider a log scaled axis or some other transformation that compresses the difference in the data. Alternatively, you might set the limit on the y-axis to highlight the difference between B and C and let the value for A either be hidden or for a line or bar plot it could extend outside the plot with an annotation indicating the value of A. You might also ask if A is so much larger than B and C, does the difference between B and C really matter?

But what do I know? :) How would I go about creating a break in an axis?

First of all, I’m so sure there’s a package out there to do this for you that I’m not going to bother with the google search. Again, my goal with these discussions isn’t to solve specific problems, but to help you think more generally about how to solve problems with R.

Let’s start by assuming we have a data frame that looks something like this…


set.seed(19760620)
f_data <- tibble(
weeks = c(1, 2, 3, 4, 8, 9),
rel_infection = runif(6, min = 0.75, 2),
ri_high = rel_infection + 0.25,
ri_low = rel_infection - 0.25)

Of course, we would plot this with geom_point() and to get the error bars we’d use something like geom_errorbar(). You might recall from last week’s newsletter that you can have a plotting symbol with one color on the border and a different one the inside. Hopefully by now, you’ve learned how to play with labs() to alter the x and y-axis labels. Let’s think about that break.

My general idea is that I need to pull the data together on the x-axis with space for one piece of missing data. I can pull everything together by recasting the weeks column as a character. Basically, I’m turning a numerical variable into a categorical variable. That compresses everything together. But how do we put a gap in between days 4 and 8? Let’s insert a fake day, day 5. To do this, I’d probably use bind_rows() to add a row to f_data where the weeks value is 5 and everything else is -1. Something like bind_rows(f_data, c(weeks = "5", rel_infection = -1, ri_high = -1, ri_low = -1)). Remember that you’ll want to set your y-axis limit to hide the value for week 5. The coord_cartesian() function should help you to set the limits on the axes. I’d probably set the minimum value on the y-axis to be 0.4 and the maximum to be 2.5. By default, the axes are drawn with some padding on both ends of the axes. To make things easier, I’d turn this off by setting expand = c(0, 0) as an argument to scale_y_continuous()

Now we have pulled days 8 and 9 back towards day 4 with a gap in between. We’d like to get rid of the 5 on the x-axis. We can probably do that with scale_x_discrete. The trick here would be to duplicate the 4 in the vector given to the breaks argument so that it appears in position 5. Then in the labels argument put "" in position 5. This will get rid of the tick mark for week 5.

Next, I’d apply theme_classic(), which is pretty similar to what these authors did. This will give us a white background that is easier to work with. Now let’s draw that break on the x-axis. Last week, I mentioned the annotate() function, we’ll use that again here to draw the diagonal lines. The annotate() function takes different geoms as the first argument. I’d like to draw line segments, which I’d normally draw with geom_segment(). But with annotate(), I’ll use geom = "segment". Then I can give the function values for the x, y, xend, and yend values. Let’s keep in mind that our x-axis crosses the y-axis at 0.4. For the first slash, I’d use annotate(geom = "segment", x = 4.7, xend = 4.9, y = 0.35, yend = 0.45). When you draw that slash, you’ll only find half of it. The part that goes below the axis is missing. To solve that problem, we can use clip = "off" as an argument to coord_cartesian(). Nice, eh? Now see if you can figure out how to make the second slash to be parallel to the right, but over to the right a smidge. Now we have the break, but we haven’t removed the axis. One thought I had was to add another annotation segment between the two slashes. We could again do that with annotate(geom = "segment") using y = 0.4 and yend = 0.4 and x = 4.8 and an appropriate value for xend. Try making linewidth = 3 and color = "red" so it’s easier to see where the segment is.

If you get that to work right, you’ll notice a problem. Do you see the problem? It appears that the data are plotted under the axis rather than on top. What I wanted to do would be to make that line white so that it masks the axis. But because it is under the axis, that strategy won’t work.

Now we need another solution. That other solution would be to remove the x-axis entirely and draw a new one made up of two segments. To remove the x-axis you can use the theme() function to modify axis.line.x by passing it element_blank(). Now to replace that axis. See if you can figure out how to modify the code you wrote for the thick red line to make a normal thickness black line that goes across the x-axis without including the break in the axis.

Got it? To be honest, before last week I didn’t know about annotate()! I think it’s a pretty handy tool for adding customized elements to a plot. If you had a hard time keeping up, here’s some unpolished code of my own minimum example of how to add a break to the x-axis (it will look much better on a laptop than on your phone). If you’re feeling pretty good about your skills, see if you can now put a break in the y-axis. While you’re at it, see if you can try one of the other strategies I mentioned for avoiding breaking your axes!


f_data %>%
bind_rows(c(weeks = 5, rel_infection = -1, ri_high = -1, ri_low = -1)) %>%
ggplot(aes(x = as.character(weeks), y = rel_infection, ymin = ri_low, ymax = ri_high)) +
geom_point() +
geom_errorbar() +
annotate(geom = "segment", x = 4.7, xend = 4.9, y = 0.35, yend = 0.45) +
annotate(geom = "segment", x = 5.1, xend = 5.3, y = 0.35, yend = 0.45) +
annotate(geom = "segment", x = c(0.5, 5.2), xend = c(4.8, 7.5), y = 0.4, yend = 0.4) +
coord_cartesian(ylim = c(0.4, 2.5), clip = "off") +
scale_y_continuous(expand = c(0, 0)) +
scale_x_discrete(breaks = c(1, 2, 3, 4, 4, 8, 9),
labels = c(1, 2, 3, 4, "", 8, 9)) +
theme_classic() +
theme(axis.line.x = element_blank())

Workshops

I'm pleased to be able to offer you one of three recent workshops! With each you'll get access to 18 hours of video content, my code, and other materials. Click the buttons below to learn more

In case you missed it…

Here are some videos that I published this week that relate to previous content from these newsletters. Enjoy!

video preview

Finally, if you would like to support the Riffomonas project financially, please consider becoming a patron through Patreon! There are multiple tiers and fun gifts for each. By no means do I expect people to become patrons, but if you need to be asked, there you go :)

I’ll talk to you more next week!

Pat

Riffomonas Professional Development

Read more from Riffomonas Professional Development
man floating holding on orange stick white people watching on the street

Hey folks, I have long since given up trying to anticipate what types of videos will resonate with people on YouTube. One of my most popular videos shows people how to make stacked bar plots. Throughout it, I tell people that these are a horrible way to visualize data. It’s my third most viewed video. I thought a video on slope plots would be popular. Nope. People panned last week’s episode. But Venn diagrams - holy cats! People are really geeking out about this week’s episodes on Venn...

Hey folks, I’m really grateful for the people who have emailed me recently to thank me for making the recreation and makeover videos. I’ve been excited to see the types of figures some of you are trying to make. It’s really been a great part of this work for me. Thank you! Eric Hill is a loyal Riffomonas Channel viewer who recently sent me an animation he made using the p5.js platform. The animation shows his son’s performance relative to other runners in the prestigious Nike Cross Nationals...

Hey folks, One of the benefits of sending out these newsletters and making my YouTube videos is that I get a ton of practice. I can’t emphasize how much practice has paid off in learning to use dplyr, ggplot2, and other packages. Reproducing published figures has really helped me to dive into parts of ggplot2 that I wouldn’t normally use because I make plots that use the features of ggplot2 that I know. By expanding my knowledge of ggplot2, I’m finding that the plots I make from scratch are...