Getting Started with fbiCDE

The fbiCDE package wraps the FBI’s Crime Data Explorer (CDE) API (https://cde.ucr.cjis.gov/LATEST/), 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.

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).

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:

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:

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
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):

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
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.

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:

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:

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:

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   <NA>     Arrestee Sex              Male 10323
#> 2        OH Larceny   <NA>     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):

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
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.

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:

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:

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:

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:

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:

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).

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
get_state_participation("OH")
#>   state_abbr total_agencies nibrs_agencies participation_rate
#> 1         OH            863            831            0.96292
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.

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
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