
Kulldorff Circular Scan Statistic for Spatial Cluster Detection
Source:R/sus_mod_spatial_scan.R
sus_mod_spatial_scan.RdApplies the Kulldorff circular spatial scan statistic to detect geographic
clusters of disease excess. The most-likely cluster and any significant
secondary clusters are identified by Monte Carlo hypothesis testing. The
analysis requires a tabular dataset with case counts and population
denominators at the municipality level, plus an sf polygon layer
providing municipality boundaries.
Usage
sus_mod_spatial_scan(
df,
cases,
population,
municipalities,
expected = NULL,
max_pop_frac = 0.5,
n_simulations = 999L,
alpha = 0.05,
lang = c("pt", "en", "es"),
verbose = TRUE
)Arguments
- df
A
data.frame(orclimasus_df) containing at minimum: acode_municolumn (character or integer) and the columns named bycases,population, and optionallyexpected.- cases
Character. Name of the column in
dfwith case counts (non-negative integer or numeric).- population
Character. Name of the column in
dfwith the at-risk population denominator.- municipalities
An
sfobject with POLYGON or MULTIPOLYGON geometry and acode_municolumn used to join todf.- expected
Character or
NULL. Name of the column indfwith the expected number of cases under the null model. WhenNULL(default),SpatialEpi::kulldorff()computes expected counts internally from the overall rate.- max_pop_frac
Numeric in (0, 1]. Maximum fraction of the total population that a single cluster window may contain. Default
0.5.- n_simulations
Positive integer. Number of Monte Carlo simulations for hypothesis testing. Default
999. Increase to9999for publication-quality p-values.- alpha
Numeric in (0, 1). Significance level for secondary cluster filtering. Default
0.05.- lang
Character. Output language:
"pt"(default),"en","es".- verbose
Logical. Print progress messages. Default
TRUE.
Value
A climasus_spatial_scan object (named list) with:
$most_likely_clusterNamed list:
location_ids(character vector ofcode_muniin the cluster),observed(numeric),expected(numeric),RR(relative risk),log_lik(log-likelihood ratio),p_value(Monte Carlo p-value).$secondary_clustersList of lists with the same structure as
most_likely_cluster, one per significant secondary cluster. Empty list when no secondary cluster is significant.$n_clustersInteger. Total number of significant clusters (1 + length of
secondary_clusters), or 0 if the most-likely cluster itself is not significant.$dataTibble with one row per municipality:
code_muni,cases,population,expected, and a logical columnin_mlcindicating membership in the most-likely cluster.$metaNamed list of analysis parameters.
$callThe matched call.
Methodology
The scan statistic imposes circular windows of variable radius over the
map. For each window the likelihood ratio (LR) under a Poisson model
(observed vs. expected cases inside vs. outside the window) is computed.
The window with the maximum LR is the most-likely cluster. Its p-value is
obtained by comparing to the empirical distribution of max-LR under
n_simulations Monte Carlo permutations. Secondary clusters are non-
overlapping windows with LR > the alpha-level critical value from the
same Monte Carlo distribution.
Computations are delegated to SpatialEpi::kulldorff(). Municipality
centroids are derived from the polygon geometry using
sf::st_centroid() after reprojecting to WGS 84 (EPSG 4326) so that
longitude/latitude coordinates are passed to kulldorff().
References
Kulldorff, M. & Nagarwalla, N. (1995). Spatial disease clusters: detection and inference. Statistics in Medicine, 14(8), 799–810. doi:10.1002/sim.4780140809
Kulldorff, M. (1997). A spatial scan statistic. Communications in Statistics - Theory and Methods, 26(6), 1481–1496. doi:10.1080/03610929708831995
Kim, A. Y. & Wakefield, J. (2010). R data and methods for spatial epidemiology: the SpatialEpi package. University of Washington.
Examples
if (FALSE) { # \dontrun{
library(geobr)
library(dplyr)
# Download municipality polygons for Minas Gerais (state code 31)
muni_sf <- geobr::read_municipality(code_muni = 31, year = 2020)
muni_sf <- dplyr::rename(muni_sf, code_muni = code_muni)
# Build a synthetic data.frame
set.seed(42)
df_cases <- tibble::tibble(
code_muni = as.character(muni_sf$code_muni),
cases = rpois(nrow(muni_sf), lambda = 5),
population = sample(5000:200000, nrow(muni_sf), replace = TRUE)
)
result <- sus_mod_spatial_scan(
df = df_cases,
cases = "cases",
population = "population",
municipalities = muni_sf,
n_simulations = 199,
lang = "pt"
)
print(result)
result$most_likely_cluster
result$secondary_clusters
} # }