#load necessary libraries and access fileslibrary(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.2.1 ✔ readr 2.2.0
✔ forcats 1.0.1 ✔ stringr 1.6.0
✔ ggplot2 4.0.3 ✔ tibble 3.3.1
✔ lubridate 1.9.5 ✔ tidyr 1.3.2
✔ purrr 1.2.2
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(dplyr)library(knitr)setwd("/Users/chloelin/Documents/Montgomery College /FALL 2026/DATA 110")hatecrimes <-read_csv("NYPD_Hate_Crimes_19-26.csv")
Rows: 4029 Columns: 14
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (9): Record Create Date, Patrol Borough Name, County, Law Code Category ...
dbl (4): Full Complaint ID, Complaint Year Number, Month Number, Complaint P...
lgl (1): Arrest Date
ℹ 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.
#convert names to lowercase, replace double space with single space, look at structure of datanames(hatecrimes) <-tolower(names(hatecrimes))names(hatecrimes) <-gsub(" ","",names(hatecrimes))head(hatecrimes)
#find hate crime biases and order thembias_count <- hatecrimes |>select(biasmotivedescription) |>group_by(biasmotivedescription) |>count() |>arrange(desc(n))head(bias_count)
#plot a bar chartggplot(hatecrimes, aes(x=biasmotivedescription))+geom_bar()
#reformat bar chartbias_count|>head(10)|>ggplot(aes(x=biasmotivedescription, y=n))+geom_col()
#flips the bar chart, n is now x and biasmotivedescription is now ybias_count |>head(10) |>ggplot(aes(x=reorder(biasmotivedescription, n), y = n)) +geom_col() +coord_flip()
#add headings, title, subtitle, and captionbias_count |>head(10) |>ggplot(aes(x=reorder(biasmotivedescription, n), y = n)) +geom_col() +coord_flip()+labs(x ="",y ="Counts of hatecrime types based on motive",title ="Bar Graph of Hate Crimes from 2019-2026",subtitle ="Counts based on the hatecrime motive",caption ="Source: NY State Division of Criminal Justice Services")
#add salmon color and change theme to minimalbias_count |>head(10) |>ggplot(aes(x=reorder(biasmotivedescription, n), y = n)) +geom_col(fill ="salmon") +coord_flip()+labs(x ="",y ="Counts of hatecrime types based on motive",title ="Bar Graph of Hate Crimes from 2019-2026",subtitle ="Counts based on the hatecrime motive",caption ="Source: NY State Division of Criminal Justice Services") +theme_minimal()
#add labels to the count of hate crimesbias_count |>head(10) |>ggplot(aes(x=reorder(biasmotivedescription, n), y = n)) +geom_col(fill ="salmon") +coord_flip()+labs(x ="",y ="Counts of hatecrime types based on motive",title ="Bar Graph of Hate Crimes from 2019-2026",subtitle ="Counts based on the hatecrime motive",caption ="Source: NY State Division of Criminal Justice Services") +theme_minimal()+geom_text(aes(label = n), hjust =-.05, size =3) +theme(axis.text.x =element_blank())
#take top 4 hate crimed groups and ranks by top hated group per yearhate_year <- hatecrimes |>filter(biasmotivedescription %in%c("ANTI-JEWISH", "ANTI-MALE HOMOSEXUAL (GAY)", "ANTI-ASIAN", "ANTI-BLACK"))|>group_by(complaintyearnumber) |>count(biasmotivedescription)|>arrange(desc(n))hate_year
#takes top hated group per county and arrange in orderhate_county <- hatecrimes |>filter(biasmotivedescription %in%c("ANTI-JEWISH", "ANTI-MALE HOMOSEXUAL (GAY)", "ANTI-ASIAN", "ANTI-BLACK"))|>group_by(county) |>count(biasmotivedescription)|>arrange(desc(n))hate_county
# A tibble: 20 × 3
# Groups: county [5]
county biasmotivedescription n
<chr> <chr> <int>
1 KINGS ANTI-JEWISH 798
2 NEW YORK ANTI-JEWISH 651
3 QUEENS ANTI-JEWISH 289
4 NEW YORK ANTI-MALE HOMOSEXUAL (GAY) 237
5 NEW YORK ANTI-ASIAN 228
6 KINGS ANTI-MALE HOMOSEXUAL (GAY) 120
7 KINGS ANTI-BLACK 99
8 BRONX ANTI-JEWISH 92
9 QUEENS ANTI-MALE HOMOSEXUAL (GAY) 91
10 KINGS ANTI-ASIAN 80
11 NEW YORK ANTI-BLACK 79
12 QUEENS ANTI-ASIAN 78
13 RICHMOND ANTI-JEWISH 76
14 QUEENS ANTI-BLACK 75
15 BRONX ANTI-MALE HOMOSEXUAL (GAY) 35
16 RICHMOND ANTI-BLACK 35
17 BRONX ANTI-BLACK 27
18 BRONX ANTI-ASIAN 10
19 RICHMOND ANTI-MALE HOMOSEXUAL (GAY) 6
20 RICHMOND ANTI-ASIAN 5
#compare year and counties rankingshate2 <- hatecrimes |>filter(biasmotivedescription %in%c("ANTI-JEWISH", "ANTI-MALE HOMOSEXUAL (GAY)", "ANTI-ASIAN", "ANTI-BLACK"))|>group_by(complaintyearnumber, county) |>count(biasmotivedescription)|>arrange(desc(n))hate2
# A tibble: 127 × 4
# Groups: complaintyearnumber, county [35]
complaintyearnumber county biasmotivedescription n
<dbl> <chr> <chr> <int>
1 2024 KINGS ANTI-JEWISH 152
2 2024 NEW YORK ANTI-JEWISH 136
3 2025 KINGS ANTI-JEWISH 136
4 2019 KINGS ANTI-JEWISH 128
5 2023 KINGS ANTI-JEWISH 126
6 2022 KINGS ANTI-JEWISH 125
7 2023 NEW YORK ANTI-JEWISH 124
8 2025 NEW YORK ANTI-JEWISH 110
9 2022 NEW YORK ANTI-JEWISH 104
10 2021 NEW YORK ANTI-ASIAN 84
# ℹ 117 more rows
#plot hate crimes per group for each yearggplot(data = hate2) +geom_bar(aes(x=complaintyearnumber, y=n, fill = biasmotivedescription),position ="dodge", stat ="identity") +labs(fill ="Hate Crime Type",y ="Number of Hate Crime Incidents",title ="Hate Crime Type in NY Counties Between 2010-2016",caption ="Source: NY State Division of Criminal Justice Services")
#plot hate crime per group per countyggplot(data = hate2) +geom_bar(aes(x=county, y=n, fill = biasmotivedescription),position ="dodge", stat ="identity") +labs(fill ="Hate Crime Type",y ="Number of Hate Crime Incidents",title ="Hate Crime Type in NY Counties Between 2010-2016",caption ="Source: NY State Division of Criminal Justice Services")
#plot each county's hate crimes per group per yearggplot(data = hate2) +geom_bar(aes(x=complaintyearnumber, y=n, fill = biasmotivedescription),position ="dodge", stat ="identity") +facet_wrap(~county) +labs(fill ="Hate Crime Type",y ="Number of Hate Crime Incidents",title ="Hate Crime Type in NY Counties Between 2010-2016",caption ="Source: NY State Division of Criminal Justice Services")
#load datasetsetwd("/Users/chloelin/Documents/Montgomery College /FALL 2026/DATA 110")nypop <-read_csv("nyc_census_pop_2020.csv")
Rows: 62 Columns: 4
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (2): Area Name, Population Percent Change
num (2): 2020 Census Population, Population Change
ℹ 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.
#rename geography to county in the datasetnypop$`Area Name`<-gsub(" County", "", nypop$`Area Name`)nypop2 <- nypop |>rename(county =`Area Name`)|>select(county, `2020 Census Population`)head(nypop2)
# A tibble: 6 × 2
county `2020 Census Population`
<chr> <dbl>
1 Albany 314848
2 Allegany 46456
3 Bronx 1472654
4 Broome 198683
5 Cattaraugus 77042
6 Cayuga 76248
#join the 2 datasetsdatajoin <-left_join(hate2, nypop2, by=c("county"))datajoin
# A tibble: 127 × 5
# Groups: complaintyearnumber, county [35]
complaintyearnumber county biasmotivedescription n 2020 Census Populati…¹
<dbl> <chr> <chr> <int> <dbl>
1 2024 KINGS ANTI-JEWISH 152 NA
2 2024 NEW Y… ANTI-JEWISH 136 NA
3 2025 KINGS ANTI-JEWISH 136 NA
4 2019 KINGS ANTI-JEWISH 128 NA
5 2023 KINGS ANTI-JEWISH 126 NA
6 2022 KINGS ANTI-JEWISH 125 NA
7 2023 NEW Y… ANTI-JEWISH 124 NA
8 2025 NEW Y… ANTI-JEWISH 110 NA
9 2022 NEW Y… ANTI-JEWISH 104 NA
10 2021 NEW Y… ANTI-ASIAN 84 NA
# ℹ 117 more rows
# ℹ abbreviated name: ¹`2020 Census Population`
#set counties in both datasets to lowercasehate_new <- hate2 |>mutate(county =as_factor(str_to_lower(as.character(county))))nypop_new <- nypop2 |>mutate(county =as_factor(str_to_lower(as.character(county))))
# A tibble: 127 × 5
# Groups: complaintyearnumber, county [35]
complaintyearnumber county biasmotivedescription n 2020 Census Populati…¹
<dbl> <fct> <chr> <int> <dbl>
1 2024 kings ANTI-JEWISH 152 2736074
2 2024 new y… ANTI-JEWISH 136 1694251
3 2025 kings ANTI-JEWISH 136 2736074
4 2019 kings ANTI-JEWISH 128 2736074
5 2023 kings ANTI-JEWISH 126 2736074
6 2022 kings ANTI-JEWISH 125 2736074
7 2023 new y… ANTI-JEWISH 124 1694251
8 2025 new y… ANTI-JEWISH 110 1694251
9 2022 new y… ANTI-JEWISH 104 1694251
10 2021 new y… ANTI-ASIAN 84 1694251
# ℹ 117 more rows
# ℹ abbreviated name: ¹`2020 Census Population`
#calculate rate per 100000 incidentsdatajoinrate <- datajoin |>mutate(rate = n/`2020 Census Population`*100000) |>arrange(desc(rate))datajoinrate
# A tibble: 127 × 6
# Groups: complaintyearnumber, county [35]
complaintyearnumber county biasmotivedescription n 2020 Census Populati…¹
<dbl> <fct> <chr> <int> <dbl>
1 2024 new y… ANTI-JEWISH 136 1694251
2 2023 new y… ANTI-JEWISH 124 1694251
3 2025 new y… ANTI-JEWISH 110 1694251
4 2022 new y… ANTI-JEWISH 104 1694251
5 2024 kings ANTI-JEWISH 152 2736074
6 2025 kings ANTI-JEWISH 136 2736074
7 2021 new y… ANTI-ASIAN 84 1694251
8 2021 new y… ANTI-JEWISH 84 1694251
9 2019 kings ANTI-JEWISH 128 2736074
10 2023 kings ANTI-JEWISH 126 2736074
# ℹ 117 more rows
# ℹ abbreviated name: ¹`2020 Census Population`
# ℹ 1 more variable: rate <dbl>
Essay
The hatecrimes dataset includes a lot of helpful information such as date, county, and bias motive that allows a thorough analysis and visualization of the data to be made. The bar charts created were allowed to be specific and clarifying. However, I do think that the dataset lacks information about the offender of the hate crime. With information about the offender of the hate crime, insight could be given about those who are likely to be targeting these groups. Further actions could be taken for prevention with organized data of common offenders.
I would like to further study this dataset by investigating the level of severity of the hate crime towards each group through the variables of law code category description, offense description, and PD code description. It could help to be able to find which groups are getting the most dangerous hate crimes on top of knowing which groups are targets of hate crimes the most. Additionally, the offence category could also be explored. By exploring offence category, it can be seen whether New York citizens are targeted most based on religion, race, or sexual orientation.