The set of descriptive and spatial analyses in this web-report is a follow up on the preliminary exploratory analysis. We take a deeper dive into the cases that had the clear by description “No police action possible or necessary.” This web-report tackles two questions about this type of resolution, which I will refer to as NPAPN:
No police action was deemed possible or necessary for 19,290 calls. There are 63 different types of cases that had been deemed NPAPN. The full list of case types and their frequencies can be downloaded as an Excel file. To keep the number of categories more manageable, I graphed the top 20 case types with this type of resolution below in the Figure 1.
## Reduce to no police action resolutions & export
no_police_action <- cad %>%
filter(Clear_By_Desc == "NO POLICE ACTION POSSIBLE OR NECESSARY")
no_police_action_case_types <- no_police_action %>%
group_by(Case_Type_Final_Desc) %>%
summarize(freq = n()
) %>%
mutate(percent = round((freq/sum(freq)) * 100, digits=2)) %>%
arrange(-freq)
## Uncomment code below to export if changes are made
# write_csv(no_police_action_case_types,
# "no_police_action_case_types.csv",
# na = "")
rm(no_police_action_case_types)
Disturbance, suspicious person, and trespass calls made up 44% of all calls about cases that were deemed NPAPN.
## Aggregate case types for graphing
no_police_action_case_freq <- no_police_action %>%
mutate(
Case_Type_Final_Desc = str_replace(
Case_Type_Final_Desc, c("PROWLER - TRESPASS",
"PROWLER - TRESPASS, PARKS EXCLUSION"), "TRESPASS"),
Case_Type_Final_Desc = str_replace(
Case_Type_Final_Desc, "DV - ARGUMENTS, DISTURBANCE (NO ARREST)",
"DISTURBANCE - DV"),
Case_Type_Final_Desc = str_replace(Case_Type_Final_Desc,
"NOISE - DIST, GENERAL (CONST, RESID, BALL PLAY)",
"DISTURBANCE - NOISE, GENERAL"),
Case_Type_Final_Desc = str_replace(Case_Type_Final_Desc,
"NOISE - DISTURBANCE (PARTY, ETC)",
"DISTURBANCE - NOISE, GENERAL"),
Case_Type_Final_Desc = str_replace(Case_Type_Final_Desc,
"SUSPICIOUS CIRCUM. - SUSPICIOUS PERSON",
"SUSPICIOUS PERSON"),
Case_Type_Final_Desc = str_replace(Case_Type_Final_Desc,
"ASSAULTS, OTHER",
"ASSAULTS"),
Case_Type_Final_Desc = str_replace(Case_Type_Final_Desc,
"PERSON - RUNAWAY",
"RUNAWAY PERSON"),
case_type_final_desc_first = str_trim(str_extract(Case_Type_Final_Desc, "[^-]+")),
case_type_final_desc_first = recode_factor(case_type_final_desc_first,
"TRESPASS, PARKS EXCLUSION" = "TRESPASS",
"CASUALTY,NON" = "CASUALTY NON CRIMINAL",
"#NAME?" = "UNKNOWN",
"UNKNOWN?" = "UNKNOWN",
"HAZ" = "HAZARDS",
"SEX OFFENSES (NON" = "SEX OFFENSES (NON RAPE)",
"MISSING" = "MISSING PERSON",
"PERSON" = "MISSING PERSON",
"HARAS" = "HARASSMENT",
"DOWN" = "DOWN PERSON",
"BURN" = "RECKLESS BURNING",
"SERVICE" = "WELFARE CHECK",
"WEAPN" = "WEAPON",
"WEAPON,PERSON WITH" = "WEAPON",
"WEAPON, PERSON WITH" = "WEAPON",
"CASUALTY NON" = "CASUALTY DRUG RELATED",
"DISTURBANCE, MISCELLANEOUS/OTHER" = "DISTURBANCE",
"NUISANCE" = "NUISANCE OR MISCHIEF",
"CHILD" = "ABANDONED, ABUSED AND NEGLECTED CHILD"
)) %>%
group_by(case_type_final_desc_first) %>%
summarize(n = n()
) %>%
mutate(percent = round((n/sum(n)) * 100, digits=2)) %>%
arrange(-n)
## Graph of top 20
no_police_action_case_freq %>%
slice(1:20) %>%
ggplot(., aes(x = n, y = reorder(case_type_final_desc_first, n), label = comma(n, accuracy=1))) +
geom_segment(aes(yend = case_type_final_desc_first), xend = 0, colour = "grey80") +
geom_point(size = 7.5, color = "mediumpurple", alpha=0.8) +
geom_text(size = 2, color = "white", fontface = "bold") +
labs(title = "Fig. 1: Top 20 cases with NPAPN Resolution",
x = "# of Calls",
y = "",
caption = "Seattle, WA 2019") +
scale_x_continuous(labels = scales::comma, breaks=pretty_breaks(n=10)) +
theme_bw() +
theme(
panel.grid.major.y = element_blank(),
axis.text.x = element_text(angle = 45, hjust=1)
)
In addition to knowing the raw frequencies of cases that were deemed NPAPN, knowing the proportion of each type of case resolved in this manner would be informative. Figure 2 shows the breakdown of this type of resolution for the top 20 case types.
The rankings shift when we consider the share of these top 20 cases deemed NPAPN. Among these case types, no case type had more than 11% of the cases in 2019 that were classified as NPAPN.
## Create lolliplot of % resolved w/o police action
# Extract top 20 case type names
top_20_case_types_no_pols_action <- no_police_action_case_freq %>%
slice(1:20) %>%
select(case_type_final_desc_first)
perc_no_police_action <- cad %>%
mutate(no_police_action = as.factor(if_else(
Clear_By_Desc == "NO POLICE ACTION POSSIBLE OR NECESSARY",
1,
0)),
no_police_action = recode_factor(no_police_action,
"0" = "OTHER RESOLUTION",
"1" = "NO POLICE ACTION POSSIBLE OR NECESSARY"),
Case_Type_Final_Desc = str_replace(
Case_Type_Final_Desc, c("PROWLER - TRESPASS",
"PROWLER - TRESPASS, PARKS EXCLUSION"), "TRESPASS"),
Case_Type_Final_Desc = str_replace(
Case_Type_Final_Desc, "DV - ARGUMENTS, DISTURBANCE (NO ARREST)",
"DISTURBANCE - DV"),
Case_Type_Final_Desc = str_replace(Case_Type_Final_Desc,
"NOISE - DIST, GENERAL (CONST, RESID, BALL PLAY)",
"DISTURBANCE - NOISE, GENERAL"),
Case_Type_Final_Desc = str_replace(Case_Type_Final_Desc,
"NOISE - DISTURBANCE (PARTY, ETC)",
"DISTURBANCE - NOISE, GENERAL"),
Case_Type_Final_Desc = str_replace(Case_Type_Final_Desc,
"SUSPICIOUS CIRCUM. - SUSPICIOUS PERSON",
"SUSPICIOUS PERSON"),
Case_Type_Final_Desc = str_replace(Case_Type_Final_Desc,
"ASSAULTS, OTHER",
"ASSAULTS"),
Case_Type_Final_Desc = str_replace(Case_Type_Final_Desc,
"PERSON - RUNAWAY",
"RUNAWAY PERSON"),
case_type_final_desc_first = str_trim(str_extract(Case_Type_Final_Desc, "[^-]+")),
case_type_final_desc_first = recode_factor(case_type_final_desc_first,
"TRESPASS, PARKS EXCLUSION" = "TRESPASS",
"CASUALTY,NON" = "CASUALTY NON CRIMINAL",
"#NAME?" = "UNKNOWN",
"UNKNOWN?" = "UNKNOWN",
"HAZ" = "HAZARDS",
"SEX OFFENSES (NON" = "SEX OFFENSES (NON RAPE)",
"MISSING" = "MISSING PERSON",
"PERSON" = "MISSING PERSON",
"HARAS" = "HARASSMENT",
"DOWN" = "DOWN PERSON",
"BURN" = "RECKLESS BURNING",
"SERVICE" = "WELFARE CHECK",
"WEAPN" = "WEAPON",
"WEAPON,PERSON WITH" = "WEAPON",
"WEAPON, PERSON WITH" = "WEAPON",
"CASUALTY NON" = "CASUALTY DRUG RELATED",
"DISTURBANCE, MISCELLANEOUS/OTHER" = "DISTURBANCE",
"NUISANCE" = "NUISANCE OR MISCHIEF",
"CHILD" = "ABANDONED, ABUSED AND NEGLECTED CHILD"
)) %>%
filter(case_type_final_desc_first %in% top_20_case_types_no_pols_action$case_type_final_desc_first) %>%
count(case_type_final_desc_first, no_police_action) %>%
group_by(case_type_final_desc_first) %>%
mutate(percent = round((n/sum(n)) * 100, digits=2),
above_average_pct = as.factor(if_else(percent > 3.65,
1,
0))) %>%
filter(no_police_action != "OTHER RESOLUTION")
## Get summary stats of % NPAPN
# pct_no_pols_action <- cad %>%
# mutate(no_police_action = as.factor(if_else(
# Clear_By_Desc == "NO POLICE ACTION POSSIBLE OR NECESSARY",
# 1,
# 0)),
# no_police_action = recode_factor(no_police_action,
# "0" = "OTHER RESOLUTION",
# "1" = "NO POLICE ACTION POSSIBLE OR NECESSARY"),
# Case_Type_Final_Desc = str_replace(
# Case_Type_Final_Desc, c("PROWLER - TRESPASS",
# "PROWLER - TRESPASS, PARKS EXCLUSION"), "TRESPASS"),
# Case_Type_Final_Desc = str_replace(
# Case_Type_Final_Desc, "DV - ARGUMENTS, DISTURBANCE (NO ARREST)",
# "DISTURBANCE - DV"),
# Case_Type_Final_Desc = str_replace(Case_Type_Final_Desc,
# "NOISE - DIST, GENERAL (CONST, RESID, BALL PLAY)",
# "DISTURBANCE - NOISE, GENERAL"),
# Case_Type_Final_Desc = str_replace(Case_Type_Final_Desc,
# "NOISE - DISTURBANCE (PARTY, ETC)",
# "DISTURBANCE - NOISE, GENERAL"),
# Case_Type_Final_Desc = str_replace(Case_Type_Final_Desc,
# "SUSPICIOUS CIRCUM. - SUSPICIOUS PERSON",
# "SUSPICIOUS PERSON"),
# Case_Type_Final_Desc = str_replace(Case_Type_Final_Desc,
# "ASSAULTS, OTHER",
# "ASSAULTS"),
# Case_Type_Final_Desc = str_replace(Case_Type_Final_Desc,
# "PERSON - RUNAWAY",
# "RUNAWAY PERSON"),
# case_type_final_desc_first = str_trim(str_extract(Case_Type_Final_Desc, "[^-]+")),
# case_type_final_desc_first = recode_factor(case_type_final_desc_first,
# "TRESPASS, PARKS EXCLUSION" = "TRESPASS",
# "CASUALTY,NON" = "CASUALTY NON CRIMINAL",
# "#NAME?" = "UNKNOWN",
# "UNKNOWN?" = "UNKNOWN",
# "HAZ" = "HAZARDS",
# "SEX OFFENSES (NON" = "SEX OFFENSES (NON RAPE)",
# "MISSING" = "MISSING PERSON",
# "PERSON" = "MISSING PERSON",
# "HARAS" = "HARASSMENT",
# "DOWN" = "DOWN PERSON",
# "BURN" = "RECKLESS BURNING",
# "SERVICE" = "WELFARE CHECK",
# "WEAPN" = "WEAPON",
# "WEAPON,PERSON WITH" = "WEAPON",
# "WEAPON, PERSON WITH" = "WEAPON",
# "CASUALTY NON" = "CASUALTY DRUG RELATED",
# "DISTURBANCE, MISCELLANEOUS/OTHER" = "DISTURBANCE",
# "NUISANCE" = "NUISANCE OR MISCHIEF",
# "CHILD" = "ABANDONED, ABUSED AND NEGLECTED CHILD"
# )) %>%
# count(case_type_final_desc_first, no_police_action) %>%
# group_by(case_type_final_desc_first) %>%
# mutate(percent = round((n/sum(n)) * 100, digits=2)) %>%
# filter(no_police_action != "OTHER RESOLUTION") %>%
# select(-no_police_action) %>%
# ungroup() %>%
# summarize(avg_pct_no_pols_action = mean(percent, na.rm = T),
# sd_pct_no_pols_action = sd(percent, na.rm = T),
# med_pct_no_pols_action = median(percent),
# max_pct_no_pols_action = max(percent)
# )
## GRAPHIC
ggplot(perc_no_police_action,
aes(x = percent,
y = reorder(case_type_final_desc_first, percent),
label = percent(percent, accuracy = 0.1, scale = 1, suffix = "%"))) +
geom_segment(aes(yend = case_type_final_desc_first), xend = 0, colour = "grey80") +
geom_point(size = 3, aes(color=factor(above_average_pct)), alpha=0.8) +
geom_text(size = 2.7, fontface = "bold", nudge_x = 0.4) +
annotate("text", x = 6.5, y = "NARCOTICS", label = "Above Average",
color = "#998ec3", size = 3, hjust = -0.1, vjust = .75) +
annotate("text", x = 6.5, y = "PROWLER", label = "Below Average",
color = "#f1a340", size = 3, hjust = -0.1, vjust = -.1) +
geom_segment(aes(x = 6.5, xend = 6.5 , y = "NARCOTICS", yend = "HAZARDS"),
arrow = arrow(length = unit(0.2,"cm")), color = "#998ec3") +
geom_segment(aes(x = 6.5, xend = 6.5 , y = "PROWLER", yend = "CRISIS COMPLAINT"),
arrow = arrow(length = unit(0.2,"cm")), color = "#f1a340") +
labs(title = "Fig. 2: Percent of cases with NPAPN Resolution",
x = "Percentage of Calls",
y = "",
subtitle = "The average percent of NPAPN resolutions among all case types was 3.6%.",
caption = "Seattle, WA 2019") +
scale_x_continuous(labels = scales::percent_format(accuracy = 0.1, scale = 1, suffix = "%"),
breaks=pretty_breaks(n = 10)) +
scale_color_manual(values=c("#f1a340", "#998ec3")) +
theme_bw() +
theme(
legend.position = "none",
panel.grid.major.y = element_blank(),
axis.text.x = element_text(angle = 0)
)
18,8447 calls about cases that had been resolved by NPAPN had valid geographic coordinates for mapping. These point locations have been mapped and aggregated to the Seattle Police Department’s geographic units as well as a density raster grid constructed using 1/8th of a square mile areal units.
The precinct-level map shows that the cases resolved by NPAPN are largely in the north and west precincts. East and south precincts are in the middle of the distribution with about half the number of cases resolved by NPAPN compared to the north and west. The southwestern precinct has the fewest hovering around 2,000.
NOTE: NA precinct includes the harbor and northern and southern periphery of Seattle. Seattle Police Department does not explain why these areas (in particular the northern and southern areas) do not get assigned to a precinct, but get assigned to sectors and beats.
West Precinct
North Precinct
East, South, and Southwest Precincts
West Precinct
North Precinct
Southwest Precinct
South Precinct
East Precinct
The interactive map below shows the NPAPN hot and cold spots. A raster grid was computed by dividing the city into one-eighth square mile regions and then aggregating the number of NPAPN resolutions within each region. The number of points per region was compared to the average of the region’s contiguous neighbor regions. Each region was compared to a neighborhood of contiguous regions. If a region’s number of NPAPN calls were significantly higher than the average NPAPN resolutions in its neighborhood, then the region is considered a hot spot. If a region’s number of NPAPN calls was significantly lower than the average NPAPN resolutions in its neighborhood, then the region is considered a cold spot.
Pan and zoom in to the different beats to see where the “hot spots” are located (e.g., bounded by highways, near parks, transit stations, etc.)
West Precinct
North Precinct
Southwest Precinct
South Precinct
East Precinct
## CALCULATE Getis- Ord GI statistic
# Create a regular grid of 1/8-mile-square crime areas via a raster
pixelsize = 660
box = round(extent(seattle_sp) / pixelsize) * pixelsize
template = raster(box, crs = proj,
nrows = (box@ymax - box@ymin) / pixelsize,
ncols = (box@xmax - box@xmin) / pixelsize)
no_police_action_pts_sp$PRESENT = 1
getisraster = rasterize(no_police_action_pts_sp,
template,
field = 'PRESENT',
fun = sum)
getisgrid = rasterToPolygons(getisraster)
# Create list of neighbors
neighbors = poly2nb(getisgrid)
weighted_neighbors = nb2listw(neighbors,
zero.policy = T)
# Perform local G analysis (Getis-Ord GI*)
getisgrid$HOTSPOT = as.vector(localG(getisgrid$layer, weighted_neighbors))
## UNCOMMENT TO COLOR GRID CELLS BASED ON Z SCORE & VIEW PLOT
breaks = c(-30, -1.96, -1, 1, 1.96, 30)
# palette=c("#0000FF80", "#8080FF80", "#FFFFFF80", "#FF808080", "#FF000080")
# col = palette[cut(getisgrid$HOTSPOT, breaks)]
# plot(getisgrid, col=col, border=NA)
# plot(seattle_sp, border="gray", add=T)
## VISUALIZE LOCAL G - HOT SPOT MAP
getisgrid <- spTransform(getisgrid, CRS("+init=epsg:4326"))
no_pol_action_getis_pal <-
colorBin(palette = c("#0000FF80",
"#8080FF80",
"#FFFFFF80",
"#FF808080",
"#FF000080"),
bins=breaks,
na.color = "#808080")
leaflet() %>%
addMapboxTiles(
style_id = "dark-v9",
username = "mapbox"
) %>%
addPolygons(data = beats_sf,
fill = F,
color = "white",
weight = 1,
label = ~paste0("Beat ", beat)
) %>%
addPolygons(data = getisgrid,
color = ~no_pol_action_getis_pal(HOTSPOT),
stroke = F,
fillOpacity = 0.5,
smoothFactor = 0.5,
) %>%
addLegend(data = getisgrid,
colors = c("#0000FF80", "#8080FF80", "#FFFFFF80", "#FF808080", "#FF000080", "#808080"),
labels = c("Cold Spot > 2 Std. Dev.",
"Cold Spot 1 - 2 Std. Dev.",
"Not Significant",
"Hot Spot 1 - 2 Std. Dev.",
"Hot Spot > 2 Std. Dev.",
"NA"),
title = "Getis-Ord GI* Hot-Spots<br>of NPAPN Resolutions") %>%
setView(lng = -122.3321, lat = 47.6062, zoom = 11)
Top Lines
Disturbance cases made up most of the NPAPN resolutions.
Suspicious person cases are also a common case type with NPAPN resolutions.
However, NPAPN resolutions made up only 6% of the resolutions for disturbance and suspicious persons cases.
Of the top 20 case types, about one-tenth of suspicious circumstances and mischief or nuisance cases in 2019 were resolved by NPAPN.
Most NPAPN resolutions were in the northern precinct, followed by the western precinct. The three beats in Sector B in the north precincts were were most of the NPAPN resolutions occurred. In the western precinct, the two beats in Sector K at the southwestern edge of the precinct - K2 and K3 - are driving the high number of NPAPN resolutions.
The highest intensity of resolutions are in the western precinct in the beats/areas that make up of the downtown/city’s core.
There are hot spots in other precincts, but they don’t really follow a pattern of being centrally or peripherally located.
Questions for next steps