Add bar chart
Browse files
app.py
CHANGED
@@ -1,6 +1,14 @@
|
|
1 |
import panel as pn
|
2 |
import pandas as pd
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
### Part 0: Getting data
|
6 |
|
@@ -23,7 +31,8 @@ def get_data():
|
|
23 |
df['county_fips'] = df['county_fips'].astype(str).str.zfill(5)
|
24 |
|
25 |
df.columns = ['state_abbr','FIPS','county','family','housing','food','transportation','healthcare',
|
26 |
-
'other_necessities','childcare','taxes','total','median_family_income',
|
|
|
27 |
|
28 |
#Some counties share names across states; adding their state code makes them unique.
|
29 |
df['county_state'] = df.county + ', ' + df.state_abbr
|
@@ -35,9 +44,10 @@ def get_data():
|
|
35 |
|
36 |
df = get_data()
|
37 |
|
|
|
38 |
### Part 1: Find and display model budget
|
39 |
|
40 |
-
#
|
41 |
def dol(value,df,row=0):
|
42 |
return '${:0,}'.format(df[value][row])
|
43 |
|
@@ -46,7 +56,7 @@ def calculate_model(user_county,user_parents,user_children):
|
|
46 |
user_family = f'{user_parents}p{user_children}c'
|
47 |
user_df = df.loc[(df.county_state == user_county) & (df.family == user_family)].reset_index(drop=True)
|
48 |
return (
|
49 |
-
f'Moderately frugal families of {user_parents} {"adult" if user_parents == 1 else "adults"} and {user_children} {"child" if user_children == 1 else "children"} living in {user_county} '
|
50 |
'tend to have a monthly family budget similar to the following:\n'
|
51 |
f'\nHousing: {dol("housing",user_df)}'
|
52 |
f'\nFood: {dol("food",user_df)}'
|
@@ -66,7 +76,7 @@ user_county = pn.widgets.AutocompleteInput(
|
|
66 |
case_sensitive=False,
|
67 |
placeholder='Input county name')
|
68 |
|
69 |
-
adult_title = pn.pane.Markdown('Number of adults in household:')
|
70 |
user_parents = pn.widgets.RadioButtonGroup(
|
71 |
name='Number of adults', options=['1', '2'], button_type='default', margin=(12,0,0,0))
|
72 |
|
@@ -102,8 +112,10 @@ def calculate_percentage(user_value,column,df):
|
|
102 |
|
103 |
#Function that takes user budget input and returns how it compares to model.
|
104 |
def calculate_budget_percentage(user_county,user_parents,user_children,user_income,user_housing,user_food,user_transportation,user_healthcare,user_childcare,user_other,user_taxes):
|
|
|
105 |
user_family = f'{user_parents}p{user_children}c'
|
106 |
user_df = df.loc[(df.county_state == user_county) & (df.family == user_family)].reset_index(drop=True)
|
|
|
107 |
income_p = calculate_percentage(user_income,'median_monthly_family_income',user_df)
|
108 |
housing_p = calculate_percentage(user_housing,'housing',user_df)
|
109 |
food_p = calculate_percentage(user_food,'food',user_df)
|
@@ -158,11 +170,13 @@ def budget_result(clicked):
|
|
158 |
|
159 |
budget_result = pn.pane.Markdown(pn.bind(budget_result,budget_submit))
|
160 |
|
|
|
161 |
### Part 3: Calculate and display most afforable counties and budgets
|
162 |
|
163 |
#Function to take user constraints, find most affordable counties, and calculate budgets
|
164 |
def calculate_comparison(user_county,user_parents,user_children,user_income,user_housing,user_food,user_transportation,user_healthcare,user_childcare,user_other,user_taxes,
|
165 |
bring_income,income_cap,income_cap_amount,state_restriction,states_allowed,result_count):
|
|
|
166 |
user_family = f'{user_parents}p{user_children}c'
|
167 |
user_df = df.loc[(df.county_state == user_county) & (df.family == user_family)].reset_index(drop=True)
|
168 |
income_p = calculate_percentage(user_income,'median_monthly_family_income',user_df)
|
@@ -173,6 +187,7 @@ def calculate_comparison(user_county,user_parents,user_children,user_income,user
|
|
173 |
childcare_p = calculate_percentage(user_childcare,'childcare',user_df)
|
174 |
other_p = calculate_percentage(user_other,'other_necessities',user_df)
|
175 |
taxes_p = calculate_percentage(user_taxes,'taxes',user_df)
|
|
|
176 |
new_df = df.loc[(df.family == user_family)].reset_index(drop=True)
|
177 |
if bring_income:
|
178 |
new_df.median_monthly_family_income = user_income
|
@@ -256,7 +271,75 @@ def comparison_result(clicked):
|
|
256 |
|
257 |
comparison_result = pn.pane.Markdown(pn.bind(comparison_result,comparison_submit))
|
258 |
|
259 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
260 |
|
261 |
template = pn.template.BootstrapTemplate(title='Affordable County Locator')
|
262 |
|
@@ -288,8 +371,14 @@ main3 = pn.Card(
|
|
288 |
styles={'background': 'WhiteSmoke'},
|
289 |
max_height = 300
|
290 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
291 |
template.main.append(
|
292 |
-
pn.Column(main1,main2,main3)
|
293 |
)
|
294 |
|
295 |
template.servable();
|
|
|
1 |
import panel as pn
|
2 |
import pandas as pd
|
3 |
+
import matplotlib
|
4 |
+
from matplotlib.figure import Figure
|
5 |
+
import matplotlib.pyplot as plt
|
6 |
+
import numpy as np
|
7 |
+
|
8 |
+
|
9 |
+
matplotlib.use("agg")
|
10 |
+
|
11 |
+
pn.extension('ipywidgets')
|
12 |
|
13 |
### Part 0: Getting data
|
14 |
|
|
|
31 |
df['county_fips'] = df['county_fips'].astype(str).str.zfill(5)
|
32 |
|
33 |
df.columns = ['state_abbr','FIPS','county','family','housing','food','transportation','healthcare',
|
34 |
+
'other_necessities','childcare','taxes','total','median_family_income',
|
35 |
+
'num_counties_in_st','st_cost_rank','st_med_aff_rank','st_income_rank']
|
36 |
|
37 |
#Some counties share names across states; adding their state code makes them unique.
|
38 |
df['county_state'] = df.county + ', ' + df.state_abbr
|
|
|
44 |
|
45 |
df = get_data()
|
46 |
|
47 |
+
|
48 |
### Part 1: Find and display model budget
|
49 |
|
50 |
+
#Helper function to represent data in US dollars
|
51 |
def dol(value,df,row=0):
|
52 |
return '${:0,}'.format(df[value][row])
|
53 |
|
|
|
56 |
user_family = f'{user_parents}p{user_children}c'
|
57 |
user_df = df.loc[(df.county_state == user_county) & (df.family == user_family)].reset_index(drop=True)
|
58 |
return (
|
59 |
+
f'Moderately frugal families of {user_parents} {"adult" if user_parents == "1" else "adults"} and {user_children} {"child" if user_children == 1 else "children"} living in {user_county} '
|
60 |
'tend to have a monthly family budget similar to the following:\n'
|
61 |
f'\nHousing: {dol("housing",user_df)}'
|
62 |
f'\nFood: {dol("food",user_df)}'
|
|
|
76 |
case_sensitive=False,
|
77 |
placeholder='Input county name')
|
78 |
|
79 |
+
adult_title = pn.pane.Markdown('Number of adults in household:') #Radio buttons don't have their name displayed as a title, so I add it here
|
80 |
user_parents = pn.widgets.RadioButtonGroup(
|
81 |
name='Number of adults', options=['1', '2'], button_type='default', margin=(12,0,0,0))
|
82 |
|
|
|
112 |
|
113 |
#Function that takes user budget input and returns how it compares to model.
|
114 |
def calculate_budget_percentage(user_county,user_parents,user_children,user_income,user_housing,user_food,user_transportation,user_healthcare,user_childcare,user_other,user_taxes):
|
115 |
+
#Repeated code. Could potentially be avoided with more binding, but that wouldn't change performance
|
116 |
user_family = f'{user_parents}p{user_children}c'
|
117 |
user_df = df.loc[(df.county_state == user_county) & (df.family == user_family)].reset_index(drop=True)
|
118 |
+
#New code
|
119 |
income_p = calculate_percentage(user_income,'median_monthly_family_income',user_df)
|
120 |
housing_p = calculate_percentage(user_housing,'housing',user_df)
|
121 |
food_p = calculate_percentage(user_food,'food',user_df)
|
|
|
170 |
|
171 |
budget_result = pn.pane.Markdown(pn.bind(budget_result,budget_submit))
|
172 |
|
173 |
+
|
174 |
### Part 3: Calculate and display most afforable counties and budgets
|
175 |
|
176 |
#Function to take user constraints, find most affordable counties, and calculate budgets
|
177 |
def calculate_comparison(user_county,user_parents,user_children,user_income,user_housing,user_food,user_transportation,user_healthcare,user_childcare,user_other,user_taxes,
|
178 |
bring_income,income_cap,income_cap_amount,state_restriction,states_allowed,result_count):
|
179 |
+
#Repeated code:
|
180 |
user_family = f'{user_parents}p{user_children}c'
|
181 |
user_df = df.loc[(df.county_state == user_county) & (df.family == user_family)].reset_index(drop=True)
|
182 |
income_p = calculate_percentage(user_income,'median_monthly_family_income',user_df)
|
|
|
187 |
childcare_p = calculate_percentage(user_childcare,'childcare',user_df)
|
188 |
other_p = calculate_percentage(user_other,'other_necessities',user_df)
|
189 |
taxes_p = calculate_percentage(user_taxes,'taxes',user_df)
|
190 |
+
#New code:
|
191 |
new_df = df.loc[(df.family == user_family)].reset_index(drop=True)
|
192 |
if bring_income:
|
193 |
new_df.median_monthly_family_income = user_income
|
|
|
271 |
|
272 |
comparison_result = pn.pane.Markdown(pn.bind(comparison_result,comparison_submit))
|
273 |
|
274 |
+
|
275 |
+
### Part 4: Bar chart of results
|
276 |
+
def calculate_bar(user_county,user_parents,user_children,user_income,user_housing,user_food,user_transportation,user_healthcare,user_childcare,user_other,user_taxes,
|
277 |
+
bring_income,income_cap,income_cap_amount,state_restriction,states_allowed,result_count):
|
278 |
+
#Repeated code:
|
279 |
+
user_family = f'{user_parents}p{user_children}c'
|
280 |
+
user_df = df.loc[(df.county_state == user_county) & (df.family == user_family)].reset_index(drop=True)
|
281 |
+
income_p = calculate_percentage(user_income,'median_monthly_family_income',user_df)
|
282 |
+
housing_p = calculate_percentage(user_housing,'housing',user_df)
|
283 |
+
food_p = calculate_percentage(user_food,'food',user_df)
|
284 |
+
transportation_p = calculate_percentage(user_transportation,'transportation',user_df)
|
285 |
+
healthcare_p = calculate_percentage(user_healthcare,'healthcare',user_df)
|
286 |
+
childcare_p = calculate_percentage(user_childcare,'childcare',user_df)
|
287 |
+
other_p = calculate_percentage(user_other,'other_necessities',user_df)
|
288 |
+
taxes_p = calculate_percentage(user_taxes,'taxes',user_df)
|
289 |
+
new_df = df.loc[(df.family == user_family)].reset_index(drop=True)
|
290 |
+
if bring_income:
|
291 |
+
new_df.median_monthly_family_income = user_income
|
292 |
+
else:
|
293 |
+
new_df.median_monthly_family_income = new_df.median_monthly_family_income.mul(income_p).round(0).astype('int64')
|
294 |
+
new_df['median_monthly_family_income_uncapped'] = new_df.median_monthly_family_income
|
295 |
+
if income_cap:
|
296 |
+
new_df.median_monthly_family_income.clip(upper=income_cap_amount,inplace=True)
|
297 |
+
if state_restriction:
|
298 |
+
new_df = new_df.loc[(new_df.state_abbr.isin(states_allowed))].reset_index(drop=True)
|
299 |
+
new_df.housing = new_df.housing.mul(housing_p).round(0).astype('int64')
|
300 |
+
new_df.food = new_df.food.mul(food_p).round(0).astype('int64')
|
301 |
+
new_df.transportation = new_df.transportation.mul(transportation_p).round(0).astype('int64')
|
302 |
+
new_df.healthcare = new_df.healthcare.mul(healthcare_p).round(0).astype('int64')
|
303 |
+
new_df.other_necessities = new_df.other_necessities.mul(other_p).round(0).astype('int64')
|
304 |
+
new_df.childcare = new_df.childcare.mul(childcare_p).round(0).astype('int64')
|
305 |
+
new_df.taxes = new_df.taxes.mul(taxes_p).round(0).astype('int64')
|
306 |
+
new_df.total = new_df.housing + new_df.food + new_df.transportation + new_df.healthcare + new_df.other_necessities + new_df.childcare + new_df.taxes
|
307 |
+
new_df.remaining_money = new_df.median_monthly_family_income - new_df.total
|
308 |
+
new_df = new_df.sort_values('remaining_money',ascending=False).reset_index(drop=True)
|
309 |
+
#New code:
|
310 |
+
new_df = new_df[:result_count]
|
311 |
+
fig = Figure(figsize=(result_count,4))
|
312 |
+
ax = fig.add_subplot(111)
|
313 |
+
ax = new_df.median_monthly_family_income.plot.bar(width=0.4, position=1, ax=ax)
|
314 |
+
ax = new_df[['housing', 'food','transportation','healthcare','childcare','other_necessities','taxes']].plot.bar(stacked=True, width=0.4, position=0, ax=ax, alpha=0.7)
|
315 |
+
ax.legend(labels=['Income','Housing','Food','Transportation','Healthcare','Childcare','Other','Taxes'],loc="upper right",bbox_to_anchor=(1 + 2/result_count, 1))
|
316 |
+
ax.set_xticks(ticks=np.arange(len(new_df)),labels=new_df.county_state.values)
|
317 |
+
ax.yaxis.set_major_formatter('${x:,.0f}')
|
318 |
+
ax.yaxis.grid(linestyle='--',alpha=.4)
|
319 |
+
ax.set_xlim([-.5, len(new_df)-.5])
|
320 |
+
plt.close(fig)
|
321 |
+
return fig
|
322 |
+
|
323 |
+
def nothing_fig():
|
324 |
+
fig = Figure(figsize=(5,4))
|
325 |
+
ax = fig.add_subplot(111)
|
326 |
+
return fig
|
327 |
+
|
328 |
+
bar = pn.bind(calculate_bar, user_county,user_parents,user_children,
|
329 |
+
user_income,user_housing,user_food,user_transportation,user_healthcare,user_childcare,user_other,user_taxes,
|
330 |
+
bring_income,income_cap,income_cap_amount,state_restriction,states_allowed,result_count)
|
331 |
+
|
332 |
+
def bar_result(clicked):
|
333 |
+
if clicked:
|
334 |
+
if user_county.value == '':
|
335 |
+
return nothing_fig()
|
336 |
+
return bar()
|
337 |
+
return nothing_fig()
|
338 |
+
|
339 |
+
bar_result = pn.pane.Matplotlib(pn.bind(bar_result,comparison_submit), dpi=144, tight=True)
|
340 |
+
|
341 |
+
|
342 |
+
### Part 5: Templating
|
343 |
|
344 |
template = pn.template.BootstrapTemplate(title='Affordable County Locator')
|
345 |
|
|
|
371 |
styles={'background': 'WhiteSmoke'},
|
372 |
max_height = 300
|
373 |
)
|
374 |
+
main4 = pn.Card(
|
375 |
+
bar_result,
|
376 |
+
title='Most Affordable Counties, bar chart',
|
377 |
+
styles={'background':'WhiteSmoke'},
|
378 |
+
collapsed=True
|
379 |
+
)
|
380 |
template.main.append(
|
381 |
+
pn.Column(main1,main2,main3,main4)
|
382 |
)
|
383 |
|
384 |
template.servable();
|