Vignette 4: Artificial Cluster Injection
Source:vignettes/04-artificial_cluster_injection.Rmd
04-artificial_cluster_injection.RmdOverview: Creation of artificial target signals
This package provides a function inject_counts to create
artificial cluster signals for addition to background data in order to
test and evaluate the detection capability of the package, particularly
the find_clusters method. The function adds a total number
of case counts to a cylindrical region. The counts are added into the
3-column input frame at locations within a fixed radius of central
location.
Available options for signal injections
For purely spatial clusters, the counts are added on a single date.
For spatio-temporal clusters of events, the counts may be spread over
multiple days. The total number of added case counts, the final
injection date, the fixed radius, and the number of inject days are all
prescribed by the user as calling arguments to
inject_counts.
There are two ways to specify the center location.
Most directly, the user may use one of the distance matrix locations for the argument
target_loc. This cluster center need not be included with case counts in the input framecases.The user may also have
inject_countsrandomly choose the center location from an area within the distance matrix where counts are dense or sparse. For this option, distance matrix locations are partitioned into deciles according to their total number of counts in the input framecases. To specify this method of injection, the caller setstaget_loctoNULLand specifies an integer from 1 to 10 forcenter_decile, where a value 1 is used to indicate a sparse-data rural location and a value of 10 indicates a dense-count urban location.
Generating Cluster Signals for Detection
Calling arguments of inject_counts are:
-
cases: a data frame of (date, location, count) triples in the same format asexample_count_data -
distance matrix: as returned by<resolution>_distance_matrix()[["distance_matrix]] -
target_loc: one of the locations in the distance matrix, or if NULL, -
center_decile: if target_loc is NULL, an integer between 1 and 10 -
radius_miles: conceptually, the maximum cluster radius about the center location; technically, the largest allowable distance from the center location centroid to the centroid of another location in the cluster -
nr_cases: total number of cases to be injected -
detect_date: final date for the injected cluster -
nr_days: number of days with injected cases; ifnr_days = 1, all injects are on detect_date
Example Cluster Signal Generation
The following example begins with the package’s
example_count_data table, randomizes counts to ensure no
initial clusters, and injects 100 case counts over a 20-mile radius at a
randomly chosen rural location. The resulting data frame is then passed
to the find_clusters to illustrate detection of the
injected signal.
# Use example count data to find clusters randomize counts to ensure no initial clusters
d <- data.table::copy(example_count_data)
# Here we simply replace the counts with randomly distribution Poisson count data, to ensure that there are no true clusters in the data prior to injection
d[, count := rpois(n = 1, lambda = 15), .(location, date)]
# set the cluster detection date (i.e. end date of prospective space-time clusters) as the final date in the dataset
detect_date <- d[, max(date)]
# get the square distance matrix for counties in Ohio, to align with the package's example data set.
distance_locations <- county_distance_matrix("OH", unit = "miles")
# Confirm there are no clusters after replace counts with random couns
cl <- find_clusters(
case = d,
distance_matrix = distance_locations$distance_matrix,
detect_date = detect_date,
distance_limit = 50
)Now, we inject a 1-day cluster with a 20-mile radius at a randomly chosen rural location
# set seed for reproducibility
set.seed(1)
# We will inject 100 cases
nr_cases1 <- 100
# the center location will not be set explicitly, but rather, chosen randomly
# using the center_decile parameter
target_loc <- NULL
center_decile <- 1
# set the radius within which the injected cases should distributed, relative to
# the center location
radius_miles <- 20
# set the number of days over which the injected cases should be distributed.
nr_days1 <- 1
# Call the function to inject the clusters
scenario_1 <- inject_counts(
cases = d,
distance_matrix = distance_locations$distance_matrix,
target_loc = target_loc,
center_decile = center_decile,
radius_miles = radius_miles,
nr_cases = nr_cases1,
nr_days = nr_days1,
end_date = detect_date
)The inject_counts function returns a named list of two
objects 1. case_counts_inj: a data frame of the same
dimensions as the original location-count-date frame passed to
inject_counts, but now updated with new artificial/injected
case counts 2. inject_tbl: a data frame holding the
injected cases (i.e. only the location-count-date rows that were added
to the original data. This is shown below, for the above example:
scenario_1[["inject_tbl"]]
#> location count date
#> <char> <num> <Date>
#> 1: 39017 51 2025-02-05
#> 2: 39061 49 2025-02-05
# Passed the updated
cluster_data <- find_clusters(
cases = scenario_1[["case_counts_inj"]],
distance_matrix = distance_locations$distance_matrix,
detect_date = detect_date,
distance_limit = 50
)
# Get tigris based shape file
oh <- tigris::counties("OH", cb = TRUE, class = "sf")
#> | | | 0% | |= | 1% | |= | 2% | |== | 2% | |== | 3% | |== | 4% | |=== | 4% | |=== | 5% | |==== | 5% | |==== | 6% | |===== | 7% | |====== | 9% | |======= | 10% | |======= | 11% | |======== | 11% | |======== | 12% | |=========== | 16% | |============== | 20% | |======================== | 34% | |================================= | 48% | |====================================== | 54% | |================================================ | 68% | |========================================================= | 82% | |========================================================== | 83% | |==================================================================== | 97% | |======================================================================| 100%
# Pass these to the map_clusters() function
map_clusters(cl = cluster_data, s = oh, s_id = "GEOID")Example with multiple artificial signals
To create multiple clusters, calls to inject_counts may
be chained as follows:
nr_cases1 <- 100
if (nr_cases1 > 0) {
set.seed(1)
target_loc <- "39037" # Darke
center_decile <- 1
radius_miles <- 30
nr_days <- 1
scen1 <- inject_counts(
d, distance_locations$distance_matrix, target_loc, center_decile, radius_miles, nr_cases1, nr_days, detect_date
)
d1 <- scen1$case_counts_inj
nr_cases2 <- 30 # Set = 0 to skip second set of injects #30
if (nr_cases2 > 0) {
target_loc <- "39045" # Fairfield
center_decile <- 1
radius_miles <- 0.1
nr_days <- 2
scen2 <- inject_counts(
d1, distance_locations$distance_matrix, target_loc, center_decile, radius_miles, nr_cases2, nr_days, detect_date
)
data_table <- scen2$case_counts_inj
} else {
data_table <- d1
}
} else {
data_table <- d
}
# Get Cluster Data
cluster_data <- find_clusters(data_table, distance_locations$distance_matrix, detect_date, distance_limit = 50)
cluster_data$cluster_alert_table
#> Key: <cluster_center>
#> cluster_center cluster_start_date cluster_end_date cluster_max_distance
#> <char> <Date> <Date> <num>
#> 1: 39037 2025-02-05 2025-02-05 27.91033
#> 2: 39045 2025-02-04 2025-02-05 0.00000
#> cluster_center_observed observed expected log_obs_exp threshold alert_gap
#> <num> <int> <num> <num> <num> <num>
#> 1: 34 177 80.32448 0.7900753 0.3165810 0.4734943
#> 2: 66 66 30.73040 0.7644024 0.5828298 0.1815726
#> alert_ratio n_cluster_locations
#> <num> <int>
#> 1: 2.495650 5
#> 2: 1.311536 1
# Again, pass to the map_clusters() function
map_clusters(cl = cluster_data, s = oh, s_id = "GEOID")