|
| 1 | +--- |
| 2 | +title: "Implementing a scorecaster for quantile calibration" |
| 3 | +--- |
| 4 | + |
| 5 | +```{r setup, include = FALSE} |
| 6 | +knitr::opts_chunk$set( |
| 7 | + collapse = TRUE, |
| 8 | + comment = "#>", |
| 9 | + warning = FALSE, |
| 10 | + message = FALSE, |
| 11 | + cache = TRUE |
| 12 | +) |
| 13 | +``` |
| 14 | + |
| 15 | + |
| 16 | +```{r packages} |
| 17 | +library(tidyverse) |
| 18 | +library(epipredict) |
| 19 | +``` |
| 20 | + |
| 21 | +First we get some forecasts. |
| 22 | + |
| 23 | +```{r forecast-output} |
| 24 | +jhu <- case_death_rate_subset |
| 25 | +fc_time_values <- seq(as.Date("2021-03-09"), as.Date("2021-12-01"), by = "4 weeks") |
| 26 | +q_levels <- c(1, 2, 5, 8, 9) / 10 |
| 27 | +forecaster <- function(x, aheads = 7) { |
| 28 | + map(aheads, ~ arx_forecaster( |
| 29 | + x, "death_rate", c("case_rate", "death_rate"), |
| 30 | + quantile_reg(quantile_levels = q_levels), |
| 31 | + arx_args_list(ahead = .x, quantile_levels = q_levels) |
| 32 | + )$predictions |> |
| 33 | + mutate(ahead = .x) |
| 34 | + ) |> list_rbind() |
| 35 | +} |
| 36 | +
|
| 37 | +out <- map( |
| 38 | + .x = fc_time_values, |
| 39 | + .f = ~forecaster(jhu %>% filter(time_value <= .x), c(7, 14, 21, 28)), |
| 40 | + .progress = TRUE |
| 41 | +) |
| 42 | +
|
| 43 | +out <- out %>% list_rbind() |
| 44 | +out <- left_join( |
| 45 | + out, |
| 46 | + jhu, |
| 47 | + by = c("target_date" = "time_value", "geo_value") |
| 48 | +) |
| 49 | +``` |
| 50 | + |
| 51 | +Now we set up the "quantile conformal score" and the tangent integrator. |
| 52 | + |
| 53 | +```{r necessary-funs} |
| 54 | +quantile_conformal_score <- function(x, actual) { |
| 55 | + UseMethod("quantile_conformal_score") |
| 56 | +} |
| 57 | +quantile_conformal_score.distribution <- function(x, actual) { |
| 58 | + l <- vctrs::vec_recycle_common(x = x, actual = actual) |
| 59 | + map2( |
| 60 | + .x = vctrs::vec_data(l$x), |
| 61 | + .y = l$actual, |
| 62 | + .f = quantile_conformal_score |
| 63 | + ) |
| 64 | +} |
| 65 | +quantile_conformal_score.dist_quantiles <- function(x, actual) { |
| 66 | + values <- vctrs::field(x, "values") |
| 67 | + quantile_levels <- vctrs::field(x, "quantile_levels") |
| 68 | + errs <- (actual - values) * (quantile_levels > 0.5) + |
| 69 | + (values - actual) * (quantile_levels < 0.5) + |
| 70 | + abs(actual - values) * (quantile_levels == 0.5) |
| 71 | + errs |
| 72 | +} |
| 73 | +
|
| 74 | +tangent_integrator <- function(x, t, KI = 1000, Csat = 2) { |
| 75 | + # defaults from https://github.com/aangelopoulos/conformal-time-series/blob/b729c3f5ff633bfc43f0f7ca08199b549c2573ac/tests/configs/ca-COVID-deaths-4wk.yaml#L41 |
| 76 | + x <- x * log(t + 1) / (Csat * (t + 1)) |
| 77 | + up <- x >= pi / 2 |
| 78 | + down <- x <= -pi / 2 |
| 79 | + x[up] <- Inf |
| 80 | + x[down] <- -Inf |
| 81 | + mid <- !up & !down |
| 82 | + x[mid] <- KI * tan(x[mid]) |
| 83 | +} |
| 84 | +``` |
| 85 | + |
| 86 | +Score the forecasts. |
| 87 | + |
| 88 | +```{r score-fcasts} |
| 89 | +out <- out |> |
| 90 | + mutate(qc_scores = quantile_conformal_score(.pred_distn, death_rate)) |
| 91 | +``` |
| 92 | + |
| 93 | +Now we would need a "scorecaster". The paper has code here: |
| 94 | +https://github.com/aangelopoulos/conformal-time-series/blob/b729c3f5ff633bfc43f0f7ca08199b549c2573ac/tests/datasets/covid-ts-proc/statewide/death-forecasting-perstate-lasso-qr.ipynb |
| 95 | + |
| 96 | +Not quite sure what the model is. Note that `epipredict::quantile_reg()` may work |
| 97 | +(without the $\ell_1$ penalty). |
0 commit comments