library(ggplot2)
#> Warning: package 'ggplot2' was built under R version 4.5.3
library(ggindex)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)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()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()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()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.
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()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()