---
title: "Getting Started with fbiCDE"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Getting Started with fbiCDE}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
The `fbiCDE` package wraps the FBI's Crime Data Explorer (CDE) API
(), giving R users direct access to Uniform
Crime Reporting (UCR) and National Incident-Based Reporting System (NIBRS)
data: crime counts and rates, arrests, NIBRS victim/offender/offense detail,
Supplemental Homicide Reports (SHR), police employment, NIBRS participation,
and officers killed (LEOKA).
No API key is required. (`get_api_key()` and `set_api_key()` remain only for
compatibility with the package's `api.data.gov`-hosted predecessor and do
nothing.)
The CDE itself knows three geographies: the nation, a state, and a single
agency. This vignette covers those. The package also builds county, city and
metro-area views on top of them; see `vignette("geography")`.
Everything below was run against the live API on
2026-10-06. Agencies backfill and revise their
reports, so re-running it will move some numbers.
```r
library(fbiCDE)
```
## Lookups
Before requesting crime data you often need a state abbreviation, an offense
code, or an agency's ORI (the 9-character identifier the CDE uses for law
enforcement agencies).
```r
states <- get_states()
head(states)
#> stateAbbreviation stateName
#> 1 AL Alabama
#> 2 AK Alaska
#> 3 AZ Arizona
#> 4 AR Arkansas
#> 5 CA California
#> 6 CO Colorado
```
`get_agency_info()` looks agencies up by name in the package's bundled agency
table (`fbi_api_agencies`), with no network call. Note that `state` here is the
full state name:
```r
get_agency_info("Oakland Police Department", state = "california", ori_only = TRUE)
#> agency_name ori
#> 905 Oakland Police Department CA0010900
```
The agencies used below:
| ORI | Agency | Used for |
|---|---|---|
| `CA0010900` | Oakland Police Department, CA | crime, police employment |
| `OHCOP0000` | Columbus Police Department, OH | arrests, participation |
| `GAAPD0000` | Atlanta Police Department, GA | homicide reports |
## Summarized crime
`get_agency_crime()` returns an agency's monthly offenses and clearances;
`get_reported_crime()` does the same for a state, or the nation when
`state_abb` is `NULL`; `get_reported_arson()` is `get_reported_crime()` for
arson.
The `offense` argument takes a code. The default `"V"` is violent crime and
`"P"` property crime; `get_offense_codes()` lists the rest:
```r
offenses <- get_offense_codes("crime-trend")
head(offenses, 8)
#> code label
#> 1 ASS Aggravated Assault
#> 2 HOM Homicide
#> 3 RPE Rape
#> 4 ROB Robbery
#> 5 ARS Arson
#> 6 BUR Burglary
#> 7 LAR Larceny-theft
#> 8 MVT Motor Vehicle Theft
```
```r
oakland_crime <- get_agency_crime("CA0010900", from = "01-2019", to = "03-2019")
oakland_crime
#> geography offense measure period count rate population
#> 1 CA0010900 V offenses 01-2019 472 108.75 434036
#> 2 CA0010900 V offenses 02-2019 368 84.79 434036
#> 3 CA0010900 V offenses 03-2019 438 100.91 434036
#> 4 CA0010900 V clearances 01-2019 106 24.42 434036
#> 5 CA0010900 V clearances 02-2019 40 9.22 434036
#> 6 CA0010900 V clearances 03-2019 37 8.52 434036
#> participated_population
#> 1 434036
#> 2 434036
#> 3 434036
#> 4 434036
#> 5 434036
#> 6 434036
```
Each row is one month of one `measure` (offenses or clearances). `rate` is per
100,000 people for that month. `population` is the population the agency
serves and `participated_population` the part of it covered by a report that
month; for a single agency they match whenever it reported.
For an agency or a state, the API also sends its state's and the nation's
rates for comparison. Those are dropped by default; `comparison = TRUE` keeps
them, labelled in `series` and `series_name` (they have no `count`):
```r
with_context <- get_agency_crime("CA0010900", from = "01-2019", to = "01-2019",
comparison = TRUE)
with_context[, c("series", "series_name", "measure", "count", "rate")]
#> series series_name measure count rate
#> 1 agency Oakland Police Department offenses 472 108.75
#> 2 agency Oakland Police Department clearances 106 24.42
#> 3 state California offenses NA 34.38
#> 4 state California clearances NA 16.12
#> 5 national United States offenses NA 27.85
#> 6 national United States clearances NA 12.20
```
```r
texas_crime <- get_reported_crime("TX", from = "01-2019", to = "03-2019")
texas_crime
#> geography offense measure period count rate population
#> 1 TX V offenses 01-2019 9690 33.64 28995881
#> 2 TX V offenses 02-2019 8554 29.69 28995881
#> 3 TX V offenses 03-2019 9768 33.89 28995881
#> 4 TX V clearances 01-2019 3426 11.89 28995881
#> 5 TX V clearances 02-2019 3083 10.70 28995881
#> 6 TX V clearances 03-2019 3266 11.33 28995881
#> participated_population
#> 1 28804627
#> 2 28815089
#> 3 28824219
#> 4 28804627
#> 5 28815089
#> 6 28824219
```
At state and national level, `participated_population / population` is the
reporting coverage: the count is the sum of the agencies that reported, not an
estimate for everyone.
```r
national_arson <- get_reported_arson(from = "01-2019", to = "03-2019")
national_arson[, c("geography", "measure", "period", "count", "rate")]
#> geography measure period count rate
#> 1 US offenses 01-2019 2598 0.81
#> 2 US offenses 02-2019 2218 0.69
#> 3 US offenses 03-2019 2771 0.86
#> 4 US clearances 01-2019 572 0.18
#> 5 US clearances 02-2019 487 0.15
#> 6 US clearances 03-2019 684 0.21
```
## Arrests
`get_arrest_count()` with the default `offense = "all"` returns monthly
arrest totals:
```r
columbus_arrests <- get_arrest_count("OHCOP0000", from = "01-2023", to = "03-2023")
columbus_arrests
#> geography offense measure period count rate population
#> 1 OHCOP0000 all arrests 01-2023 617 67.89 908834
#> 2 OHCOP0000 all arrests 02-2023 606 66.68 908834
#> 3 OHCOP0000 all arrests 03-2023 674 74.16 908834
#> participated_population
#> 1 908834
#> 2 908834
#> 3 908834
```
Naming an offense returns that offense's monthly series. Names come at three
levels of detail (`list_ucr_arrest_offenses()` lists them); within one level
the counts do not overlap. The API addresses an offense by a numeric code, one
per breakdown (`ucr_arrest_offense_codes`), so a name spanning several codes
costs one request per code and the series is their sum:
```r
head(list_ucr_arrest_offenses(level = "category"))
#> [1] "Aggravated Assault"
#> [2] "All Other Offenses"
#> [3] "Arson"
#> [4] "Burglary"
#> [5] "Counterfeiting/Forgery"
#> [6] "Destruction/Damage/Vandalism of Property"
drugs <- get_arrest_count(state_abb = "OH", offense = "Drug/Narcotic Offenses",
from = "01-2023", to = "03-2023")
drugs[, c("offense", "period", "count", "rate")]
#> offense period count rate
#> 1 Drug/Narcotic Offenses 01-2023 1897 16.96
#> 2 Drug/Narcotic Offenses 02-2023 1884 16.93
#> 3 Drug/Narcotic Offenses 03-2023 2174 19.46
```
One name to watch: `"Drug Abuse Violations"` is only the drug arrests not
classed as possession or sale. The category `"Drug/Narcotic Offenses"` above
is the total.
`get_arrest_demographics()` returns arrestees by sex, race and age by sex,
summed over the date range, for all offenses or one. For one offense, that is
the age-by-offense cross-tabulation:
```r
larceny <- get_arrest_demographics(state_abb = "OH", offense = "Larceny",
from = "01-2023", to = "12-2023")
larceny[larceny$demographic_type == "Arrestee Sex", ]
#> geography offense period demographic_type demographic_value count
#> 1 OH Larceny Arrestee Sex Male 10323
#> 2 OH Larceny Arrestee Sex Female 9177
```
`vignette("juvenile-arrests")` works through these at state, agency and metro
level.
## NIBRS
`get_nibrs_victim()`, `get_nibrs_offender()` and `get_nibrs_offense()` return
one NIBRS breakdown for one offense, summed over the date range. The offense
must be a code: a summary group such as `"V"`, `"ROB"` or `"BUR"`, or a NIBRS
offense code such as `"13B"` (simple assault):
```r
head(list_nibrs_offenses(), 10)
#> code label
#> 1 V Violent Crime
#> 2 P Property Crime
#> 3 ASS Aggravated Assault
#> 4 HOM Homicide
#> 5 RPE Rape
#> 6 ROB Robbery
#> 7 ARS Arson
#> 8 BUR Burglary
#> 9 LAR Larceny-theft
#> 10 MVT Motor Vehicle Theft
```
```r
get_nibrs_victim(state_abb = "OH", offense = "ROB", variable = "age",
from = "01-2023", to = "12-2023")
#> geography offense period demographic_type demographic_value count
#> 1 OH ROB age 0-9 29
#> 2 OH ROB age 10-19 847
#> 3 OH ROB age 20-29 1711
#> 4 OH ROB age 30-39 1421
#> 5 OH ROB age 40-49 932
#> 6 OH ROB age 50-59 665
#> 7 OH ROB age 60-69 455
#> 8 OH ROB age 70-79 135
#> 9 OH ROB age 80-89 26
#> 10 OH ROB age Unknown 25
#> 11 OH ROB age 90-Older 9
```
The variables differ by function: `list_nibrs_victim_variables()`,
`list_nibrs_offender_variables()` and `list_nibrs_offense_variables()` list
them.
```r
get_nibrs_offense(state_abb = "OH", offense = "ROB", variable = "weapons",
from = "01-2023", to = "12-2023")[, c("demographic_value", "count")]
#> demographic_value count
#> 1 None 2615
#> 2 Other 116
#> 3 Rifle 24
#> 4 Poison 1
#> 5 Firearm 474
#> 6 Handgun 1332
#> 7 Shotgun 9
#> 8 Unknown 221
#> 9 Explosives 2
#> 10 Asphyxiation 3
#> 11 Blunt Object 68
#> 12 Other Firearm 24
#> 13 Personal Weapons 928
#> 14 Rifle (Automatic) 1
#> 15 Firearm (Automatic) 0
#> 16 Handgun (Automatic) 31
#> 17 Shotgun (Automatic) 0
#> 18 Motor Vehicle/Vessel 28
#> 19 Fire/Incendiary Device 2
#> 20 Knife/Cutting Instrument 239
#> 21 Other Firearm (Automatic) 0
#> 22 Drugs/Narcotics/Sleeping Pills 2
```
An offense *name* returns no data from the API, so the package rejects it and
suggests the code:
```r
get_nibrs_victim(state_abb = "OH", offense = "robbery")
#> Error: 'robbery' is not a NIBRS offense code; the API returns no data for offense names. Did you mean "ROB" (Robbery)? See list_nibrs_offenses().
```
An agency or state that did not report to NIBRS in the period returns no rows,
with a message.
## Supplemental Homicide Reports (SHR)
`get_shr()` returns monthly homicide counts from the SHR, for an agency, a
state, or the nation:
```r
get_shr(ori = "GAAPD0000", from = "01-2019", to = "06-2019")
#> geography period count
#> 1 GAAPD0000 01-2019 8
#> 2 GAAPD0000 02-2019 9
#> 3 GAAPD0000 03-2019 9
#> 4 GAAPD0000 04-2019 7
#> 5 GAAPD0000 05-2019 7
#> 6 GAAPD0000 06-2019 7
```
A zero can mean "not reported". Florida, for one, sends almost no SHR data:
```r
fl <- get_shr(state_abb = "FL", from = "01-2019", to = "12-2019")
ga <- get_shr(state_abb = "GA", from = "01-2019", to = "12-2019")
c(Florida = sum(fl$count), Georgia = sum(ga$count))
#> Florida Georgia
#> 9 484
```
## Police employment
`get_police_employment()` returns sworn officers and civilian staff by sex,
for an agency, a state or the nation. It uses four-digit years rather than
`MM-YYYY`:
```r
oakland_pe <- get_police_employment("CA0010900", from = "2018", to = "2020")
oakland_pe
#> year male_officers female_officers male_civilians female_civilians ori
#> 1 2018 633 98 60 219 CA0010900
#> 2 2019 636 104 63 220 CA0010900
#> 3 2020 632 108 83 233 CA0010900
#> male_total female_total civilians_total officers_total employees_total
#> 1 693 317 279 731 1010
#> 2 699 324 283 740 1023
#> 3 715 341 316 740 1056
#> participated_population employees_per_1000
#> 1 430230 2.35
#> 2 434036 2.36
#> 3 437923 2.41
```
A state's or the nation's counts are sums over the agencies that reported
that year, and that set changes, so compare years by the rate rather than the
count:
```r
texas_pe <- get_police_employment(state_abb = "TX", from = "2018", to = "2020")
texas_pe[, c("year", "employees_total", "participated_population",
"employees_per_1000")]
#> year employees_total participated_population employees_per_1000
#> 1 2018 70186 20609733 3.40
#> 2 2019 86760 25744202 3.36
#> 3 2020 97865 28635940 3.41
```
Texas's employee count rose 39% from 2018 to 2020
because more of the state reported; per 1,000 people covered it barely moved.
## NIBRS participation
`get_agency_participation()`, `get_state_participation()` and
`get_region_participation()` report whether agencies report to NIBRS *now*,
from the live agency directory. They are a current snapshot, not a history,
and they count agencies, not people: a state with few NIBRS agencies can still
have near-complete reporting coverage through the older summary system (see
the `participated_population` columns above).
```r
get_agency_participation("OHCOP0000")
#> ori agency_name agency_type_name state_abbr state_name
#> 1 OHCOP0000 Columbus Police Department City OH Ohio
#> county is_nibrs nibrs_start_date
#> 1 DELAWARE, FAIRFIELD, FRANKLIN TRUE 2003-01-01
```
```r
get_state_participation("OH")
#> state_abbr total_agencies nibrs_agencies participation_rate
#> 1 OH 863 831 0.96292
```
```r
get_region_participation("South")
#> region total_agencies nibrs_agencies participation_rate
#> 1 South 8119 7105 0.8751078
```
## Officers killed (LEOKA)
`get_leoka()` and `get_leoka_monthly()` return national counts of law
enforcement officers **feloniously killed**, from 2020 on. Despite the
program's name (Law Enforcement Officers Killed and Assaulted) these are not
assault counts. The API returns national figures only.
```r
get_leoka(2020, 2021)
#> year month total_officers total_incidents total_officers_dod
#> 1 2020 NA 46 44 2
#> 2 2021 NA 73 68 4
#> total_officers_doi total_incidents_dod total_incidents_doi
#> 1 44 2 42
#> 2 69 4 64
```
```r
get_leoka_monthly(2021, 1)
#> year month total_officers total_incidents total_officers_dod
#> 1 2021 1 6 4 0
#> total_officers_doi total_incidents_dod total_incidents_doi
#> 1 6 0 4
```