Py.Cafe

jragh./

fire-incident-visualizer

Fire Incident Visualizer

DocsPricing
  • app.py
  • requirements.txt
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# 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)]