Question: how do results change when the tracker learns from NHSN data as published, rather than from finalized values?
Setup:
Configs are ref/settle_days/learn:
| axis | values |
|---|---|
ref |
paper, op |
settle_days |
14 (one week of extra delay, the reference) or
7 (none) |
learn |
final: latest vintage. exact: re-run from
the first round at every round on the data published by then, as prod
does. vintage: one run where each outcome keeps the value
it had at the round that revealed it (a stateful tracker that never
revisits a step) |
A round with reference date d sees data published by
d - 3. Vintages start 2024-11-19; earlier as-of dates use
that first vintage. “WIS reduction %” is 100 × (base WIS − calibrated
WIS) / base WIS, so positive = calibrated WIS lower than base. “L1
coverage bias” is lower-is-better.
suppressPackageStartupMessages(source(here::here("R/load_all.R")))
archive <- get_nhsn_data_archive("flu")
truth <- nhsn_read_truth("flu", archive)
vintages <- nhsn_read_vintages("flu", archive)
hub_fc <- hub_read_forecasts(hub_dir = here::here(params$hub_dir))
grid <- tidyr::expand_grid(ref = c("paper", "op"), settle_days = c(7L, 14L), learn = c("final", "vintage", "exact"))
cals <- purrr::pmap(grid, function(ref, settle_days, learn) {
cal_run(hub_fc, truth, ref, settle_days = settle_days, learn = learn, vintages = vintages, workers = params$workers)
})
names(cals) <- with(grid, paste(ref, settle_days, learn, sep = "/"))
LIVE <- c("2024-2025", "2025-2026")
first_version <- min(vintages$version)
n_clamped <- sum(unique(hub_fc$reference_date) - 3L < first_version &
hub_label_seasons(unique(hub_fc$reference_date))$season %in% LIVE)
mv <- cal_month_view(cals, seasons = LIVE, horizons = NULL)
# Runs drawn in the month view; the tables keep all twelve.
mv_plot_cfgs <- c("op/14/final", "op/14/exact", "op/7/vintage", "op/7/exact", "paper/14/final", "paper/14/exact")
0 live rounds are issued before the first vintage (2024-11-19); only
burn-in rounds use that vintage in place of their own. Configs are
ref/settle_days/learn.
Scores by calendar month of the reference date, one figure per season, states only (US excluded). Rows are the three metrics, columns the horizons:
100 × (WIS_base − WIS_cal) / WIS_base within the month, so
positive = calibrated WIS lower than base. Grey bars are the month’s
share of the season’s base WIS, so a large swing in a month with a small
bar costs little overall.100 × (bias_base − bias_cal) / bias_base, so positive =
calibrated coverage bias lower than base. The bias is computed within
the month: the mean over the 23 levels of |share of outcomes at or below
the quantile − level|. A season-pooled bias lets under-prediction on the
ramp cancel over-prediction after the peak; the monthly one does not.
When the base’s bias is already small, a small absolute change shows as
a large percentage; the month-average column of the coverage table below
gives the absolute values.A month holds one to five rounds of about 52 states, and outcomes in the same round move together, so a single month’s coverage bias is noisy and sits above zero even for a calibrated forecaster. That’s why the plot shows it relative to the base.
mv_plot_month <- mv$month
if (!is.null(get0("mv_plot_cfgs"))) {
mv_plot_month <- mv_plot_month %>% filter(config %in% mv_plot_cfgs) %>% mutate(config = droplevels(config))
}
for (s in sort(unique(mv_plot_month$season))) {
cat("\n\n", cal_h(2), " ", s, "\n\n", sep = "")
print(cal_month_plot(mv_plot_month, s, colours = get0("mv_cols"), linetype = get0("mv_linetype"), colour_by = get0("mv_colour_by") %||% "config"))
}
cat("\n\n", cal_h(2), " By horizon\n\n", sep = "")
cat("WIS reduction % (positive = calibrated WIS lower than base), states only; `both` pools the seasons.\n\n")
WIS reduction % (positive = calibrated WIS lower than base), states
only; both pools the seasons.
print(knitr::kable(cal_month_wis_table(mv)))
| config | 2024-2025 h-1 | 2024-2025 h0 | 2024-2025 h1 | 2024-2025 h2 | 2024-2025 h3 | 2025-2026 h-1 | 2025-2026 h0 | 2025-2026 h1 | 2025-2026 h2 | 2025-2026 h3 | both h-1 | both h0 | both h1 | both h2 | both h3 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| paper/7/final | +8.4 | +2.5 | +0.7 | -1.0 | -3.8 | -0.8 | -0.1 | -3.4 | -4.8 | -5.8 | +4.5 | +1.7 | -0.8 | -2.5 | -4.6 |
| paper/7/vintage | -0.5 | +0.9 | +0.7 | -0.2 | -2.3 | -2.3 | -0.1 | -2.7 | -3.0 | -3.6 | -1.3 | +0.6 | -0.5 | -1.3 | -2.8 |
| paper/7/exact | +5.7 | +2.1 | +0.8 | -0.8 | -3.5 | -0.9 | -0.3 | -3.4 | -4.5 | -5.6 | +2.9 | +1.3 | -0.7 | -2.2 | -4.3 |
| paper/14/final | +6.3 | +1.8 | +0.3 | -1.8 | -5.0 | -2.0 | -1.5 | -4.8 | -5.7 | -6.6 | +2.9 | +0.7 | -1.6 | -3.3 | -5.6 |
| paper/14/vintage | +4.6 | +1.7 | +0.7 | -1.3 | -4.3 | -0.5 | -0.4 | -3.7 | -4.7 | -6.0 | +2.5 | +1.0 | -0.9 | -2.6 | -4.9 |
| paper/14/exact | +4.6 | +1.6 | +0.5 | -1.5 | -4.5 | -2.0 | -1.7 | -4.9 | -5.6 | -6.5 | +1.9 | +0.6 | -1.5 | -3.1 | -5.3 |
| op/7/final | +12.9 | +4.0 | +3.1 | +3.1 | +4.4 | +1.3 | +4.6 | -0.2 | -1.1 | -1.1 | +8.1 | +4.2 | +1.9 | +1.5 | +2.4 |
| op/7/vintage | -0.1 | +2.3 | +2.3 | +2.5 | +4.2 | +0.4 | +2.5 | -0.0 | -0.7 | -0.7 | +0.1 | +2.3 | +1.4 | +1.3 | +2.4 |
| op/7/exact | +8.3 | +3.4 | +2.9 | +3.0 | +4.4 | +1.0 | +4.4 | -0.1 | -1.0 | -1.1 | +5.3 | +3.7 | +1.8 | +1.4 | +2.4 |
| op/14/final | +11.1 | +3.7 | +2.6 | +2.5 | +3.9 | +0.9 | +4.4 | +0.0 | -0.6 | -0.8 | +6.9 | +3.9 | +1.7 | +1.3 | +2.2 |
| op/14/vintage | +6.5 | +3.2 | +2.5 | +2.4 | +4.0 | +0.8 | +3.9 | +0.1 | -0.6 | -0.7 | +4.1 | +3.4 | +1.6 | +1.3 | +2.2 |
| op/14/exact | +7.6 | +3.3 | +2.5 | +2.5 | +3.9 | +0.8 | +4.2 | +0.1 | -0.5 | -0.8 | +4.8 | +3.6 | +1.6 | +1.3 | +2.2 |
print(knitr::kable(cal_month_cov_table(mv), caption = CAL_MONTH_COV_CAPTION))
| config | 2024-2025 h-1 | 2024-2025 h0 | 2024-2025 h1 | 2024-2025 h2 | 2024-2025 h3 | 2025-2026 h-1 | 2025-2026 h0 | 2025-2026 h1 | 2025-2026 h2 | 2025-2026 h3 | both h-1 | both h0 | both h1 | both h2 | both h3 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| base | 0.183 / 0.191 | 0.131 / 0.146 | 0.135 / 0.152 | 0.146 / 0.169 | 0.159 / 0.190 | 0.037 / 0.084 | 0.078 / 0.089 | 0.055 / 0.098 | 0.050 / 0.108 | 0.046 / 0.117 | 0.110 / 0.134 | 0.104 / 0.116 | 0.094 / 0.123 | 0.097 / 0.137 | 0.101 / 0.151 |
| paper/7/final | 0.077 / 0.166 | 0.048 / 0.122 | 0.043 / 0.133 | 0.047 / 0.171 | 0.044 / 0.211 | 0.019 / 0.073 | 0.015 / 0.090 | 0.012 / 0.109 | 0.013 / 0.115 | 0.021 / 0.123 | 0.029 / 0.116 | 0.019 / 0.105 | 0.025 / 0.120 | 0.027 / 0.141 | 0.028 / 0.164 |
| paper/7/vintage | 0.204 / 0.209 | 0.107 / 0.135 | 0.100 / 0.132 | 0.094 / 0.157 | 0.070 / 0.200 | 0.083 / 0.097 | 0.075 / 0.085 | 0.051 / 0.110 | 0.038 / 0.111 | 0.043 / 0.125 | 0.143 / 0.149 | 0.091 / 0.108 | 0.074 / 0.120 | 0.064 / 0.132 | 0.055 / 0.160 |
| paper/7/exact | 0.105 / 0.155 | 0.064 / 0.116 | 0.054 / 0.126 | 0.055 / 0.168 | 0.048 / 0.208 | 0.014 / 0.070 | 0.010 / 0.087 | 0.015 / 0.110 | 0.015 / 0.115 | 0.023 / 0.124 | 0.047 / 0.110 | 0.029 / 0.100 | 0.032 / 0.118 | 0.032 / 0.139 | 0.032 / 0.163 |
| paper/14/final | 0.076 / 0.172 | 0.042 / 0.128 | 0.037 / 0.141 | 0.034 / 0.175 | 0.046 / 0.227 | 0.016 / 0.079 | 0.009 / 0.085 | 0.015 / 0.109 | 0.025 / 0.119 | 0.039 / 0.120 | 0.030 / 0.122 | 0.021 / 0.105 | 0.024 / 0.124 | 0.027 / 0.145 | 0.029 / 0.170 |
| paper/14/vintage | 0.117 / 0.159 | 0.069 / 0.119 | 0.057 / 0.133 | 0.047 / 0.169 | 0.045 / 0.220 | 0.029 / 0.077 | 0.027 / 0.080 | 0.028 / 0.107 | 0.029 / 0.116 | 0.047 / 0.122 | 0.073 / 0.115 | 0.047 / 0.098 | 0.041 / 0.119 | 0.036 / 0.141 | 0.037 / 0.168 |
| paper/14/exact | 0.100 / 0.160 | 0.057 / 0.122 | 0.050 / 0.135 | 0.042 / 0.172 | 0.046 / 0.223 | 0.013 / 0.076 | 0.008 / 0.085 | 0.018 / 0.106 | 0.028 / 0.118 | 0.040 / 0.120 | 0.044 / 0.115 | 0.030 / 0.102 | 0.032 / 0.120 | 0.033 / 0.143 | 0.031 / 0.168 |
| op/7/final | 0.122 / 0.147 | 0.083 / 0.114 | 0.084 / 0.120 | 0.089 / 0.140 | 0.080 / 0.161 | 0.012 / 0.063 | 0.017 / 0.066 | 0.007 / 0.094 | 0.015 / 0.111 | 0.027 / 0.114 | 0.066 / 0.102 | 0.050 / 0.089 | 0.041 / 0.106 | 0.043 / 0.125 | 0.041 / 0.136 |
| op/7/vintage | 0.193 / 0.202 | 0.105 / 0.130 | 0.103 / 0.133 | 0.105 / 0.148 | 0.093 / 0.162 | 0.048 / 0.078 | 0.052 / 0.075 | 0.025 / 0.093 | 0.026 / 0.111 | 0.028 / 0.113 | 0.120 / 0.136 | 0.078 / 0.101 | 0.062 / 0.112 | 0.059 / 0.128 | 0.052 / 0.136 |
| op/7/exact | 0.138 / 0.156 | 0.089 / 0.118 | 0.088 / 0.123 | 0.094 / 0.142 | 0.083 / 0.160 | 0.013 / 0.063 | 0.021 / 0.065 | 0.008 / 0.093 | 0.016 / 0.111 | 0.026 / 0.113 | 0.075 / 0.107 | 0.054 / 0.090 | 0.045 / 0.107 | 0.045 / 0.125 | 0.042 / 0.135 |
| op/14/final | 0.122 / 0.150 | 0.084 / 0.116 | 0.085 / 0.124 | 0.089 / 0.142 | 0.077 / 0.165 | 0.013 / 0.067 | 0.016 / 0.069 | 0.007 / 0.098 | 0.017 / 0.111 | 0.027 / 0.112 | 0.066 / 0.106 | 0.050 / 0.091 | 0.041 / 0.110 | 0.044 / 0.125 | 0.039 / 0.137 |
| op/14/vintage | 0.147 / 0.164 | 0.093 / 0.121 | 0.091 / 0.127 | 0.094 / 0.144 | 0.081 / 0.164 | 0.024 / 0.072 | 0.029 / 0.069 | 0.012 / 0.096 | 0.021 / 0.110 | 0.026 / 0.113 | 0.086 / 0.115 | 0.060 / 0.093 | 0.049 / 0.110 | 0.049 / 0.126 | 0.043 / 0.137 |
| op/14/exact | 0.135 / 0.155 | 0.090 / 0.119 | 0.089 / 0.126 | 0.092 / 0.143 | 0.080 / 0.164 | 0.014 / 0.066 | 0.018 / 0.067 | 0.007 / 0.098 | 0.017 / 0.110 | 0.026 / 0.112 | 0.074 / 0.107 | 0.053 / 0.091 | 0.044 / 0.111 | 0.045 / 0.125 | 0.041 / 0.136 |
Median over locations of |vintage − exact| in the calibrated median,
relative to the base median, by month, one panel per
ref/settle_days:
drift <- purrr::map(unique(paste(grid$ref, grid$settle_days, sep = "/")), function(cfg) {
pick <- function(mode) {
cals[[paste(cfg, mode, sep = "/")]]$forecasts %>%
filter(level == 0.5, !is_burn_in, !is.na(value_base)) %>%
select(location, horizon, reference_date, value_base, value_cal)
}
inner_join(pick("vintage"), pick("exact"), by = c("location", "horizon", "reference_date", "value_base"), suffix = c("_cheap", "_exact")) %>%
mutate(config = cfg)
}) %>%
bind_rows() %>%
mutate(month = lubridate::floor_date(reference_date, "month")) %>%
group_by(config, horizon, month) %>%
summarize(rel = median(abs(value_cal_cheap - value_cal_exact) / pmax(value_base, 1)), .groups = "drop")
ggplot(drift, aes(month, 100 * rel, colour = factor(horizon), group = horizon)) +
geom_line() +
geom_point(size = 0.8) +
facet_wrap(~config) +
labs(x = NULL, y = "median |vintage − exact| / base median (%)", colour = "horizon")