Time Series Forecast with Uncertainty Band in ggplot2 (R)

The same plot in 14 other libraries — Python: Altair, Bokeh, lets-plot, Matplotlib, Plotly, plotnine, Pygal, Seaborn; Julia: Makie.jl; JavaScript: Chart.js, D3.js, Apache ECharts, Highcharts, MUI X Charts. Compare all 15 side by side: Time Series Forecast with Uncertainty Band in Python, R, Julia and JavaScript.

A time series plot that displays historical observed data followed by a forecast projection with confidence intervals or uncertainty bands. The plot clearly distinguishes between the historical period and the forecast period using a vertical line marker, with shaded bands representing different confidence levels (typically 80% and 95%). This visualization is essential for communicating prediction uncertainty in forecasting applications, helping stakeholders understand both the expected values and the range of possible outcomes.

Time Series Forecast with Uncertainty Band rendered with ggplot2

Renders

R source (ggplot2)

#' anyplot.ai
#' timeseries-forecast-uncertainty: Time Series Forecast with Uncertainty Band
#' Library: ggplot2 3.5.1 | R 4.4.1
#' Quality: 88/100 | Updated: 2026-05-19

library(ggplot2)
library(ragg)

set.seed(42)

# --- Theme tokens -----------------------------------------------------------
THEME       <- Sys.getenv("ANYPLOT_THEME", "light")
PAGE_BG     <- if (THEME == "light") "#FAF8F1" else "#1A1A17"
INK         <- if (THEME == "light") "#1A1A17" else "#F0EFE8"
INK_SOFT    <- if (THEME == "light") "#4A4A44" else "#B8B7B0"
IMPRINT   <- c("#009E73", "#C475FD", "#4467A3", "#BD8233",
                 "#AE3030", "#2ABCCD", "#954477")
# On dark backgrounds, orange at low alpha reads as muddy brown; use higher
# alpha so the hue stays clearly orange rather than blending to near-black.
ALPHA_95    <- if (THEME == "light") 0.10 else 0.30
ALPHA_80    <- if (THEME == "light") 0.18 else 0.45

# --- Data generation (monthly sales forecast) --------------------------------
# Historical: 36 months of actual sales data
dates_hist <- seq(as.Date("2022-01-01"), by = "month", length.out = 36)
actual_sales <- 50000 + cumsum(rnorm(36, 500, 1000)) +
                5000 * sin(seq(0, 4 * pi, length.out = 36))

# Forecast: 6 months ahead
dates_fcst <- seq(dates_hist[length(dates_hist)] + 31, by = "month", length.out = 6)
forecast_values <- tail(actual_sales, 1) + cumsum(rnorm(6, 400, 800))

# Uncertainty widens with forecast horizon (realistic forecast behavior)
forecast_std <- 2000 * sqrt(seq_len(6))
forecast_lower_80 <- forecast_values - qnorm(0.9)   * forecast_std
forecast_upper_80 <- forecast_values + qnorm(0.9)   * forecast_std
forecast_lower_95 <- forecast_values - qnorm(0.975) * forecast_std
forecast_upper_95 <- forecast_values + qnorm(0.975) * forecast_std

# Combine into single dataframe
df <- tibble::tibble(
  date     = c(dates_hist, dates_fcst),
  actual   = c(actual_sales, rep(NA, 6)),
  forecast = c(rep(NA, 36), forecast_values),
  lower_80 = c(rep(NA, 36), forecast_lower_80),
  upper_80 = c(rep(NA, 36), forecast_upper_80),
  lower_95 = c(rep(NA, 36), forecast_lower_95),
  upper_95 = c(rep(NA, 36), forecast_upper_95)
)

# --- Plot -------------------------------------------------------------------
p <- ggplot(df, aes(x = date)) +
  # 95% confidence band (lighter shade)
  geom_ribbon(aes(ymin = lower_95, ymax = upper_95, fill = "95% CI"),
              alpha = ALPHA_95, color = NA) +
  # 80% confidence band (darker shade)
  geom_ribbon(aes(ymin = lower_80, ymax = upper_80, fill = "80% CI"),
              alpha = ALPHA_80, color = NA) +
  # Forecast line (dashed per spec)
  geom_line(aes(y = forecast, color = "Forecast"),
            linewidth = 1.2, linetype = "dashed") +
  # Historical data line
  geom_line(aes(y = actual, color = "Historical"), linewidth = 1.2) +
  # Forecast start marker
  geom_vline(xintercept = dates_hist[36] + 15.5, linetype = "dashed",
             color = INK_SOFT, linewidth = 0.8, alpha = 0.6) +
  scale_color_manual(
    values = c("Historical" = IMPRINT[1], "Forecast" = IMPRINT[2]),
    breaks = c("Historical", "Forecast")
  ) +
  scale_fill_manual(
    values = c("80% CI" = IMPRINT[2], "95% CI" = IMPRINT[2]),
    breaks = c("95% CI", "80% CI")
  ) +
  scale_x_date(expand = expansion(mult = c(0.02, 0.05))) +
  labs(
    title = "timeseries-forecast-uncertainty · r · ggplot2 · anyplot.ai",
    x     = "Date",
    y     = "Monthly Sales ($)",
    color = NULL,
    fill  = NULL
  ) +
  theme_minimal(base_size = 8) +
  theme(
    plot.background   = element_rect(fill = PAGE_BG, color = PAGE_BG),
    panel.background  = element_rect(fill = PAGE_BG, color = NA),
    panel.grid.major.y = element_line(color = INK_SOFT, linewidth = 0.2),
    panel.grid.major.x = element_blank(),
    panel.grid.minor   = element_blank(),
    axis.title        = element_text(color = INK,      size = 12),
    axis.text         = element_text(color = INK_SOFT, size = 10),
    plot.title        = element_text(color = INK,      size = 14),
    legend.position   = "top",
    legend.text       = element_text(color = INK_SOFT, size = 10),
    legend.background = element_rect(fill = PAGE_BG,   color = NA),
    legend.spacing.x  = unit(1, "cm")
  )

# --- Save -------------------------------------------------------------------
ggsave(
  filename = sprintf("plot-%s.png", THEME),
  plot     = p,
  device   = ragg::agg_png,
  width    = 8,
  height   = 4.5,
  units    = "in",
  dpi      = 400
)

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/timeseries-forecast-uncertainty/ggplot2/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "timeseries-forecast-uncertainty",
  "language": "r",
  "library": "ggplot2",
  "page": "https://anyplot.ai/timeseries-forecast-uncertainty/r/ggplot2",
  "hub": "https://anyplot.ai/timeseries-forecast-uncertainty",
  "code_json": "https://api.anyplot.ai/specs/timeseries-forecast-uncertainty/ggplot2/code",
  "spec_json": "https://api.anyplot.ai/specs/timeseries-forecast-uncertainty",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/timeseries-forecast-uncertainty/r/ggplot2/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/timeseries-forecast-uncertainty/r/ggplot2/plot-dark.png",
  "quality_score": 88.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Time Series Forecast with Uncertainty Band on anyplot.ai.

Other implementations