Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update plotly dash overview dashboards (combined old dashboards) #1288

Merged
merged 54 commits into from
Aug 4, 2022
Merged
Changes from 5 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
d1aa1dd
Updte dashboard to newest version
joshuayhwu Jun 13, 2022
39e63b0
Resolve interaction issue with flask
joshuayhwu Jun 15, 2022
e3d4b59
Remove flask comment
joshuayhwu Jun 21, 2022
4d51ab1
Update batch_get_data function docstring
joshuayhwu Jun 21, 2022
8d35608
Update import alphabetical order
joshuayhwu Jun 22, 2022
f5a34a5
Update import format on neighborhood.py
joshuayhwu Jun 22, 2022
7306652
Removed obsolete import statement
joshuayhwu Jun 22, 2022
0b7cd2a
Neighborhood Combine draft
joshuayhwu Jun 24, 2022
5aa43e4
Overview first page draft
joshuayhwu Jun 28, 2022
f2f84df
Updating combined dashboard
joshuayhwu Jul 1, 2022
8118f9e
Combined overview dashboard
joshuayhwu Jul 3, 2022
86a2803
Deleted redundant dashboards
joshuayhwu Jul 20, 2022
43e4d25
Documentation for ncSUmComp
joshuayhwu Jul 20, 2022
952a1c0
Add print status logs
joshuayhwu Jul 20, 2022
2b23073
Split into two pr
joshuayhwu Jul 24, 2022
d6a4b69
Update app.py comment
joshuayhwu Jul 27, 2022
b9a47b4
Capitalize title constant
joshuayhwu Jul 27, 2022
50dc742
Update api path var names
joshuayhwu Jul 27, 2022
ccd1d53
Remove duplicate code
joshuayhwu Jul 27, 2022
17f637b
Remove duplicate req source code
joshuayhwu Jul 27, 2022
62f741c
Update stas_df var name
joshuayhwu Jul 27, 2022
3a3a6bc
Update req_count var name
joshuayhwu Jul 27, 2022
154dabd
update date range api path var
joshuayhwu Jul 27, 2022
d109f28
Update var to snake case
joshuayhwu Jul 27, 2022
c3e92ba
Update indicator style literals
joshuayhwu Jul 27, 2022
10483df
Border style
joshuayhwu Jul 27, 2022
090f732
Equal space box style
joshuayhwu Jul 27, 2022
17d141d
Dashboard outline
joshuayhwu Jul 27, 2022
7f3d39e
two chart_style
joshuayhwu Jul 27, 2022
8d4b82f
Remove unused code
joshuayhwu Jul 27, 2022
3de735f
Update api path with urlencode
joshuayhwu Jul 27, 2022
c97b6c7
Update API path names
joshuayhwu Jul 27, 2022
c513549
Remove flask from app.py
joshuayhwu Jul 29, 2022
d98a844
Adding period at end of comment
joshuayhwu Jul 29, 2022
4cb5e71
Update comments
joshuayhwu Jul 29, 2022
fab34ce
Renamed file to overview_combined
joshuayhwu Jul 29, 2022
363fff3
make string two lines
joshuayhwu Jul 29, 2022
b17f5e7
Fixed duplicate file extension
joshuayhwu Jul 29, 2022
73c52fd
Add created date filter
joshuayhwu Jul 29, 2022
9d54237
Added helper function
joshuayhwu Jul 29, 2022
8eb319a
Add helper func documenttaion
joshuayhwu Jul 29, 2022
71c99c8
Utilize helper functions
joshuayhwu Jul 29, 2022
7d260bc
Add constant for slice index
joshuayhwu Jul 29, 2022
77a13c8
Final adjustments
joshuayhwu Jul 29, 2022
20555f2
Update result_count_gb name
joshuayhwu Aug 2, 2022
38d117e
Use constant instaed of dict
joshuayhwu Aug 2, 2022
34c162a
Encapsulating graph into functions
joshuayhwu Aug 2, 2022
11b8dee
Add func to cal summary viz
joshuayhwu Aug 2, 2022
2a082d1
Resolved groupby type error
joshuayhwu Aug 2, 2022
05d1d97
Replace visual with visualizations
joshuayhwu Aug 4, 2022
865f581
Remove return var in docstring
joshuayhwu Aug 4, 2022
443e852
Remove excessive comments
joshuayhwu Aug 4, 2022
ff4eafb
Place variable in func
joshuayhwu Aug 4, 2022
965cad2
resolve merged conflict
joshuayhwu Aug 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 193 additions & 93 deletions server/dash/dashboards/overview_combined.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,140 +10,240 @@
from design import LABELS

# COMMON VARIABLES.
AGENCY_NAME_FIELD = 'agency_name'
COUNCIL_NAME_FIELD = 'council_name'
CREATED_DATE_FILTER = 'created_date>=2016-01-01'
CREATED_YEAR_FIELD = 'created_year'
NON_TOP4_INDEX_START = 4
REPORT_API_PATH_ROOT = "/reports?"
REQ_TYPE_STATS_API_PATH = '/types/stats'
REQ_TYPE_STATS_API_PATH = '/types/stats'
SOURCE_NAME_FIELD = 'source_name'
TITLE = "OVERVIEW COMBINED DASHBOARD"
TYPE_NAME_FIELD = 'type_name'

# HELPER FUNCTIONS.
# API HELPER FUNCTION.
def generate_dataframe_from_api(api_params, group_by_col):
"""Generates the dataframe output from the reports API.

This function takes the "api_params" dictionary and "group_by_col" string and outputs the relevant fields
in the raw dataframe and groupby dataframe from the reports API.

Args:
api_params: a dictionary of parameters for calling the API.
group_by_col: the column name we use to groupby and output the result_df_gb dataframe.
group_by_col: the column name we use to groupby and output the result_counts_df dataframe.

Returns:
result_df: the raw dataframe from calling the api with parameters from "api_params".
result_df_gb: dataframe output from result_df group by the column "group_by_col" and aggregating the sum.
result_counts_df: dataframe output from result_df group by the column "group_by_col" and aggregating the sum.
"""
DATA_API_PATH = REPORT_API_PATH_ROOT + urllib.parse.urlencode(api_params)
print(" * Downloading data from API path: " + DATA_API_PATH)
result_df = pd.read_json(API_HOST + DATA_API_PATH)
result_df_gb = result_df.groupby([group_by_col])['counts'].sum().sort_values().to_frame()
result_counts_df = result_df.groupby([group_by_col])['counts'].sum().sort_values().to_frame()
print(" * Dataframe has been loaded from API path: " + DATA_API_PATH)
return result_df, result_df_gb
return result_df, result_counts_df

# DATA
# DATAFRAMES

# Loading the dataframe for the NCs and corresponding requests.
nc_req_count_api_params = {'field': 'council_name', 'filter': CREATED_DATE_FILTER}
joshuayhwu marked this conversation as resolved.
Show resolved Hide resolved
_, nc_req_count_df = generate_dataframe_from_api(nc_req_count_api_params, nc_req_count_api_params['field'])
_, nc_req_count_df = generate_dataframe_from_api(nc_req_count_api_params, COUNCIL_NAME_FIELD)

# Loading the data for the number of new requests.
new_req_count_api_params = {'field': 'created_year', 'filter': CREATED_DATE_FILTER}
_, new_req_count_df = generate_dataframe_from_api(new_req_count_api_params, new_req_count_api_params['field'])
_, new_req_count_df = generate_dataframe_from_api(new_req_count_api_params, CREATED_YEAR_FIELD)

# Loading the count of each request types overall.
req_count_api_params = {'field': 'type_name', 'filter': CREATED_DATE_FILTER}
req_count_df, req_count = generate_dataframe_from_api(new_req_count_api_params, new_req_count_api_params['field'])
req_count_df, req_count = generate_dataframe_from_api(req_count_api_params, TYPE_NAME_FIELD)

# Loading the total number of request source.
req_source_count_api_params = {'field': 'source_name', 'filter': CREATED_DATE_FILTER}
req_source_count_df, req_source_count = generate_dataframe_from_api(req_source_count_api_params, req_source_count_api_params['field'])
req_source_count_df, req_source_count = generate_dataframe_from_api(
req_source_count_api_params, SOURCE_NAME_FIELD)

# Loading the number of Request Agencies.
req_agency_count_api_params = {'field': 'agency_name', 'filter': CREATED_DATE_FILTER}
req_agency_count_df, agency_count = generate_dataframe_from_api(req_agency_count_api_params, req_agency_count_api_params['field'])
req_agency_count_df, agency_count = generate_dataframe_from_api(
req_agency_count_api_params, AGENCY_NAME_FIELD)

# VISUALS HELPER FUNCTIONS.
joshuayhwu marked this conversation as resolved.
Show resolved Hide resolved

# Compute values for the indicator visuals.
joshuayhwu marked this conversation as resolved.
Show resolved Hide resolved
def get_counts_dict():
joshuayhwu marked this conversation as resolved.
Show resolved Hide resolved
"""Compute values for the indicator visuals.

This function compute the summary statistics including number of new requests, number of neighborhood
councils, number of requests, number of request sources, number of request agencies, and store them
in a dictionary.

Returns:
indicator_count_dict: dictionary storing the summary statistics for indicator visuals.
"""
indicator_count_dict = {}
indicator_count_dict['new_req_count'] = new_req_count_df['counts'].sum()
indicator_count_dict['nc_count'] = nc_req_count_df.shape[0] - 1
indicator_count_dict['req_count'] = req_count_df.shape[0]
indicator_count_dict['source_count'] = req_source_count.shape[0]
indicator_count_dict['agency_count'] = agency_count.shape[0]

return indicator_count_dict

# Request Share by Agency Pie Chart.
req_agency_count_df = agency_count
req_agency_count_df.sort_values('counts', ascending=False, inplace=True)
req_agency_count_df.loc['Others'] = req_agency_count_df[NON_TOP4_INDEX_START:].sum()
req_agency_count_df.sort_values('counts', ascending=False, inplace=True)
req_agency_count_df = req_agency_count_df[:NON_TOP4_INDEX_START + 1]
req_agency_count_df.index = req_agency_count_df.index.map(lambda x: '<br>'.join(textwrap.wrap(x, width=16)))

req_share_by_agency_pie_chart = px.pie(
req_agency_count_df,
names=req_agency_count_df.index,
values='counts',
labels=LABELS,
hole=.3,
title="Total Requests by Agency",
)
req_share_by_agency_pie_chart.update_layout(margin=dict(l=100, r=100, b=100, t=100))
def make_agency_pie_chart(agency_count):
"""Generates the request share by agency pie chart.

This function takes the "agency_count" dataframe, aggregate all agencies with less than top 4 request counts as "Others",
and outputs a pie chart "req_share_by_agency_pie_chart" showing the share of requests by agencies.

Args:
agency_count: a pandas dataframe with the different agencies and corresopnding request counts.

Returns:
req_share_by_agency_pie_chart: a pie chart showing the share of requests by agencies.
"""
req_agency_count_df = agency_count
req_agency_count_df.sort_values('counts', ascending=False, inplace=True)
req_agency_count_df.loc['Others'] = req_agency_count_df[NON_TOP4_INDEX_START:].sum()
req_agency_count_df.sort_values('counts', ascending=False, inplace=True)
req_agency_count_df = req_agency_count_df[:NON_TOP4_INDEX_START + 1]
req_agency_count_df.index = req_agency_count_df.index.map(
lambda x: '<br>'.join(textwrap.wrap(x, width=16)))

req_share_by_agency_pie_chart = px.pie(
req_agency_count_df,
names=req_agency_count_df.index,
values='counts',
labels=LABELS,
hole=.3,
title="Total Requests by Agency",
)
req_share_by_agency_pie_chart.update_layout(margin=dict(l=100, r=100, b=100, t=100))
return req_share_by_agency_pie_chart

# Request Type by Source Bar Chart.
req_source_count_df = req_source_count
req_source_count_df.sort_values('counts', ascending=False, inplace=True)
req_source_count_df.loc['Others'] = req_source_count_df[NON_TOP4_INDEX_START:].sum()
req_source_count_df.sort_values('counts', ascending=False, inplace=True)
req_source_count_df = req_source_count_df[:NON_TOP4_INDEX_START + 1]

req_source_bar_chart = px.bar(
req_source_count_df,
y=req_source_count_df.index,
x='counts',
title="Total Requests by Source",
orientation='h'
)
def make_req_type_source_bar_chart(req_source_count):
"""Generates the request type by source bar chart.

This function takes the "req_source_count" dataframe, aggregate all request source with less than
top 4 request counts as "Others", and outputs a horizontal bar chart "req_source_bar_chart" showing
the share of requests by sources.

Args:
req_source_count: a pandas dataframe with the different request sources and corresopnding request counts.

Returns:
req_source_bar_chart: a horizontal bar chart showing the share of requests by sources.
"""
req_source_count_df = req_source_count
req_source_count_df.sort_values('counts', ascending=False, inplace=True)
req_source_count_df.loc['Others'] = req_source_count_df[NON_TOP4_INDEX_START:].sum()
req_source_count_df.sort_values('counts', ascending=False, inplace=True)
req_source_count_df = req_source_count_df[:NON_TOP4_INDEX_START + 1]

req_source_bar_chart = px.bar(
req_source_count_df,
y=req_source_count_df.index,
x='counts',
title="Total Requests by Source",
orientation='h'
)
return req_source_bar_chart

# Median Request Days to Close Box Plot.
print(" * Downloading data from API path: " + REQ_TYPE_STATS_API_PATH)
stats_df = pd.read_json(API_HOST + REQ_TYPE_STATS_API_PATH)
stats_df = stats_df.sort_values('median', ascending=False)
print(" * Dataframe has been loaded from API path: " + REQ_TYPE_STATS_API_PATH)

med_days_to_close_box_plot = go.Figure()
med_days_to_close_box_plot.add_trace(
go.Box(
y=stats_df.type_name,
q1=stats_df['q1'],
median=stats_df['median'],
q3=stats_df['q3'],
marker_color='#29404F',
fillcolor='#E17C05',
def make_days_to_close_box_plot():
"""Generates the request days to close box plot.

This function calls the stats api via "REQ_TYPE_STATS_API_PATH", retrieves quartile values for request
time to close, and generates a box plot to visualize median request day to close box plot.

Returns:
med_days_to_close_box_plot: a box plot showing the median day to close for each request type.
joshuayhwu marked this conversation as resolved.
Show resolved Hide resolved
"""
print(" * Downloading data from API path: " + REQ_TYPE_STATS_API_PATH)
stats_df = pd.read_json(API_HOST + REQ_TYPE_STATS_API_PATH)
stats_df = stats_df.sort_values('median', ascending=False)
print(" * Dataframe has been loaded from API path: " + REQ_TYPE_STATS_API_PATH)

med_days_to_close_box_plot = go.Figure()
med_days_to_close_box_plot.add_trace(
go.Box(
y=stats_df.type_name,
q1=stats_df['q1'],
median=stats_df['median'],
q3=stats_df['q3'],
marker_color='#29404F',
fillcolor='#E17C05',
)
)
med_days_to_close_box_plot.update_xaxes(
dtick=5
)
med_days_to_close_box_plot.update_layout(
title="Total Median Days to Close by Type",
)
)
med_days_to_close_box_plot.update_xaxes(
dtick=5
)
med_days_to_close_box_plot.update_layout(
title="Total Median Days to Close by Type",
)
return med_days_to_close_box_plot

# Day of Week Bar Chart.
start_date = datetime.date.today() - datetime.timedelta(days=365)
end_date = datetime.date.today() - datetime.timedelta(days=1)
date_range_req_data_params = {'filter':f"created_date>={start_date}", 'filter':f"created_date<={end_date}"}
date_range_req_df, _ = generate_dataframe_from_api(date_range_req_data_params, 'created_date')
date_range_req_df['created_date'] = pd.to_datetime(date_range_req_df['created_date'])
date_range_req_df['day_of_week'] = date_range_req_df['created_date'].dt.day_name()
day_of_week_df = date_range_req_df.groupby(['day_of_week']).agg('sum').reset_index()

num_req_by_day_bar_chart = px.bar(
day_of_week_df,
x="day_of_week",
y="counts",
labels=LABELS,
)
num_req_by_day_bar_chart.update_xaxes(categoryorder='array', categoryarray=[
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"])
def make_day_of_week_bar_chart():
"""Generates the day of week bar chart.

This function calls the reports api , retrieves 1 year worth of request data, and generates
joshuayhwu marked this conversation as resolved.
Show resolved Hide resolved
a vertical bar chart showing the number of requests for every day of the week.

Returns:
num_req_by_day_bar_chart: a bar chart showing the total number of requests for every day
of the week throughout the past year.
"""
start_date = datetime.date.today() - datetime.timedelta(days=365)
end_date = datetime.date.today() - datetime.timedelta(days=1)
date_range_req_data_params = {
'filter': f"created_date>={start_date}", 'filter': f"created_date<={end_date}"}
date_range_req_df, _ = generate_dataframe_from_api(date_range_req_data_params, 'created_date')
date_range_req_df['created_date'] = pd.to_datetime(date_range_req_df['created_date'])
date_range_req_df['day_of_week'] = date_range_req_df['created_date'].dt.day_name()
day_of_week_df = date_range_req_df.groupby(['day_of_week']).agg('sum').reset_index()

num_req_by_day_bar_chart = px.bar(
day_of_week_df,
x="day_of_week",
y="counts",
labels=LABELS,
)
num_req_by_day_bar_chart.update_xaxes(categoryorder='array', categoryarray=[
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"])
return num_req_by_day_bar_chart

# Total Request by NC.
req_by_nc_bar_chart = px.bar(
nc_req_count_df,
x=nc_req_count_df.index,
y='counts',
labels=LABELS,
title="Total Requests by Neighborhood Councils",
)
req_by_nc_bar_chart.update_layout(font=dict(size=12))
def make_req_by_nc_bar_chart(nc_req_count_df):
"""Generates the total request by nc bar chart.

This function takes the "nc_req_count_df" dataframe and outputs a bar chart
"req_by_nc_bar_chart" showing the total number of requests by Neighborhood Councils (NCs).

Args:
nc_req_count_df: a pandas dataframe with the different NCs and corresopnding request counts.

Returns:
req_source_bar_chart: a horizontal bar chart showing the share of requests by sources.
"""
req_by_nc_bar_chart = px.bar(
nc_req_count_df,
x=nc_req_count_df.index,
y='counts',
labels=LABELS,
title="Total Requests by Neighborhood Councils",
)
req_by_nc_bar_chart.update_layout(font=dict(size=12))
return req_by_nc_bar_chart

# VISUALS.
indicator_count_dict = get_counts_dict()
req_share_by_agency_pie_chart = make_agency_pie_chart(agency_count)
req_source_bar_chart = make_req_type_source_bar_chart(req_source_count)
med_days_to_close_box_plot = make_days_to_close_box_plot()
num_req_by_day_bar_chart = make_day_of_week_bar_chart()
req_by_nc_bar_chart = make_req_by_nc_bar_chart(nc_req_count_df)

# LAYOUT VARIABLES.
INDICATOR_CARD_STYLE = {"text-align": 'center',
Expand All @@ -162,15 +262,15 @@ def generate_dataframe_from_api(api_params, group_by_col):
html.P(DASHBOARD_OUTLINE, style={'font-size': '18px', 'font-style': 'italic'}),

html.Div([
html.Div([html.H2(f"{new_req_count_df['counts'].sum():,}"), html.Label(
html.Div([html.H2(f"{indicator_count_dict['new_req_count']:,}"), html.Label(
"Total Requests")], style=INDICATOR_CARD_STYLE),
html.Div([html.H2(nc_req_count_df.shape[0] - 1), html.Label("Neighborhoods")],
html.Div([html.H2(indicator_count_dict['nc_count']), html.Label("Neighborhoods")],
style=INDICATOR_CARD_STYLE),
html.Div([html.H2(req_count_df.shape[0]), html.Label(
html.Div([html.H2(indicator_count_dict['req_count']), html.Label(
"Request Types")], style=INDICATOR_CARD_STYLE),
html.Div([html.H2(req_source_count.shape[0]), html.Label(
html.Div([html.H2(indicator_count_dict['source_count']), html.Label(
"Request Source")], style=INDICATOR_CARD_STYLE),
html.Div([html.H2(agency_count.shape[0]), html.Label(
html.Div([html.H2(indicator_count_dict['agency_count']), html.Label(
"Request Agency")], style=INDICATOR_CARD_STYLE)
], style=EQUAL_SPACE_BOX_STYLE),

Expand Down