
Nowcast a tbl_now with any supported modelling package
run_nowcast.RdFits a nowcasting model to a tbl_now and returns the result in a
package-agnostic shape, so that models from different packages can be
compared, scored and combined (nowcast_ensemble()) without any manual
reshaping.
The function is named run_nowcast() rather than nowcast() because
diseasenowcasting already exports a nowcast() function; keeping the
names distinct means both packages can be attached at once.
Usage
run_nowcast(x, engine = engine_diseasenowcasting(), verbose = TRUE)Arguments
- x
A
tbl_nowobject.- engine
A
engine()object: the modelling package and every argument it takes, includingmin_dateandquantile_levels. Defaults toengine_diseasenowcasting().The data and
verboseare the only things that sit outside it. That is the point: an argument in an outer...had to be routed to the right backend by name, and one that missed simply vanished, leaving the model at its default with nothing to say so.- verbose
Logical. Whether to report what is being done.
Value
A tbl_nowcast object.
Engines
engine_diseasenowcasting()Bayesian structural time series. The
tbl_nowis passed in directly, so strata and temporal effects are picked up automatically.engine_baselinenowcast()Fast, assumption-light baseline built from the reporting triangle. Stratified objects are nowcast one triangle per stratum.
engine_epinowcast()Bayesian model with separate delay and reference modules;
preprocess_argscontrolstbl_now_to_epinowcast().engine_surveillance()Höhle & an der Heiden's nowcast, fed by
tbl_now_to_surveillance(). The package models one series, so a stratified object is fitted one stratum at a time.engine_epinow2()EpiNow2::estimate_infections(), fed bytbl_now_to_EpiNow2();EpiNow2::regional_epinow()when the object declares strata.engine_nobbs()Nowcasting by Bayesian Smoothing, fed by
tbl_now_to_nobbs(), which expands counts to the one row per case NobBS counts.
engine() covers any other registered method, including one you wrote
yourself. Every modelling package is an optional dependency: it is only needed
when you ask for its engine.
What the object contributes, and what it does not
run_nowcast() reads the tbl_now's declarations and hands each package the
shape it wants. Three of those declarations behave differently enough to be
worth stating plainly.
Strata. How many strata a backend can honour is a property of the package, not of this one. Where a backend cannot, it warns and pools rather than pretending:
| engine | how strata are modelled |
"baselinenowcast" | one reporting triangle, and one fit, per stratum |
"surveillance" | one fit per stratum; the package models a single series |
"EpiNow2" | regional_epinow() instead of estimate_infections() |
"epinowcast" | passed to the model as by, so they are fitted jointly |
"diseasenowcasting" | fitted jointly; the package returns a [draws x time x stratum] array, one slice per combination |
"NobBS" | NobBS.strat() instead of NobBS() |
Every backend takes any number of strata. The two that model one series
at a time ("surveillance") or accept a single column
("NobBS.strat()") are given the interaction of the declared columns,
which is what nowcasting each combination separately means; the label is
split back into its columns on the way out.
The one thing that can go wrong is a stratum value that already contains
the " | " used to join them. That is an error rather than a guess, because
silently mis-assigning strata is worse than refusing.
Whatever strata columns come back are what the result reports as its
strata.
Columns you did not declare are summed away by the converters (see
tbl_now_to_baselinenowcast()). A tbl_now built from covid_colombia
without strata = sex is nowcast as one pooled series, not silently split.
Temporal effects. add_temporal_effects() specs are lazy; the converters
materialise them into ordinary columns, so they travel with the data. Whether
the model then uses them is a separate question, and mostly the answer is
"only if you say so":
"diseasenowcasting"receives thetbl_nowitself and reads the effects off it, so they enter the model with no further work."epinowcast"carries them as covariates you name in a module formula, e.g.reference = epinowcast::enw_reference(~ 1 + day_of_week, data = ...). Without a formula referring to them they are inert.every other backend ignores them: the columns ride along so you can split on them, and nothing else happens.
Censored delays. A per-case censoring flag (see add_is_censored_report()) puts
a censored and an uncensored row in the same (event date, report date) cell,
and a reporting triangle has one slot per cell. Every backend that goes
through a converter therefore collapses the flag with a warning — counts
are summed over it, line lists drop the column. "diseasenowcasting" is the
exception: it is handed the object untouched, so the flag reaches the package
intact. To estimate a delay distribution from censored data, use
tbl_now_to_epidist() instead; nowcasting and delay estimation are different
jobs.
How each model is specified, and how to change it
run_nowcast() does not invent priors or model structure: it calls each
package with that package's own defaults and passes the engine's
arguments straight through. The defaults are not always the ones you want, and
two are worth knowing before you read the output.
engine_epinowcast() runs three modules, all at their package defaults.
The expectation module is enw_expectation(r = ~ 0 + (1 | day:.group)) – a
random effect per day on the growth rate, which is a random walk on the
log expected counts in all but name. The reference module is
enw_reference(parametric = ~ 1, distribution = "lognormal") – a single
lognormal reporting delay, constant over time. The report module is
enw_report(non_parametric = ~ 0) – no day-of-week reporting effect.
Each is a named argument of the engine, and preprocess_args carries
tbl_now_to_epinowcast()'s own arguments:
run_nowcast(nowobj, engine_epinowcast(
preprocess_args = list(max_delay = 30),
report = epinowcast::enw_report(~ 1 + day_of_week, data = pobs),
fit = epinowcast::enw_fit_opts(chains = 4, iter_sampling = 1000)
))engine_epinow2() – read this before trusting the output.
EpiNow2::estimate_infections() defaults to delays = delay_opts(), which is
Fixed(0): no reporting delay at all. Its generation_time = gt_opts()
is Fixed(1), a one-day generation time. Those defaults describe a process
with nothing to nowcast, so supply the epidemiology yourself. EpiNow2 also
models \(R_t\) with a Gaussian process by default
(rt_opts(rw = 0, gp_on = "R_t-1")) rather than a random walk:
run_nowcast(nowobj, engine_epinow2(
generation_time = EpiNow2::gt_opts(EpiNow2::example_generation_time),
delays = EpiNow2::delay_opts(EpiNow2::example_reporting_delay),
rt = EpiNow2::rt_opts(rw = 7) # weekly random walk instead
))engine_diseasenowcasting() uses the package's own defaults, reading
strata, covariates and temporal effects off the object. model, type and
n_draws go to diseasenowcasting::nowcast().
engine_baselinenowcast() is not Bayesian and has no priors: the delay is
estimated from the reporting triangle and applied. draws sets the number of
nowcast samples, and max_delay caps the triangle's width.
engine_nobbs() has a fixed model; what you tune is max_D (maximum
delay) and moving_window (how much history is fitted). moving_window
counts event periods and must not exceed the history you hand it – ask
for more and NobBS pads its grid backwards and returns zero for every date,
with no error. specs takes its prior list.
engine_surveillance() takes fit_method, which is
surveillance's own method argument renamed so it cannot collide with
the engine's method; it defaults to "bayes.notrunc.bnb". D, when and
control are derived from the object when you do not give them.
See also
engine() and nowcast_engines to specify which model to fit and how;
autoplot() and tidy() to look at
the result; nowcast_ensemble() to combine several nowcasts;
nowcast_backtest() and score_nowcast() to find out whether they are any
good; nowcast_fit() and nowcast_tidy() to add a backend of your own, and
example_engine() for the shortest complete one. The
One dataset, many nowcasts article
fits the same data with every supported package.
Examples
data(denguedat)
# A short recent window keeps the example quick.
recent <- subset(denguedat, onset_week >= as.Date("2010-06-01"))
dengue <- tbl_now(recent,
event_date = onset_week, report_date = report_week, verbose = FALSE
)
# Every nowcast goes the same way: describe the model with an engine, then
## hand it and the data to `run_nowcast()`.
#
## `example_engine()` is a toy that ignores the reporting delay entirely; it is
# used here only so the example runs without a modelling package. Swap in a
## real one -- `engine_baselinenowcast()`, `engine_epinowcast()`,
## `engine_nobbs()` -- for anything you intend to act on.
nc <- run_nowcast(dengue, example_engine(), verbose = FALSE)
nc
#> ── A <tbl_nowcast> from method "example" ───────────────────────────────────────
#> • now: "2010-12-20"
#> • event dates: 26
#> • quantile levels: 0.025, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, and 0.975
#> • draws: none (quantiles only)
#>
#> Nowcast at "2010-11-29" (q50, 2.5-97.5% interval):
#> • 35 [28, 42]
#>
#> # A tibble: 6 × 3
#> onset_week .quantile_level .value
#> <date> <dbl> <dbl>
#> 1 2010-06-07 0.025 127
#> 2 2010-06-07 0.05 129
#> 3 2010-06-07 0.1 132
#> 4 2010-06-07 0.25 141
#> 5 2010-06-07 0.5 157
#> 6 2010-06-07 0.75 173
#> ℹ 228 more rows. Use `as_tibble()` for all of them.
# The result is a tbl_nowcast: one row per event date and quantile level.
head(tibble::as_tibble(nc))
#> # A tibble: 6 × 3
#> onset_week .quantile_level .value
#> <date> <dbl> <dbl>
#> 1 2010-06-07 0.025 127
#> 2 2010-06-07 0.05 129
#> 3 2010-06-07 0.1 132
#> 4 2010-06-07 0.25 141
#> 5 2010-06-07 0.5 157
#> 6 2010-06-07 0.75 173
# A real model is the same call with a real engine.
if (requireNamespace("baselinenowcast", quietly = TRUE)) {
nc <- run_nowcast(dengue, engine_baselinenowcast(draws = 100), verbose = FALSE)
nc
}
#> Warning: baselinenowcast expects incremental counts; converting `x` to "count-incidence"
#> with `to_count()`.
#> Warning: 29 reference times available and 36 are specified.
#> ℹ All 29 reference times will be used.
#> ── A <tbl_nowcast> from method "baselinenowcast" ───────────────────────────────
#> • now: "2010-12-20"
#> • event dates: 29
#> • quantile levels: 0.025, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, and 0.975
#> • draws: 100
#>
#> Nowcast at "2010-12-20" (q50, 2.5-97.5% interval):
#> • 13 [0, 163.3]
#>
#> # A tibble: 6 × 3
#> onset_week .quantile_level .value
#> <date> <dbl> <dbl>
#> 1 2010-06-07 0.025 157
#> 2 2010-06-07 0.05 157
#> 3 2010-06-07 0.1 157
#> 4 2010-06-07 0.25 157
#> 5 2010-06-07 0.5 157
#> 6 2010-06-07 0.75 157
#> ℹ 255 more rows. Use `as_tibble()` for all of them.