@@ -111,67 +111,63 @@ presymptomatic_transmission_to_alpha <- function(presymptomatic_transmission) {
111111 res$minimum
112112}
113113
114- #' Calculate proportion of runs that have controlled outbreak
114+ #' Outbreak extinction functions
115115#'
116- #' @inherit detect_extinct details
116+ #' @description
117+ #' `extinct_prob()`: Calculate proportion of runs that have controlled outbreak
117118#'
118- #' @inheritParams detect_extinct
119+ #' ` detect_extinct()`: Calculate whether outbreaks went extinct or not
119120#'
120- #' @return a single `numeric` with the probability of extinction
121- #' @export
121+ #' @details
122+ #' The data passed to `scenario` has to be produced by [scenario_sim()].
123+ #' It cannot be produced by [outbreak_model()] as it requires the `sim` column,
124+ #' which is only appended in [scenario_sim()].
122125#'
123- #' @examples
124- #' res <- scenario_sim(
125- #' n = 10,
126- #' initial_cases = 1,
127- #' offspring = offspring_opts(
128- #' community = \(n) rnbinom(n = n, mu = 2.5, size = 0.16),
129- #' isolated = \(n) rnbinom(n = n, mu = 0.5, size = 1)
130- #' ),
131- #' delays = delay_opts(
132- #' incubation_period = \(n) rweibull(n = n, shape = 2.32, scale = 6.49),
133- #' onset_to_isolation = \(n) rweibull(n = n, shape = 1.65, scale = 4.28)
134- #' ),
135- #' event_probs = event_prob_opts(
136- #' asymptomatic = 0,
137- #' presymptomatic_transmission = 0.5,
138- #' symptomatic_ascertained = 0.2
139- #' ),
140- #' interventions = intervention_opts(quarantine = FALSE),
141- #' sim = sim_opts(cap_max_days = 350, cap_cases = 4500)
142- #' )
143- #' extinct_prob(res, cap_cases = 4500)
144- extinct_prob <- function(outbreak_df_week, cap_cases, week_range = 12:16) {
145-
146- checkmate::assert_data_frame(outbreak_df_week)
147- checkmate::assert_number(cap_cases, lower = 0)
148- checkmate::assert_numeric(week_range)
149-
150- n <- max(outbreak_df_week$sim)
151-
152- extinct_runs <- detect_extinct(outbreak_df_week, cap_cases, week_range)
153- sum(extinct_runs$extinct) / n
154- }
155-
156-
157- #' Calculate whether outbreaks went extinct or not
126+ #' ***Warning***: the output from [scenario_sim()] contains an `cap_cases`
127+ #' attribute which is used by [extinct_prob()] and [detect_extinct()],
128+ #' therefore if you modify the output of [scenario_sim()] before passing
129+ #' to [extinct_prob()] be careful not to drop the attribute (e.g.
130+ #' from subsetting the `data.table`).
158131#'
159- #' @details
160- #' The `cap_cases` argument should be equal to the value supplied to
161- #' [outbreak_model()] (possibly passed from [scenario_sim()]).
132+ #' @param scenario a `data.table`: weekly cases output by [scenario_sim()]
133+ #' @param extinction_week By default `NULL` but also accepts a positive
134+ #' `integer` scalar or `integer` vector to test if the outbreak has gone
135+ #' extinct (i.e. no new cases) by this week (zero indexed):
136+ #' * By default (`NULL`) the extinction status is stored in the output of
137+ #' `scenario_sim()` which is supplied to the `scenario` argument. If
138+ #' `extinction_week` is not specified or specified as `NULL` then the
139+ #' pre-computed extinction status from the outbreak simulation will be used.
140+ #' This is defined as all infectious cases have had the opportunity to
141+ #' transmit but no new cases are generated.
142+ #' * A single `integer` to test if extinction has occurred by this week.
143+ #' For example, `extinction_week = 5` tests whether the outbreak is
144+ #' extinct by week 5 (inclusive) until the end of the outbreak.
145+ #' * An `integer` vector of length two can be supplied to provide the lower
146+ #' and upper bounds (inclusive) of the week range to test for whether
147+ #' extinction occurred by this window. For example
148+ #' `extinction_week = c(5, 10)` will test whether the outbreak went extinct
149+ #' by week 5 and there we no new cases between weeks 5 and 10 (inclusive).
150+ #' * An `integer` vector of length _n_ can be supplied to provide the weeks
151+ #' to test for whether extinction occurred by this window. For example
152+ #' `extinction_week = 12:16` will test that there are no new infections
153+ #' between 12 and 16 weeks after the initial cases (inclusive). These
154+ #' integer sequences will most likely be contiguous but the function
155+ #' does allow non-contiguous integer sequences.
156+ #'
157+ #' If extinction occurs before the `extinction_week` window then the outbreak
158+ #' extinction is considered extinct, however, if the extinction occurs within
159+ #' the `extinction_week` window it is not considered extinct. Therefore,
160+ #' using a single `integer` for `extinction_week` and thinking of this as
161+ #' "_has the outbreak gone extinct by week X_".
162162#'
163- #' @param outbreak_df_week a `data.table`: weekly cases produced by the
164- #' outbreak model
165- #' @inheritParams sim_opts
166- #' @param week_range a positive `integer` vector: giving the (zero indexed)
167- #' week range to test for whether an extinction occurred. Default is `12:16`.
168- #' @importFrom data.table as.data.table fifelse
163+ #' @importFrom data.table setDT fifelse data.table
169164#'
170- #' @return A `data.table`, with two columns `sim` and `extinct`, for a binary
165+ #' @return
166+ #' `extinct_prob()`: a single `numeric` with the probability of extinction
167+ #'
168+ #' `detect_extinct()`: a `data.table`, with two columns `sim` and `extinct`, for a binary
171169#' classification of whether the outbreak went extinct in each simulation
172170#' replicate. `1` is an outbreak that went extinct, `0` if not.
173- #' @autoglobal
174- #' @export
175171#'
176172#' @examples
177173#' res <- scenario_sim(
@@ -193,16 +189,89 @@ extinct_prob <- function(outbreak_df_week, cap_cases, week_range = 12:16) {
193189#' interventions = intervention_opts(quarantine = FALSE),
194190#' sim = sim_opts(cap_max_days = 350, cap_cases = 4500)
195191#' )
196- #' detect_extinct(outbreak_df_week = res, cap_cases = 4500)
197- detect_extinct <- function(outbreak_df_week, cap_cases, week_range = 12:16) {
192+ #'
193+ #' # calculate probability of extinction
194+ #' extinct_prob(res)
195+ #'
196+ #' # determine if each outbreak simulation replicate has gone extinct
197+ #' detect_extinct(res)
198+ #'
199+ #' # calculate extinction in the last 2 weeks of the simulated outbreak
200+ #' # (i.e. the penultimate and last week of the outbreak)
201+ #' extinct_prob(res, extinction_week = max(res$week) - 1)
202+ #'
203+ #' # calculate extinction as no new cases between weeks 12 and 16 of the outbreak
204+ #' extinct_prob(res, extinction_week = 12:16)
205+ #' @name extinction
206+ NULL
198207
199- checkmate::assert_data_frame(outbreak_df_week)
200- checkmate::assert_number(cap_cases, lower = 0)
201- checkmate::assert_integerish(week_range)
208+ #' @rdname extinction
209+ #' @export
210+ extinct_prob <- function(scenario,
211+ extinction_week = NULL) {
212+
213+ extinct_runs <- detect_extinct(
214+ scenario = scenario,
215+ extinction_week = extinction_week
216+ )
217+ sum(extinct_runs$extinct) / max(scenario$sim)
218+ }
219+
220+ #' @rdname extinction
221+ #' @autoglobal
222+ #' @export
223+ detect_extinct <- function(scenario,
224+ extinction_week = NULL) {
225+
226+ extinct <- attr(scenario, which = "extinct", exact = TRUE)
227+ if (is.null(extinction_week)) {
228+ if (!is.null(extinct)) {
229+ message(
230+ "Calculating extinction using the extinction status from ",
231+ "the simulation."
232+ )
233+ return(
234+ data.table(
235+ sim = 1:max(scenario$sim),
236+ extinct = as.integer(extinct)
237+ )
238+ )
239+ } else {
240+ stop(
241+ "`extinction_week` not specified and `scenario` is missing the ",
242+ "`extinct` attribute.\n Use `scenario_sim()` to simulate `scenario`, ",
243+ "or specify `extinction_week`.",
244+ call. = FALSE
245+ )
246+ }
247+ }
248+
249+ checkmate::assert_data_frame(scenario)
250+ checkmate::assert_integerish(extinction_week, min.len = 1)
251+
252+ if (length(extinction_week) == 1) {
253+ extinction_week <- extinction_week:max(scenario$week)
254+ } else if (length(extinction_week) == 2) {
255+ extinction_week <- min(extinction_week):max(extinction_week)
256+ }
257+ stopifnot(
258+ "`extinction_week` not in simulated outbreak data" =
259+ all(extinction_week %in% scenario$week)
260+ )
261+ message(
262+ "Calculating extinction as no new cases within weeks: ",
263+ min(extinction_week), " to ", max(extinction_week), " (inclusive)."
264+ )
265+
266+ cap_cases <- attr(scenario, which = "cap_cases", exact = TRUE)
267+ stopifnot(
268+ "`scenario` is missing the `cap_cases` attribute.
269+ Use `scenario_sim()` to simulate `scenario`" = !is.null(cap_cases)
270+ )
202271
203- outbreak_df_week <- as.data.table(outbreak_df_week )
204- outbreak_df_week <- outbreak_df_week [week %in% week_range ]
205- outbreak_df_week [, list(
272+ scenario <- setDT(scenario )
273+ scenario <- scenario [week %in% extinction_week ]
274+ scenario [, list(
206275 extinct = fifelse(all(weekly_cases == 0 & cumulative < cap_cases), 1, 0)
207276 ), by = sim][]
208277}
0 commit comments