Mobility Incident Analysis Report

Author

Natalio Ochoa (Data Scientist)

Published

August 20, 2025

1. Introduction and Objective

This report details the Natural Language Processing (NLP) analysis of atico.csv, a dataset containing textual descriptions of mobility incidents from emergency room records. The primary objective is to transform this unstructured text into actionable insights by identifying key risk factors and accident patterns involving pedestrians, cyclists, and micromobility users. Our methodology leverages the R ecosystem, primarily the tidyverse for data manipulation and visualization.

2. Data Loading and Preparation

First, we load the necessary R libraries and the dataset. The data is cleaned by selecting relevant columns, filtering out entries without descriptions, and standardizing column names.

Code
# STEP 1: LOAD LIBRARIES
# ------------------------------------
# We load the required libraries for the entire analysis.
library(tidyverse)
library(tidytext)
library(wordcloud)
library(RColorBrewer)
library(forcats)

# STEP 2: LOAD DATA
# ------------------------------------
# Using the specific file path provided.
file_path <- "/Users/nataliochoa/Documents/NLP/atico.csv"

# Using read.csv from base R as requested
accidents_df <- read.csv(file_path, stringsAsFactors = FALSE, fileEncoding = "UTF-8-BOM")

# STEP 3: PREPARE AND CLEAN DATA
# ------------------------------------
data_to_plot <- accidents_df %>%
  select(participant_role_detail, CollisionGroup, accident_detail) %>%
  filter(!is.na(accident_detail) & accident_detail != "") %>%
  mutate(
    participant_role_detail = str_replace_all(participant_role_detail, "_", " ") %>% str_to_title(),
    # Clean up CollisionGroup for better plotting
    CollisionGroup = if_else(is.na(CollisionGroup) | CollisionGroup == "Not collision", 
                             "No-Collision Incident", str_to_title(CollisionGroup))
  )

cat(paste("Successfully loaded and prepared", nrow(data_to_plot), "incident records for analysis.\n"))
Successfully loaded and prepared 1729 incident records for analysis.

3. Feature Engineering: Accident Severity Score

To quantify the severity of each incident, we developed a proxy score based on keywords found in the descriptions. Words associated with severe outcomes (e.g., “fracture”, “unconscious”) are assigned higher weights. This allows us to compare severity across different incident types.

Code
# Keyword dictionary with associated weights
severity_keywords <- c(
  "unconscious" = 10, "consciousness" = 10, "fracture" = 8, "head" = 7, 
  "blacked out" = 7, "fell hard" = 6, "flung" = 5, "thrown" = 5, 
  "flew" = 5, "rolled" = 4, "hit" = 3, "bruise" = 2, "scraped" = 1, 
  "scratched" = 1
)

# Function to calculate the severity score
calculate_severity_r <- function(text) {
  text_lower <- tolower(paste(text, collapse = " "))
  if (nchar(text_lower) == 0) return(0)
  score <- sum(sapply(names(severity_keywords), function(keyword) {
    str_count(text_lower, fixed(keyword)) * severity_keywords[[keyword]]
  }))
  return(score)
}

# Apply the function to create the new 'severity_score' column
data_to_plot <- data_to_plot %>%
  rowwise() %>%
  mutate(severity_score = calculate_severity_r(accident_detail)) %>%
  ungroup()

cat("Severity scores calculated. Displaying top 5 most severe incidents by score:\n")
Severity scores calculated. Displaying top 5 most severe incidents by score:
Code
data_to_plot %>%
  select(participant_role_detail, CollisionGroup, severity_score) %>%
  arrange(desc(severity_score)) %>%
  head(5) %>%
  knitr::kable(caption = "Top 5 Incidents by Severity Score")
Top 5 Incidents by Severity Score
participant_role_detail CollisionGroup severity_score
Electric Bicycle Car 41
Pedestrian No-Collision Incident 36
Electric Bicycle No-Collision Incident 35
Conventional Bicycle No-Collision Incident 35
Conventional Bicycle Car 34

4. Key Findings and Visualizations

The following visualizations highlight the key patterns discovered in the data.

4.1. Most Common Accident Scenarios

Collisions with cars and non-collision incidents (e.g., falls) are the two most frequent categories, underscoring where safety efforts should be focused.

Code
ggplot(data_to_plot, aes(y = fct_rev(fct_infreq(CollisionGroup)), fill = CollisionGroup)) +
  geom_bar(show.legend = FALSE) +
  geom_text(stat = 'count', aes(label = after_stat(count)), hjust = -0.3, size = 3.5) +
  labs(
    title = "Most Common Accident Scenarios",
    x = "Number of Incidents",
    y = "Primary Accident Cause"
  ) +
  theme_minimal(base_size = 12)
Figure 1: This bar chart displays the total count of incidents categorized by their primary cause, clearly showing the dominance of vehicle collisions and falls.

4.2. Severity by Participant Role

While all participants are at risk, pedestrians consistently experience incidents with the highest average severity scores, followed closely by cyclists.

Code
ggplot(data_to_plot, aes(x = severity_score, y = reorder(participant_role_detail, severity_score, FUN = median), fill = participant_role_detail)) +
  geom_boxplot(show.legend = FALSE, alpha = 0.8) +
  labs(
    title = "Accident Severity Score by Participant Role",
    x = "Severity Score (Keyword-based)",
    y = "Participant Role"
  ) +
  theme_minimal(base_size = 12)
Figure 2: This box plot visualizes the distribution of severity scores for each participant role, with the median represented by the central line.

4.3. Key Risk Factors from Textual Data

A word cloud of the most frequent terms in the incident descriptions highlights the core components of these events: intersections, sidewalks, left turns, and falls are dominant themes.

Code
data("stop_words")

words_for_cloud <- data_to_plot %>%
  unnest_tokens(word, accident_detail) %>%
  anti_join(stop_words, by = "word") %>%
  filter(!word %in% c("pt", "pt's", "st", "ave", "road")) %>%
  filter(!str_detect(word, "^[0-9]+$")) %>%
  count(word, sort = TRUE)

set.seed(123)
wordcloud(
  words = words_for_cloud$word,
  freq = words_for_cloud$n,
  min.freq = 20, max.words = 150,
  random.order = FALSE, rot.per = 0.35,
  colors = brewer.pal(8, "Dark2")
)
Figure 3: This word cloud illustrates the most frequent terms in accident descriptions. The larger the word, the more often it appeared.

5. Actionable Insights & Recommendations

Based on this analysis, we propose the following data-driven recommendations to improve urban mobility safety.

Table 1: Summary of data-driven recommendations for improving urban mobility safety.
Recommendation Supporting Evidence Expected Impact
1. Protect Intersections Collisions at intersections, especially involving left turns, are the most frequent and severe accident type (Fig 1 & 2). Significant reduction in severe injuries and fatalities for pedestrians and cyclists.
2. ‘Dooring’ Awareness Campaign A specific and frequent cluster of accidents involves cyclists colliding with the opening doors of parked cars. Reduction of a highly preventable accident type through simple driver education (e.g., the ‘Dutch Reach’).
3. Proactive Sidewalk Maintenance A large volume of incidents involves pedestrians tripping on uneven pavement, cracks, or tree roots (Fig. 1). Improved pedestrian safety and accessibility, reducing fall-related injuries, especially for vulnerable populations.
4. Micromobility Safety Education Many non-collision incidents for e-scooter and e-bike users are related to loss of control, sudden braking, and speed. Reduced single-participant accidents and promotion of safer operating practices for new mobility devices.