-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.py
358 lines (322 loc) · 13.8 KB
/
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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
from dash.dependencies import Input, Output
import plotly_express as px
from urllib.request import urlopen
from plotly.express.colors import sequential
import plotly.graph_objects as go
import Code.cityhealth.CityHealth as CH
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
# df = pd.read_csv('https://plotly.github.io/datasets/country_indicators.csv')
# available_indicators = df['Indicator Name'].unique()
static_image_route = '/images/tracts/'
def recompute():
import pickle
import json
import geopandas as gpd
data = CH.preprocess_data()
method = "ridge_regression"
model, coefs = CH.train_model(data, test_size=0.3, method=method, plot_weights=False)
nyc_data = CH.preprocess_data(NYC_only=True, impute=False, drop_na=False)
weights = CH.feature_weights(data, coefs=coefs, weight_label=method + "_coef")
city_average = round(data["Diabetes"].mean(),1)
tract_dict = [dict(label=x, value=x) for x in data.index]
# Find mix/max for radar plot
min_max = CH.min_max_scores(data, weights)
# Load map dataset
geo = gpd.read_file('data/cityhealth-newyork-190506/CHDB_data_tract_NY v5_3_withgeo_v2.geojson')
shapes = json.loads(geo['geometry'].apply(lambda x: x).to_json())
# Create text field in geo for hover information
geo['hovertext'] = 'Diabetes Rate: ' + geo['Diabetes'].astype(str) + '%' + '<br>' + \
geo['neighborhood_name'].astype(str) + '<br>' + \
'FIP Tract: ' + geo['fips_state_county_tract_code'].astype(str)
# Save all objects as a dict in a pickle
pickle.dump({'data':nyc_data,
'model':model,
'method':method,
'coefs':coefs,
'weights':weights,
'city_average':city_average,
'tract_dict':tract_dict,
'min_max':min_max,
'geo':geo,
'shapes':shapes}, open("./data/saved_objects.pickle", 'wb'))
def import_precomputed(pickle_path="./data/saved_objects.pickle"):
import pickle
p = pickle.load(open(pickle_path, "rb"))
return [p[x] for x in p]
data, model, method, coefs, weights, city_average, tract_dict, min_max, geo, shapes = import_precomputed(pickle_path="./data/saved_objects.pickle")
n_factors_dict =[{'label':x, 'value':x} for x in range(1,len(coefs))]
app.layout = html.Div([
html.H1('Making health local'),
dcc.Tabs([
dcc.Tab(label='App', id="App", children=[
## Tract dropdown
html.Div([
html.P([dcc.Dropdown(id="tract_id", options=tract_dict, value=36085032300)]),
]),
## Tract details
html.Div([
html.Div([
html.H2('Community View', style={'text-align': 'center'}),
html.Div([
html.Img(id='image', style={'height':'50%', 'width':'50%', 'display': 'block', 'margin-left': 'auto', 'margin-right': 'auto'})
]),
html.Div([
html.H3(id='community_view_tract_id'),
html.H3(id='community_view_diabetes_rate'),
], style={'padding': '20 20'})
],
style={'width': '29%', 'display': 'inline-block'}
),
## Map
html.Div([
dcc.Graph(
id='tract_map',
clickData={'points': [{'location': '1'}]},
style={'height': '100%'}
)
],
style={'width': '69%', 'display': 'inline-block', 'float': 'right'}
),
], style={'height': '100vh'}),
## Polar plot
html.Div([
html.Div([
html.Div([
html.Div([
html.H3(id='radial_predicted'),
html.H3(id='radial_actual'),
html.H3(id='radial_city_average'),
html.H5('N factors:'),
html.P([dcc.Dropdown(id="n_factors", options=n_factors_dict, value=6)]),
], style={'flex': '1'}
),
html.Div([]),
],style={'display': 'flex', 'justify-content': 'center', 'align-items': 'center', 'height': '100%'},
)
], style={'width': '29%', 'display': 'inline-block', 'height': '700px'}
),
html.Div([
dcc.Loading([
dcc.Graph(id="graph", style={"width": "90%", "display": "inline-block"})
]),
],
style={'width': '69%', 'display': 'inline-block', 'float': 'right'}
# style={'float': 'right'}
),
]),
]), # END TAB1
dcc.Tab(label='About', id='About', children=[
html.Div(style={'margin':'100px'}, children=[
html.Div(className="about_div", children=[
html.H2('Why was this app made?'),
dcc.Markdown('''
- Type II Diabetes (T2D) is one of the most prevalent health conditions
in industrialized nations. In America alone, #### in #### people suffer with T2D
and is projected to reach #### by ####. T2D increases the risk for cardiovascular
disease by 2-4 times, which is by far the leading cause of death in industrialized nations.
The overall goal of this app is empower users of all kinds to better understand the various
factors underlying risk for T2D in many diverse communities across America.
- This app is ultimately meant to be used by anyone who has an interest in exploring T2D risk factors
across a wide range of communities. Some specific use cases could include (but are by no means limited to):
1. Public health officials
2. City planners
3. Hospital systems
4. Community health advocates and activists
5. Residents who are interested in learning about their own communities.
''')
]),
html.Div(className="about_div", children=[
html.H2('What can this app do?'),
dcc.Markdown('''
+ This app makes use of the large amount of publicly available data and analyzes it using state-of-art AI/machine learning algorithms.
+ Through this approach, it can:
1. Visualize communities at high risk for T2D.
2. Identify the risk factors that are most likely driving T2D in each specific
community (i.e. provide a community-specific \\"risk profile\\"").
3. Offer a list of recommendations and resources that our algorithm predicts are most
likely to have an impact on the health of that community. Each set of recommendations is
specifically tailored to each community based on their risk profile.
''')
]),
html.Div(className="about_div", children=[
html.H2('Where does this data come from?'),
dcc.Markdown('''
+ [CityHealth Dashboard](https://www.cityhealthdashboard.com)
+ [IPUMS](https://ipums.org)
- [Health Surveys](https://healthsurveys.ipums.org)
- [NHGIS](https://www.nhgis.org)
+ Google Maps
''')
]),
html.Div(className="about_div", children=[
html.H2('Who are you?'),
dcc.Markdown('''
''')
]),
html.Div(className="about_div", children=[
html.H2('How exactly do you predict diabetes risk?'),
dcc.Markdown('''
Programming and unicorn sparkles.
''')
])
])
])
])
])
# Update tract detail summary section
outputs = ['community_view_tract_id', 'community_view_diabetes_rate']
@app.callback(
[Output(output, 'children') for output in outputs],
[Input(component_id='tract_map', component_property='hoverData')]
)
def update_tract_detail_summary(tract_map):
''' Update the tract ID for the summary of the tract
param: tract_map
return: div
'''
try:
location = int(tract_map['points'][0]['location'])
tract_id = int(geo.iloc[location]['fips_state_county_tract_code'])
diabetes_rate = geo.iloc[location]['Diabetes']
# zipcode =
except:
tract_id, diabetes_rate = 'None', 'None'
return 'Tract: {}'.format(tract_id),'Diabetes rate: {}%'.format(diabetes_rate)
# Show chosen tract shape in Community View
@app.callback(
dash.dependencies.Output('image', 'src'),
[Input(component_id='tract_map', component_property='hoverData')])
def update_image_src(tract_map):
try:
location = int(tract_map['points'][0]['location'])
fip = geo.iloc[location]['fips_state_county_tract_code']
except:
fip = '36005000100'
# return static_image_route + fip + '.png'
return 'https://github.com/m3ngineer/Diabetes_Hackathon_Data/blob/master/images/tracts/{}.png?raw=True'.format(fip)
# Add a static image route that serves images from desktop
# Be *very* careful here - you don't want to serve arbitrary files
# from your computer or server
@app.server.route('{}<image_path>.png'.format(static_image_route))
def serve_image(image_path):
image_name = '{}.png'.format(image_path)
# if image_name not in list_of_images:
# raise Exception('"{}" is excluded from the allowed static files'.format(image_path))
return flask.send_from_directory(image_directory, image_name)
# Function to make interactive Map
@app.callback(Output("tract_map", "figure"), [Input("tract_id", "value")])
def map_tracts(tract_id):
''' Creates geographic heatmap of diabetes rates binned into quartiles '''
bin_labels = [0, .25, .5, .75, 1]
geo['Diabetes_bin'], bins_perc_diabetes = pd.qcut(geo['Diabetes'],
q=5,
retbins=True,
labels=bin_labels)
fig = go.Figure(go.Choroplethmapbox(geojson=shapes,
locations = [str(i) for i in geo.index.values],
z=geo['Diabetes_bin'],
colorscale=[(0, "rgb(222,235,247)"),
(.25, "rgb(198,219,239)"),
(.5, "rgb(107,174,214)"),
(.75, "rgb(33,113,181)"),
(1, "rgb(8,48,107)")],
zmin=0, zmax=1,
marker_opacity=0.75, marker_line_width=0,
text = geo['hovertext'],
colorbar=dict(
title="Diabetes Rate",
tickvals=[0, 0.2, 0.4, 0.6, 0.8, 1],
ticktext=['{}%'.format(label) for label in list(bins_perc_diabetes.astype(str))],
)
)
)
fig.update_layout(
mapbox_style="carto-positron",
mapbox_zoom=10,
mapbox_center = {"lat": 40.7410224, "lon": -73.9939661},
margin={"r":0,"t":0,"l":0,"b":0}
)
return fig
# Function to make polar radial chart
dropdowns = ["tract_id"]
@app.callback(
[Output("graph", "figure"),
Output('radial_predicted', 'children'),
Output('radial_actual', 'children'),
Output('radial_city_average', 'children')],
[Input('tract_map', "clickData"),
Input('n_factors','value')]
)
def generate_spider_plot(tract_map, n_factors=6):
''' '''
location = int(tract_map['points'][0]['location'])
tract_id = int(geo.iloc[location]['fips_state_county_tract_code'])
check = int(n_factors)
print(check)
polar_data = CH.prepare_polar(data, weights, stcotr_fips=tract_id, n_factors=n_factors)
predicted, actual = CH.predict_value(model, data, stcotr_fips=tract_id, y_var="Diabetes")
radial_predicted = "Predicted = " + str(round(predicted,1)) + " %"
radial_actual = "Actual = " + str(actual) + " %"
radial_city_average = "City Average = "+str(city_average)+" %"
return px.line_polar(
polar_data,
r="Risk Score",
theta="metric_name",
height=700,
line_close=True
).update_traces(
fill='toself',
# fillcolor='rgba(65, 105, 225, 0.60)',
fillcolor='rgba(8, 48, 107, 0.75)',
mode = "lines",
line_color = "#F0F0F0",
# marker=dict(
# color="#4169e1",
# symbol="circle",
# size=12 )
).update_layout(
title = dict(
text = '',
x = 0,
y = .925),
font_size = 15,
showlegend = False,
# margin=dict(l=50,r=50),
# xaxis=dict(automargin=True),
# yaxis=dict(automargin=True),
polar = dict(
bgcolor = "rgba(240,240,240, .85)",
angularaxis = dict(
linewidth = 3,
showline=True,
linecolor='rgba(255,255,255, .85)',
color="black"
), radialaxis = dict(
side = "clockwise",
showline = True,
linewidth = 2,
gridcolor = "white",
gridwidth = 2,
color = "rgba(0, 0, 0, .85)",
visible =True,
range=[min_max["min"], min_max["max"]]
)
),
paper_bgcolor = "rgba(0,0,0,0)",
annotations = [
go.layout.Annotation(
x=0.5,
y=-0.15,
showarrow=False,
text="Custom x-axis title",
xref="paper",
yref="paper"
)],
), radial_predicted, radial_actual, radial_city_average
if __name__ == '__main__':
app.run_server(port=8080, debug=True)