Skip to contents

A showcase of what ggfunction can do. Each example is self-contained.

Parametric curves

A lemniscate of Bernoulli with an arrowhead and tail marker:

lemniscate <- function(t) {
  r <- sqrt(pmax(2 * cos(2 * t), 0))
  c(r * cos(t), r * sin(t))
}

ggplot() +
  geom_function_1d_2d(
    fun = lemniscate, tlim = c(0, 1.9 * pi),
    tail_point = TRUE,
    arrow = grid::arrow(angle = 30, length = grid::unit(0.02, "npc"),
                        type = "closed")
  ) +
  coord_equal() +
  theme_void()

Lissajous figures with different frequency ratios:

make_lissajous <- function(a, b) {
  function(t) c(sin(a * t), cos(b * t))
}

p1 <- ggplot() +
  geom_function_1d_2d(fun = make_lissajous(3, 2), tlim = c(0, 2 * pi)) +
  coord_equal() + ggtitle("3:2") + theme_void()

p2 <- ggplot() +
  geom_function_1d_2d(fun = make_lissajous(5, 4), tlim = c(0, 2 * pi)) +
  coord_equal() + ggtitle("5:4") + theme_void()

p3 <- ggplot() +
  geom_function_1d_2d(fun = make_lissajous(7, 6), tlim = c(0, 2 * pi)) +
  coord_equal() + ggtitle("7:6") + theme_void()

p1 + p2 + p3

Scalar fields

The same 2D function shown as a raster, contour lines, and filled contours:

f_wave <- function(v) sin(2 * pi * v[1]) + sin(2 * pi * v[2])

p1 <- ggplot() +
  geom_function_2d_1d(fun = f_wave, xlim = c(-1, 1), ylim = c(-1, 1)) +
  ggtitle("Raster") + coord_equal()

p2 <- ggplot() +
  geom_function_2d_1d(fun = f_wave, xlim = c(-1, 1), ylim = c(-1, 1),
                       type = "contour") +
  ggtitle("Contour") + coord_equal()

p3 <- ggplot() +
  geom_function_2d_1d(fun = f_wave, xlim = c(-1, 1), ylim = c(-1, 1),
                       type = "contour_filled") +
  ggtitle("Filled contour") + coord_equal()

p1 + p2 + p3

Vector fields

A rotation field rendered as arrows and as streamlines:

f_rot <- function(u) c(-u[2], u[1])

p1 <- ggplot() +
  geom_function_2d_2d(fun = f_rot, xlim = c(-1, 1), ylim = c(-1, 1)) +
  coord_equal() + ggtitle("Arrows")

p2 <- ggplot() +
  geom_function_2d_2d(fun = f_rot, xlim = c(-1, 1), ylim = c(-1, 1),
                       type = "stream") +
  coord_equal() + ggtitle("Streamlines")

p1 + p2

Multimodal HDR

Highest density regions shine for multimodal distributions where equal-tailed intervals would miss probability mass:

bimodal <- function(x) 0.4 * dnorm(x, -2, 0.8) + 0.6 * dnorm(x, 2, 1)

ggplot() +
  geom_pdf(fun = bimodal, xlim = c(-5, 6), shade_hdr = 0.8,
           fill = "orchid") +
  labs(title = "80% Highest Density Region", subtitle = "Bimodal mixture") +
  theme_minimal()

Tail shading and rejection regions

Shade both tails simultaneously with shade_outside = TRUE — a natural visual for hypothesis testing:

ggplot() +
  geom_pdf(
    fun = dnorm, xlim = c(-4, 4),
    p_lower = 0.025, p_upper = 0.975,
    shade_outside = TRUE, fill = "firebrick"
  ) +
  labs(title = expression(alpha == 0.05 ~ "two-sided rejection region")) +
  theme_minimal()

Discrete step functions

CDF, quantile function, and survival function side-by-side for the same Binomial(10, 0.3) distribution:

binom_args <- list(size = 10, prob = 0.3)

p1 <- ggplot() +
  geom_cdf_discrete(pmf_fun = dbinom, xlim = c(0, 10), args = binom_args) +
  ggtitle("CDF")

p2 <- ggplot() +
  geom_qf_discrete(pmf_fun = dbinom, xlim = c(0, 10), args = binom_args) +
  ggtitle("Quantile function")

p3 <- ggplot() +
  geom_survival_discrete(pmf_fun = dbinom, xlim = c(0, 10), args = binom_args) +
  ggtitle("Survival")

p1 + p2 + p3

Non-integer support

A PMF on a custom support — the distribution of the sample mean from a discrete uniform on {0,0.5,1}\{0, 0.5, 1\} with n=2n = 2:

mean_probs <- function(x) {
  vals <- c(0, 0.5, 1)
  grid <- expand.grid(x1 = vals, x2 = vals)
  means <- rowMeans(grid)
  tab <- table(means) / nrow(grid)
  ifelse(as.character(x) %in% names(tab), as.numeric(tab[as.character(x)]), 0)
}

ggplot() +
  geom_pmf(fun = Vectorize(mean_probs), xlim = c(0, 1),
           support = seq(0, 1, by = 0.25)) +
  labs(title = "Sample mean distribution (n = 2)") +
  theme_minimal()

Empirical vs. theoretical

Overlay an empirical quantile function with the theoretical quantile function to visually assess goodness-of-fit:

set.seed(123)
df <- data.frame(x = rnorm(40))

ggplot(df, aes(x = x)) +
  geom_eqf(color = "steelblue") +
  geom_qf(fun = qnorm, args = list(mean = 0, sd = 1),
           color = "firebrick", linewidth = 0.8) +
  labs(title = "Empirical vs. theoretical quantile function",
       subtitle = "Blue = data with KS band, Red = N(0, 1)") +
  theme_minimal()