Understanding the ggplot2 Annotation Issue with Facets

When creating visualizations in R using ggplot2, adding annotations with annotate() is a standard approach. On single-panel plots, annotate() seamlessly accepts vectors for coordinates and labels, allowing you to add multiple text labels in a single line of code.

However, when you apply faceting using facet_wrap() or facet_grid(), passing vectors to annotate() throws an aesthetic error similar to this:

Error in `check_aesthetics()`:
! Aesthetics must be either length 1 or the same as the data (14).
✖ Fix the following mappings: `label`.

Why Does annotate() Fail on Faceted Plots?

Under the hood, annotate() builds a lightweight, dummy layer data frame. When faceting is enabled, ggplot2 attempts to map the aesthetics of that layer across the faceting layout. Because the dummy layer created by annotate() lacks the faceting variable, ggplot2 struggles to reconcile vector lengths greater than 1 against the plot's facet structure.

While chaining multiple annotate() statements works as a quick workaround, it is not scalable when dealing with many annotations.

The Best Solution: Use geom_text() with a Custom Data Frame

The most idiomatic and scalable solution in ggplot2 is replacing annotate() with geom_text() (or geom_label()) powered by a dedicated annotation data frame.

Scenario 1: Adding the Same Vectorized Labels to Every Facet

If you want the exact same set of labels placed at identical coordinates across every facet, construct a data frame without the faceting variable. ggplot2 will automatically replicate the annotations across all facets:

library(ggplot2)

# Create a data frame for annotations
anno_df <- data.frame(
  x = c(3, 5),
  y = c(5000, 10000),
  label = c('hello', 'world')
)

# Plot using geom_text instead of annotate
ggplot(diamonds, aes(x = carat, y = price)) +
  geom_point() +
  facet_wrap(~ color) +
  geom_text(data = anno_df, aes(x = x, y = y, label = label))

Scenario 2: Adding Facet-Specific Annotations

If your goal is to place different labels or distinct coordinates on specific facets, include the faceting variable in your annotation data frame:

# Create facet-specific annotations
facet_anno_df <- data.frame(
  color = factor(c("D", "E"), levels = levels(diamonds$color)),
  x = c(3, 4),
  y = c(15000, 12000),
  label = c("High Value D", "Noticeable E")
)

ggplot(diamonds, aes(x = carat, y = price)) +
  geom_point() +
  facet_wrap(~ color) +
  geom_text(data = facet_anno_df, aes(x = x, y = y, label = label))

Summary

While annotate() is convenient for single plots, switching to geom_text() or geom_label() with a explicit data frame is the recommended standard for faceted plots in ggplot2. This approach is clean, vectorizable, and scales efficiently regardless of the number of facets or labels required.