Numbered markers on other plots

Scatter plots and maps are the most obvious choicies where numbered markers can be used. However, geom_focus() needs only an x and a y position, so it isn’t limited to scatter plots. Positions can be continuous, discrete or dates, and can come from your data or from a stat. This article adds numbered markers to line charts, bar charts and distributions.

library(ggplot2)
#> Warning: package 'ggplot2' was built under R version 4.5.3
library(ggindex)

Events on a line chart

To mark events on a time series, put them in their own data frame and pass it to geom_focus() with data. Here each event gets the unemployment value of its date, so the marker sits on the line.

events <- data.frame(
  date = as.Date(c("2001-03-01", "2007-12-01", "2008-09-15")),
  event = c(
    "Dot-com recession begins",
    "Great Recession begins",
    "Lehman Brothers collapses"
  )
)
events$unemploy <- approx(economics$date, economics$unemploy, events$date)$y

ggplot(economics, aes(date, unemploy)) +
  geom_line() +
  geom_focus(aes(info = event), data = events) +
  scale_info_discrete("Events") +
  labs(x = NULL, y = "Unemployed (thousands)") +
  theme_minimal()

Bar charts

On a discrete axis, markers sit at the centre of each category. Nudge them past the end of the bar with position_nudge(). Here the bars are horizontal, so the markers move along x.

classes <- as.data.frame(table(class = mpg$class), responseName = "n")

ggplot(classes, aes(n, class)) +
  geom_col(fill = "grey80") +
  geom_focus(
    aes(info = class, focus = n > 40),
    position = position_nudge(x = 3)
  ) +
  scale_info_discrete("Most common") +
  labs(x = "Models", y = NULL) +
  theme_minimal()

Points in a distribution

Number single observations on top of a box plot, such as outliers you want to name. Observations that share an info value share a number: both new beetles are 2.

ggplot(mpg, aes(class, hwy)) +
  geom_boxplot(outliers = FALSE) +
  geom_focus(aes(info = model, focus = hwy > 40)) +
  scale_info_discrete("Outliers") +
  labs(x = NULL, y = "Highway miles per gallon") +
  theme_minimal()

Summary statistics on a distribution

To mark the mean, median and mode on a density curve, compute them first and look up the height of the curve at each one with approx(). Putting the value in info makes the legend double as a table of the statistics.

waiting <- faithful$waiting
dens <- density(waiting)
centres <- data.frame(
  stat = c("Mean", "Median", "Mode"),
  x = c(mean(waiting), median(waiting), dens$x[which.max(dens$y)])
)
centres$y <- approx(dens$x, dens$y, centres$x)$y
centres$info <- sprintf("%s: %.1f min", centres$stat, centres$x)

ggplot(faithful, aes(waiting)) +
  geom_density(fill = "grey90") +
  geom_segment(
    aes(x = x, xend = x, y = 0, yend = y),
    data = centres,
    colour = "grey50",
    linetype = "dashed"
  ) +
  geom_focus(aes(x, y, info = info), data = centres) +
  scale_info_discrete(NULL) +
  labs(x = "Minutes to next eruption", y = "Density") +
  theme_minimal()

On a cumulative distribution, quantiles are points on the curve: the median is where it crosses 0.5. Label the markers with the percentage and explain it in the legend.

quartiles <- data.frame(p = c(0.25, 0.5, 0.75))
quartiles$waiting <- quantile(waiting, quartiles$p, type = 1)
quartiles$label <- quartiles$p * 100
quartiles$info <- sprintf(
  "%g%% erupt within %g min",
  quartiles$label,
  quartiles$waiting
)

ggplot(faithful, aes(waiting)) +
  stat_ecdf(geom = "step") +
  geom_focus(aes(y = p, label = label, info = info), data = quartiles) +
  scale_info_discrete("Quartiles") +
  labs(x = "Minutes to next eruption", y = "Cumulative probability") +
  theme_minimal()

type = 1 makes quantile() return observed values, so the markers sit on the steps of the empirical curve.

Positions from a stat

geom_focus() takes a stat like other geoms, so markers can show computed values. With stat = "summary" each marker sits at the class mean and shows it as the label.

ggplot(mpg, aes(class, hwy)) +
  geom_jitter(width = 0.2, height = 0, colour = "grey70") +
  geom_focus(
    aes(label = after_stat(round(y))),
    stat = "summary",
    fun = mean
  ) +
  labs(x = NULL, y = "Highway miles per gallon") +
  theme_minimal()

Other coordinate systems

Markers are placed by the plot’s coordinate system, so they follow it. The bar chart above in polar coordinates:

ggplot(classes, aes(class, n)) +
  geom_col(fill = "grey80") +
  geom_focus(aes(info = class, focus = n > 40)) +
  scale_info_discrete("Most common") +
  coord_radial() +
  labs(x = NULL, y = NULL) +
  theme_minimal()