🚨 CityCrimeMap

Methodology

How CityCrimeMap ingests, normalizes, classifies, and models crime data for 22 US cities. Every step below is implemented in publicly readable Python. Nothing is proprietary.

1. Data ingest

We consume each city's official open-data portal directly. The three portal families we support cover roughly 95% of US cities that publish crime data at all:

Ingest is handled by the open-source tidycop library (MIT), a Python port of Anthony Galvan's R tidycops package. We add nothing over the wire that the city hasn't already published; we simply pull, normalize, and cache. The registry that defines each city's field mapping is a single YAML file you can inspect.

2. Offense classification

Every city defines its own offense codes — NIBRS, UCR variants, custom vocabularies, or plain-English descriptions. Direct passthrough would make cross-city comparison impossible. We normalize into eight categories:

Shooting
Robbery
Assault
Burglary
Theft
Arson
Vandalism
Arrest

Classification uses the open-source tidycop-spotcrime package. Rows that don't map cleanly are labelled Unclassified rather than force-fit into a bucket. Coverage varies by city; see each city's page for the current breakdown.

3. Geocoding (where needed)

Most feeds publish latitude/longitude. When a city publishes addresses but no coordinates (currently: Boston), we resolve them via the U.S. Census Bureau batch geocoder, with a SQLite cache to avoid re-hitting the API. Unmatched rows are counted honestly as "N incidents could not be located" beneath the map.

4. Predictive risk model

The predicted-risk overlay is generated by tidycop-hotspots, a pure-Python implementation of the machine-learning Risk Terrain Modeling methodology published by Wheeler and Steenbeek in the Journal of Quantitative Criminology (2021). No ArcGIS Pro license required. The implementation is MIT-licensed and readable.

Grid

Cities are gridded into square cells of 200–300 meters depending on incident density. Denser cities get larger cells so cell counts stay uniformly-distributed enough for training; smaller cities get tighter cells so the map has visible contrast. Hex grids are also supported upstream and are a possible future refinement.

Features

Model

sklearn.ensemble.RandomForestRegressor with n_estimators=400, min_samples_leaf=5. Random forests are the baseline model in the Wheeler & Steenbeek paper and continue to perform competitively with gradient-boosted trees at this problem scale, with a fraction of the tuning burden.

Training / test split

We split by time, not by row — the first ≈65% of the window is training, the last ≈35% is held-out test. This is the leakage-free protocol Wheeler & Steenbeek recommend. Fitting the KDE feature on the training window only is important; refitting on the whole window would leak test-window information into the feature vector.

Inference

We deliberately do inference twice. The fitted tree is applied first to the training-window KDE to compute the honest test-set PAI. It is applied a second time to a KDE built from the full window, and that surface is what we ship on the map — so the user sees where things are hot right now, not where they were hot last month. The fitted decision function is the same both times; only the input feature vector changes.

Output

We keep only the top 10% of positive-risk cells and drop the rest, which shrinks the payload from ≈15,000 cells to a few dozen. Each output cell carries risk (0–1), rank / rank_of for ordering, and pred_count — the raw predicted incident count per cell over the training-window duration.

5. Validation — Predictive Accuracy Index (PAI)

Every model is validated with the Predictive Accuracy Index:

PAI = (incidents in flagged cells / total incidents) ÷ (flagged cell area / total area)

A PAI of 1.0 is random. 2.0 is twice as good as random; 4.0 is four times. We report PAI at 10% of the area (the standard reporting convention in the RTM literature) for every city. See the predictions page for the current per-city numbers.

6. Bias and limitations

Reported crime data does not equal real crime. It reflects both criminal activity and where policing looks. Predictive models built on that data inherit both. We flag this explicitly rather than pretend otherwise:

7. Reproducibility

Everything on this site is reproducible from three open-source Python packages:

All MIT licensed. Clone the repo, install the packages, and you can regenerate every map on this site.

← Back to predictions overview

Free email alerts

Get your city crime alerts in your inbox

SpotCrime sends a daily email with crimes reported near any address you choose. Free, no app required, unsubscribe anytime.

Sign up for your city alerts on SpotCrime →

Opens spotcrime.com in a new tab.