ggplot2: Is there a fix for jagged, poor-quality text produced by geom_text()?

RGgplot2

R Problem Overview


While adding annotation text to a plot I noticed that geom_text() produced unsightly, jagged text, while annotate() produced smooth, nice-looking text. Does anyone know why this happens and if there's any way to fix it? I know I could just use annotate() here, but there are probably cases where geom_text() is preferable, and I'd like to find a fix. Also, geom_text() can't be intended to give poor-looking text, so either I'm doing something wrong, or I've run into some sort of subtle side effect.

Here's some fake data and the code to produce the graph, plus an image showing the results.

library(ggplot2)
age = structure(list(age = c(41L, 40L, 43L, 44L, 40L, 42L, 44L, 45L, 
        44L, 41L, 43L, 40L, 43L, 43L, 40L, 42L, 43L, 44L, 43L, 41L)), 
        .Names = "age", row.names = c(NA, -20L), class = "data.frame")
ggplot(age, aes(age)) + 
  geom_histogram() +
  scale_x_continuous(breaks=seq(40,45,1)) +
  stat_bin(binwidth=1, color="black", fill="blue") +
  geom_text(aes(41, 5.2, 
            label=paste("Average = ", round(mean(age),1))), size=12) +
  annotate("text", x=41, y=4.5, 
           label=paste("Average = ", round(mean(age$age),1)), size=12)

enter image description here

R Solutions


Solution 1 - R

geom_text, despite not using anything directly from the age data.frame, is still using it for its data source. Therefore, it is putting 20 copies of "Average=42.3" on the plot, one for each row. It is that multiple overwriting that makes it look so bad. geom_text is designed to put text on a plot where the information comes from a data.frame (which it is given, either directly or indirectly in the original ggplot call). annotate is designed for simple one-off additions like you have (it creates a geom_text, taking care of the data source).

If you really want to use geom_text(), just reset the data source:

ggplot(age, aes(age)) + 
  scale_x_continuous(breaks=seq(40,45,1)) +
  stat_bin(binwidth=1, color="black", fill="blue") +
  geom_text(aes(41, 5.2, 
            label=paste("Average = ", round(mean(age$age),1))), size=12,
            data = data.frame()) +
  annotate("text", x=41, y=4.5, 
           label=paste("Average = ", round(mean(age$age),1)), size=12)

enter image description here

Solution 2 - R

Try geom_text(..., check_overlap = TRUE)*

From the docs ?geom_text, check_overlap says:

> If TRUE, text that overlaps previous text in the same layer will not be plotted.

library(ggplot2)
age = structure(list(age = c(41L, 40L, 43L, 44L, 40L, 42L, 44L, 45L, 
                             44L, 41L, 43L, 40L, 43L, 43L, 40L, 42L, 43L, 44L, 43L, 41L)), 
                .Names = "age", row.names = c(NA, -20L), class = "data.frame")

ggplot(age, aes(age)) + 
  geom_histogram() +
  stat_bin(binwidth=1) +
  geom_text(aes(41, 5.2, label=paste("Average = ", round(mean(age),1))), 
            size=12, 
            check_overlap = TRUE) 

*This is essentially the answer that Dave Gruenewald posted in a comment to Brian's excellent answer. I'm just trying to make that answer more visible!

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questioneipi10View Question on Stackoverflow
Solution 1 - RBrian DiggsView Answer on Stackoverflow
Solution 2 - RRich PaulooView Answer on Stackoverflow