Importing Libraries and Data set

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.3     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.0
## ✔ ggplot2   3.4.3     ✔ tibble    3.2.1
## ✔ lubridate 1.9.2     ✔ tidyr     1.3.0
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(plotly)
## 
## Attaching package: 'plotly'
## 
## The following object is masked from 'package:ggplot2':
## 
##     last_plot
## 
## The following object is masked from 'package:stats':
## 
##     filter
## 
## The following object is masked from 'package:graphics':
## 
##     layout
library(dplyr)
library(ggplot2)
library(ggmap)
## ℹ Google's Terms of Service: <https://mapsplatform.google.com>
##   Stadia Maps' Terms of Service: <https://stadiamaps.com/terms-of-service/>
##   OpenStreetMap's Tile Usage Policy: <https://operations.osmfoundation.org/policies/tiles/>
## ℹ Please cite ggmap if you use it! Use `citation("ggmap")` for details.
## 
## Attaching package: 'ggmap'
## 
## 
## The following object is masked from 'package:plotly':
## 
##     wind
library(highcharter)
## Registered S3 method overwritten by 'quantmod':
##   method            from
##   as.zoo.data.frame zoo 
## Highcharts (www.highcharts.com) is a Highsoft software product which is
## not free for commercial and Governmental use
library(stringr)
library(datasets)
library(ggfortify)
library(htmltools)
library(GGally)
## Registered S3 method overwritten by 'GGally':
##   method from   
##   +.gg   ggplot2
library(leaflet)
library(DataExplorer)
setwd("/Users/nnokigbo/Downloads")
crashes <- read_csv("crashes.csv", locale = locale(encoding = "Latin1"))
## Warning: One or more parsing issues, call `problems()` on your data frame for details,
## e.g.:
##   dat <- vroom(...)
##   problems(dat)
## Rows: 172105 Columns: 43
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (38): Report Number, Agency Name, ACRS Report Type, Crash Date/Time, Rou...
## dbl  (5): Local Case Number, Speed Limit, Vehicle Year, Latitude, Longitude
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(crashes)
## # A tibble: 6 × 43
##   `Report Number` `Local Case Number` `Agency Name`           `ACRS Report Type`
##   <chr>                         <dbl> <chr>                   <chr>             
## 1 MCP3040003N               190026050 Montgomery County Poli… Property Damage C…
## 2 EJ78850038                230034791 Gaithersburg Police De… Property Damage C…
## 3 MCP2009002G               230034583 Montgomery County Poli… Property Damage C…
## 4 MCP3201004C               230035036 Montgomery County Poli… Property Damage C…
## 5 MCP23290028               230035152 Montgomery County Poli… Property Damage C…
## 6 MCP295200DV               230032956 Montgomery County Poli… Property Damage C…
## # ℹ 39 more variables: `Crash Date/Time` <chr>, `Route Type` <chr>,
## #   `Road Name` <chr>, `Cross-Street Type` <chr>, `Cross-Street Name` <chr>,
## #   `Off-Road Description` <chr>, Municipality <chr>,
## #   `Related Non-Motorist` <chr>, `Collision Type` <chr>, Weather <chr>,
## #   `Surface Condition` <chr>, Light <chr>, `Traffic Control` <chr>,
## #   `Driver Substance Abuse` <chr>, `Non-Motorist Substance Abuse` <chr>,
## #   `Person ID` <chr>, `Driver At Fault` <chr>, `Injury Severity` <chr>, …

Tidying the Dataset

So off first glance, I notice that some things are not in the format I want them to be so let’s clean some of the data.

First I will replace the spaces in column names to underscores to make them easier to work with.

colnames(crashes) <- gsub(" ", "_", colnames(crashes))

Next I will get rid of columns that have too many NA’s to be useful. I will also get rid of columns that will not benefit me.

drop = c("Circumstance", "Off-Road_Description", "Related_Non-Motorist", "Non-Motorist_Substance_Abuse", "Report_Number", "Local_Case_Number", "Person_ID", "Vehicle_ID", "Vehicle_First_Impact_Location", "Vehicle_Second_Impact_Location")

crashes = crashes[,!(names(crashes) %in% drop)]

colnames(crashes)
##  [1] "Agency_Name"            "ACRS_Report_Type"       "Crash_Date/Time"       
##  [4] "Route_Type"             "Road_Name"              "Cross-Street_Type"     
##  [7] "Cross-Street_Name"      "Municipality"           "Collision_Type"        
## [10] "Weather"                "Surface_Condition"      "Light"                 
## [13] "Traffic_Control"        "Driver_Substance_Abuse" "Driver_At_Fault"       
## [16] "Injury_Severity"        "Driver_Distracted_By"   "Drivers_License_State" 
## [19] "Vehicle_Damage_Extent"  "Vehicle_Body_Type"      "Vehicle_Movement"      
## [22] "Vehicle_Continuing_Dir" "Vehicle_Going_Dir"      "Speed_Limit"           
## [25] "Driverless_Vehicle"     "Parked_Vehicle"         "Vehicle_Year"          
## [28] "Vehicle_Make"           "Vehicle_Model"          "Equipment_Problems"    
## [31] "Latitude"               "Longitude"              "Location"

Questions I Aim to Answer

Some questions I aim to answer throughout this project are:

What are drivers most distracted by?

Which vehicle make has experienced the most crashes in this data set?

Which vehicle make produces the highest average speed limit on the road.

How is the location of the crashes mapped out?


Distracted Drivers

I am curious what distractions truly cause cars to crash.

crashes |>
  group_by(Driver_Distracted_By) |>
  count(Driver_Distracted_By) |> 
  arrange(desc(n))
## # A tibble: 19 × 2
## # Groups:   Driver_Distracted_By [19]
##    Driver_Distracted_By                                   n
##    <chr>                                              <int>
##  1 NOT DISTRACTED                                    106709
##  2 UNKNOWN                                            32804
##  3 LOOKED BUT DID NOT SEE                             21544
##  4 INATTENTIVE OR LOST IN THOUGHT                      4319
##  5 OTHER DISTRACTION                                   3245
##  6 DISTRACTED BY OUTSIDE PERSON OBJECT OR EVENT         970
##  7 BY OTHER OCCUPANTS                                   418
##  8 OTHER CELLULAR PHONE RELATED                         372
##  9 OTHER ELECTRONIC DEVICE (NAVIGATIONAL PALM PILOT)    325
## 10 TALKING OR LISTENING TO CELLULAR PHONE               282
## 11 NO DRIVER PRESENT                                    280
## 12 BY MOVING OBJECT IN VEHICLE                          212
## 13 EATING OR DRINKING                                   196
## 14 ADJUSTING AUDIO AND OR CLIMATE CONTROLS              134
## 15 USING OTHER DEVICE CONTROLS INTEGRAL TO VEHICLE       92
## 16 TEXTING FROM A CELLULAR PHONE                         66
## 17 USING DEVICE OBJECT BROUGHT INTO VEHICLE              63
## 18 DIALING CELLULAR PHONE                                48
## 19 SMOKING RELATED                                       26

This is interesting. The most prevalent category is “Not Distracted”. This could be inaccurate because people are more like to lie when it could save them from being in trouble. However, for this data set, I want to look at the ones that were actually distracted and I want to take out the ones that don’t have many instances.

crashes |>
  group_by(Driver_Distracted_By) |>
  count(Driver_Distracted_By) |> 
  arrange(desc(n)) |>
  filter(n > 250) |>
  filter(!Driver_Distracted_By == "NOT DISTRACTED") |>
  filter(!Driver_Distracted_By == "UNKNOWN") |>
    filter(!Driver_Distracted_By == "BY MOVING OJECT IN VEHICLE") |>
  filter(!Driver_Distracted_By == "EATING OR DRINKING") |>
  filter(!Driver_Distracted_By == "ADJUSTING AUDIO AND OR CLIMATE CONTROLS") |>
  filter(!Driver_Distracted_By == "USING OTHER DEVICE CONTROLS INTEGRAL TO VEHICLE") |>
    filter(!Driver_Distracted_By == "TEXTING FROM A CELLULAR PHONE") |>
  filter(!Driver_Distracted_By == "USING DEVICE OBJECT BROUGHT INTO VEHICLE") |>
    filter(!Driver_Distracted_By == "DIALING CELLULAR PHONE") |>
  filter(!Driver_Distracted_By == "SMOKING RELATED")
## # A tibble: 9 × 2
## # Groups:   Driver_Distracted_By [9]
##   Driver_Distracted_By                                  n
##   <chr>                                             <int>
## 1 LOOKED BUT DID NOT SEE                            21544
## 2 INATTENTIVE OR LOST IN THOUGHT                     4319
## 3 OTHER DISTRACTION                                  3245
## 4 DISTRACTED BY OUTSIDE PERSON OBJECT OR EVENT        970
## 5 BY OTHER OCCUPANTS                                  418
## 6 OTHER CELLULAR PHONE RELATED                        372
## 7 OTHER ELECTRONIC DEVICE (NAVIGATIONAL PALM PILOT)   325
## 8 TALKING OR LISTENING TO CELLULAR PHONE              282
## 9 NO DRIVER PRESENT                                   280
crashes_bar <- crashes |>
  filter(!Driver_Distracted_By == "NOT DISTRACTED") |>
  filter(!Driver_Distracted_By == "UNKNOWN")|>
filter(!Driver_Distracted_By == "BY MOVING OJECT IN VEHICLE") |>
  filter(!Driver_Distracted_By == "EATING OR DRINKING") |>
  filter(!Driver_Distracted_By == "ADJUSTING AUDIO AND OR CLIMATE CONTROLS") |>
  filter(!Driver_Distracted_By == "USING OTHER DEVICE CONTROLS INTEGRAL TO VEHICLE") |>
    filter(!Driver_Distracted_By == "TEXTING FROM A CELLULAR PHONE") |>
  filter(!Driver_Distracted_By == "USING DEVICE OBJECT BROUGHT INTO VEHICLE") |>
    filter(!Driver_Distracted_By == "DIALING CELLULAR PHONE") |>
  filter(!Driver_Distracted_By == "SMOKING RELATED")
ggplot(crashes_bar, aes(y = Driver_Distracted_By)) +
  geom_bar() +
  labs(
    x = "Counts",
    y = "Distraction"
  )

Vehicle Make

So I want to know which vehicle make produces the most amount of crashes in this data set. First, the Vehicle Make column is a bit dirty so I want to create a more tidy set of groups for them.

crashesmod <- crashes |>
  relocate(Vehicle_Make) |>
  mutate(Vehicle_Make = case_when((grepl("TOY", crashes$Vehicle_Make) == TRUE) ~ "Toyota",
                               (grepl("NISS", crashes$Vehicle_Make) == TRUE) ~ "Nissan",
                               (grepl("UNKN", crashes$Vehicle_Make) == TRUE) ~ "Unknown",
                            (grepl("SUB", crashes$Vehicle_Make) == TRUE) ~ "Subaru",
                             (grepl("VOLK", crashes$Vehicle_Make) == TRUE) ~ "Volkswagon",
                            (grepl("YAM", crashes$Vehicle_Make) == TRUE) ~ "Yamaha",
                            (grepl("CHR", crashes$Vehicle_Make) == TRUE) ~ "Chrysler",
                            (grepl("GIL", crashes$Vehicle_Make) == TRUE) ~ "Gill",
                            (grepl("HON", crashes$Vehicle_Make) == TRUE) ~ "Honda",
                             (grepl("LEX", crashes$Vehicle_Make) == TRUE) ~ "Lexus",
                            (grepl("CHEV", crashes$Vehicle_Make) == TRUE) ~ "Chevrolet",
                             (grepl("FORD", crashes$Vehicle_Make) == TRUE) ~ "Ford",
                            (grepl("FREI", crashes$Vehicle_Make) == TRUE) ~ "Freight",
                            (grepl("HYUN", crashes$Vehicle_Make) == TRUE) ~ "Hyundai",
                            (grepl("MAZ", crashes$Vehicle_Make) == TRUE) ~ "Mazda",
                            (grepl("HARL", crashes$Vehicle_Make) == TRUE) ~ "Harley Davidson",
                            (grepl("DODG", crashes$Vehicle_Make) == TRUE) ~ "Dodge",
                            (grepl("RAM", crashes$Vehicle_Make) == TRUE) ~ "Ram",
                            (grepl("INFI", crashes$Vehicle_Make) == TRUE) ~ "Infinity",
                            (grepl("ACUR", crashes$Vehicle_Make) == TRUE) ~ "Acura",
                            (grepl("MER", crashes$Vehicle_Make) == TRUE) ~ "Mercedez",
                            (grepl("BM", crashes$Vehicle_Make) == TRUE) ~ "BMW",
                            (grepl("GM", crashes$Vehicle_Make) == TRUE) ~ "GMC",
                            (grepl("VOLVO", crashes$Vehicle_Make) == TRUE) ~ "Volvo",
                            (grepl("TESLA", crashes$Vehicle_Make) == TRUE) ~ "Tesla",
                            (grepl("AUD", crashes$Vehicle_Make) == TRUE) ~ "Audi",
                            (grepl("CORV", crashes$Vehicle_Make) == TRUE) ~ "Corvette",
                            (grepl("CAD", crashes$Vehicle_Make) == TRUE) ~ "Cadillac",
                            (grepl("JAG", crashes$Vehicle_Make) == TRUE) ~ "Jaguar",
                            (grepl("PETER", crashes$Vehicle_Make) == TRUE) ~ "Peterbuilt",
                            (grepl("JEEP", crashes$Vehicle_Make) == TRUE) ~ "Jeep",
                            (grepl("KEN", crashes$Vehicle_Make) == TRUE) ~ "Kenworth",
                            (grepl("HINO", crashes$Vehicle_Make) == TRUE) ~ "Hino",
                             (grepl("KIA", crashes$Vehicle_Make) == TRUE) ~ "Kia"))
unique(crashesmod$Vehicle_Make)
##  [1] "Honda"           "GMC"             "Ford"            "Kia"            
##  [5] "Toyota"          "Gill"            "Subaru"          "BMW"            
##  [9] "Hyundai"         "Volkswagon"      "Dodge"           "Nissan"         
## [13] "Lexus"           "Yamaha"          "Unknown"         "Infinity"       
## [17] "Mazda"           "Acura"           "Freight"         "Chevrolet"      
## [21] "Harley Davidson" "Volvo"           "Chrysler"        "Mercedez"       
## [25] "Peterbuilt"      "Jaguar"          "Kenworth"        "Jeep"           
## [29] "Hino"            NA                "Ram"             "Cadillac"       
## [33] "Tesla"           "Audi"            "Corvette"

Now that I’ve tidied the column, let’s see which

crashesmod |>
  group_by(Vehicle_Make) |>
  count(Vehicle_Make) |>
  arrange(desc(n))|>
  filter(!Vehicle_Make == "NA")
## # A tibble: 34 × 2
## # Groups:   Vehicle_Make [34]
##    Vehicle_Make     n
##    <chr>        <int>
##  1 Toyota       32745
##  2 Honda        24676
##  3 Ford         17154
##  4 Nissan       11355
##  5 Chevrolet    10927
##  6 Hyundai       6294
##  7 Dodge         5365
##  8 Mercedez      4253
##  9 Subaru        3821
## 10 Lexus         3806
## # ℹ 24 more rows

To answer my question of which vehicle make has experienced the most crashes in this data set, the answer is Toyota. This is very interesting because Toyota are usually very reliable vehicle that last a long time. I doubt the answer is because of the make of the actual car. I believe this is the answer because a lot of people have Toyotas so it would make sense that they crash the most. I would like to have to data to see how this information owuld present if it were per-capita

Average Speed Limit on Road of Crash Based on Make

Now that we have already cleaned the vehicle make column, let’s create a treemap to see the average speed limit on road of crash based on make.

speedlimitplot <- crashesmod |>
  group_by(Vehicle_Make) |>
  summarise(avg = mean(Speed_Limit, na.rm = TRUE)) |>
  filter(!Vehicle_Make == "NA") |>
hchart(type = "treemap", hcaes(x = Vehicle_Make, value = avg, color = avg, colorByPoint = TRUE)) |>
  hc_colorAxis(
    maxColor = "#DB7CBD",
    minColor = "#FFFFFF") |>
  hc_add_theme(hc_theme_bloom()) |>
  hc_title(text = "Average Speed Limit on Road of Crash based on Car Model") |>
  hc_subtitle(text = "Shaded by Mean Speed Limit on Road of Crash") |>
  hc_caption(text = "Source: Maryland Car Crashes Dataset")

speedlimitplot

It seems that the vehicle make with the highest average speed limit that the crash occured on is Harley Davidson. This information could be incorrect universally though. Most of the cars with high average speed limits are cars that are not very common. More common cars would have more accurate data because they have more data. This is a good thing to keep in mind.

Mapping Crashes

popupcrashes <- paste0(
  "<b>Date/Time:  </b>", crashes$`Crash_Date/Time`, "<br>",
  "<b>Type:  </b>", crashes$ACRS_Report_Type, "<br>",
  "<b>Conditions:  </b>", crashes$Surface_Condition, "<br>",
  "<b>Type:  </b>", crashes$Road_Name, "<br>"
)
leaflet() |>
  setView(lng = -76.951315, lat = 39.0838, zoom = 13) |>
  addProviderTiles("Esri.WorldStreetMap") |>
  addCircles(
  data = crashes,
  radius = 25,
  color = "#715145",
  fillOpacity = 1,
  popup = popupcrashes
  )
## Assuming "Longitude" and "Latitude" are longitude and latitude, respectively