hyzhang00's picture
Update app.py
15ca59b verified
raw
history blame
25.8 kB
import streamlit as st
import pandas as pd
import plotly.express as px
import altair as alt
import folium
from folium.plugins import HeatMap, MarkerCluster
from streamlit_folium import st_folium
@st.cache_data
def load_and_preprocess_data(file_path):
# Read the data
df = pd.read_csv(file_path)
# Basic preprocessing
df = df.drop(['X', 'Y'], axis=1)
df.dropna(subset=['Incidentid', 'DateTime', 'Year', 'Latitude', 'Longitude'], inplace=True)
# Convert Year to int
df['Year'] = df['Year'].astype(int)
# Fill missing values
numeric = ['Age_Drv1', 'Age_Drv2']
for col in numeric:
df[col].fillna(df[col].median(), inplace=True)
categorical = ['Gender_Drv1', 'Violation1_Drv1', 'AlcoholUse_Drv1', 'DrugUse_Drv1',
'Gender_Drv2', 'Violation1_Drv2', 'AlcoholUse_Drv2', 'DrugUse_Drv2',
'Unittype_Two', 'Traveldirection_Two', 'Unitaction_Two', 'CrossStreet']
for col in categorical:
df[col].fillna('Unknown', inplace=True)
# Remove invalid ages
df = df[
(df['Age_Drv1'] <= 90) &
(df['Age_Drv2'] <= 90) &
(df['Age_Drv1'] >= 16) &
(df['Age_Drv2'] >= 16)
]
# Create age groups
bins = [15, 25, 35, 45, 55, 65, 90]
labels = ['16-25', '26-35', '36-45', '46-55', '56-65', '65+']
df['Age_Group_Drv1'] = pd.cut(df['Age_Drv1'], bins=bins, labels=labels)
df['Age_Group_Drv2'] = pd.cut(df['Age_Drv2'], bins=bins, labels=labels)
return df
def create_severity_violation_chart(df, age_group=None):
# Apply age group filter if selected
if age_group != 'All Ages':
df = df[(df['Age_Group_Drv1'] == age_group) | (df['Age_Group_Drv2'] == age_group)]
# Combine violations from both drivers
violations_1 = df.groupby(['Violation1_Drv1', 'Injuryseverity']).size().reset_index(name='count')
violations_2 = df.groupby(['Violation1_Drv2', 'Injuryseverity']).size().reset_index(name='count')
violations_1.columns = ['Violation', 'Severity', 'count']
violations_2.columns = ['Violation', 'Severity', 'count']
violations = pd.concat([violations_1, violations_2])
violations = violations.groupby(['Violation', 'Severity'])['count'].sum().reset_index()
# Create visualization
fig = px.bar(
violations,
x='Violation',
y='count',
color='Severity',
title=f'Crash Severity Distribution by Violation Type - {age_group}',
labels={'count': 'Number of Incidents', 'Violation': 'Violation Type'},
height=600
)
fig.update_layout(
xaxis_tickangle=-45,
legend_title='Severity Level',
barmode='stack'
)
return fig
def get_top_violations(df, age_group):
if age_group == 'All Ages':
violations = pd.concat([
df['Violation1_Drv1'].value_counts(),
df['Violation1_Drv2'].value_counts()
]).groupby(level=0).sum()
else:
filtered_df = df[
(df['Age_Group_Drv1'] == age_group) |
(df['Age_Group_Drv2'] == age_group)
]
violations = pd.concat([
filtered_df['Violation1_Drv1'].value_counts(),
filtered_df['Violation1_Drv2'].value_counts()
]).groupby(level=0).sum()
# Convert to DataFrame and format
violations_df = violations.reset_index()
violations_df.columns = ['Violation Type', 'Count']
violations_df['Percentage'] = (violations_df['Count'] / violations_df['Count'].sum() * 100).round(2)
violations_df['Percentage'] = violations_df['Percentage'].map('{:.2f}%'.format)
return violations_df.head()
@st.cache_data
def create_map(df, selected_year):
filtered_df = df[df['Year'] == selected_year]
m = folium.Map(
location=[33.4255, -111.9400],
zoom_start=12,
control_scale=True,
tiles='CartoDB positron'
)
marker_cluster = MarkerCluster().add_to(m)
for _, row in filtered_df.iterrows():
folium.Marker(
location=[row['Latitude'], row['Longitude']],
popup=f"Accident at {row['Longitude']}, {row['Latitude']}<br>Date: {row['DateTime']}<br>Severity: {row['Injuryseverity']}",
icon=folium.Icon(color='red')
).add_to(marker_cluster)
heat_data = filtered_df[['Latitude', 'Longitude']].values.tolist()
HeatMap(heat_data, radius=15, max_zoom=13, min_opacity=0.3).add_to(m)
return m
def create_injuries_fatalities_chart(crash_data, unit_type):
# 5th visualization title
st.header("5. Total Injuries and Fatalities by Month")
# Filter rows where we have valid data for all necessary columns
crash_data = crash_data[['DateTime', 'Totalinjuries', 'Totalfatalities', 'Unittype_One', 'Unittype_Two']].dropna()
# Convert "DateTime" to datetime type
crash_data['DateTime'] = pd.to_datetime(crash_data['DateTime'], errors='coerce')
crash_data['Month'] = crash_data['DateTime'].dt.month_name()
# sort months in order
month_order = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
crash_data['Month'] = pd.Categorical(crash_data['Month'], categories=month_order, ordered=True)
# Dropdown for Unit Type selection
# Dropdown for Unit Type selection
# st.sidebar.selectbox("Select Unit Type", options=['Total'] + crash_data['Unittype_One'].dropna().unique().tolist()) # previous location of dropdown in sidebar
# unit_type = st.selectbox("Select Unit Type", options=['Total'] + crash_data['Unittype_One'].dropna().unique().tolist())
# unit_type_pairs = set()
# for _, row in crash_data[['Unittype_One', 'Unittype_Two']].dropna().iterrows():
# if row['Unittype_One'] != 'Driverless' or row['Unittype_Two'] != 'Driverless':
# pair = ' vs '.join(sorted([row['Unittype_One'], row['Unittype_Two']]))
# unit_type_pairs.add(pair)
# # unit_type_pairs = list(unit_type_pairs) # modified as below to sort the dropdown options in alphabetical order
# unit_type_pairs = sorted(list(unit_type_pairs))
# unit_type = st.selectbox("Select Unit Type Pair", options=['Total'] + unit_type_pairs)
# Filter data based on the selected unit type
if unit_type == 'Total':
filtered_data = crash_data
else:
unit_one, unit_two = unit_type.split(' vs ')
filtered_data = crash_data[((crash_data['Unittype_One'] == unit_one) & (crash_data['Unittype_Two'] == unit_two)) |
((crash_data['Unittype_One'] == unit_two) & (crash_data['Unittype_Two'] == unit_one))]
# Group data by month and calculate total injuries and fatalities
monthly_sum = filtered_data.groupby('Month').agg({'Totalinjuries': 'sum', 'Totalfatalities': 'sum'}).reset_index()
# Reshape the data for easier plotting
injuries = monthly_sum[['Month', 'Totalinjuries']].rename(columns={'Totalinjuries': 'Value'})
injuries['Measure'] = 'Total Injuries'
fatalities = monthly_sum[['Month', 'Totalfatalities']].rename(columns={'Totalfatalities': 'Value'})
fatalities['Measure'] = 'Total Fatalities'
combined_data = pd.concat([injuries, fatalities])
# Originally tried to use bar chart but switched to line chart for better trend visualization
# alt.Chart(monthly_sum).mark_bar().encode(
# x=alt.X('Month', sort=month_order, title='Month'),
# y=alt.Y('Totalinjuries', title='Total Injuries', axis=alt.Axis(titleColor='blue', labelColor='blue', tickColor='blue')),
# color=alt.value('blue'),
# tooltip=['Month', 'Totalinjuries']
# ).properties(
# title='Total Injuries and Fatalities by Month',
# width=300,
# height=300
# ) + alt.Chart(monthly_sum).mark_bar().encode(
# x=alt.X('Month', sort=month_order, title='Month'),
# y=alt.Y('Totalfatalities', title='Total Fatalities', axis=alt.Axis(titleColor='red', labelColor='red', tickColor='red')),
# color=alt.value('red'),
# tooltip=['Month', 'Totalfatalities']
# )
# Tried to figure out how to plot a legend using altair
# line_chart = alt.Chart(monthly_sum).mark_line(point=True).encode(
# x=alt.X('Month', sort=month_order, title='Month'),
# y=alt.Y('Totalinjuries', title='Total Injuries & Fatalities', axis=alt.Axis(titleColor='black')),
# color=alt.value('blue'),
# tooltip=['Month', 'Totalinjuries']
# ).properties(
# title=f'Total Injuries and Fatalities by Month for Unit Type Pair: {unit_type}',
# width=600,
# height=400
# ) + alt.Chart(monthly_sum).mark_line(point=True).encode(
# x=alt.X('Month', sort=month_order, title='Month'),
# y=alt.Y('Totalfatalities', axis=alt.Axis(titleColor='red')),
# color=alt.value('red'),
# tooltip=['Month', 'Totalfatalities']
# ).configure_legend(
# titleFontSize=14,
# labelFontSize=12,
# titleColor='black',
# labelColor='black'
# )
# Plot line chart
line_chart = alt.Chart(combined_data).mark_line(point=True).encode(
x=alt.X('Month:N', sort=month_order, title='Month'),
y=alt.Y('Value:Q', title='Total Injuries & Fatalities'),
color=alt.Color('Measure:N', title='', scale=alt.Scale(domain=['Total Injuries', 'Total Fatalities'], range=['blue', 'red'])),
tooltip=['Month', 'Measure:N', 'Value:Q']
).properties(
title=f'Total Injuries and Fatalities by Month for Unit Type Pair: {unit_type}',
width=600,
height=400
)
# # Combine the charts (trying to make legend)
# combined_chart = alt.layer(line_chart_injuries, line_chart_fatalities).properties(
# title=f'Total Injuries and Fatalities by Month for Unit Type Pair: {unit_type}',
# width=600,
# height=400
# ).configure_legend(
# titleFontSize=14,
# labelFontSize=12,
# titleColor='black',
# labelColor='black'
# )
return line_chart
def create_crash_trend_chart(df, weather=None):
if weather and weather != 'All Conditions':
df = df[df['Weather'] == weather]
# Group data by year and count unique Incident IDs
trend_data = df.groupby('Year')['Incidentid'].nunique().reset_index()
trend_data.columns = ['Year', 'Crash Count']
# Create line graph
fig = px.line(
trend_data,
x='Year',
y='Crash Count',
title=f'Crash Trend Over Time ({weather})',
labels={'Year': 'Year', 'Crash Count': 'Number of Unique Crashes'},
markers=True,
height=600
)
fig.update_traces(line=dict(width=2), marker=dict(size=8))
fig.update_layout(legend_title_text='Trend')
return fig
def create_category_distribution_chart(df, selected_category, selected_year):
# Filter by selected year
if selected_year != 'All Years':
df = df[df['Year'] == int(selected_year)]
# Group by selected category and Injury Severity
grouped_data = df.groupby([selected_category, 'Injuryseverity']).size().reset_index(name='Count')
# Calculate percentages for each category value
total_counts = grouped_data.groupby(selected_category)['Count'].transform('sum')
grouped_data['Percentage'] = (grouped_data['Count'] / total_counts * 100).round(2)
# Create the stacked bar chart using Plotly
fig = px.bar(
grouped_data,
x=selected_category,
y='Count',
color='Injuryseverity',
text='Percentage',
title=f'Distribution of Incidents by {selected_category} ({selected_year})',
labels={'Count': 'Number of Incidents', selected_category: 'Category'},
height=600,
)
# Customize the chart appearance
fig.update_traces(texttemplate='%{text}%', textposition='inside')
fig.update_layout(
barmode='stack',
xaxis_tickangle=-45,
legend_title='Injury Severity',
margin=dict(t=50, b=100),
)
return fig
def main():
st.set_page_config(page_title="Terrific Tempe Traffic", layout="wide")
st.markdown("""
<style>
.reportview-container {
font-size: 20px;
}
h1, h2, h3, h4, h5, h6 {
font-size: 150%;
}
p {
font-size: 125%;
}
</style>
""", unsafe_allow_html=True)
st.markdown("""
<style>
.title {
text-align: center;
padding: 25px;
}
</style>
""", unsafe_allow_html=True)
st.markdown("<div class='title'><h1> Accident Analysis for City of Tempe,Arizona </h1></div>", unsafe_allow_html=True)
st.markdown("""
**Team Members:**
- Janhavi Tushar Zarapkar ([email protected]
- Hangyue Zhang ([email protected])
- Andrew Nam ([email protected])
- Nirmal Attarde ([email protected])
- Maanas Sandeep Agrawa ([email protected])
""")
st.markdown("""
### Introduction to the Traffic Accident Dataset
This dataset contains detailed information about traffic accidents in the city of **Tempe**. It includes various attributes of the accidents, such as the severity of injuries, the demographics of the drivers involved, the locations of the incidents, and the conditions at the time of the accidents. The dataset covers accidents that occurred over several years, with data on factors like **weather conditions**, **road surface conditions**, the **time of day**, and the type of **violations** (e.g., alcohol or drug use) that may have contributed to the accident.
The data was sourced from **Tempe City's traffic incident reports** and provides a comprehensive view of the factors influencing road safety and accident severity in the city. By analyzing this dataset, we can gain insights into the key contributors to traffic incidents and uncover trends that could help improve traffic safety measures, urban planning, and law enforcement policies in the city.
""")
# Load data
df = load_and_preprocess_data('1.08_Crash_Data_Report_(detail).csv')
if 'Weather' not in df.columns:
df['Weather'] = 'Unknown'
# Create tabs for different visualizations
tab1, tab2, tab3, tab4, tab5 = st.tabs(["Crash Statistics", "Crash Map", "Crash Trend", "Crash Injuries/Fatalities","Distribution by Category"])
with tab1:
# Age group selection
age_groups = ['All Ages', '16-25', '26-35', '36-45', '46-55', '56-65', '65+']
selected_age = st.selectbox('Select Age Group:', age_groups)
# Create and display chart
fig = create_severity_violation_chart(df, selected_age)
st.plotly_chart(fig, use_container_width=True)
# Display statistics
if selected_age == 'All Ages':
total_incidents = len(df)
else:
total_incidents = len(df[
(df['Age_Group_Drv1'] == selected_age) |
(df['Age_Group_Drv2'] == selected_age)
])
# Create two columns for statistics
col1, col2 = st.columns(2)
with col1:
st.markdown(f"### Total Incidents")
st.markdown(f"**{total_incidents:,}** incidents for {selected_age}")
with col2:
st.markdown("### Top Violations")
top_violations = get_top_violations(df, selected_age)
st.table(top_violations)
with tab2:
# Year selection for map
years = sorted(df['Year'].unique())
selected_year = st.selectbox('Select Year:', years)
map_col, desc_col = st.columns([7, 3])
with map_col:
# Create and display map
st.markdown("### Crash Location Map")
map_placeholder = st.empty()
with map_placeholder:
m = create_map(df, selected_year)
map_data = st_folium(
m,
width=None,
height=800,
key=f"map_{selected_year}",
returned_objects=["null_drawing"]
)
with desc_col:
st.markdown("""
### Traffic Crash Location Map
This interactive map visualizes traffic accidents in Tempe for the selected year. It combines **marker clustering** and a **heatmap** to show:
1. **Accident Markers**: Red markers indicate individual accidents, with popups displaying the coordinates, date/time, and severity of each incident.
2. **Heatmap**: The heatmap highlights accident hotspots with colors ranging from blue (low frequency) to yellow (moderate) and red (high frequency), showing areas with more frequent accidents.
**Key Features:**
* **Interactive Year Selection**: Users can select a year to view accidents for that specific time.
* **Accident Patterns**: The map reveals accident-prone areas and severity patterns, helping identify dangerous locations.
**Color Scheme:**
* **Red**: Individual accident markers.
* **Blue to Red**: Heatmap colors indicate accident frequency, from low (blue) to high (red).
This map provides insights into accident trends and can help guide safety improvements in the city.
""")
with tab3:
# Weather condition filter
weather = ['All Conditions'] + sorted(df['Weather'].unique())
selected_weather = st.selectbox('Select Weather Condition:', weather)
# Create and display line graph
trend_col, desc_col = st.columns([7, 3])
with trend_col:
st.markdown("### Crash Trend Over Time")
trend_fig = create_crash_trend_chart(df, selected_weather)
# Update the figure layout for larger size
trend_fig.update_layout(
height=800, # Increased height
width=None, # Let width be responsive
margin=dict(l=50, r=50, t=50, b=50)
)
st.plotly_chart(trend_fig, use_container_width=True)
with desc_col:
st.markdown("""
## **Crash Trend Over Time**
This interactive line chart visualizes the trend of unique traffic crashes over the years, optionally filtered by weather conditions. It highlights how crash frequency changes over time, helping identify trends and potential contributing factors.
**Key Features:**
* **Time Trend Analysis**: Displays the total number of unique crashes for each year, showing long-term patterns.
* **Weather Filter**: Users can filter the data by weather conditions (e.g., "Rainy", "Sunny") to analyze how weather impacts crash trends.
* **Interactive Tooltips**: Hovering over data points reveals the exact crash count for each year, providing detailed insights.
**Color Scheme and Design:**
* **Line and Markers**: A smooth line connects data points, with prominent markers for each year to highlight trends clearly.
* **Dynamic Title**: The chart updates its title to reflect the selected weather condition or "All Conditions" for the overall trend.
**Insights:**
This chart helps uncover:
* Annual fluctuations in crash incidents.
* Correlations between weather conditions and crash frequencies.
* Historical patterns that can guide future safety measures and urban planning decisions
""")
with tab4:
# Dropdown for Unit Type selection
unit_type_pairs = set()
for _, row in df[['Unittype_One', 'Unittype_Two']].dropna().iterrows():
if row['Unittype_One'] != 'Driverless' or row['Unittype_Two'] != 'Driverless':
pair = ' vs '.join(sorted([row['Unittype_One'], row['Unittype_Two']]))
unit_type_pairs.add(pair)
unit_type_pairs = sorted(list(unit_type_pairs))
unit_type = st.selectbox("Select Unit Type Pair", options=['Total'] + unit_type_pairs)
# # Create 5th Visualization: Injuries and fatalities chart
# injuries_fatalities_chart = create_injuries_fatalities_chart(df, unit_type)
# st.altair_chart(injuries_fatalities_chart, use_container_width=True)
# st.markdown("""
# This line chart shows the **total number of injuries and fatalities by month for the selected unit type pair**. The blue line represents total injuries, while the red line represents total fatalities. Observing the trends over the months can help identify any seasonal patterns or peaks in traffic incidents involving specific unit types.
# - **Total Injuries**: The blue line indicates how injuries vary over different months, highlighting any particular spikes or declines.
# - **Total Fatalities**: The red line shows the trend for fatalities, which is generally much lower compared to injuries.
# - **Unit Types**: The dropdown selection allows users to filter the data by specific unit type pairs (e.g., Driver vs Pedestrian) or view the overall trend across all types.
# This visualization aims to provide an intuitive understanding of how injuries and fatalities are distributed across the year, helping stakeholders develop targeted safety measures.
# """)
chart_col, desc_col = st.columns([7, 3])
with chart_col:
# Create 5th Visualization: Injuries and fatalities chart
injuries_fatalities_chart = create_injuries_fatalities_chart(df, unit_type)
injuries_fatalities_chart = injuries_fatalities_chart.properties(
height=800 # Make the chart taller to match the description column
)
st.altair_chart(injuries_fatalities_chart, use_container_width=True)
with desc_col:
st.markdown("""
## Injuries and Fatalities Trends
This line chart shows the **total number of injuries and fatalities by month for the selected unit type pair**. The visualization helps identify seasonal patterns and critical trends in traffic incidents involving specific unit types.
**Key Features:**
* **Injuries Trend** (Blue Line)
- Tracks monthly injury counts
- Shows seasonal variations
- Identifies peak incident periods
* **Fatalities Trend** (Red Line)
- Monitors monthly fatality counts
- Generally lower than injuries
- Highlights critical safety concerns
* **Interactive Selection**
- Filter by specific unit type pairs
- Compare different vehicle combinations
- View overall trends across all types
**Applications:**
- Identify high-risk months
- Guide seasonal safety measures
- Inform emergency response planning
- Support targeted intervention strategies
This visualization aids stakeholders in developing effective safety measures and resource allocation strategies throughout the year.
""")
with tab5:
# Dropdown for category selection
categories = [
'Collisionmanner',
'Lightcondition',
'Weather',
'SurfaceCondition',
'AlcoholUse_Drv1',
'Gender_Drv1',
]
selected_category = st.selectbox("Select Category:", categories)
# Dropdown for year selection
years = ['All Years'] + sorted(df['Year'].dropna().unique().astype(int).tolist())
selected_year = st.selectbox("Select Year:", years)
chart_col, desc_col = st.columns([7, 3])
with chart_col:
st.markdown(f"### Distribution of Incidents by {selected_category}")
distribution_chart = create_category_distribution_chart(df, selected_category, selected_year)
# Update the figure layout for larger size
distribution_chart.update_layout(
height=800, # Increased height
width=None, # Let width be responsive
margin=dict(l=50, r=50, t=50, b=50)
)
st.plotly_chart(distribution_chart, use_container_width=True)
with desc_col:
st.markdown("""
## Distribution by Category
This visualization explores the distribution of traffic incidents across various categories, such as Collision Manner, Weather, Surface Condition, Alcohol Use, and Driver Gender. Each bar represents a specific category value (e.g., "Male" or "Female" for Gender), and the bars are divided into segments based on Injury Severity (e.g., Minor, Moderate, Serious, Fatal).
**Key features include:**
* Interactive Filters: Select a category and filter by year to analyze trends over time.
* Insightful Tooltips: Hover over each segment to view the exact count and percentage of incidents for a given severity level.
* Comparative Analysis: Quickly identify how different conditions or behaviors correlate with injury severity.
This chart provides actionable insights into factors contributing to traffic incidents and their outcomes, helping stakeholders target interventions and improve road safety.
""")
if __name__ == "__main__":
main()