This project analyzes police incident report data to uncover spatial and temporal patterns in crime occurrence by type, location, and time. The goal is to provide actionable insights for public safety, urban planning, and community outreach by identifying when and where certain crimes are most likely to occur.
Specifically, this project aims to: - Identify patterns and trends in crime incidents by type, date, and location. - Group neighborhoods based on crime type frequency and distribution. - Predict whether an incident is a Part 1 or Part 2 crime using incident characteristics.
library(readr)
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(tidyr)
library(lubridate)
##
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
##
## date, intersect, setdiff, union
library(ggplot2)
library(leaflet)
library(cluster)
library(factoextra)
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
library(caret)
## Loading required package: lattice
library(pROC)
## Type 'citation("pROC")' for a citation.
##
## Attaching package: 'pROC'
## The following objects are masked from 'package:stats':
##
## cov, smooth, var
# Read the CSV file
incidents <- read_csv("incidents_part1_part2.csv")
## Rows: 160388 Columns: 18
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (6): the_geom, the_geom_webmercator, dc_dist, psa, location_block, text...
## dbl (9): cartodb_id, objectid, hour, dc_key, ucr_general, point_x, point_y,...
## dttm (1): dispatch_date_time
## date (1): dispatch_date
## time (1): dispatch_time
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# View structure and summary
glimpse(incidents)
## Rows: 160,388
## Columns: 18
## $ the_geom <chr> NA, NA, NA, NA, NA, NA, "0101000020E6100000707272…
## $ cartodb_id <dbl> 121, 122, 123, 124, 125, 1862, 1864, 1882, 1884, …
## $ the_geom_webmercator <chr> NA, NA, NA, NA, NA, NA, "0101000020110F00007F24E0…
## $ objectid <dbl> 15591523, 21013233, 21460869, 22010146, 21816905,…
## $ dc_dist <chr> "06", "02", "39", "35", "09", "22", "06", "08", "…
## $ psa <chr> "1", NA, "3", NA, "1", "3", "1", "1", "1", "3", "…
## $ dispatch_date_time <dttm> 2024-04-20 00:19:00, 2024-09-18 12:08:00, 2024-1…
## $ dispatch_date <date> 2024-04-19, 2024-09-18, 2024-10-08, 2024-10-21, …
## $ dispatch_time <time> 20:19:00, 08:08:00, 00:57:00, 06:24:00, 12:34:00…
## $ hour <dbl> 20, 8, 0, 6, 12, 4, 9, 0, 9, 13, 14, 21, 3, 19, 2…
## $ dc_key <dbl> 202406018360, 202402045910, 202439067028, 2024350…
## $ location_block <chr> "1200 BLOCK Melon St", "7400 BLOCK BUSTLETON AVE"…
## $ ucr_general <dbl> 1400, 600, 400, 500, 1400, 100, 100, 100, 100, 10…
## $ text_general_code <chr> "Vandalism/Criminal Mischief", "Thefts", "Aggrava…
## $ point_x <dbl> 0.00000, 0.00000, 0.00000, 0.00000, 0.00000, NA, …
## $ point_y <dbl> 0.00000, 0.00000, 0.00000, 0.00000, 0.00000, NA, …
## $ lat <dbl> NA, NA, NA, NA, NA, NA, 39.96020, 40.03032, 40.03…
## $ lng <dbl> NA, NA, NA, NA, NA, NA, -75.15478, -75.02343, -75…
summary(incidents)
## the_geom cartodb_id the_geom_webmercator objectid
## Length:160388 Min. : 121 Length:160388 Min. :12131841
## Class :character 1st Qu.:3205918 Class :character 1st Qu.:15603510
## Mode :character Median :3253210 Mode :character Median :18463566
## Mean :3244576 Mean :18470620
## 3rd Qu.:3293706 3rd Qu.:21370027
## Max. :3359135 Max. :27372184
##
## dc_dist psa dispatch_date_time
## Length:160388 Length:160388 Min. :2024-01-01 00:00:00
## Class :character Class :character 1st Qu.:2024-04-09 21:07:45
## Mode :character Mode :character Median :2024-07-05 05:11:30
## Mean :2024-07-03 21:35:13
## 3rd Qu.:2024-09-28 17:55:30
## Max. :2024-12-31 23:59:00
##
## dispatch_date dispatch_time hour
## Min. :2023-12-31 Min. :00:00:00.000000 Min. : 0.00
## 1st Qu.:2024-04-09 1st Qu.:09:59:00.000000 1st Qu.: 9.00
## Median :2024-07-05 Median :13:52:00.000000 Median :13.00
## Mean :2024-07-03 Mean :13:36:51.313939 Mean :13.15
## 3rd Qu.:2024-09-28 3rd Qu.:17:57:00.000000 3rd Qu.:17.00
## Max. :2024-12-31 Max. :23:59:00.000000 Max. :23.00
## NA's :2
## dc_key location_block ucr_general text_general_code
## Min. :1.992e+11 Length:160388 Min. : 100 Length:160388
## 1st Qu.:2.024e+11 Class :character 1st Qu.: 600 Class :character
## Median :2.024e+11 Mode :character Median : 700 Mode :character
## Mean :2.037e+11 Mean : 969
## 3rd Qu.:2.024e+11 3rd Qu.:1100
## Max. :2.024e+14 Max. :2600
##
## point_x point_y lat lng
## Min. :-80.50 Min. : 0.00 Min. :39.88 Min. :-80.50
## 1st Qu.:-75.18 1st Qu.:39.96 1st Qu.:39.96 1st Qu.:-75.18
## Median :-75.16 Median :39.99 Median :39.99 Median :-75.16
## Mean :-75.14 Mean :39.99 Mean :39.99 Mean :-75.15
## 3rd Qu.:-75.11 3rd Qu.:40.03 3rd Qu.:40.03 3rd Qu.:-75.11
## Max. : 0.00 Max. :42.22 Max. :42.22 Max. :-74.96
## NA's :6731 NA's :6731 NA's :6744 NA's :6744
incidents <- incidents %>%
mutate(
dispatch_date = as.Date(dispatch_date),
dispatch_time = hms::as_hms(dispatch_time),
DayOfWeek = wday(dispatch_date, label=TRUE, week_start=1),
Hour = hour(dispatch_time)
) %>%
drop_na(lat, lng, text_general_code, dc_dist)
head(incidents)
## # A tibble: 6 × 20
## the_geom cartodb_id the_geom_webmercator objectid dc_dist psa
## <chr> <dbl> <chr> <dbl> <chr> <chr>
## 1 0101000020E61000007072… 1864 0101000020110F00007… 27366086 06 1
## 2 0101000020E6100000604A… 1882 0101000020110F00009… 27366104 08 1
## 3 0101000020E610000090FB… 1884 0101000020110F0000E… 27366106 08 1
## 4 0101000020E61000007CFF… 1904 0101000020110F00002… 27366126 03 3
## 5 0101000020E61000009028… 1954 0101000020110F00007… 27366177 15 1
## 6 0101000020E610000008E7… 1959 0101000020110F00007… 27366182 19 <NA>
## # ℹ 14 more variables: dispatch_date_time <dttm>, dispatch_date <date>,
## # dispatch_time <time>, hour <dbl>, dc_key <dbl>, location_block <chr>,
## # ucr_general <dbl>, text_general_code <chr>, point_x <dbl>, point_y <dbl>,
## # lat <dbl>, lng <dbl>, DayOfWeek <ord>, Hour <int>
ggplot(incidents, aes(x = text_general_code)) +
geom_bar(fill = "steelblue") +
coord_flip() +
labs(title = "Crime Types Frequency", x = "Crime Type", y = "Count")
ggplot(incidents, aes(x=DayOfWeek)) +
geom_bar(fill="tomato") +
labs(title="Crime Count by Day of the Week", x="Day", y="Count")
## Heatmap of Crime by Hour and Day
incidents %>%
count(DayOfWeek, Hour) %>%
ggplot(aes(x=Hour, y=DayOfWeek, fill=n)) +
geom_tile() +
scale_fill_viridis_c() +
labs(title="Heatmap: Crime Count by Hour and Day", x="Hour", y="Day")
## Interactive Crime Map
leaflet(incidents) %>%
addTiles() %>%
addCircleMarkers(lng = ~lng, lat = ~lat,
color = "blue", radius = 2, stroke = FALSE)
incidents <- incidents %>%
mutate(Crime_Severity = case_when(
ucr_general >= 100 & ucr_general < 200 ~ "Part 1",
TRUE ~ "Part 2"
))
table(incidents$Crime_Severity)
##
## Part 1 Part 2
## 254 153390
crime_cluster <- incidents %>%
group_by(dc_dist) %>%
summarise(
Total_Incidents = n(),
Part1_Incidents = sum(Crime_Severity == "Part 1", na.rm = TRUE),
Part2_Incidents = sum(Crime_Severity == "Part 2", na.rm = TRUE)
) %>%
filter(!is.na(dc_dist))
head(crime_cluster)
## # A tibble: 6 × 4
## dc_dist Total_Incidents Part1_Incidents Part2_Incidents
## <chr> <int> <int> <int>
## 1 01 3046 4 3042
## 2 02 8978 9 8969
## 3 03 6485 6 6479
## 4 05 2344 1 2343
## 5 06 2062 3 2059
## 6 07 3889 3 3886
crime_cluster_scaled <- crime_cluster %>%
select(Part1_Incidents, Part2_Incidents) %>%
scale()
dim(crime_cluster_scaled)
## [1] 22 2
head(crime_cluster_scaled)
## Part1_Incidents Part2_Incidents
## [1,] -0.8437444 -1.1811062
## [2,] -0.2846367 0.6000466
## [3,] -0.6201013 -0.1482359
## [4,] -1.1792091 -1.3911662
## [5,] -0.9555660 -1.4765125
## [6,] -0.9555660 -0.9274714
set.seed(123)
kmeans_result <- kmeans(crime_cluster_scaled, centers = 3, nstart = 25)
# Add cluster labels to crime_cluster data frame
crime_cluster$cluster <- factor(kmeans_result$cluster)
# Visualize clusters
fviz_cluster(kmeans_result, data = crime_cluster_scaled,
geom = "point",
stand = FALSE,
ellipse.type = "norm",
label = crime_cluster$dc_dist) +
labs(title = "K-means Clustering of Districts by Crime Severity Counts")
crime_cluster %>%
group_by(cluster) %>%
summarise(
Avg_Part1 = mean(Part1_Incidents),
Avg_Part2 = mean(Part2_Incidents),
Avg_Total = mean(Total_Incidents),
Districts = n()
)
## # A tibble: 3 × 5
## cluster Avg_Part1 Avg_Part2 Avg_Total Districts
## <fct> <dbl> <dbl> <dbl> <int>
## 1 1 22.9 9883. 9906. 7
## 2 2 8.78 7595. 7604. 9
## 3 3 2.5 2642. 2644. 6
# Encode Crime_Severity as factor (target variable)
incidents <- incidents %>%
mutate(Crime_Severity_Factor = factor(Crime_Severity, levels = c("Part 2", "Part 1")))
# Select relevant features for modeling
model_data <- incidents %>%
select(Crime_Severity_Factor, DayOfWeek, Hour, text_general_code, dc_dist) %>%
drop_na()
# Convert categorical variables to factors
model_data$DayOfWeek <- factor(model_data$DayOfWeek, ordered = FALSE)
model_data$dc_dist <- factor(model_data$dc_dist)
model_data$text_general_code <- factor(model_data$text_general_code)
set.seed(123)
trainIndex <- createDataPartition(model_data$Crime_Severity_Factor, p = 0.7, list = FALSE)
train_data <- model_data[trainIndex, ]
test_data <- model_data[-trainIndex, ]
logit_model <- train(
Crime_Severity_Factor ~ DayOfWeek + Hour + text_general_code + dc_dist,
data = train_data,
method = "glm",
family = binomial(),
trControl = trainControl(method = "cv", number = 5)
)
## Warning: glm.fit: algorithm did not converge
## Warning: glm.fit: algorithm did not converge
## Warning: glm.fit: algorithm did not converge
## Warning in predict.lm(object, newdata, se.fit, scale = 1, type = if (type == :
## prediction from rank-deficient fit; attr(*, "non-estim") has doubtful cases
## Warning: glm.fit: algorithm did not converge
## Warning: glm.fit: algorithm did not converge
## Warning: glm.fit: algorithm did not converge
summary(logit_model$finalModel)
##
## Call:
## NULL
##
## Coefficients:
## Estimate
## (Intercept) -2.657e+01
## DayOfWeekTue -4.630e-12
## DayOfWeekWed -5.171e-12
## DayOfWeekThu -3.008e-12
## DayOfWeekFri -7.549e-12
## DayOfWeekSat -8.098e-12
## DayOfWeekSun -4.095e-12
## Hour -3.170e-14
## `text_general_codeAggravated Assault No Firearm` -1.383e-12
## `text_general_codeAll Other Offenses` 2.262e-13
## text_general_codeArson 1.999e-12
## `text_general_codeBurglary Non-Residential` -8.643e-13
## `text_general_codeBurglary Residential` 1.115e-12
## `text_general_codeDisorderly Conduct` -7.138e-13
## `text_general_codeDRIVING UNDER THE INFLUENCE` -2.399e-12
## text_general_codeEmbezzlement 1.093e-11
## `text_general_codeForgery and Counterfeiting` -4.069e-12
## text_general_codeFraud -1.184e-12
## `text_general_codeGambling Violations` -5.652e-12
## `text_general_codeHomicide - Criminal` 5.313e+01
## `text_general_codeHomicide - Justifiable` 5.313e+01
## `text_general_codeLiquor Law Violations` -6.830e-15
## `text_general_codeMotor Vehicle Theft` -3.148e-15
## `text_general_codeNarcotic / Drug Law Violations` -4.964e-14
## `text_general_codeOffenses Against Family and Children` 1.122e-14
## `text_general_codeOther Assaults` -1.121e-14
## `text_general_codeOther Sex Offenses (Not Commercialized)` -5.831e-15
## `text_general_codeProstitution and Commercialized Vice` -5.117e-14
## `text_general_codePublic Drunkenness` 3.268e-15
## text_general_codeRape -1.286e-15
## `text_general_codeReceiving Stolen Property` -1.587e-14
## `text_general_codeRobbery Firearm` -1.209e-15
## `text_general_codeRobbery No Firearm` -7.618e-15
## `text_general_codeTheft from Vehicle` -1.958e-15
## text_general_codeThefts -8.957e-15
## `text_general_codeVagrancy/Loitering` 4.541e-15
## `text_general_codeVandalism/Criminal Mischief` -6.181e-15
## `text_general_codeWeapon Violations` -5.599e-15
## dc_dist02 -1.496e-14
## dc_dist03 -2.175e-14
## dc_dist05 -8.126e-15
## dc_dist06 -1.113e-14
## dc_dist07 -3.054e-15
## dc_dist08 3.312e-13
## dc_dist09 4.117e-15
## dc_dist12 9.518e-16
## dc_dist14 1.365e-14
## dc_dist15 1.545e-14
## dc_dist16 8.146e-14
## dc_dist17 4.719e-14
## dc_dist18 3.022e-14
## dc_dist19 1.225e-14
## dc_dist22 2.073e-14
## dc_dist24 9.777e-14
## dc_dist25 -7.878e-15
## dc_dist26 -1.687e-13
## dc_dist35 -1.985e-13
## dc_dist39 7.051e-15
## dc_dist77 1.265e-14
## Std. Error z value
## (Intercept) 1.181e+04 -0.002
## DayOfWeekTue 3.885e+03 0.000
## DayOfWeekWed 3.920e+03 0.000
## DayOfWeekThu 3.942e+03 0.000
## DayOfWeekFri 3.977e+03 0.000
## DayOfWeekSat 4.124e+03 0.000
## DayOfWeekSun 4.155e+03 0.000
## Hour 1.944e+02 0.000
## `text_general_codeAggravated Assault No Firearm` 1.005e+04 0.000
## `text_general_codeAll Other Offenses` 8.968e+03 0.000
## text_general_codeArson 1.829e+04 0.000
## `text_general_codeBurglary Non-Residential` 1.366e+04 0.000
## `text_general_codeBurglary Residential` 1.109e+04 0.000
## `text_general_codeDisorderly Conduct` 2.207e+04 0.000
## `text_general_codeDRIVING UNDER THE INFLUENCE` 1.672e+04 0.000
## text_general_codeEmbezzlement 3.165e+04 0.000
## `text_general_codeForgery and Counterfeiting` 3.511e+04 0.000
## text_general_codeFraud 9.462e+03 0.000
## `text_general_codeGambling Violations` 4.880e+04 0.000
## `text_general_codeHomicide - Criminal` 2.811e+04 0.002
## `text_general_codeHomicide - Justifiable` 3.564e+05 0.000
## `text_general_codeLiquor Law Violations` 4.494e+04 0.000
## `text_general_codeMotor Vehicle Theft` 8.975e+03 0.000
## `text_general_codeNarcotic / Drug Law Violations` 1.157e+04 0.000
## `text_general_codeOffenses Against Family and Children` 2.486e+04 0.000
## `text_general_codeOther Assaults` 8.610e+03 0.000
## `text_general_codeOther Sex Offenses (Not Commercialized)` 1.569e+04 0.000
## `text_general_codeProstitution and Commercialized Vice` 2.712e+04 0.000
## `text_general_codePublic Drunkenness` 6.904e+04 0.000
## text_general_codeRape 2.005e+04 0.000
## `text_general_codeReceiving Stolen Property` 1.485e+04 0.000
## `text_general_codeRobbery Firearm` 1.333e+04 0.000
## `text_general_codeRobbery No Firearm` 1.182e+04 0.000
## `text_general_codeTheft from Vehicle` 9.242e+03 0.000
## text_general_codeThefts 8.513e+03 0.000
## `text_general_codeVagrancy/Loitering` 1.461e+05 0.000
## `text_general_codeVandalism/Criminal Mischief` 8.992e+03 0.000
## `text_general_codeWeapon Violations` 1.168e+04 0.000
## dc_dist02 8.920e+03 0.000
## dc_dist03 9.334e+03 0.000
## dc_dist05 1.172e+04 0.000
## dc_dist06 1.223e+04 0.000
## dc_dist07 1.030e+04 0.000
## dc_dist08 9.473e+03 0.000
## dc_dist09 8.626e+03 0.000
## dc_dist12 8.963e+03 0.000
## dc_dist14 8.916e+03 0.000
## dc_dist15 8.576e+03 0.000
## dc_dist16 9.939e+03 0.000
## dc_dist17 1.047e+04 0.000
## dc_dist18 9.318e+03 0.000
## dc_dist19 8.846e+03 0.000
## dc_dist22 8.729e+03 0.000
## dc_dist24 8.902e+03 0.000
## dc_dist25 8.868e+03 0.000
## dc_dist26 9.330e+03 0.000
## dc_dist35 9.068e+03 0.000
## dc_dist39 9.004e+03 0.000
## dc_dist77 1.627e+04 0.000
## Pr(>|z|)
## (Intercept) 0.998
## DayOfWeekTue 1.000
## DayOfWeekWed 1.000
## DayOfWeekThu 1.000
## DayOfWeekFri 1.000
## DayOfWeekSat 1.000
## DayOfWeekSun 1.000
## Hour 1.000
## `text_general_codeAggravated Assault No Firearm` 1.000
## `text_general_codeAll Other Offenses` 1.000
## text_general_codeArson 1.000
## `text_general_codeBurglary Non-Residential` 1.000
## `text_general_codeBurglary Residential` 1.000
## `text_general_codeDisorderly Conduct` 1.000
## `text_general_codeDRIVING UNDER THE INFLUENCE` 1.000
## text_general_codeEmbezzlement 1.000
## `text_general_codeForgery and Counterfeiting` 1.000
## text_general_codeFraud 1.000
## `text_general_codeGambling Violations` 1.000
## `text_general_codeHomicide - Criminal` 0.998
## `text_general_codeHomicide - Justifiable` 1.000
## `text_general_codeLiquor Law Violations` 1.000
## `text_general_codeMotor Vehicle Theft` 1.000
## `text_general_codeNarcotic / Drug Law Violations` 1.000
## `text_general_codeOffenses Against Family and Children` 1.000
## `text_general_codeOther Assaults` 1.000
## `text_general_codeOther Sex Offenses (Not Commercialized)` 1.000
## `text_general_codeProstitution and Commercialized Vice` 1.000
## `text_general_codePublic Drunkenness` 1.000
## text_general_codeRape 1.000
## `text_general_codeReceiving Stolen Property` 1.000
## `text_general_codeRobbery Firearm` 1.000
## `text_general_codeRobbery No Firearm` 1.000
## `text_general_codeTheft from Vehicle` 1.000
## text_general_codeThefts 1.000
## `text_general_codeVagrancy/Loitering` 1.000
## `text_general_codeVandalism/Criminal Mischief` 1.000
## `text_general_codeWeapon Violations` 1.000
## dc_dist02 1.000
## dc_dist03 1.000
## dc_dist05 1.000
## dc_dist06 1.000
## dc_dist07 1.000
## dc_dist08 1.000
## dc_dist09 1.000
## dc_dist12 1.000
## dc_dist14 1.000
## dc_dist15 1.000
## dc_dist16 1.000
## dc_dist17 1.000
## dc_dist18 1.000
## dc_dist19 1.000
## dc_dist22 1.000
## dc_dist24 1.000
## dc_dist25 1.000
## dc_dist26 1.000
## dc_dist35 1.000
## dc_dist39 1.000
## dc_dist77 1.000
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 2.6355e+03 on 107550 degrees of freedom
## Residual deviance: 6.2397e-07 on 107492 degrees of freedom
## AIC: 118
##
## Number of Fisher Scoring iterations: 25
logit_preds <- predict(logit_model, test_data)
confusionMatrix(logit_preds, test_data$Crime_Severity_Factor)
## Confusion Matrix and Statistics
##
## Reference
## Prediction Part 2 Part 1
## Part 2 46017 0
## Part 1 0 76
##
## Accuracy : 1
## 95% CI : (0.9999, 1)
## No Information Rate : 0.9984
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 1
##
## Mcnemar's Test P-Value : NA
##
## Sensitivity : 1.0000
## Specificity : 1.0000
## Pos Pred Value : 1.0000
## Neg Pred Value : 1.0000
## Prevalence : 0.9984
## Detection Rate : 0.9984
## Detection Prevalence : 0.9984
## Balanced Accuracy : 1.0000
##
## 'Positive' Class : Part 2
##
# ROC curve and AUC
logit_probs <- predict(logit_model, test_data, type = "prob")[, "Part 1"]
roc_logit <- roc(response = test_data$Crime_Severity_Factor, predictor = logit_probs, levels = c("Part 2", "Part 1"))
## Setting direction: controls < cases
plot(roc_logit, main = "ROC Curve for Logistic Regression")
auc(roc_logit)
## Area under the curve: 1
This project analyzed crime incident data to uncover spatial and temporal patterns and to classify incidents as Part 1 or Part 2 crimes. The exploratory analysis revealed clear trends in crime frequency by type, day of the week, and hour, which can inform targeted public safety efforts.
The logistic regression model demonstrated excellent predictive performance for distinguishing between Part 1 and Part 2 crimes, achieving an AUC of 1.0 on the evaluation dataset. While this indicates a strong fit, it is important to validate the model further to guard against overfitting and ensure generalizability.
Overall, this analysis provides valuable insights for law enforcement and community stakeholders, enabling more informed decisions about resource allocation and crime prevention strategies. Future work could expand on these findings by incorporating additional features, exploring other classification methods, and applying the model to real-time incident data for proactive crime monitoring.