Doris Chen
2023-07-25
Shooting incidents have caused harm to many American families and attracted widespread attention in society. We looked into the data from NYPD, trying to discover patterns in the occurrence time, locations and victims of the incidents.
The data lists a breakdown of every shooting incident that occurred in New York City going back to 2006 through the end of 2022. More details please check the Data Source.
library(tidyverse)
library(lubridate)
library(lessR)
library(caret)
url <- "https://data.cityofnewyork.us/api/views/833y-fsy8/rows.csv?accessType=DOWNLOAD"
NYPD_shooting <- read_csv(url, show_col_types = FALSE)
We take a Look at the data to get some general ideas.
str(NYPD_shooting)
The result is very long, so I set results='hide' to
avoid a long list in the report.
There are 27,312 observations of 21 variables. Quite a few of them are about locations or coordinates of the incidents which we’re not going to analysis this time. Also there are many NA’s in some variables, and the data types need to be modified.
We select variables which we are interested in and clean the data.
NYPD_S <-NYPD_shooting %>%
select(INCIDENT_KEY, OCCUR_DATE, OCCUR_TIME, BORO, PRECINCT, STATISTICAL_MURDER_FLAG,
PERP_AGE_GROUP, PERP_SEX, PERP_RACE, VIC_AGE_GROUP, VIC_SEX, VIC_RACE) %>%
mutate(INCIDENT_KEY = as.factor(INCIDENT_KEY), OCCUR_DATE = mdy(OCCUR_DATE),
BORO = as.factor(BORO), PRECINCT = as.factor(PRECINCT),
STATISTICAL_MURDER_FLAG = as.factor(STATISTICAL_MURDER_FLAG),
PERP_AGE_GROUP = as.factor(PERP_AGE_GROUP), PERP_SEX = as.factor(PERP_SEX),
PERP_RACE = as.factor(PERP_RACE), VIC_AGE_GROUP = as.factor(VIC_AGE_GROUP),
VIC_SEX = as.factor(VIC_SEX), VIC_RACE = as.factor(VIC_RACE))
summary(NYPD_S)
## INCIDENT_KEY OCCUR_DATE OCCUR_TIME BORO
## 173354054: 18 Min. :2006-01-01 Length:27312 BRONX : 7937
## 23749375 : 12 1st Qu.:2009-07-18 Class1:hms BROOKLYN :10933
## 24717013 : 12 Median :2013-04-29 Class2:difftime MANHATTAN : 3572
## 33478089 : 12 Mean :2014-01-06 Mode :numeric QUEENS : 4094
## 33706902 : 12 3rd Qu.:2018-10-15 STATEN ISLAND: 776
## 35803777 : 12 Max. :2022-12-31
## (Other) :27234
## PRECINCT STATISTICAL_MURDER_FLAG PERP_AGE_GROUP PERP_SEX
## 75 : 1557 FALSE:22046 18-24 :6222 (null): 640
## 73 : 1452 TRUE : 5266 25-44 :5687 F : 424
## 67 : 1216 UNKNOWN:3148 M :15439
## 44 : 1020 <18 :1591 U : 1499
## 79 : 1012 (null) : 640 NA's : 9310
## 47 : 953 (Other): 680
## (Other):20102 NA's :9344
## PERP_RACE VIC_AGE_GROUP VIC_SEX
## BLACK :11432 <18 : 2839 F: 2615
## WHITE HISPANIC: 2341 1022 : 1 M:24686
## UNKNOWN : 1836 18-24 :10086 U: 11
## BLACK HISPANIC: 1314 25-44 :12281
## (null) : 640 45-64 : 1863
## (Other) : 439 65+ : 181
## NA's : 9310 UNKNOWN: 61
## VIC_RACE
## AMERICAN INDIAN/ALASKAN NATIVE: 10
## ASIAN / PACIFIC ISLANDER : 404
## BLACK :19439
## BLACK HISPANIC : 2646
## UNKNOWN : 66
## WHITE : 698
## WHITE HISPANIC : 4049
As we can see from the summary, the data of victim is quite complete, while more than 50% of ‘unknown’ or NA’s regarding perpetrator. So we’ll put away perpetrator’s information for now, only focusing on time, location and victims this time.
NYPD_S <-NYPD_S %>%
select(-c(PERP_AGE_GROUP, PERP_SEX, PERP_RACE)) %>%
mutate(YEAR = year(OCCUR_DATE), MONTH = month(OCCUR_DATE), HOUR = hour(OCCUR_TIME))
According to the Data Footnotes, ‘Each INCIDENT_KEY represents a victim but similar duplicate keys are counted as one incident.’ So we decided to create a new table to analysis the pattern of incidents.
incidents <- NYPD_S %>%
distinct(INCIDENT_KEY, .keep_all = TRUE) %>%
select(INCIDENT_KEY, YEAR, MONTH, HOUR, BORO)
Now we check if there is NA in any column of the two tables.
colSums(is.na(NYPD_S))
## INCIDENT_KEY OCCUR_DATE OCCUR_TIME
## 0 0 0
## BORO PRECINCT STATISTICAL_MURDER_FLAG
## 0 0 0
## VIC_AGE_GROUP VIC_SEX VIC_RACE
## 0 0 0
## YEAR MONTH HOUR
## 0 0 0
colSums(is.na(incidents))
## INCIDENT_KEY YEAR MONTH HOUR BORO
## 0 0 0 0 0
There are no NA’s in the two tables.
We’ll visualize the data to answer some questions.
Question 1: what is the trend of shooting incidents happened in New York from 2006 to 2022?
incidents %>%
ggplot(aes(x = factor(YEAR))) +
geom_bar(fill = 'brown') +
labs(title = "Shooting Incidents in New York", subtitle = "From 2006 to 2022",
x = "Year", y = "Incidents") +
geom_text(stat = 'count', aes(label = ..count..), vjust = 1.5, col = 'white', size = 3) +
theme_classic()
As the plot demonstrated, the number of incidents was declining from 2006 to 2019, especially during 2014 to 2019, but shooting up again by nearly 50% during pandemic, reaching to a high point(1562) in 2021.
So it raised another question:
Question 2: which borough did the shooting incidents happen the most?
summary(incidents$BORO)
## BRONX BROOKLYN MANHATTAN QUEENS STATEN ISLAND
## 6019 8806 2747 3229 619
The shooting happened the most in Brooklyn, 8806 incidents in total over the last 17 years.
Here is the trend of shooting incidents happened in different boroughs from 2006 to 2022.
incidents %>%
ggplot(aes(x = factor(YEAR))) +
facet_wrap(~ BORO) +
geom_bar(aes(fill = BORO)) +
labs(title = "Shooting Incidents in Boroughs of New York", subtitle = "From 2006 to 2022",
x = "Year", y = "Incidents") +
geom_text(stat = 'count', aes(label = ..count..), vjust = -0.5, size = 2.5) +
theme_classic()+
theme(axis.text.x = element_text(angle = 90), legend.position = "bottom",
legend.title = element_blank())
Now we’ll discover if there is a pattern of time when the incidents happened.
Question 3: What hours did the incidents happen the most during a day?
incidents %>%
ggplot(aes(x = factor(HOUR))) +
geom_bar(fill = 'lightgrey') +
labs(title = "Shooting Incidents Occurence Time", x = "Hour", y = "Incidents") +
geom_text(stat = 'count', aes(label = ..count..), vjust = -0.5, size = 3) +
theme_classic()
The plot shows a clear pattern that the incidents happened the most at late night and early morning, especially during 10pm to 2am.
Question 4: What months did the incidents happen the most during a year?
incidents %>%
ggplot(aes(x = factor(MONTH))) +
geom_bar(fill = 'lightgrey') +
labs(title = "Shooting Incidents Occuring Months", x = "Month", y = "Incidents") +
geom_text(stat = 'count', aes(label = ..count..), vjust = -0.5, size = 3) +
theme_classic()
The incidents happened more during summer. Given that more incidents happened at night, especially 10pm to 2am, probably because more people hanging out at summer nights.
Now we’ll check about the information of victims.
Question 5: What are the genders, age groups and races of the victims?
table(NYPD_S$VIC_SEX)
##
## F M U
## 2615 24686 11
Over 90% of the victims were male.
table(NYPD_S$VIC_AGE_GROUP)
##
## <18 1022 18-24 25-44 45-64 65+ UNKNOWN
## 2839 1 10086 12281 1863 181 61
There are 61 “UNKNOWN” in victim age groups, since it’s less than 0.2%, we decide to ignore it. Also, we will change the one with “1022” to “UNKNOWN”.
NYPD_S$VIC_AGE_GROUP <- replace(NYPD_S$VIC_AGE_GROUP, NYPD_S$VIC_AGE_GROUP == 1022, 'UNKNOWN')
Vic_Age <- NYPD_S %>%
select(VIC_AGE_GROUP) %>%
filter(VIC_AGE_GROUP != 'UNKNOWN')
# The level of "1022" and "UNKNOWN" still exist, so we use "droplevels" here to remove the unused level.
Vic_Age$VIC_AGE_GROUP <- droplevels(Vic_Age$VIC_AGE_GROUP)
## Using "lessR" package
PieChart(VIC_AGE_GROUP, hole = 0, values = "%", data = Vic_Age,
main = "Age Groups of Victims", labels_cex = 0.8, quiet = TRUE)
82% of victims were aging from 18 to 44, including 45% from 25 to 44, and 37% from 18 to 24.
Vic_Race <- NYPD_S %>%
select(VIC_RACE) %>%
filter(VIC_RACE != 'UNKNOWN')
Vic_Race$VIC_RACE <- droplevels(Vic_Race$VIC_RACE)
PieChart(VIC_RACE, hole = 0, values = "%", data = Vic_Race,
main = "Races of Victims", labels_cex = 0.8, quiet = TRUE)
71% of the victims were black, 25% were Hispanic(including 15% White Hispanic and 10% Black Hispanic).
There is a variable shows if the incident was murder or not.
table(NYPD_S$STATISTICAL_MURDER_FLAG)
##
## FALSE TRUE
## 22046 5266
19.28% of the incidents were murder. We’ll try to create a model using the variables of time(hour), location(precinct) and victims(sex, age, race) to predict if it is a murder or not.
NYPD <- NYPD_S %>%
select(-c(INCIDENT_KEY, OCCUR_DATE, OCCUR_TIME, BORO, YEAR, MONTH))
Data splitting
set.seed(1234)
inTrain <- createDataPartition(y = NYPD$STATISTICAL_MURDER_FLAG, p = 0.80, list = FALSE)
training <- NYPD[inTrain, ]
testing <- NYPD[-inTrain, ]
Fit a model
mod <- train(STATISTICAL_MURDER_FLAG ~., data = training, method = 'glm',
trControl = trainControl(method = "cv", number = 3), family = "binomial")
Prediction and Testing
pred <- predict(mod, newdata = testing)
confusionMatrix(pred, testing$STATISTICAL_MURDER_FLAG)
## Confusion Matrix and Statistics
##
## Reference
## Prediction FALSE TRUE
## FALSE 4409 1051
## TRUE 0 2
##
## Accuracy : 0.8076
## 95% CI : (0.7969, 0.818)
## No Information Rate : 0.8072
## P-Value [Acc > NIR] : 0.4809
##
## Kappa : 0.0031
##
## Mcnemar's Test P-Value : <0.0000000000000002
##
## Sensitivity : 1.000000
## Specificity : 0.001899
## Pos Pred Value : 0.807509
## Neg Pred Value : 1.000000
## Prevalence : 0.807213
## Detection Rate : 0.807213
## Detection Prevalence : 0.999634
## Balanced Accuracy : 0.500950
##
## 'Positive' Class : FALSE
##
The accuracy is 80.76%, but it actually only predicted 2 “True” murders while there were 1051. This suggests that there is no apparent pattern formed by the combination of time, location and victims in murder shooting incidents from the data.
After exploring the data, we have several findings as following:
Also, we tried to build a model using time, location and victims to predict the type of incidents, but it didn’t work very well. This may suggest that there is no apparent pattern of time, precinct and victims in murder shooting incidents from the data.
Two aspects of bias may exist in the above analysis:
1. Data: Over 50% of missing data regarding perpetrators’ information.
It could be random, or could exist certain bias during data entry.
2. Personal: When talking about shooting or criminal incidents, I
realized that I may have a little bias regarding of the perpetrators and
victims, like people at some education levels or grew up from certain
environment might be more likely to commit crime, or due to the social
bias, individuals of some races may be more vulnerable to becoming
victims.
Due to the missing data, I didn’t look into the perpetrators’ data this time but the victims. I aware of my bias, so I didn’t put too much weight of analyzing the victims’ races. Instead, I was trying to find a pattern of when the incidents happened and where it happened the most, also drawing a general picture of the genders, age groups and races of victims.
sessionInfo()
## R version 4.2.1 (2022-06-23)
## Platform: x86_64-apple-darwin17.0 (64-bit)
## Running under: macOS Big Sur ... 10.16
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.2/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.2/Resources/lib/libRlapack.dylib
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] caret_6.0-93 lattice_0.20-45 lessR_4.2.9 lubridate_1.8.0
## [5] forcats_0.5.2 stringr_1.5.0 dplyr_1.0.10 purrr_1.0.1
## [9] readr_2.1.3 tidyr_1.3.0 tibble_3.1.8 ggplot2_3.4.1
## [13] tidyverse_1.3.2
##
## loaded via a namespace (and not attached):
## [1] googledrive_2.0.0 colorspace_2.0-3 deldir_1.0-6
## [4] ellipsis_0.3.2 class_7.3-20 fs_1.5.2
## [7] proxy_0.4-27 rstudioapi_0.14 farver_2.1.1
## [10] listenv_0.9.0 bit64_4.0.5 prodlim_2019.11.13
## [13] fansi_1.0.4 xml2_1.3.3 codetools_0.2-18
## [16] splines_4.2.1 leaps_3.1 cachem_1.0.6
## [19] robustbase_0.99-0 knitr_1.40 jsonlite_1.8.4
## [22] pROC_1.18.0 broom_1.0.1 dbplyr_2.2.1
## [25] png_0.1-8 compiler_4.2.1 httr_1.4.4
## [28] backports_1.4.1 assertthat_0.2.1 Matrix_1.5-3
## [31] fastmap_1.1.0 gargle_1.2.1 cli_3.6.0
## [34] htmltools_0.5.4 tools_4.2.1 gtable_0.3.1
## [37] glue_1.6.2 reshape2_1.4.4 Rcpp_1.0.9
## [40] cellranger_1.1.0 jquerylib_0.1.4 vctrs_0.5.2
## [43] nlme_3.1-157 iterators_1.0.14 timeDate_4022.108
## [46] xfun_0.37 gower_1.0.1 globals_0.16.2
## [49] openxlsx_4.2.5.2 rvest_1.0.3 lifecycle_1.0.3
## [52] googlesheets4_1.0.1 future_1.31.0 DEoptimR_1.1-0
## [55] MASS_7.3-57 scales_1.2.1 ipred_0.9-13
## [58] vroom_1.6.1 hms_1.1.2 parallel_4.2.1
## [61] RColorBrewer_1.1-3 curl_4.3.3 sass_0.4.2
## [64] rpart_4.1.16 latticeExtra_0.6-30 stringi_1.7.8
## [67] highr_0.9 foreach_1.5.2 e1071_1.7-13
## [70] hardhat_1.2.0 zip_2.2.2 lava_1.7.1
## [73] rlang_1.0.6 pkgconfig_2.0.3 evaluate_0.17
## [76] labeling_0.4.2 recipes_1.0.4 bit_4.0.5
## [79] tidyselect_1.2.0 parallelly_1.34.0 plyr_1.8.8
## [82] magrittr_2.0.3 R6_2.5.1 generics_0.1.3
## [85] DBI_1.1.3 pillar_1.8.1 haven_2.5.1
## [88] withr_2.5.0 survival_3.3-1 nnet_7.3-17
## [91] future.apply_1.10.0 modelr_0.1.9 crayon_1.5.2
## [94] interp_1.1-3 utf8_1.2.3 ellipse_0.4.3
## [97] tzdb_0.3.0 rmarkdown_2.20 jpeg_0.1-10
## [100] grid_4.2.1 readxl_1.4.1 data.table_1.14.2
## [103] ModelMetrics_1.2.2.2 reprex_2.0.2 digest_0.6.29
## [106] stats4_4.2.1 munsell_0.5.0 bslib_0.4.0