# check out https://dash.plotly.com/ for documentation
# And check out https://py.cafe/maartenbreddels for more examples
from dash import Dash, Input, Output, callback, dcc, html
import pandas as pd
import plotly_express as px
import numpy as np
app = Dash(__name__)
## URL connecting to our CSV ##
github_url = 'https://raw.githubusercontent.com/jragh/plotlymeetup/refs/heads/main/June_2025/Fire%20Incidents%20Data%20Raw.csv'
## Pandas read CSV to bring our data into our workspace here
fire_incidents_df = pd.read_csv(github_url, low_memory=False)
## Basic exploration of our data
## First 10 rows of our data ##
print(fire_incidents_df.head(10))
## Print the list of columns that are available to us ##
print(fire_incidents_df.columns)
## Creating our First Graph ##
## Need to set our date field as a datetime, lets use TFS Alarm Time ##
## Overwrites the old column of same name ##
fire_incidents_df['TFS_Alarm_Time'] = pd.to_datetime(fire_incidents_df['TFS_Alarm_Time'])
## Let's do a quick Group By
fire_incidents_years = fire_incidents_df.groupby(fire_incidents_df['TFS_Alarm_Time'].dt.year)['_id'].nunique().reset_index()
## Rename Columns for easier readability
fire_incidents_years = fire_incidents_years.rename(columns={'_id': 'Number of Incidents', 'TFS_Alarm_Time': 'Incident Year'})
fire_incidents_years = fire_incidents_years.sort_values(by='Incident Year')
print(fire_incidents_df.groupby([fire_incidents_df['TFS_Alarm_Time'].dt.year, 'Final_Incident_Type'])['Incident_Number'].nunique().reset_index())
## Set up our figure object ##
## fiy = fire_incident_years ##
fiy = px.bar(fire_incidents_years, x='Number of Incidents', y='Incident Year',
orientation='h', color_discrete_sequence=['#a10000'])
fiy.update_yaxes(type='category')
## Lets see what happened with the incident type maybe ? ##
fire_incident_type_year = fire_incidents_df.groupby([fire_incidents_df['TFS_Alarm_Time'].dt.year, 'Final_Incident_Type'])['Incident_Number'].nunique().reset_index()
print(fire_incident_type_year.columns)
fire_incident_type_year = fire_incident_type_year.rename(columns = {'TFS_Alarm_Time': 'Incident Year', 'Final_Incident_Type': 'Final Incident Type', 'Incident_Number': 'Number of Incidents'})
fire_incident_type_year = fire_incident_type_year.sort_values(by=['Incident Year', 'Final Incident Type'])
fity = px.line(fire_incident_type_year, x = 'Incident Year', y = 'Number of Incidents', markers=True, color='Final Incident Type')
fity.update_layout(legend=dict(
orientation="h",
yanchor="bottom",
y=1.02,
xanchor="right",
x=1
), hovermode='x unified')
fity.update_xaxes(type='category')
#### Third Chart ####
## Estimated Property Damage vs TFS Arrival Time ##
## Highlighted color shows if there was a smoke alarm ##
fire_incidents_time = fire_incidents_df.loc[(fire_incidents_df['Estimated_Dollar_Loss'] <= 125000.00)].copy()
## Convert columns to datetime columns ##
fire_incidents_time['TFS_Alarm_Time'] = pd.to_datetime(fire_incidents_time['TFS_Alarm_Time'])
fire_incidents_time['TFS_Arrival_Time'] = pd.to_datetime(fire_incidents_time['TFS_Arrival_Time'])
fire_incidents_time['Fire_Under_Control_Time'] = pd.to_datetime(fire_incidents_time['Fire_Under_Control_Time'])
## Feature engineer 2 columns: Total Seconds to Arrival, Seconds from Arrival to Control ##
fire_incidents_time['TFS_Time_To_Arrival'] = (fire_incidents_time['TFS_Arrival_Time'] - fire_incidents_time['TFS_Alarm_Time']).astype('timedelta64[s]').dt.total_seconds()
fire_incidents_time['TFS_Arrival_To_Control'] = (fire_incidents_time['Fire_Under_Control_Time'] - fire_incidents_time['TFS_Arrival_Time']).dt.total_seconds()
fire_incidents_time['Smoke Detector Status Clean'] = np.where(fire_incidents_time['Smoke_Alarm_at_Fire_Origin_Alarm_Type'].isin(['8 - Not applicable - no smoke alarm or presence undetermined', '9 - Type undetermined']), 'No Smoke Detector', 'Smoke Detector Present')
## Further filter my data ##
## Only Look at data that has fire resolutions less than 3000 seconds ##
fire_incidents_time = fire_incidents_time.loc[fire_incidents_time['TFS_Arrival_To_Control'] < 3000]
fig_fire_dmg_time = px.scatter(fire_incidents_time, x='Estimated_Dollar_Loss', y='TFS_Arrival_To_Control', color='Smoke Detector Status Clean')
fig_fire_dmg_time.update_traces(marker={'line': {'width': 0.6}, 'size': 6.5, 'opacity': .65})
fig_fire_dmg_time.update_yaxes(title='TFS Arrival To Control (Seconds)')
fig_fire_dmg_time.update_xaxes(title='Estimated Damage ($CAD)')
fig_fire_dmg_time.update_layout(legend=dict(
orientation="h",
yanchor="bottom",
y=1.02,
xanchor="right",
x=1,
title='Smoke Detector Status'
), hovermode='closest')
## Maybe some Barcharts for us to investigate ##
## Create a copy of our dataset ##
fire_incidents_raw_smoke = fire_incidents_df.copy()
## Generate Incident Year ##
fire_incidents_raw_smoke['Incident_Year'] = pd.to_datetime(fire_incidents_raw_smoke['TFS_Alarm_Time']).dt.year
## Flag for Smoke Alarm Status ##
fire_incidents_raw_smoke['Smoke_Alarm_Status'] = np.where(fire_incidents_raw_smoke['Smoke_Alarm_at_Fire_Origin_Alarm_Type'].isin(['8 - Not applicable - no smoke alarm or presence undetermined', '9 - Type undetermined']), 'No Smoke Detector', 'Smoke Detector Present')
## Group By, Only including records from 2018 onwards, Property Damage less than 125,000 (Cut out outliers, basic)
fire_incidents_raw_smoke = fire_incidents_raw_smoke[(fire_incidents_raw_smoke['Incident_Year'] >= 2018) & (fire_incidents_raw_smoke['Estimated_Dollar_Loss'] <= 125000.00)].groupby(['Incident_Year', 'Smoke_Alarm_Status']).agg(
Total_Fire_Incidents=('Incident_Number', 'nunique'),
Total_Persons_Saved=('Count_of_Persons_Rescued', 'sum'),
Average_Person_Saved_Per_Fire=('Count_of_Persons_Rescued', 'mean'),
Average_Dollar_Loss = ('Estimated_Dollar_Loss', 'mean')
).reset_index()
graphing_dict_callback = {}
for col in list(fire_incidents_raw_smoke.columns)[2:]:
graphing_dict_callback[' '.join(col.split('_'))] = {'x_axis': 'Year',
'y_axis': ' '.join(col.split('_')),
'figure': px.bar(fire_incidents_raw_smoke, x='Incident_Year', y=col, color='Smoke_Alarm_Status', barmode='group')}
md = """
# This is our Fire Data Demo
*This is our basic canvas to visualize our data*
See [The dash examples index](https://dash-example-index.herokuapp.com/) for more examples.
"""
app.layout = html.Div(
children=[
dcc.Markdown(children=md, link_target="_blank"),
dcc.Dropdown(id="dropdown", options=["red", "green", "blue", "orange"]),
html.H2(children=['Number of Fire Incidents By Incident Year']),
## You can make text styled if you know HTML ##
## This is italicized for example ##
html.P('Total Number of Fire Incidents By Year (All Years)',
style={'fontSize': '0.85rem', 'color': 'grey', 'marginTop': '0', 'fontStyle': 'italic'}),
## Lets Create our first Graph ##
## Just displays our figure from above ##
dcc.Graph(figure=fiy),
html.Br(),
## Second GRaph underneath by year ##
html.H2(children=['Number of Fire Incidents By Incident Year & Incident Type']),
html.P('Exploring the Increase in Fire Incidents YoY',
style={'fontSize': '0.85rem', 'color': 'grey', 'marginTop': '0', 'fontStyle': 'italic'}),
dcc.Graph(figure=fity, style={'minWidth': '100%'}),
html.Br(),
## Third Graph Below showing the relationship between Arrival Time and Estimated damage ##
html.H2('Firefighter Arrival Time vs Estimates Fire Damage'),
html.P('Color Highlights Presence of Smoke Detector',
style={'fontSize': '0.85rem', 'color': 'grey', 'marginTop': '0', 'fontStyle': 'italic'}),
dcc.Graph(figure=fig_fire_dmg_time, style={'minWidth': '100%'}),
html.Br(),
## Fourth Section demonstrating the calback ##
html.H2('Smoke Alarm Status Charts'),
html.P('Choose a Smoke Alarm Status Visualization From The Dropdown',
style={'fontSize': '0.85rem', 'color': 'grey', 'marginTop': '0', 'fontStyle': 'italic'}),
dcc.Dropdown(id='smoke-selection', options=list(graphing_dict_callback.keys()), value=list(graphing_dict_callback.keys())[0]),
html.Div(id='smoke-return-div', children=[])
]
)
@callback(
Output("smoke-return-div", "children"),
Input("smoke-selection", "value"),
)
def update_smoke_alarm(chart):
header = html.H4(str(chart))
graph_children = dcc.Graph(figure=graphing_dict_callback[chart]['figure'])
return [header, graph_children]
##figure_return = graphing_dict_callback[chart]['figure']
##return [header, dcc.Graph(figure_return)]