Overview
UK police forces face the challenge of geographically uneven crime distribution — some neighbourhoods record sustained high volumes while others remain consistently quiet. Without quantitative prediction, patrol resource allocation relies on lag-heavy bureaucratic processes rather than forward-looking evidence. This project builds a supervised regression pipeline that forecasts how many crimes will be recorded in each LSOA (Lower Super Output Area) in the following calendar month, providing a data-driven input for resource planning decisions.
The project was mandatory assessed coursework for CO3093 Big Data and Predictive Analytics at the University of Leicester, worth 70% of the module grade. The dataset — 1.1M Metropolitan Police incident records from 2023 — and the high-level task structure (data preparation, regression modelling, clustering, MapReduce, written report) were specified by the module. The specific approach to feature engineering, model selection, evaluation design, and analytical framing was entirely my own work.
The intended audience is technically literate non-specialists: police analysts or policymakers who want to understand crime prediction at neighbourhood level. The report was written to be accessible to this audience rather than only to module assessors.
Approach & Architecture
The foundational design decision was the analytical unit. Rather than modelling individual incidents (1.1M rows, and not the right prediction target), I first reduced the dataset to 58,353 LSOA–month rows by counting incidents per area per month using a plain Python MapReduce implementation. This crime_count aggregate became the regression target and the unit of all subsequent analysis.
Chronological train–test split. Data was split with Feb–Sep 2023 for training and Oct–Dec 2023 as the holdout. A random split was explicitly rejected — it would mix adjacent months of the same LSOA across train and test, creating data leakage and producing over-optimistic metrics that do not reflect real predictive performance. Chronological splitting is the only valid design for a forecasting task.
Lag and rolling features as primary predictors. EDA revealed a Pearson correlation of r = 0.961 between last month’s crime count and the current month’s count for the same LSOA. This persistence signal made lag_1_crime_count the most predictive feature by a wide margin. rolling_mean_3 (trailing three-month average) added marginal noise smoothing, and month_num captured a weak seasonal signal.
K-Means clustering as a regression feature. Rather than treating the clustering requirement as a disconnected analysis, I fed the resulting district_cluster label (k=7, selected via elbow and silhouette diagnostics on a 50,000-row subsample) into the regression models as a spatial context feature. This integration gave the clustering work architectural purpose and connected it to the modelling section of the report. DBSCAN was not explored; K-Means was appropriate for the roughly convex geography of London at this scale.
A naive persistence baseline as benchmark. A model that cannot outperform “just use last month’s value” has not demonstrated genuine learning. Including the naive lag-1 baseline was a deliberate guard against overconfidence — and it turned out to be the most important decision in the evaluation design.
Development & Learning
Data cleaning removed 97,644 rows (~8.6%) from the raw 1,135,031 incidents. The first significant problem was missing LSOA codes (2.08% of records). I initially attempted to impute these from ONS geographic lookup tables using available coordinates, but every row with a missing LSOA also lacked usable coordinate data, making geographic imputation infeasible. Those rows were dropped. Additional cleaning removed location outliers (39,827 rows outside 1.5×IQR on longitude/latitude) and deduplicated Crime IDs (36,551 removed). After cleaning, zero missing LSOAs and zero duplicate IDs remained.
The MapReduce aggregation was implemented in plain Python using map() and functools.reduce. Map emitted (LSOA, Month) → 1 pairs; shuffle grouped by key; reduce summed to produce the 58,353 LSOA–month rows. The sum was verified to equal the cleaned incident count exactly, confirming no data was lost or double-counted.
The most unexpected finding was the result order: the naive baseline (RMSE 10.39) outperformed Linear Regression (RMSE 11.27), which outperformed Random Forest (RMSE 14.56). I initially treated the Random Forest’s underperformance as a possible bug. Investigating confirmed it was a genuine result: in a four-feature persistence-dominated problem, the forest fragmented a smooth lag relationship into piecewise rules and produced larger errors in the high-count tail where RMSE costs are highest. I reported this as a substantive analytical finding rather than a tuning failure — a model that isn’t better than “use last month’s value” has not demonstrated learning on this problem formulation.
A second acknowledged limitation: the district_cluster label was encoded as an integer (0–6), which implies an ordinal relationship between clusters that doesn’t exist. One-hot encoding was the correct approach; keeping it as an integer was accepted as a lightweight tradeoff given the feature’s small marginal contribution to model performance, and was noted explicitly as technical debt in the report.