Simulating Outbreaks to Measure Detection Power

This guide, part of Cluster Detection Power & Benchmarking, focuses on the part of a benchmark that decides whether its results mean anything: the synthetic outbreaks themselves.

Problem Context & Constraints

A power estimate is only as meaningful as the outbreaks it was computed against. The default in most methodological work is a circular cluster with a uniform excess — every area within a radius gets the same relative risk. That shape is convenient, it suits circular scan statistics perfectly, and it resembles few real outbreaks.

Real outbreaks come in recognisable forms. Point-source outbreaks — a contaminated food venue, a cooling tower — produce risk that decays with distance from the source, highest at the centre and fading outward rather than stopping at an edge. Corridor outbreaks follow linear features: a river for vector-borne disease, a highway for a pollutant, a commuter rail line for a respiratory pathogen. Diffuse outbreaks raise risk modestly across a broad region, such as an environmental exposure affecting a whole valley. Each is detected differently, and a benchmark built only on circles says nothing about the other two.

Three constraints shape realistic simulation. Shapes must resemble the hazards the program monitors. Excess risks must be modest, because the outbreaks that are hard to detect — and therefore the ones a benchmark exists to evaluate — have relative risks of 1.3 to 2, not 5. And outbreaks must be placed across the geography, since power at one location says little about power at another.

Three Outbreak Shapes Worth Simulating Three panels over the same grid of areas. The point-source outbreak raises risk most at a single location and decays with distance, with no sharp edge. The corridor outbreak raises risk in a narrow band along a river, following its curve across many areas. The diffuse outbreak raises risk modestly across a broad region. Each panel notes which detection approach the shape favours: point-source suits circular scan windows, corridors suit local statistics or flexible windows, and diffuse excesses suit global tests. Circles are one shape among several, and not the most common Point-source decays with distance Corridor follows a river or road Diffuse modest excess, broad area simulate the shapes your program actually monitors for

Prerequisites

  • python 3.11
  • numpy 1.26.4, geopandas 0.14.4, shapely 2.0.4, pandas 2.2.2
  • The analysis geography with population, in a projected metric coordinate reference system
  • Linear features for corridor scenarios — rivers, highways, transit lines
  • A list of outbreak scenarios with shapes, extents and excess risks, ideally informed by past investigations

Step-by-Step Solution

Build a relative risk vector for each shape, calibrate it to a target number of excess cases rather than only a relative risk, place it at many locations, and simulate counts.

# Realistic outbreak relative-risk surfaces for power benchmarking.
# Pinned: numpy==1.26.4, geopandas==0.14.4, shapely==2.0.4, pandas==2.2.2
import logging

import numpy as np
import pandas as pd
import geopandas as gpd

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("outbreaks")

SEED = 20260918


def point_source(areas: gpd.GeoDataFrame, source, peak_rr: float, decay_m: float) -> np.ndarray:
    """Excess risk decaying exponentially with distance from a source point."""
    d = areas.geometry.centroid.distance(source).to_numpy()
    return 1.0 + (peak_rr - 1.0) * np.exp(-d / decay_m)


def corridor(areas: gpd.GeoDataFrame, line, peak_rr: float, width_m: float) -> np.ndarray:
    """Excess risk along a linear feature, fading with distance from the line."""
    d = areas.geometry.centroid.distance(line).to_numpy()
    return 1.0 + (peak_rr - 1.0) * np.exp(-(d / width_m) ** 2)


def diffuse(areas: gpd.GeoDataFrame, region, rr: float) -> np.ndarray:
    """Uniform modest excess across a broad region."""
    inside = areas.geometry.centroid.within(region).to_numpy()
    return np.where(inside, rr, 1.0)


def expected_excess(pop: np.ndarray, rr: np.ndarray, total: int) -> float:
    """Excess cases implied by rr for a fixed case total."""
    base = total * pop / pop.sum()
    shifted = total * pop * rr / (pop * rr).sum()
    return float((shifted - base)[rr > 1.0].sum())


def calibrate_to_excess(pop, rr_shape_fn, target_excess: float, total: int,
                        lo: float = 1.01, hi: float = 10.0) -> float:
    """Find the peak relative risk that yields a target number of excess cases."""
    for _ in range(60):
        mid = (lo + hi) / 2
        if expected_excess(pop, rr_shape_fn(mid), total) < target_excess:
            lo = mid
        else:
            hi = mid
    return (lo + hi) / 2


def placements(areas: gpd.GeoDataFrame, n: int, stratify_col: str = "urbanicity",
               seed: int = SEED) -> pd.DataFrame:
    """Sample outbreak centres stratified by urbanicity so rural power is measured too."""
    rng = np.random.default_rng(seed)
    per = max(1, n // areas[stratify_col].nunique())
    picks = (areas.groupby(stratify_col, group_keys=False)
                  .apply(lambda g: g.sample(min(per, len(g)), random_state=rng.integers(1e9))))
    log.info("placed %d outbreak centres across %s strata", len(picks),
             areas[stratify_col].nunique())
    return picks[["geoid", stratify_col, "geometry"]]

Calibrating to a target number of excess cases, rather than only a relative risk, is what makes scenarios comparable across locations. A 1.5-fold point-source excess centred in a dense suburb produces many more extra cases than the same excess centred in a farming county, and power follows excess cases far more closely than relative risk. Fixing the excess at, say, fifteen extra cases and solving for the peak relative risk at each location asks a sharper question: can the method find fifteen extra cases here?

Stratifying placements by urbanicity ensures the rural areas — where power is usually lowest and where a program is most likely to be blind — are represented rather than swamped by the many urban candidates.

Relative Risk Is Not the Same as Excess Cases Two outbreak placements with the same peak relative risk of one point five. In a dense suburb the affected areas hold a large population, so the outbreak produces about ninety extra cases and is detected almost every time. In a rural county the affected areas hold few people, so the same relative risk produces about four extra cases and is almost never detected. Calibrating each scenario to a fixed number of excess cases instead produces comparable detection challenges across locations. Same relative risk, twenty-fold difference in extra cases Dense suburb, RR 1.5 about 90 extra cases power ≈ 0.98 Rural county, RR 1.5 about 4 extra cases power ≈ 0.06 calibrate scenarios to a fixed excess to ask the same question everywhere

Validation & Edge Cases

Decay parameters without a basis. A point-source decay of 2 km versus 10 km changes the outbreak’s extent fivefold. Base decay on past investigations — how far from a venue cases lived — or run a range and report power across it.

Corridors that extend beyond the study area. A river corridor running out of the state produces a truncated outbreak whose detectability depends on where it is cut. Clip corridors to the study area and report the fraction of the planted excess retained.

Outbreaks that overlap existing true clusters. If the real data contain a genuine excess, a planted outbreak on top of it is detected for the wrong reason. Simulate on null baselines generated from population, not on the observed cases.

Calibration failures. In very sparse areas no plausible relative risk yields the target excess. Cap the relative risk at a realistic maximum and record the placements that could not reach the target — they are the program’s hard blind spots.

Temporal shape ignored. A spatial benchmark plants an excess in a single period, but real outbreaks build over days or weeks. If the program runs a prospective space-time method, give each scenario an onset curve as well — a sharp rise for a point source, a slow ramp for an environmental exposure — and measure both whether the outbreak is found and how many periods pass first. A method that finds every outbreak three weeks late has high power and little public health value.

Case totals that drift with the plant. When excess cases are added on top of a fixed baseline, the simulated total rises with the outbreak, and methods that condition on the total — most scan statistics do — respond to that shift as well as to the location. Decide which regime matches the program’s data: a conditional design redistributes a fixed total, while an additive design adds cases. Report which one was used, because the two give noticeably different power at small excesses.

Too few placements per stratum. A rural stratum with five placements gives an unstable power estimate. Allocate placements so each stratum has enough for a meaningful estimate, even if urban placements are then fewer than their share.

INFO outbreaks placed 120 outbreak centres across 4 strata
INFO benchmark calibrated point-source excess=15: peak RR ranges 1.08 (urban) to 3.90 (rural, capped at 3.0 for 11 centres)
Power by Shape and Setting Grouped bars of detection power at a fixed excess of fifteen cases for three outbreak shapes across four urbanicity strata. Point-source outbreaks are detected well in urban and suburban strata and poorly in rural ones. Corridor outbreaks are detected moderately everywhere. Diffuse outbreaks are detected poorly in urban strata, where the excess is spread across many populous areas, and better in small towns. The pattern shows that no single power number describes a method. No single power number describes a method urban suburban small town rural point-source corridor diffuse detection power at a fixed excess of 15 cases

Compliance Notes

Record every scenario’s shape, parameters, target excess and calibrated peak relative risk, the placement strategy and seed, the placements that hit the relative-risk cap, and the linear features used for corridors with their source.

Simulated outbreaks are synthetic and contain no protected data, so the scenario library can and should be shared with partner agencies running comparable programs. A common scenario library makes power estimates comparable across jurisdictions, which is otherwise impossible because each agency’s benchmark rests on its own idiosyncratic choices.