Delay-Adjusted Nowcasting

The right-truncation problem

From sample collection to sequence upload, there is a delay of typically 1–4 weeks. This means that when you look at the latest data, the most recent weeks are always incomplete — not because fewer people were infected, but because results have not arrived yet.

If you ignore this and plot raw counts, you see a false decline in the most recent weeks. This is called right-truncation bias.

Estimating the delay distribution

survinger fits a parametric delay distribution accounting for the fact that we can only observe delays shorter than the time elapsed since collection (right-truncation correction).

library(survinger)
data(sarscov2_surveillance)

design <- surv_design(
  data = sarscov2_surveillance$sequences,
  strata = ~ region,
  sequencing_rate = sarscov2_surveillance$population[c("region", "seq_rate")],
  population = sarscov2_surveillance$population
)

delay_fit <- surv_estimate_delay(design, distribution = "negbin")
print(delay_fit)
#> ── Reporting Delay Distribution ────────────────────────────────────────────────
#> Distribution: "negbin"
#> Strata: none (pooled)
#> Observations: 1349
#> Mean delay: 9.9 days
#> 
#> # A tibble: 1 × 5
#>   stratum distribution    mu  size converged
#>   <chr>   <chr>        <dbl> <dbl> <lgl>    
#> 1 all     negbin        9.95  3.52 TRUE
plot(delay_fit)

Reporting probability

Given the fitted delay, we can ask: what fraction of sequences collected d days ago have been reported by now?

days <- c(7, 14, 21, 28)
probs <- surv_reporting_probability(delay_fit, delta = days)
data.frame(days_ago = days, prob_reported = round(probs, 3))
#>   days_ago prob_reported
#> 1        7         0.403
#> 2       14         0.797
#> 3       21         0.949
#> 4       28         0.989

Sequences collected 7 days ago may only be partially reported, while those from 28 days ago are nearly complete.

Nowcasting

Nowcasting inflates observed counts by dividing by the reporting probability, giving a better estimate of the true number:

nowcast <- surv_nowcast_lineage(design, delay_fit, "BA.2.86")
plot(nowcast)
Observed (grey bars) vs nowcasted (orange line) counts for BA.2.86
Observed (grey bars) vs nowcasted (orange line) counts for BA.2.86

The grey bars show what has been observed; the orange line shows the delay-corrected estimate. The gap is largest in the most recent weeks.

Combined design + delay correction

The main inference function applies both corrections simultaneously:

adjusted <- surv_adjusted_prevalence(design, delay_fit, "BA.2.86")
print(adjusted)
#> ── Design-Weighted Delay-Adjusted Prevalence ───────────────────────────────────
#> Correction: "design:hajek+delay:direct"
#> 
#> # A tibble: 26 × 9
#>    time     lineage n_obs_raw n_obs_adjusted prevalence     se ci_lower ci_upper
#>    <chr>    <chr>       <int>          <dbl>      <dbl>  <dbl>    <dbl>    <dbl>
#>  1 2024-W01 BA.2.86        53             53    0       0             0   0     
#>  2 2024-W02 BA.2.86        68             68    0.00597 0.0178        0   0.0408
#>  3 2024-W03 BA.2.86        40             40    0.143   0.126         0   0.389 
#>  4 2024-W04 BA.2.86        41             41    0       0             0   0     
#>  5 2024-W05 BA.2.86        48             48    0       0             0   0     
#>  6 2024-W06 BA.2.86        52             52    0       0             0   0     
#>  7 2024-W07 BA.2.86        62             62    0.00740 0.0204        0   0.0473
#>  8 2024-W08 BA.2.86        55             55    0.0195  0.0332        0   0.0847
#>  9 2024-W09 BA.2.86        43             43    0.0261  0.0480        0   0.120 
#> 10 2024-W10 BA.2.86        46             46    0.0697  0.0621        0   0.191 
#> # ℹ 16 more rows
#> # ℹ 1 more variable: mean_report_prob <dbl>

The mean_report_prob column shows how complete each week’s data is. Low values indicate that the delay correction is doing heavy lifting.

Choosing a delay distribution

mirror server hosted at Truenetwork, Russian Federation.