Skip to contents
library(gdq)
library(dplyr, warn.conflicts = FALSE)
library(tidyr, warn.conflicts = FALSE)
library(ggplot2)
library(kableExtra)

donations <- gdq::gdq_donations |>
  augment_donations()
runs <- gdq::gdq_runs
gdq_donation_totals <-
  donations |>
  group_by(event) |>
  summarize(
    total = sum(amount),
    avg = mean(amount),
    count = n(),
    gdq = unique(gdq),
    year = unique(year),
    .groups = "drop"
  ) |>
  arrange(as.numeric(year)) |>
  mutate(
    cumtot = cumsum(total),
    cummean = cummean(total)
  )
gdq_donation_totals |>
  ggplot(aes(x = year, y = total, fill = gdq)) +
  geom_col(position = position_dodge2(preserve = "single"), alpha = .75) +
  scale_x_year_discrete() +
  scale_y_currency(
    breaks = seq(0, 10e6, 5e5),
    minor_breaks = seq(0, 10e6, 2.5e5)
  ) +
  scale_colorfill_gdq(guide = "none") +
  labs(
    title = "Games Done Quick: Donation Totals",
    subtitle = glue::glue(
      "Donation totals for <span style='color:{gdq_pal[['AGDQ']]}'>AGDQ</span> and <span style='color:{gdq_pal[['SGDQ']]}'>SGDQ</span> as per the official tracker page"
    )
  ) +
  theme_gdq()

Relative to Average

gdq_donation_totals |>
  ggplot(aes(x = as.integer(year), y = cumtot, color = gdq, fill = gdq)) +
  scale_x_year() +
  scale_y_currency() +
  scale_colorfill_gdq(guide = "none") +
  geom_line(linewidth = 1.15) +
  geom_point(shape = 21, size = 3, color = "white", stroke = 1.5) +
  labs(
    title = "Games Done Quick: Cumulative Donation Totals",
    subtitle = glue::glue(
      "Donation totals for <span style='color:{gdq_pal[['AGDQ']]}'>AGDQ</span> and <span style='color:{gdq_pal[['SGDQ']]}'>SGDQ</span> as per the official tracker page"
    )
  ) +
  theme_gdq()


gdq_donation_totals |>
  ggplot(aes(x = as.integer(year), y = total, color = gdq, fill = gdq)) +
  scale_x_year() +
  scale_y_currency() +
  scale_colorfill_gdq(guide = "none") +
  geom_line(linewidth = 1.15) +
  geom_point(shape = 21, size = 3, color = "white", stroke = 1.5) +
  labs(
    title = "Games Done Quick: Donation Totals",
    subtitle = glue::glue(
      "Donation totals for <span style='color:{gdq_pal[['AGDQ']]}'>AGDQ</span> and <span style='color:{gdq_pal[['SGDQ']]}'>SGDQ</span> as per the official tracker page"
    )
  ) +
  theme_gdq()

gdq_donation_totals |>
  group_by(gdq) |>
  mutate(lagtotal = total - lag(total, default = 0)) |>
  ggplot(aes(x = as.integer(year), y = lagtotal, color = gdq, fill = gdq)) +
  geom_col(alpha = 5 / 12, position = position_dodge2()) +
  scale_x_year() +
  scale_y_currency() +
  scale_colorfill_gdq(guide = "none") +
  labs(
    title = "Games Done Quick: Donation Delta",
    subtitle = glue::glue(
      "Difference in donation totals for <span style='color:{gdq_pal[['AGDQ']]}'>AGDQ</span> and <span style='color:{gdq_pal[['SGDQ']]}'>SGDQ</span>, e.g. AGDQ2022 - AGDQ2021"
    )
  ) +
  theme_gdq()

Relative Progress

Unfortunately finding the proper “end time” is not that easy, since the end time of the last run (or finale section) typically comes way before the last of the donations have trickled in.
Also, the last “run” listed on the runs page is sometimes the finale, sometimes the last game, sometimes an explicit total:?

full_join(
  runs |>
    group_by(event) |>
    slice_tail(n = 1) |>
    select(event, run, run_end),
  donations |>
    group_by(event) |>
    slice_tail(n = 1) |>
    select(event, amount, donation_time = time),
  by = "event"
) |>
  arrange(donation_time) |>
  mutate(
    hours_since_end = (run_end %--% donation_time) / lubridate::dhours(1)
  ) |>
  select(
    event,
    last_run = run,
    last_run_end = run_end,
    last_donation = donation_time,
    amount,
    hours_since_end
  ) |>
  kable() |>
  kable_styling()
event last_run last_run_end last_donation amount hours_since_end
AGDQ2011 NA NA 2011-01-17 20:27:17 0.80 NA
SGDQ2011 kingdom hearts 2 NA 2011-08-14 05:18:36 5.00 NA
AGDQ2012 final fantasy vii part 2 NA 2012-01-16 21:35:17 363.99 NA
SGDQ2012 bonus games NA 2012-05-30 04:40:46 1.00 NA
AGDQ2013 Bonus Games NA 2013-01-23 14:28:05 2000.00 NA
SGDQ2013 notes: NA 2013-08-14 14:33:29 1500.00 NA
AGDQ2014 bonus stream NA 2014-03-24 19:46:02 5.00 NA
SGDQ2014 Bonus Stream NA 2014-07-25 06:26:44 15.00 NA
AGDQ2015 Finale! NA 2015-01-15 21:14:47 50.00 NA
SGDQ2015 Finale! NA 2015-08-05 00:37:21 10.00 NA
AGDQ2016 Final Fantasy IV Any% no 64 floor glitch NA 2016-05-10 05:16:19 5.00 NA
SGDQ2016 Super Mario 64 70 Star Encore 70 star NA 2016-07-18 21:45:31 35.00 NA
AGDQ2017 Undertale True Pacifist Ending NA 2017-02-03 02:30:16 6.00 NA
SGDQ2017 Earthbound Any% Glitchless NA 2017-07-22 20:12:35 50.00 NA
AGDQ2018 The Legend of Zelda: Breath of the Wild All Main Quests (No Amiibo) NA 2018-03-26 18:53:24 10.00 NA
SGDQ2018 FINALE post-GDQ blues NA 2018-10-28 02:41:36 25.00 NA
AGDQ2019 Finale! 100% NA 2019-02-16 22:47:40 11.50 NA
SGDQ2019 Finale The End NA 2019-08-27 03:21:43 25.00 NA
AGDQ2020 Finale The End NA 2020-01-17 18:21:56 25.00 NA
SGDQ2020 Finale The End NA 2020-10-15 16:41:04 10825.00 NA
AGDQ2021 Finale The End NA 2021-05-13 21:35:11 5.00 NA
SGDQ2021 Finale The End NA 2021-07-11 11:45:15 300.00 NA
AGDQ2022 Finale The End NA 2022-01-26 00:02:42 10.00 NA
SGDQ2022 Finale The End NA 2022-08-02 19:52:12 77.00 NA
AGDQ2023 Finale The End NA 2023-03-03 04:29:07 5.00 NA
SGDQ2023 Finale! The End% NA 2023-06-05 14:37:12 10.00 NA
AGDQ2024 Finale! The End% NA 2024-01-22 20:02:12 16318.00 NA
SGDQ2024 Finale The End NA 2024-07-16 17:15:34 678.00 NA
AGDQ2025 Finale Finale NA 2025-01-14 02:12:32 18190.00 NA
SGDQ2025 Summer Games Done Quick 2025 Finale Finale NA 2025-07-20 13:17:22 25.00 NA

How about we compare first/last donation and first/last run times per event.

bind_rows(
  runs |>
    group_by(event) |>
    summarize(
      type = "run",
      first = min(run_start, na.rm = TRUE),
      last = max(run_end, na.rm = TRUE)
    ),
  donations |>
    group_by(event) |>
    summarize(
      type = "donation",
      first = min(time, na.rm = TRUE),
      last = max(time, na.rm = TRUE)
    )
) |>
  arrange(first) |>
  mutate(
    event = factor(event, levels = event_index$event)
  ) |>
  ggplot(aes(y = 0, xmin = first, xmax = last, fill = type, color = type)) +
  facet_wrap(facets = vars(event), scales = "free", nrow = 4) +
  geom_errorbarh(height = .5, linewidth = 1.5, alpha = 2 / 3) +
  scale_x_datetime(breaks = scales::pretty_breaks(n = 3)) +
  scale_y_continuous(limits = c(-1, 1)) +
  scale_color_brewer(palette = "Dark2", guide = "none") +
  labs(
    title = "First and Last <span style='color:#1B9E77'>Donation</span> and <span style='color:#D95F02'>Run</span>  per Event",
    subtitle = "Donations are logged long before and after the main event period",
    x = "Date"
  ) +
  theme_gdq(
    axis.text.x = element_text(size = rel(.8)),
    axis.line.y = element_blank(),
    axis.text.y = element_blank(),
    axis.title.y = element_blank()
  )

Trying to reasonably determine start and end of the main event period to scale donation progress relatively.

donations |>
  filter(time_rel >= 0 & time_rel <= 1.01) |>
  group_by(event) |>
  mutate(amount_total = cumsum(amount)) |>
  ungroup() |>
  mutate(
    highlight = event == latest_event()
  ) |>
  ggplot(aes(
    x = time_rel,
    y = amount_total,
    color = highlight,
    group = event
  )) +
  geom_step(aes(linewidth = highlight)) +
  scale_x_continuous(labels = scales::percent_format()) +
  scale_y_currency(breaks = scales::pretty_breaks()) +
  scale_color_manual(
    values = c("TRUE" = gdq_pal[["GDQ"]], "FALSE" = "gray"),
    labels = c("TRUE" = latest_event(), "FALSE" = "Previous events"),
    guide = "none"
  ) +
  scale_linewidth_manual(
    values = c("TRUE" = 2, "FALSE" = 1),
    guide = "none"
  ) +
  labs(
    title = "Latest event donation progression",
    subtitle = "Compared to previous events, with time (roughly) relative to start/end of event",
    y = "Cumulative donation total",
    x = "Event Progression"
  ) +
  theme_gdq()

For Fun Model

donation_totals_mod <- gdq_donation_totals |>
  mutate(
    year = as.integer(year),
    year = if_else(gdq == "SGDQ", year + .5, year)
  )

mod <- lm(total ~ gdq + year + I(year^2), data = donation_totals_mod)
broom::glance(mod)
#> # A tibble: 1 × 12
#>   r.squared adj.r.squared   sigma statistic  p.value    df logLik   AIC   BIC
#>       <dbl>         <dbl>   <dbl>     <dbl>    <dbl> <dbl>  <dbl> <dbl> <dbl>
#> 1     0.916         0.906 327945.      93.9 4.47e-14     3  -421.  853.  860.
#> # ℹ 3 more variables: deviance <dbl>, df.residual <int>, nobs <int>

broom::augment(mod) |>
  select(total, gdq, year, .fitted) |>
  left_join(donation_totals_mod, by = join_by(total, gdq, year)) |>
  pivot_longer(
    cols = c(total, .fitted),
    names_to = "type",
    values_to = "total"
  ) |>
  mutate(
    type = case_match(
      type,
      ".fitted" ~ "Predicted",
      "total" ~ "Observed"
    )
  ) |>
  ggplot(aes(x = year, y = total, color = type, fill = type)) +
  geom_line() +
  geom_point() +
  scale_y_currency() +
  scale_color_brewer(
    palette = "Dark2",
    name = NULL,
    aesthetics = c("color", "fill")
  ) +
  labs() +
  theme_gdq()