2020-10-26

Functions

The code I use to derive the victim and offender counts can be found below.

library(tidyverse)
library(data.table)

get_crime <- function(x){
  data.table::fcase(
    ##Violent Crimes 1-20
    x == 1, "Completed rape",
    x == 2, "Attempted rape",
    x == 3, "Sexual attack with serious assault",
    x == 4, "Sexual attack with minor assault",
    x == 5, "Completed robbery with injury from serious assault",
    x == 6, "Completed robbery with injury from minor assault",
    x == 7, "Completed robbery without injury from minor assault",
    x == 8, "Attempted robbery with injury from serious assault",
    x == 9, "Attempted robbery with injury from minor assault",
    x == 10, "Attempted robbery without injury",
    x == 11, "Completed aggravated assault with injury",
    x == 12, "Attempted aggravated assault with weapon",
    x == 13, "Threatened assault with weapon",
    x == 14, "Simple assault completed with injury",
    x == 15, "Sexual assault without injury",
    x == 16, "Unwanted sexual contact without force",
    x == 17, "Assault without weapon without injury",
    x == 18, "Verbal threat of rape",
    x == 19, "Verbal threat of sexual assault",
    x == 20, "Verbal threat of assault",
    
    ##Purse snatching/pocket picking 21-23
    x == 21, "Completed purse snatching",
    x == 22, "Attempted purse snatching",
    x == 23, "Pocket picking (completed only)",
    
    ##Property Crimes
    x == 31, "Completed burglary, forcible entry",
    x == 32, "Completed burglary, unlawful entry without force",
    x == 33, "Attempted forcible entry",
    x == 40, "Completed motor vehicle theft",
    x == 41, "Attempted motor vehicle theft",
    x == 54, "Completed theft less than $10",
    x == 55, "Completed theft $10 to $49",
    x == 56, "Completed theft $50 to $249",
    x == 57, "Completed theft $250 or greater",
    x == 58, "Completed theft value NA",
    x == 59, "Attempted theft",
    default = NA_character_ )
}

recode_serious_crimes <- function(x){
  data.table::fcase(
    x %in% 1:4, "Rape/Sexual attack",
    x %in% 5:10, "Robbery",
    x %in% 11:13, "Assault",
    x %in% 14:20, "Minor violent crime",
    x %in% 21:23, "Purse snatch/pocket picking",
    x %in% 31:59, "Property crimes",
    default = NA_character_ )
}


get_race <- function(x){
  data.table::fcase(
    x == 1, "White",
    x == 2, "Black",
    x == 3, "American Indian",
    x == 4, "Asian",
    x == 5, "Hawaiian",
    x == 6, "White-Black",
    x == 7, "White-American Indian",
    x == 8, "White-Asian",
    x == 9, "White-Hawaiian",
    x == 10, "Black-American Indian",
    x == 11, "Black-Asian",
    x == 12, "Black-Hawaiian",
    x == 13, "American Indian-Asian",
    x == 14, "Asian-Hawaiian",
    x == 15, "White-Black-American Indian",
    x == 16, "White-Black-Asian",
    x == 17, "White-American Indian-Asian",
    x == 18, "White-Asian-Hawaiian",
    x == 19, "2 or 3 races",
    x == 20, "4 or 5 races",
    default = NA_character_)
}

get_race_category <- function(race, hisp){
  data.table::fcase(
    race %chin% c("White") & hisp == "Non-Hispanic", "White",
    race %chin% c("White") & hisp == "Hispanic", "Hispanic", 
    ##Hispanic getting at mestizos, i.e. Amerindian with Iberian (Spanish) admixture - 
    ## what most people think of as "Hispanic", but classified as "White"
    ##- ignoring Black and Asian hispanic.  There is a very small percentage of white hispanics 
    ## with no amerindian ancestry who according to this 
    ## criterion should be classified as 'White', but its negligible.  
    race %chin% c("Black", "White-Black", "Black-Asian", "Black-Hawaiian", "Black-American Indian"), "Black",
    race %chin% c("Asian", "Hawaiian", "Asian-Hawaiian"), "Asian", ##Asian/PAcific Islander/Hawaiian 
    race %chin% c("American Indian"), "American Indian", 
    ##all others, it not missing, "Other"
    !is.na(race), "Other",
    default = NA_character_
  )
}


get_race_category_v2 <- function(race, hisp){
  data.table::fcase(
    hisp == "Hispanic", "Hispanic", 
    race %chin% c("White") & hisp == "Non-Hispanic", "White",
    race %chin% c("Black") & hisp == "Non-Hispanic", "Black",
    race %chin% c("Asian") & hisp == "Non-Hispanic", "Asian",
    !is.na(race), "Other",
    default = NA_character_
  )
}



process_ncvs_002_files <- function(dt){
  require(data.table)
  require(magrittr)
  
    dt[, .(
      IDHH,
      HH_WEIGHT = V2116,
      WGTHHCY, ##ADJUSTED HOUSEHOLD WEIGHT - COLLECTION YEAR
      
      hh_Race0 = data.table::fcase(
        V2031 == 1, "White", ##White, but not specifying Hispanic
        V2031 == 2, "Black",
        V2031 == 6, "White-Black",
        V2031 %in% c(3,21), "Other",
        V2031 == 22, "White-Other",
        V2031 == 23, "Black-Other",
        V2031 == 24, "White-Black-Other",
        default = NA_character_),
      pp_Race0 = get_race(V2040A),
      pp_Hisp = fcase(V2041 == 1, "Hispanic", V2041 == 2, "Non-Hispanic", default = NA_character_),
      ref_Race0 = get_race(V2049A),
      ref_Hisp = fcase(V2050 == 1, "Hispanic", V2050 == 2, "Non-Hispanic", default = NA_character_))] %>% 
  
    .[, `:=`(
      hh_Race = data.table::fcase(
        hh_Race0 %chin% c("White","White-Other"), "White/Hispanic",
        hh_Race0 %chin% c("Black", "Black-Other", "White-Black"), "Black", 
        ##Note:  due to social construction, etc.  White-Black is coded as Black, but this is negligible
        hh_Race0 %chin% c("Other", "White-Black-Other"), "Other",
        default = NA_character_),
      pp_Race = get_race_category(pp_Race0, pp_Hisp),
      ref_Race = get_race_category(ref_Race0, ref_Hisp)
  )]
  
}

process_ncvs_003_files <- function(dt){
  require(data.table)
  require(magrittr)
  
  dt[, .(
        YEARQ,
        IDHH, IDPER, 
        WGTPERCY,
        PP_weight = V3080,
        Num_incidents = V3081, ##Num Incidents
        Race0 = get_race(V3023A), 
        Hisp = data.table::fcase(V3024 == 1, "Hispanic", V3024 == 2, "Non-Hispanic", default = NA_character_))] %>% 
    
  .[, `:=` (
    Race = get_race_category(Race0, Hisp),
    Race_v2 = get_race_category_v2(Race0, Hisp)
    )]
}

process_ncvs_004_files <- function(dt){
  
  require(data.table)
  require(magrittr)
  
    ##  In 2015, incident weight is called V4527
    ## "Beginning in 2016, the incident weight (V4527) was adjusted to account for series victimizations 
    ##and was renamed SERIES_IWEIGHT."
  if("V4527" %in% names(dt) & !"SERIES_IWEIGHT" %in% names(dt)){
    if(max(dt$V4527, na.rm=TRUE) > 0){
      data.table::setnames(dt, old = c("V4527"), new = c("SERIES_IWEIGHT"), skip_absent = FALSE) 
    }
  }
    
  
   ##Rule V4022 != 1 excludes cases outside US

   dt[V4022 != 1] %>% 
     .[, V4016 := fifelse(V4016 %in% 11:996, 10, V4016)] %>%  ##recode series, no greater than 10]
   
     .[, .(
          YEARQ,
          Year = V4015,
          Incident_num = V4012,
          Crime = get_crime(V4529),
          Crime_type = recode_serious_crimes(V4529),
          Crime_code = V4529,
          IDHH, 
          IDPER, 
          WGTVICCY = fifelse(V4019 == 2, WGTVICCY * V4016, WGTVICCY), ##adust for series
          SERIES_WEIGHT, 
          Incident_Weight = SERIES_IWEIGHT,
          
          off_Race0 = data.table::fcase(
            V4246C == 1 | V4281  == 1 | V4285A == 2, "Black",
            V4246B == 1 | V4280 == 1 | V4285A == 1, "White",
            V4246D == 1 | V4282A == 1 | V4285A == 3, "Native American",
            V4246E == 1 | V4282B == 1 | V4285A == 4, "Asian",
            V4246F == 1 | V4282C == 1 | V4285A == 5, "Hawaiian",
            default = NA_character_),
          
          ##Excluding multiple offenders of various races
          off_Race0_v2 = data.table::fcase(
            V4285A %in% 1:5, "Multiple",
            V4246C == 1 | V4281  == 1, "Black",
            V4246B == 1 | V4280 == 1,  "White",
            V4246D == 1 | V4282A == 1, "Native American",
            V4246E == 1 | V4282B == 1, "Asian",
            V4246F == 1 | V4282C == 1, "Hawaiian",
            default = NA_character_),
          
          off_Hisp = data.table::fcase(
            V4237A == 1 | V4252A == 1 | V4252B %in% c(1), "Hispanic",
            V4237A == 2 | V4252A == 2 | V4252B %in% c(2), "Non-Hispanic",
            default = NA_character_))] %>% 
    
    .[, `:=`(
          `Offender Race` = data.table::fcase(
            off_Race0 == "White" & off_Hisp == "Non-Hispanic", "White",
            off_Race0 == "White" & is.na(off_Hisp), "White", ## Counting White with missing Hispanic designation as White
            
            off_Race0 == "White" & off_Hisp == "Hispanic", "Hispanic",
            off_Race0 == "Black", "Black",
            off_Race0 %chin% c("Asian", "Hawaiian"), "Asian",
            off_Race0 == "Native American", "Native American",
            default = NA_character_),
          
          ##Excluding all hispanic; Other = Hawaiian, Native American
          `Offender Race v2` = data.table::fcase(
            off_Hisp == "Hispanic", "Hispanic",
            off_Race0 == "White" & off_Hisp == "Non-Hispanic", "White",
            off_Race0 == "Black" & off_Hisp == "Non-Hispanic", "Black",
            off_Race0 == "Asian" & off_Hisp == "Non-Hispanic", "Asian",
            off_Race0 %chin% c("Native American", "Hawaiian"), "Other",
            default = NA_character_),
          
          ##version 2 + excluding multiple

          `Offender Race v3` = data.table::fcase(
            off_Hisp == "Hispanic", "Hispanic",
            off_Race0_v2 == "White" & off_Hisp == "Non-Hispanic", "White",
            off_Race0_v2 == "Black" & off_Hisp == "Non-Hispanic", "Black",
            off_Race0_v2 == "Asian" & off_Hisp == "Non-Hispanic", "Asian",
            off_Race0_v2 == "Multiple", "Multiple Races",
            off_Race0_v2 %chin% c("Native American", "Hawaiian"), "Other",
            default = NA_character_)
          )]
}



merge_and_summarise <- function(dt_004, dt_003, 
                                keep_codes = 1:20, 
                                grouping_vars = c("Weight_type")){
  require(data.table)
  
  weight_cols <- c("SERIES_WEIGHT", "Incident_Weight", "WGTPERCY", "UNWEIGHTED")
  
  dt <- dt_004[Crime_code %in% keep_codes] 
  dt <- data.table::merge.data.table(x = dt, y = dt_003, 
                                     by = c("IDPER", "IDHH", "YEARQ"), 
                                     all.x = TRUE) 
  dt[, UNWEIGHTED := 1]
  
  keep_vars <- unique(c(weight_cols, 
                        grouping_vars, 
                        "Crime", "Crime_code", "Race", "Offender Race"))
  keep_vars <- keep_vars[which(keep_vars != "Weight_type")]
  dt <- dt[, ..keep_vars]
  
  weight_cols <- weight_cols[which(weight_cols %in% names(dt))]
  dt <- data.table::melt.data.table(data = dt, 
                                    measure.vars = weight_cols, 
                                    variable.name = "Weight_type", 
                                    value.name = "Weight")

  gVars <- unique(c("Weight_type", grouping_vars))
  gVars <- gVars[which(gVars %in% names(dt))]
  
  dt[, `:=`(
      `Total Known Race` = sum(Weight[which(!is.na(`Offender Race`))], na.rm=TRUE),
      `Total` = sum(Weight, na.rm=TRUE)
  ), by = gVars]
  
  gVars <- unique(c("Weight_type", "Offender Race", grouping_vars))
  gVars <- gVars[which(gVars %in% names(dt))]
  
  dt <- dt[, .(
      `Total Known Race` = first(`Total Known Race`),
      `Total` = first(`Total`),
      `Victimized by Offender Race` = sum(Weight, na.rm=TRUE)), by = gVars]
  
  dt[, `:=`(
      `Percent Known Race` = `Victimized by Offender Race`/`Total Known Race`,
      `Percent Total` = `Victimized by Offender Race`/Total,
      `Offender Race` = factor(`Offender Race`, 
                               levels = c("White", "Black", "Hispanic", 
                                          "Asian", "Native American"))
      )]
  
  return(dt)
}




show_crime_summary <- function(df, grouping_vars = NULL, 
                               use_weight = c("series", "incident", 
                                              "person", "unweighted")){
  require(data.table)
  require(reactable)
  
  use_weight = match.arg(use_weight)
  use_weight = switch(use_weight, "series" = "SERIES", 
                      "incident" = "INCIDENT", 
                      "person" = "WGTPERCY",
                      "unweighted" = "UNWEIGHTED")
  
  dt <- data.table::as.data.table(df)
  data.table::setnames(dt, skip_absent = TRUE,
                       old = c("Crime_type", "Weight_type"), 
                       new = c("Offense", "Weight"))
  
  dt <- dt[!is.na(`Offender Race`)]
  dt[, Weight := dplyr::recode(Weight, 
                               "Incident_Weight" = "INCIDENT", 
                               "SERIES_WEIGHT" = "SERIES")]
  
  keep_vars <- unique(c(grouping_vars, "Offender Race", "Offense", 
                        "Weight", "Year", 
                        "Victimizations", 
                        "Percent", 
                        "Percent Known Race"))
  keep_vars <- keep_vars[which(keep_vars %in% names(dt))]
  dt <- dt[, ..keep_vars]
  
  if("Offense" %in% names(dt)){
    dt[, Offense := factor(Offense, levels = c("Robbery", 
                                               "Assault", 
                                               "Rape/Sexual attack"))]
  }
  
  dt <- dt[Weight == use_weight]
  dt[, Weight := NULL]
  
  order_vars <- c("Offender Race", "Offense", "Year" )
  order_vars <- order_vars[which(order_vars %in% names(dt))]
  data.table::setorderv(dt, cols = order_vars)
  
  grouping_vars <- grouping_vars[which(grouping_vars %in% names(dt))]
  if(length(grouping_vars) == 0){grouping_vars <- NULL}

  reactable::reactable(dt, striped = TRUE, highlight = TRUE, 
                       bordered = TRUE, 
                       pagination = FALSE, 
                       compact = FALSE, 
                       resizable = FALSE, 
                       groupBy = grouping_vars,
                     
     defaultColDef = colDef(
     
     minWidth = 135, align = "left",
     style = list(fontSize = 12, 
                  
                  fontFamily = "'Roboto Condensed', 'Arial Narrow', 
                                  Merriweather,  Arial, 
                  Helvetica, sans-serif"),
     format = list(cell = colFormat(separators = TRUE, 
                                    percent = TRUE, 
                                    digits = 2)),
     headerStyle =  list(fontWeight = "bold", fontSize = 16),
     footerStyle =  list(fontWeight = "bold"))
     
   ,columns = list(
     Weight = colDef(align = "left", 
                     minWidth = 85, 
                     aggregate = "unique"),
     `Offender Race` = colDef(align = "left", 
                              minWidth = 95, 
                              aggregate = "unique"),
     `Year` = colDef(align = "center", 
                     minWidth = 45, 
                     aggregate = "unique", 
                     format = colFormat(separators = FALSE, 
                                        percent = FALSE, 
                                        digits = 0)),
     Victimizations = colDef(align = "center", 
                             minWidth = 75, 
                             aggregate = "sum", 
                             format = colFormat(separators = TRUE, 
                                                percent = FALSE, digits = 0)),
     Offense = colDef(aggregate = "unique"),
     `Percent` = colDef(aggregate = "mean", 
                        minWidth = 55, 
                        align = "center", 
                        format = colFormat(separators = TRUE, 
                                           percent = TRUE, digits = 2)),
     `Percent Known Race` = colDef(aggregate = "mean", 
                                   minWidth = 55, 
                                   align = "center", 
                                   format = colFormat(separators = TRUE, 
                                                      percent = TRUE, digits = 2))
   )
                     )
}

Victim Surveys (NCVS)

Data pertaining to interracial violent victimizations (excluding murder) can be obtained from the National Crime Victimization Survey (NCVS). The Bureau of Justice Statistics (BJS) provides the following description on their website:

“The Bureau of Justice Statistics’ (BJS) National Crime Victimization Survey (NCVS) is the nation’s primary source of information on criminal victimization. Each year, data are obtained from a nationally representative sample of about 240,000 interviews on criminal victimization, involving 160,000 unique persons in about 95,000 households…. The NCVS collects information on nonfatal personal crimes (i.e., rape or sexual assault, robbery, aggravated and simple assault, and personal larceny) … both reported and not reported to police.”

The BJS has made available on their website data pertaining to interracial violence for 2018 and 2019, which can be downloaded as excel files.

Unfortunately, the BJS has not published analogous files for earlier versions of the survey from 2015 and 2016, and the format of the data they do publish changes from year to year. However, the raw data can be obtained from the Inter-university Consortium for Political and Social Research (ICPSR). The drawback is that these data are not easily accessible to the general public. The files are huge, require code books to interpret, and are generally unmanageable without some prior experience in coding and/or data analysis.

My presentation of the ICPSR data is mostly reproducible but requires that you first download the .tsv formatted files from the following links:

For practitioners of R, note that because the files are so large, I load and process the files using the data.table package, which might not be as familiar as the tidyverse or base R. For more information on data.table syntax, see: https://rdatatable.gitlab.io/data.table/index.html .

Setup

Loading data…

##2015 Data

nc_15_003 <- data.table::fread(paste0(myDataFolder, "DATA/NCVS-ICPSR-2015-36448/DS0003/36448-0003-Data.tsv")) %>% 
  process_ncvs_003_files(.)
nc_15_004 <- data.table::fread(paste0(myDataFolder, "DATA/NCVS-ICPSR-2015-36448/DS0004/36448-0004-Data.tsv")) %>% 
  process_ncvs_004_files(.)

##2016 Data

nc_16_003 <- data.table::fread(paste0(myDataFolder, "DATA/NCVS-ICPSR-2016-36828/DS0003/36828-0003-Data.tsv")) %>% 
  process_ncvs_003_files(.)
nc_16_004 <- data.table::fread(paste0(myDataFolder,"DATA/NCVS-ICPSR-2016-36828/DS0004/36828-0004-Data.tsv")) %>% 
  process_ncvs_004_files(.)

##2018 Data

nc_18_003 <- data.table::fread(paste0(myDataFolder,"DATA/NCVS-ICPSR-2018-37297/DS0003/37297-0003-Data.tsv")) %>% 
  process_ncvs_003_files(.)
nc_18_004 <- data.table::fread(paste0(myDataFolder,"DATA/NCVS-ICPSR-2018-37297/DS0004/37297-0004-Data.tsv")) %>% 
  process_ncvs_004_files(.)

All Nonfatal Violent Offenses

d_vc_15 <- merge_and_summarise(nc_15_004, nc_15_003, keep_codes = 1:20, 
                               grouping_vars = c("Weight_type")) %>% 
  .[, Year := 2015]

d_vc_16 <- merge_and_summarise(nc_16_004, nc_16_003, keep_codes = 1:20, 
                               grouping_vars = c("Weight_type")) %>% 
  .[, Year := 2016]

d_vc_18 <- merge_and_summarise(nc_18_004, nc_18_003, keep_codes = 1:20, 
                               grouping_vars = c("Weight_type")) %>% 
  .[, Year := 2018]

d_vc_all <- data.table::rbindlist(list(d_vc_15, d_vc_16, d_vc_18)) 

d_vc_all %>% show_crime_summary(., grouping_vars = c("Offender Race"))

Serious Nonfatal Violent Offenses

Below I include only criminal codes 1-13. These are offenses which are more likely to be reported and for which offenders are more likely to be arrested.

d_vc_15 <- merge_and_summarise(nc_15_004, nc_15_003, keep_codes = 1:13, 
                               grouping_vars = c("Weight_type")) %>% 
  .[, Year := 2015]

d_vc_16 <- merge_and_summarise(nc_16_004, nc_16_003, keep_codes = 1:13, 
                               grouping_vars = c("Weight_type")) %>% 
  .[, Year := 2016]

d_vc_18 <- merge_and_summarise(nc_18_004, nc_18_003, keep_codes = 1:13, 
                               grouping_vars = c("Weight_type")) %>% 
  .[, Year := 2018]

d_vc_major <- data.table::rbindlist(list(d_vc_15, d_vc_16, d_vc_18)) 

d_vc_major %>% show_crime_summary(., grouping_vars = c("Offender Race"))

Serious Nonfatal Violent Offenses by Offense Type

d_vc_15 <- merge_and_summarise(nc_15_004, nc_15_003, keep_codes = 1:13, 
                               grouping_vars = c("Weight_type", "Crime_type")) %>% 
  .[, Year := 2015]

d_vc_16 <- merge_and_summarise(nc_16_004, nc_16_003, keep_codes = 1:13, 
                               grouping_vars = c("Weight_type", "Crime_type")) %>% 
  .[, Year := 2016]

d_vc_18 <- merge_and_summarise(nc_18_004, nc_18_003, keep_codes = 1:13, 
                               grouping_vars = c("Weight_type", "Crime_type")) %>% 
  .[, Year := 2018]

d_vc_major_offense <- data.table::rbindlist(list(d_vc_15, d_vc_16, d_vc_18)) 

d_vc_major_offense %>% show_crime_summary(., 
                                          grouping_vars = c("Offender Race",
                                                            "Offense"))

Reported Crimes (NIBRS)

Importantly, these data do not distinguish between Hispanic and Non-Hispanic Whites.

SOURCES:

Setup

nibrs_offense <- readr::read_csv("Data/NIBRS/nibrs-race-by-offense.csv") %>% 
  tidyr::pivot_longer(-c(Offense, Year, Total), 
                      names_to = "Race", 
                      values_to = "offenders") %>% 
  dplyr::mutate(Offense = factor(Offense, 
                                 levels = c("Murder", "Robbery", 
                                            "Assault", "Rape")),
                Race = dplyr::recode(Race, "White" = "White/Hispanic"),
                Race = factor(Race, levels = c("White/Hispanic", 
                                               "Black", 
                                               "Asian", 
                                               "Native American", 
                                               "Hawaiian", 
                                               "Unknown")))

nibrs_offense_sum <- nibrs_offense %>% 
  dplyr::select(-Total) %>% 
  #dplyr::filter(Offense != "Murder") %>%
  dplyr::group_by(Year) %>% 
 dplyr::mutate(
    Total = sum(offenders),
    `Total Known Race` = sum(offenders[which(Race != "Unknown")])
    ) %>% 
  dplyr::group_by(Year, Race) %>% 
  dplyr::summarise(
    Offenders = sum(offenders),
    Total = first(Total),
    Percent = Offenders/Total,
    `Offenders Known Race` = sum(offenders[which(Race != "Unknown")]),
    `Total Known Race` = first(`Total Known Race`),
    `Percent Known Race` = `Offenders Known Race`/`Total Known Race`
  )

All Violent Offenses

nibrs_offense_sum %>% 
  dplyr::rename(`Offender Race` = Race) %>% 
  dplyr::arrange(Year,`Offender Race`) %>% 
  dplyr::select(`Offender Race`, Year,
                `Percent of Total Offenders` = `Percent`,  
                `Percent Known Race` ) %>%  

 reactable::reactable(., striped = TRUE, highlight = TRUE, 
                      bordered = TRUE, pagination = FALSE, 
                      compact = FALSE, resizable = FALSE, 
                       groupBy = c("Offender Race"),
   defaultColDef = colDef(
     #minWidth = 150, 
     style = list(fontSize = 12, fontFamily = "'Roboto Condensed', 
                  'Arial Narrow', Merriweather,  Arial, Helvetica, sans-serif"),
     align = "left",
     format = list(cell = colFormat(separators = TRUE, 
                                    percent = TRUE, digits = 2)),
     headerStyle =  list(fontWeight = "bold", 
                         fontSize = 15),
     footerStyle =  list(fontWeight = "bold"))
   
   ,columns = list(
     #Weight = colDef(align = "right"),
     Year = colDef(aggregate = "unique", 
                   format = colFormat(separators = FALSE, 
                                      percent = FALSE, digits = 0)),
     `Percent of Total Offenders` = colDef(aggregate = "mean", 
                                           minWidth = 75, 
                                           align = "center", 
                                           format = colFormat(separators = TRUE, 
                                                              percent = TRUE, 
                                                              digits = 2)),
     `Percent Known Race` = colDef(aggregate = "mean",
                                   minWidth = 75, 
                                   align = "center", 
                                   format = colFormat(separators = TRUE, 
                                                      percent = TRUE, 
                                                      digits = 2))
   )
                     )

By Crime Type

d_tbl <- nibrs_offense %>% 
  dplyr::select(-Total) %>% 
  dplyr::group_by(Year, Offense) %>% 
 dplyr::mutate(
    Total = sum(offenders),
    `Total Known Race` = sum(offenders[which(Race != "Unknown")])) %>% 
  dplyr::group_by(Year, Race, Offense) %>% 
  dplyr::summarise(
    Offenders = sum(offenders),
    Total = first(Total),
    Percent = Offenders/Total,
    `Offenders Known Race` = sum(offenders[which(Race != "Unknown")]),
    `Total Known Race` = first(`Total Known Race`),
    `Percent Known Race` = `Offenders Known Race`/`Total Known Race`,
  .groups = "drop") 


d_tbl %>% 
  dplyr::rename(`Offender Race` = Race) %>% 
  dplyr::arrange(Offense, `Offender Race`, Year) %>% 
  dplyr::select(Offense, `Offender Race`, Year, 
                `Percent of Total Offenders` = `Percent`,  
                `Percent Known Race` ) %>%  #%>% DT::datatable(.) %>% DT::formatPercentage(., columns = 4:5)

 reactable::reactable(., striped = TRUE, highlight = TRUE, 
                      bordered = TRUE, pagination = FALSE, 
                      compact = FALSE, resizable = FALSE, 
                       groupBy = c("Offender Race", "Offense"),
     defaultColDef = colDef(
       #minWidth = 150, 
       style = list(fontSize = 12, 
                    fontFamily = "'Roboto Condensed', 
                    'Arial Narrow', Merriweather,  
                    Arial, Helvetica, sans-serif"),
       align = "left",
       format = list(cell = colFormat(separators = TRUE, 
                                      percent = TRUE, digits = 2)),
       headerStyle =  list(fontWeight = "bold", fontSize = 15),
       footerStyle =  list(fontWeight = "bold"))
     
   ,columns = list(
     #Weight = colDef(align = "right"),
     Offense = colDef(aggregate = "unique"),
     Year = colDef(aggregate = "unique", 
                   format = colFormat(separators = FALSE, 
                                      percent = FALSE, 
                                      digits = 0)),
     `Percent of Total Offenders` = colDef(aggregate = "mean", 
                                           minWidth = 75, 
                                           align = "center", 
                                           format = colFormat(separators = TRUE, 
                                                              percent = TRUE, 
                                                              digits = 2)),
     `Percent Known Race` = colDef(aggregate = "mean", 
                                   minWidth = 75, 
                                   align = "center", 
                                   format = colFormat(separators = TRUE, 
                                                      percent = TRUE, 
                                                      digits = 2))
   )
                     )

Arrests (NIBRS)

Importantly, these data do not distinguish between Hispanic and Non-Hispanic Whites.

SOURCES:

The files include the following crimes found in the above excel worksheets:

  • Assault Offenses - line 8
  • Homicide Offenses - line 9
  • Robbery - line 25
  • Sex Offenses line 12, labeled “Rape”

Setup

nibrs_arrest <- readr::read_csv("Data/NIBRS/nibrs-race-by-arrest.csv") %>% 
  tidyr::pivot_longer(-c(Offense, Year, Total), names_to = "Race", values_to = "arrests") %>% 
  dplyr::mutate(Offense = factor(Offense, levels = c("Murder", "Robbery", "Assault", "Rape")),
                Race = dplyr::recode(Race, "White" = "White/Hispanic"),
                Race = factor(Race, levels = c("White/Hispanic",  "Black", "Asian", 
                                               "Native American", "Hawaiian", "Unknown")))


nibrs_arrest_sum <- nibrs_arrest %>% 
  dplyr::select(-Total) %>% 
  #dplyr::filter(Offense != "Murder") %>%
  dplyr::group_by(Year) %>% 
  dplyr::mutate(
    Total = sum(arrests),
    `Total Known Race` = sum(arrests[which(Race != "Unknown")])
    ) %>% 
  dplyr::group_by(Year, Race) %>% 
  dplyr::summarise(
    Arrests = sum(arrests),
    Total = first(Total),
    Percent = Arrests/Total,
    `Arrests Known Race` = sum(arrests[which(Race != "Unknown")]),
    `Total Known Race` = first(`Total Known Race`),
    `Percent Known Race` = `Arrests Known Race`/`Total Known Race`
  )

All Violent Crimes

nibrs_arrest_sum %>% 
  dplyr::rename(`Offender Race` = Race) %>% 
  dplyr::arrange(Year,`Offender Race`) %>% 
  dplyr::select(`Offender Race`, Year,
                `Percent of Total Offenders` = `Percent`,  
                `Percent Known Race` ) %>%  

 reactable::reactable(., striped = TRUE, highlight = TRUE, 
                      bordered = TRUE, pagination = FALSE, 
                      compact = FALSE, resizable = FALSE, 
                       groupBy = c("Offender Race"),
     defaultColDef = colDef(
       #minWidth = 150, 
       style = list(fontSize = 12, 
                    fontFamily = "'Roboto Condensed', 
                    'Arial Narrow', Merriweather,  
                    Arial, Helvetica, sans-serif"),
       align = "left",
       format = list(cell = colFormat(separators = TRUE, 
                                      percent = TRUE, digits = 2)),
       headerStyle =  list(fontWeight = "bold", fontSize = 15),
       footerStyle =  list(fontWeight = "bold"))
                     
                     
     ,columns = list(
       #Weight = colDef(align = "right"),
       Year = colDef(aggregate = "unique", 
                     format = colFormat(separators = FALSE, 
                                        percent = FALSE, 
                                        digits = 0)),
       `Percent of Total Offenders` = colDef(aggregate = "mean", 
                                             minWidth = 75, 
                                             align = "center", 
                                             format = colFormat(separators = TRUE, 
                                                                percent = TRUE, 
                                                                digits = 2)),
       `Percent Known Race` = colDef(aggregate = "mean", 
                                     minWidth = 75,
                                     align = "center", 
                                     format = colFormat(separators = TRUE, 
                                                        percent = TRUE, 
                                                        digits = 2))
     )
                     )

By Crime Type

d_tbl <- nibrs_arrest %>% 
  dplyr::select(-Total) %>% 
  dplyr::group_by(Year, Offense) %>% 
 dplyr::mutate(
    Total = sum(arrests),
    `Total Known Race` = sum(arrests[which(Race != "Unknown")])
    ) %>% 
  dplyr::group_by(Year, Race, Offense) %>% 
  dplyr::summarise(
    Arrests = sum(arrests),
    Total = first(Total),
    Percent = Arrests/Total,
    `Arrests Known Race` = sum(arrests[which(Race != "Unknown")]),
    `Total Known Race` = first(`Total Known Race`),
    `Percent Known Race` = `Arrests Known Race`/`Total Known Race`, 
    .groups = "drop") 

d_tbl %>% 
  dplyr::rename(`Offender Race` = Race) %>% 
  dplyr::arrange(Offense, `Offender Race`, Year) %>% 
  dplyr::select(Offense, `Offender Race`, Year,
                `Percent of Total Offenders` = `Percent`,  
                `Percent Known Race` ) %>%  #%>% DT::datatable(.) %>% DT::formatPercentage(., columns = 4:5)

 reactable::reactable(., striped = TRUE, highlight = TRUE, 
                      bordered = TRUE, pagination = FALSE, 
                      compact = FALSE, resizable = FALSE, 
                       groupBy = c("Offender Race", "Offense"),
                     defaultColDef = colDef(
                       #minWidth = 150, 
                       style = list(fontSize = 12, fontFamily = "'Roboto Condensed', 
                                    'Arial Narrow', Merriweather,  Arial, Helvetica, sans-serif"),
                       align = "left",
                       format = list(cell = colFormat(separators = TRUE, percent = TRUE, digits = 2)),
                       headerStyle =  list(fontWeight = "bold", fontSize = 15),
                       footerStyle =  list(fontWeight = "bold"))
                     ,columns = list(
                       #Weight = colDef(align = "right"),
                       Offense = colDef(aggregate = "unique"),
                       Year = colDef(aggregate = "unique", 
                                     format = colFormat(separators = FALSE, 
                                                        percent = FALSE, 
                                                        digits = 0)),
                       `Percent of Total Offenders` = colDef(aggregate = "mean", 
                                                             minWidth = 75, 
                                                             align = "center", 
                                                             format = colFormat(separators = TRUE, 
                                                                                percent = TRUE, 
                                                                                digits = 2)),
                       `Percent Known Race` = colDef(aggregate = "mean", 
                                                     minWidth = 75, 
                                                     align = "center", 
                                                     format = colFormat(separators = TRUE, 
                                                                        percent = TRUE, 
                                                                        digits = 2))
                     )
                     )