crashmap
Road-safety network screening in Python. crashmap ranks road segments,
sliding windows and intersections by crash risk with the methods of the
Highway Safety Manual (AASHTO 2010, Part B, Chapter 4), fits negative binomial
safety performance functions (SPFs), computes Empirical Bayes (EB) estimates,
evaluates treatments with before-after studies and finds crash hotspots.
It is written for road-safety engineers and analysts who identify high-risk sites (black spots) from crash records. The core needs only NumPy; SciPy, pandas and GeoPandas are optional.
pip install crashmap # NumPy only
pip install "crashmap[all]" # + SciPy, pandas, GeoPandas/Shapely
Quick start
import crashmap as cm
crashes = cm.read_crashes_csv("crashes.csv") # x, y, severity, date ...
roads = cm.read_roads_geojson("roads.geojson") # LineStrings with an "aadt" attribute
segments = cm.fixed_length_segments(roads, 500) # 500 m sections
snap = cm.snap_to_lines(crashes, segments, max_distance=25)
print(snap.summary()) # good / fair / ambiguous / unmatched
sites = cm.build_segment_sites(segments, crashes, snap, years=5)
spf = cm.load_spf("spf.toml") # or cm.SPF.segment(a, b, k=...)
result = cm.screen(sites, "eb", spf=spf)
for row in result.top(10): # ties at the cut are kept
print(row["rank"], row["id"], round(row["score"], 2), row["expected"])
cm.write_geojson(result, "ranked.geojson")
cm.write_report(result, "screening.html")
Coordinates must be planar (metres in a projected CRS). The CLI detects
longitude/latitude input and projects it to a local metric frame
(crashmap.LocalProjection); output GeoJSON is written back in lon/lat.
Command line
crashmap screen crashes.csv roads.geojson --method eb --spf spf.toml -o ranked.geojson --report report.html
crashmap screen crashes.csv roads.geojson --method critical-rate --segment-length 500
crashmap screen crashes.csv roads.geojson --site-type window --window 480 --step 160
crashmap screen crashes.csv roads.geojson --site-type intersection --max-distance 76 --method rate
crashmap fit-spf sites.csv --years 5 --length-unit km -o spf.toml --cure cure.csv
crashmap before-after treated.csv --method eb --k 0.4 -o result.json
crashmap <command> --help lists every option (column names, severity scale,
--dayfirst dates, EPDO weights, confidence level, ...).
Methods
Method (screen(method=...)) |
Score (higher = more promising) | Needs | Reference |
|---|---|---|---|
frequency |
Average crash frequency, crashes/year | crashes | HSM Ch. 4 |
rate |
Crashes per million vehicle-km (or -mi); per million entering vehicles at intersections | AADT, length / entering volume | HSM Ch. 4 |
critical-rate |
Observed minus critical rate, R_c = R_a + P sqrt(R_a/E) + 1/(2E) |
AADT, length / entering volume | HSM Ch. 4 |
epdo |
EPDO average crash frequency, sum(w_s N_s)/years; default weights K 542, A/B/C 11, O 1 |
severity | HSM Ch. 4 |
rsi |
Relative severity index: average crash cost (by severity or crash type) | severity or type, costs | HSM Ch. 4 |
excess-predicted |
(N_obs - N_pred)/years |
SPF | HSM Ch. 4 |
eb |
Excess expected frequency (N_exp - N_pred)/years, N_exp = w N_pred + (1-w) N_obs, w = 1/(1 + k sum N_pred) |
SPF with k | HSM Ch. 4; Part C App. A |
proportion |
Probability that the share of a crash type exceeds the threshold proportion (Beta-binomial) | crash type or severity | HSM Ch. 4 |
Other building blocks:
| Area | Functions |
|---|---|
| Crash data | read_crashes_csv, read_crashes_geojson, crashes_from_dataframe, CrashSet (KABCO severities, dates, crash types), normalize_severity (KABCO, fatal/serious/slight, labels, 1..5 codes), parse_datetime |
| Roads and sites | read_roads_geojson, roads_from_geodataframe, fixed_length_segments, homogeneous_segments, sliding_windows, extract_intersections, build_segment_sites, build_window_sites, build_intersection_sites |
| Snapping | snap_to_lines, snap_to_points with max_distance and quality flags good / fair / ambiguous / unmatched |
| SPFs | SPF.segment (N = L exp(a + b ln AADT)), SPF.intersection (N = exp(a + b ln AADT_maj + c ln AADT_min)), custom forms, k constant or k/L, TOML/JSON I/O, fit_spf, fit_nb2 (pure-NumPy NB2 maximum likelihood), cure_plot |
| Before-after | naive_before_after, comparison_group_before_after, eb_before_after -> CMF, standard error, confidence interval (Hauer 1997) |
| Spatial | kde_grid / kde_at (planar KDE), network_kde (built-in simple NKDE or an optional nkde backend), getis_ord_gi_star with distance-band or touching weights |
| Ranking and output | rank (min/max/dense/ordinal/average ties), top_n, to_markdown, to_html (escaped), ranked_geojson, write_geojson |
| Synthetic data | synthetic_network, synthetic_sites, simulate_nb_counts -- crashes drawn from a known SPF |
SPF files
[spf]
name = "Rural two-lane segments (HSM base model)"
form = "segment" # N = L * exp(a + b ln AADT), crashes per year
a = -8.2276 # ln(365e-6) - 0.312
b = 1.0
k = 0.236
k_form = "per_length" # k_i = k / L_i
length_unit = "mi"
form = "intersection" takes a, b, c; form = "custom" takes an
intercept, an exposure variable and [[spf.terms]] tables with
variable, coefficient and transform (ln or linear). fit_spf(...).spf.save("spf.toml")
writes the same layout. SPFs must be calibrated to local conditions before use.
Validation
The test suite (98 % line coverage) checks the implementation against independent results; all data are synthetic.
- NB2 maximum likelihood vs statsmodels. On a synthetic 80-site data set
(
tests/data/nb2_reference.csv),fit_nb2reproduces statsmodels 0.15.0NegativeBinomial(loglike_method="nb2")to 1e-12: coefficients -5.387829 and 0.673613, alpha 0.282714, standard errors 1.508463, 0.157971 and 0.085230, log-likelihood -186.414967. - Parameter recovery. Crashes simulated from
N = L exp(-6.2 + 0.75 ln AADT)with k = 0.5 (3,000 sites) are fitted back within four standard errors; CURE residuals stay inside the +/-2 sigma band. - Hand calculations. EB weights and expected crashes (including the HSM rural
two-lane base model with
k = 0.236/L), critical rates and the HSM P values (1.036/1.282/1.645/2.326), crash rates, EPDO and RSI, the threshold-proportion Beta prior, and Hauer's naive, comparison-group and EB before-after formulas. - Known truth. On a synthetic grid where two roads carry four times the SPF
risk,
ebandexcess-predictedrank exactly those two roads first; EB estimates have lower error than raw counts; a simulated treatment with CMF 0.70 applied to high-count sites is recovered by the EB before-after study (0.703 +/- 0.008) while the naive study overstates the effect (0.669). - Ranking edge cases. All tie rules,
nanscores, empty input, and top-N cuts through ties.
Limitations
- Planar geometry only; use a projected CRS (or the local projection, adequate for city- and corridor-scale studies).
- Roads must be noded (split at junctions) for intersection extraction and
network KDE. Crashes are snapped to the geometrically nearest site; junction
crashes are flagged
ambiguousbut not reassigned. - SPFs are user supplied or fitted with a constant overdispersion.
k_form="per_length"infit_spfis an approximation (alpha scaled at the mean length), not a length-dependent dispersion model. No calibration factor (C) is estimated. - The EB screening uses totals over the study period without yearly
calibration factors (HSM
C_y);proportionrequires extra-binomial variation between sites and raises an error otherwise. - Before-after standard errors use Hauer's large-sample approximations; the comparison-group method assumes a Poisson comparison ratio unless its variance is given. Confidence intervals are normal approximations.
- The built-in network KDE is the "simple" kernel of Xie & Yan (2008), which
inflates densities at junctions with three or more legs. The optional
nkdebackend (or any callable) can replace it. - Getis-Ord p-values are not corrected for multiple testing.
- Screening ranks sites for diagnosis; it does not establish causes or countermeasures. Crash records are personal data: keep them out of public repositories.
References
- AASHTO (2010). Highway Safety Manual, 1st ed. American Association of State Highway and Transportation Officials, Washington, DC. Part B, Chapter 4 (network screening); Part C, Appendix A (EB method).
- Hauer, E. (1997). Observational Before-After Studies in Road Safety. Pergamon, Oxford.
- Hauer, E., Harwood, D. W., Council, F. M., Griffith, M. S. (2002). Estimating safety by the empirical Bayes method: a tutorial. Transportation Research Record 1784, 126-131.
- Persaud, B., Lyon, C. (2007). Empirical Bayes before-after safety studies: lessons learned from two decades of experience and future directions. Accident Analysis & Prevention 39(3), 546-555.
- Hauer, E., Bamfo, J. (1997). Two tools for finding what function links the dependent variable to the explanatory variables. Proceedings of the ICTCT 1997 Conference, Lund.
- Cameron, A. C., Trivedi, P. K. (2013). Regression Analysis of Count Data, 2nd ed. Cambridge University Press.
- Ord, J. K., Getis, A. (1995). Local spatial autocorrelation statistics: distributional issues and an application. Geographical Analysis 27(4), 286-306.
- Xie, Z., Yan, J. (2008). Kernel density estimation of traffic accidents in a network space. Computers, Environment and Urban Systems 32(5), 396-406.
Citation
See CITATION.cff. MIT licensed, (c) 2026 Yusuf Eminoğlu.
Metadata
Release files for crashmap 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| crashmap-0.1.1.tar.gz | 84.8 kB | With provenance | Details for crashmap-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| crashmap-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for crashmap-0.1.1-py3-none-any.whl |
Total release size: 151.7 kB
Release files / crashmap-0.1.1.tar.gz
| Download URL | crashmap-0.1.1.tar.gz |
|---|---|
| Size | 84.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ef0dbea479c51ba979966c9f3f0e2f6fc65da898603b0054ade69df2e12809f2
|
|
BLAKE2b-256 checksum How to use checksums |
dfe652404b33120efa0a09c269148c339e19578a05a3b35866bc006646dfafef
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.
Transparency logRelease files / crashmap-0.1.1-py3-none-any.whl
| Download URL | crashmap-0.1.1-py3-none-any.whl |
|---|---|
| Size | 66.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
42ab01fa92cb3dffbc44e98730f719c0919b69b4e02b352fd0b1d69662e6d4d8
|
|
BLAKE2b-256 checksum How to use checksums |
8b26d8bc58b8944039c7d5bf2631d59f7a8f56aa6eefbebf7b293cd94c98899d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.
Transparency logSecurity for crashmap 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for crashmap-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.