15 min read

Digging into NJOAG Law Enforcement Officer Diversity data - Part I

A map of the largest percapita municipal PDs in NJ is a map of the Jersey Shore, plus Hi-Nella

NJOAG Use of Force and Law Enforcement Officer Diversity data sets

The NJ Office of the Attorney General has started releasing data on police use of force and on law enforcement officer diversity, which I have re-packaged as R data packages NJOAGUOF and NJOAGLEOD.

I’d like to start digging into this data to see what it shows. I’ll start with the municipal agencies – in particular, agency sizes. 1

Packages used in this post

In this post, I’ll be using the following packages.
Package Title Version
njoagleod NJ OAG Law Enforcement Officer Diversity data package 1.1.3
tidyverse Easily Install and Load the ‘Tidyverse’ 1.3.1
tidycensus Load US Census Boundary and Attribute Data as ‘tidyverse’ and ‘sf’-Ready Data Frames 1.2.2
tigris Load Census TIGER/Line Shapefiles 1.6.1
sf Simple Features for R 1.0-7
ggrepel Automatically Position Non-Overlapping Text Labels with ‘ggplot2’ 0.9.1

This is not a tutorial for using these packages, but all the R code used to generate the plots and tables will be included.

Both NJOAG packages mentioned above may be installed from github, though we will only use njoagleod in this post.

install_github("tor-gu/njoaguof")
install_github("tor-gu/njoagleod")

Municipal agencies and census data

Building a population and officer_count table by municipality

Let’s start by getting municipal-level census data using the tidycensus package. 2 We will use the municipality table in the njoagleod package for the list of counties. For the moment, we only need the population of each municipality.

library(tidyverse)
library(tidycensus)
library(njoagleod)
# Get a vector of NJ counties
counties <- municipality %>% pull(county) %>% unique() %>% sort()
# Get the population of each municipality using tidycensus::get_estimates
municipality_pop <- counties %>%
  map_df(
    ~ get_estimates(
      geography = "county subdivision",
      state = "NJ",
      county = .,
      year = 2019,
      variables = "POP"
    ) %>%
      separate(
        NAME,
        sep = ", ",
        into = c("municipality", "county", "state")
      )
  ) %>%
  rename(population=value) %>%
  select(-variable, -state)
Table 1: head(municipality_pop)
municipality county GEOID population
Buena borough Atlantic County 3400108680 4284
Egg Harbor City city Atlantic County 3400120350 4052
Hamilton township Atlantic County 3400129280 25746
Mullica township Atlantic County 3400149410 5856
Somers Point city Atlantic County 3400168430 10174
Absecon city Atlantic County 3400100100 8818

Now let’s get the officer count for each municipal police department and combine it with the census data. Not every municipality has its own police department, and there may be municipal police departments that are excluded for some reason (for example, because they have not reported their data to the OAG).

# Get the officer count for each agency
officer_count_pop <- officer %>%
  count(agency_county, agency_name, name = "officer_count") %>%
  left_join(agency,
            by = c("agency_county", "agency_name")) %>%
  filter(!is.na(municipality)) %>%
  left_join(municipality,
            by = c("agency_county" = "county", "municipality")) %>%
  select(GEOID, agency_name, officer_count) %>%
  left_join(municipality_pop, by = "GEOID")
Table 2: head(officer_count_pop)
GEOID agency_name officer_count municipality county population
3400100100 Absecon City PD 29 Absecon city Atlantic County 8818
3400102080 Atlantic City PD 257 Atlantic City city Atlantic County 37743
3400107810 Brigantine PD 35 Brigantine city Atlantic County 8650
3400120350 Egg Harbor City PD 17 Egg Harbor City city Atlantic County 4052
3400120290 Egg Harbor Twp PD 92 Egg Harbor township Atlantic County 42249
3400125560 Galloway Twp PD 70 Galloway township Atlantic County 35618

Checking that the table is representative of New Jersey.

As we mentioned above, our officer_count_pop table will not include a row for every municipality of NJ. Before digging in too far, let’s get a sense of what we are including and what we are missing.

Statewide, the municipal agencies for which we have data cover nearly 93% of the population of New Jersey. In all, there are about 408 residents per municipal police officer in these municipalities.

# We cover nearly 93% of the population of NJ
sum(officer_count_pop$population) / sum(municipality_pop$population)
## [1] 0.93
# About 408 residents per municipal police officer
sum(officer_count_pop$population) / sum(officer_count_pop$officer_count)
## [1] 408

At the county level, the coverage varies from from about 58% of the population (Salem county) up to 100% (Hudson and Passaic), and the number of residents per officer ranges from 511 (Somerset) to 196 (Cape May). Note that in Cape May, our data covers only 73% of the population.

# Get the population for each municipality that is 'missed' and summarize by
# county
missed_county_pop <- municipality_pop %>%
  anti_join(officer_count_pop, by = "GEOID") %>%
  group_by(county) %>%
  summarize(missed_population = sum(population))
# Get the total population by county
county_pop <- municipality_pop %>%
  group_by(county) %>%
  summarize(population = sum(population))
# Combine this to get the county-level coverage -- the proportion of the 
# population of each county with a municipal PD for which we have the data
coverage <- county_pop %>%
  left_join(missed_county_pop, by = "county") %>%
  mutate(missed_population = replace_na(missed_population, 0)) %>%
  mutate(coverage = (population - missed_population) / population) %>%
  select(county, missed_population, coverage)
# Now add the county-level municipal PD officer count
officer_count_pop_by_county <- officer_count_pop %>%
  group_by(county) %>%
  summarize(officer_count = sum(officer_count),
            population = sum(population)) %>%
  mutate(resident_per_officer = population / officer_count) %>%
  left_join(coverage, by = "county")
Table 3: officer_pop_by_county
county officer_count population resident_per_officer missed_population coverage
Atlantic County 809 244289 302 19381 0.93
Bergen County 2170 923035 425 9167 0.99
Burlington County 842 406464 483 38885 0.91
Camden County 949 413077 435 93394 0.82
Cape May County 341 66866 196 25173 0.73
Cumberland County 286 110990 388 38537 0.74
Essex County 2385 762478 320 36497 0.95
Gloucester County 541 245192 453 46444 0.84
Hudson County 2067 672391 325 0 1.00
Hunterdon County 204 94252 462 30119 0.76
Mercer County 755 350981 465 16449 0.96
Middlesex County 1524 757317 497 67745 0.92
Monmouth County 1507 578260 384 40535 0.93
Morris County 983 448845 457 43000 0.91
Ocean County 1261 604996 480 2190 1.00
Passaic County 1101 501826 456 0 1.00
Salem County 106 35971 339 26414 0.58
Somerset County 626 320092 511 8842 0.97
Sussex County 222 97770 440 42718 0.70
Union County 1372 542753 396 13588 0.98
Warren County 148 61596 416 43671 0.59

Though the outliers like Cape May and Hudson may suggest a correlation between our coverage rate and the number of residents per officer – which would in turn suggest we may be oversampling municipalities with larger police departments – the actual correlation is not significant:

# Weighted by population, there is no significant correlation between
# residents_per_officer and our coverage.
lm(resident_per_officer ~ coverage,
   officer_count_pop_by_county,
   weights = population) %>%
  summary() %>%
  magrittr::use_series("coefficients")
##             Estimate Std. Error t value Pr(>|t|)
## (Intercept)      430        184   2.334    0.031
## coverage         -11        196  -0.054    0.957
# Plot number of residents per officer by coverage
outliers <- officer_count_pop_by_county %>%
  filter(
    population == min(population) | population == max(population) |
      coverage == min(coverage) | coverage == max(coverage) |
      resident_per_officer == min(resident_per_officer) |
      resident_per_officer == max(resident_per_officer)
  )
officer_count_pop_by_county %>%
  ggplot(aes(x = coverage, y = resident_per_officer)) +
  geom_point(aes(size = population)) +  
  ggrepel::geom_label_repel(data = outliers, aes(label = county)) +
  labs(
    title =
      str_wrap(
        "County population vs proportion of residents with single-municipality PDs"
      ),
    subtitle = "2019 Population Estimates, 2021 Officer Counts",
    caption = "Source: US Census, NJ OAG"
  )

Mapping municipalities with large police departments relative to their population

Now that we have our data set, let’s take a look at the municipalities with the largest police departments relative to their population:

top_fifteen <- officer_count_pop %>%
  mutate(residents_per_officer = population / officer_count) %>%
  slice_min(n = 15, order_by = residents_per_officer) %>%
  select(agency_name,
         county,
         residents_per_officer,
         officer_count,
         population)
Table 4: top_fifteen (largest PDs relative to population)
agency_name county residents_per_officer officer_count population
Mantoloking Boro PD Ocean County 28 9 249
Deal PD Monmouth County 34 21 719
Harvey Cedars Boro PD Ocean County 38 9 345
Stone Harbor Boro PD Cape May County 48 17 810
Allenhurst PD Monmouth County 54 9 483
Avalon Boro PD Cape May County 56 22 1236
Longport PD Atlantic County 61 14 851
Hi-Nella PD Camden County 78 11 858
Sea Isle City PD Cape May County 85 24 2029
Lavallette PD Ocean County 85 22 1866
Long Beach Twp PD Ocean County 88 35 3071
Ship Bottom Boro PD Ocean County 89 13 1153
Beach Haven PD Ocean County 93 13 1205
Bayhead Boro PD Ocean County 109 9 977
Surf City PD Ocean County 109 11 1199

In case the names of these relatively smaller municipalities don’t mean much to you, let’s take a look at them on a map. We’ll use the tigris package to get the shapefiles for the municipalities from the US census:

library(tigris)
options(tigris_use_cache = TRUE)
nj_municipality_map <- counties %>%
  map_df( ~ county_subdivisions("NJ", county = ., class = "sf"))

Next we join the officer count data to the map and plot.

# Add the residents_per_officer to the map
officer_count_pop <- officer_count_pop %>%
  mutate(residents_per_officer = population / officer_count)
map_with_values <-
  left_join(nj_municipality_map, officer_count_pop, by = "GEOID")

# Select the top values for labeling
labels <- map_with_values %>%
  slice_min(n = 15, order_by = residents_per_officer) %>%
  arrange(desc(INTPTLAT))

# Map the officer density
scale_breaks <- c(30, 40, 60, 120)
ggplot(map_with_values) +
  geom_sf(aes(fill = residents_per_officer, geometry = geometry, size = .2 )) +
  ggrepel::geom_label_repel(
    data = labels,
    mapping = aes(label = NAME, geometry = geometry),
    stat = "sf_coordinates",
    nudge_x = -.7,
    min.segment.length = 0
  ) +
  scale_size_identity() +
  scale_fill_gradient(
    na.value = "lightgrey",
    low = "white",
    high = "red",
    trans = "reciprocal",
    breaks = scale_breaks,
    name = "Residents Per Officer"
  ) +
  labs(
    title = str_wrap(
      "NJ municipalities with large police departments relative to population"
    ),
    subtitle = "2019 Population Estimates, 2021 Officer Counts",
    caption = "Source: US Census, NJ OAG"
  ) +
  theme(
    axis.ticks = element_blank(),
    axis.text.x = element_blank(),
    axis.text.y = element_blank(),
    axis.title.x = element_blank(),
    axis.title.y = element_blank(),
    panel.background = element_rect(fill = "lightblue"),
    panel.grid.major = element_line(color = "lightblue"),
    legend.position = "bottom"
  )

So, a map of the largest percapita municipal PDs in NJ is a map of the Jersey Shore, plus Hi-Nella.

Part-time residents and part-time officers

Hi-Nella

Before we get to the shore towns, let’s take a quick look at the borough of Hi-Nella. A population of 858 and 11 police officers – that sure seems like a lot!

Back in 2010, the Star-Ledger reported on Hi-Nella (Towns that Shouldn’t Exist Part 2: Borough of Hi-Nella). At that time, the population was 1029 and the police department had 13 officers. But, the Star-Ledger notes, this a “mostly part-time police force”.

The NJ OAG data set does not distinguish part-time from full-time officers, so it is not clear how much of the Hi-Nella PD is part time. The Hi-Nella PD website lists a Chief, a Lieutenant and a Detective, presumably all full-time, and no other officers. Perhaps the department has three full-time and 8 part-time officers, which is still on the large side.

Jersey Shore towns

The other fourteen agencies on the list are all on the Jersey Shore. The most extreme example is Mantoloking, with 9 officers and only 249 residents.

Mantoloking

The wikipedia page for Mantoloking gives a good explanation for why this might be: during the summer months, the population swells to approximately 5000. For a populuation of 5000, nine officers is actually lower than the state average: more than 500 residents per officer. Indeed, the Mantoloking PD website indicates that the department hires seasonal officers in the summer, and that it has nine full-time officers.

Note the contrast with Hi-Nella – both Hi-Nella and Mantoloking employ part-time officers, but part-time officers in Hi-Nella are included in the agency size, while part-time officers in Mantoloking are excluded. It is not clear how most departments have counted their part-time officers. 3

Returning the the shore towns, it is clear that many of them have seasonal patterns similar to Mantoloking. For example, Deal’s population increases ten-fold in the summer.

Isolating the shore towns

The NJ map includes several landless non-municipal county subdivisions off the coast, which we can identify by filtering on ALAND == 0.

# Select the landless regions and plot the GEOIDs
water <- nj_municipality_map %>% filter(ALAND == 0)
ggplot(nj_municipality_map) +
  geom_sf(aes(geometry = geometry)) +
  geom_sf_label(data = water,
                mapping = aes(label = GEOID, geometry = geometry)) +
  labs(title = str_wrap("Landless county subdivision GEOIDs in NJ"),
       caption = "Source: US Census") +
  theme(
    axis.text.x = element_blank(),
    axis.text.y = element_blank(),
    axis.title.x = element_blank(),
    axis.title.y = element_blank()
  )

We can take a stab at defining the “Jersey Shore” as those municipalities that border any of these regions, excluding the Delaware Bay region 3401100000.

# Build the jersey shore map
jersey_shore_water <- water %>% filter(GEOID != "3401100000")
jersey_shore_indices <-
  sf::st_intersects(jersey_shore_water, nj_municipality_map) %>%
  unlist() %>% unique()
jersey_shore <-
  nj_municipality_map[jersey_shore_indices, ] %>% setdiff(water)

# Plot the jersey_shore towns
ggplot(jersey_shore) + geom_sf() +
  ggrepel::geom_label_repel(
    mapping = aes(label = NAME, geometry = geometry),
    stat = "sf_coordinates",
    nudge_x = -.7,
    min.segment.length = 0,
    size = 3
  ) +
  labs(title = str_wrap("52 \"Jersey Shore\" municipalities"),
       caption = "Source: US Census") +
  theme(
    axis.text.x = element_blank(),
    axis.text.y = element_blank(),
    axis.title.x = element_blank(),
    axis.title.y = element_blank()
  )

Now we can add a flag, shore_town, to the officer_count_pop table:

officer_count_pop <- officer_count_pop %>% 
  mutate(shore_town=GEOID %in% jersey_shore$GEOID)
Table 5: head(officer_count_pop) (with shore_town flag)
GEOID agency_name officer_count municipality county population residents_per_officer shore_town
3400100100 Absecon City PD 29 Absecon city Atlantic County 8818 304 FALSE
3400102080 Atlantic City PD 257 Atlantic City city Atlantic County 37743 147 TRUE
3400107810 Brigantine PD 35 Brigantine city Atlantic County 8650 247 TRUE
3400120350 Egg Harbor City PD 17 Egg Harbor City city Atlantic County 4052 238 FALSE
3400120290 Egg Harbor Twp PD 92 Egg Harbor township Atlantic County 42249 459 FALSE
3400125560 Galloway Twp PD 70 Galloway township Atlantic County 35618 509 TRUE

Comparing the Jersey Shore to the rest of the state

As expected, the small Jersey Shore towns (like Mantoloking and Deal) have noticeably larger police forces relative to their population than the rest of the state. But this distinction starts to break down with larger shore towns:

# Plot the officer count vs the population for small and medium towns,
# separating the shore towns from the rest of the state
officer_count_pop %>%
  mutate(town_size = factor(
    case_when(
      population < 5000 ~ "Small",
      population < 50000 ~ "Medium",
      TRUE ~ "Large"
    ),
    levels = c("Small", "Medium", "Large")
  )) %>%
  filter(town_size != "Large") %>%
  ggplot(aes(x = population, y = officer_count, color = shore_town)) +
  geom_point() +
  geom_smooth(se = FALSE, method="loess") +
  facet_wrap("town_size", scales = "free") +
  labs(title = "NJ Residents Per Municipal Law Enforcement Officer",
       subtitle = "Small and Medium Sized Towns, Jersey Shore vs Rest of State",
       caption = "Source: US Census, NJ OAG") +
  theme(legend.position = "bottom")

If we look at just the 26 small shore towns – population at most 5000 – the number of residents per officer is 111, compared to 415 for the rest of the state:

shore_vs_non_shore <- officer_count_pop %>%
  mutate(small_shore_town = (population <= 5000 & shore_town)) %>%
  group_by(small_shore_town) %>%
  summarise(officer_count = sum(officer_count),
            population = sum(population)) %>%
  mutate(res_per_officer = population / officer_count)
Table 6: shore_vs_non_shore
small_shore_town officer_count population res_per_officer
FALSE 19731 8187311 415
TRUE 468 52130 111

With this in mind, let’s redo our look at the municipalities with the largest police departments relative to population after excluding the small shore towns. Our new top fifteen is:

officer_count_pop_core <- officer_count_pop %>%
  filter(population > 5000 | shore_town == FALSE)

top_fifteen_core <- officer_count_pop_core %>% 
  slice_min(n = 15, order_by = residents_per_officer) %>%
  select(agency_name,
         county,
         residents_per_officer,
         officer_count,
         population)
Table 7: top_fifteen_core (largest PDs relative to population, exluding small shore towns)
agency_name county residents_per_officer officer_count population
Hi-Nella PD Camden County 78 11 858
Far Hills PD Somerset County 113 8 903
Lower Alloways Creek Twp PD Salem County 119 14 1672
South Hackensack PD Bergen County 122 20 2435
Moonachie PD Bergen County 129 21 2702
Chesilhurst PD Camden County 135 12 1618
West Wildwood Boro PD Cape May County 138 4 550
Fairfield PD Essex County 144 52 7474
Atlantic City PD Atlantic County 147 257 37743
Pemberton Boro PD Burlington County 147 9 1324
Alpine PD Bergen County 154 12 1844
Essex Fells PD Essex County 161 13 2088
Ocean City PD Cape May County 161 68 10971
Winfield PD Union County 167 9 1503
Oceangate Boro PD Ocean County 170 12 2035

Hi-Nella is the new leader, followed by Far Hills and Lower Alloways Creek. It is not clear if either Far Hills or Lower Alloways Creek is including part-time officers, like Hi-Nella does. The size of the Lower Alloways Creek PD may be related to the presence of the nuclear power plant. West Wildwood and Ocean Gate should probably be considered part of the Jersey Shore, though they don’t actually border the ocean.

Geographically, no clear pattern appears, except perhaps that South Jersey is somewhat over-represented:

# Add the residents_per_officer to the map, excluding shore towns
map_with_values <-
  left_join(nj_municipality_map, officer_count_pop_core, by = "GEOID")

# Map the officer density
labels <- map_with_values %>%
  slice_min(n = 15, order_by = residents_per_officer) %>%
  arrange(desc(INTPTLAT))

# Plot the map
scale_breaks <- c(90, 120, 180, 360)
ggplot(map_with_values) +
  geom_sf(aes(fill = residents_per_officer, geometry = geometry, size = .2 )) +
  ggrepel::geom_label_repel(
    data = labels,
    mapping = aes(label = NAME, geometry = geometry),
    stat = "sf_coordinates",
    nudge_x = -.7,
    min.segment.length = 0
  ) +
  scale_size_identity() +
  scale_fill_gradient(
    na.value = "lightgrey",
    low = "white",
    high = "red",
    trans = "reciprocal",
    breaks = scale_breaks,
    name = "Residents Per Officer"
  ) +
  labs(
    title = str_wrap(
      "NJ municipalities with large police departments relative to population, small shore towns excluded"
    ),
    subtitle = "2019 Population Estimates, 2021 Officer Counts",
    caption = "Source: US Census, NJ OAG"
  ) +
  theme(
    axis.ticks = element_blank(),
    axis.text.x = element_blank(),
    axis.text.y = element_blank(),
    axis.title.x = element_blank(),
    axis.title.y = element_blank(),
    panel.background = element_rect(fill = "lightblue"),
    panel.grid.major = element_line(color = "lightblue"),
    legend.position = "bottom"
  )


  1. The municipal PD officer counts are also available in the FBI Uniform Crime Reports.↩︎

  2. Note that the tidycensus package needs to be supplied with a census API key, which is not shown in the code snippet. See here.↩︎

  3. The FBI UCF claims that the numbers are for “Full-time Law Enforcement Employees”, and lists 13 as the size of the Hi-Nella PD in 2018. Since Hi-Nella is evidently improperly including part-time officers its report to the FBI, we suspect that the inclusion of part-time officers in the NJ OAG report is also an error, and that most municipalities in the NJ OAG Law Enforcement Officer Diversity data set are including only full-time officers. According to the NJ OAG, all officers in the dataset are full-time.↩︎