From 0b8ae70c6a56abb215e81da1ec9f289676023ae3 Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Fri, 30 Sep 2022 12:25:09 -0700 Subject: [PATCH 01/21] Adding line chart --- server/dash/pages/nc_summary_comparison.py | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index f24156f72..93aabbb37 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -76,10 +76,26 @@ def generate_summary_dropdowns(): ], style=merge_dict(EQUAL_SPACE_STYLE, {"width": "97.5vw", "height": "10vh"})) +def generate_summary_line_chart(): + """Generates the line chart for the summary dashboard. + This function generates the html elements for the + number of requests line chart for summary dashboard. + Return: + Dash html div element containing overlapping line chart. + """ + return html.Div(dcc.Graph(id="nc_avg_comp_line_chart", style={"height": "40vh", + "width": "97.4vw"}), + style=merge_dict(BORDER_STYLE, {"height": "40vh", "width": "97.4vw"})) + + + + # LAYOUT. layout = html.Div([ html.Div(children=[ generate_summary_dropdowns(), + html.Div(html.Br(), style={"height": "0.5vh"}), + generate_summary_line_chart() ]), ]) @@ -112,3 +128,55 @@ def generate_dynamic_filter(selected_nc): req_types = sorted(list(set(df["typeName"]))) return req_types, ' ' + + +@callback( + Output("nc_avg_comp_line_chart", "figure"), + [Input("selected_nc", "value")] +) +def update_line_chart(selected_nc): + """Generates a line chart visualizations for LA 311 requests data + based on the two selected neighborhood conucils. + This function takes the selected neighborhood council (nc) value + from the "selected_nc" dropdown and output a line chart showing + the number of requests throughout the day and the average number + of requests throughout the day (total number of requests / all 99 neighborhood councils). + Args: + selected_nc: A string argument automatically detected by Dash + callback function when "selected_nc" element is selected in the layout. + Returns: + nc_avg_comp_line_chart: line chart showing the number of requests + throughout the day for the selected neighborhood council and average + """ + # If dropdown value is empty, use all data available. + if not selected_nc: + df = api_data_df + else: + df = api_data_df[api_data_df.council_name == selected_nc] + + # Calculating the average number of requests throughout the day. + neighborhood_sum_df = df.groupby(["created_date"]).agg("sum").reset_index() # noqa + total_sum_df = api_data_df.groupby(["created_date"]).agg("sum").reset_index() + total_sum_df["nc_avg"] = total_sum_df["counts"] / 99 + merged_df = neighborhood_sum_df.merge(total_sum_df["nc_avg"].to_frame(), + left_index=True, right_index=True) # noqa + + nc_avg_comp_line_chart = px.line( + merged_df, + x="created_date", + y=["counts", "nc_avg"], + color_discrete_sequence=DISCRETE_COLORS, + labels=LABELS, + title="Number of " + selected_nc + + " Requests compared with the average Neighborhood Council" + ) + + nc_avg_comp_line_chart.update_xaxes( + tickformat="%a\n%m/%d", + ) + + nc_avg_comp_line_chart.update_traces( + mode="markers+lines" + ) # add markers to lines. + + return nc_avg_comp_line_chart \ No newline at end of file From 978abc8c43f2fcdc6a300a0bca4a3fda92e15cd7 Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Fri, 30 Sep 2022 12:56:44 -0700 Subject: [PATCH 02/21] fix selected_request --- server/dash/pages/nc_summary_comparison.py | 233 ++++++++++++++++++++- 1 file changed, 227 insertions(+), 6 deletions(-) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index 93aabbb37..d41c8f5c7 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -57,6 +57,21 @@ def merge_dict(d1, d2): SUMMARY_DASHBOARD_TITLE = "LA 311 Requests - Neighborhood Council Summary Dashboard" COMPARISON_DASHBOARD_TITLE = "LA 311 Requests - Neighborhood Council Comparison Dashboard" +def generate_summary_header(): + """Generates the header for the summary dashboard. + This function generates the html elements for the + title and data quality toggle for summary dashboard. + Return: + Dash html div element containing title and data + quality toggle. + """ + return html.Div(children=[ + html.H2(SUMMARY_DASHBOARD_TITLE, style={"vertical-align": "middle"}), + html.Div([daq.ToggleSwitch(id="data_quality_switch", value=True, + style={"height": "0.5vh"}, size=35), + html.Div(id="data_quality_output")], style={"font-family": "Open Sans"}) + ], style=merge_dict(EQUAL_SPACE_STYLE, {"vertical-align": "middle", + "height": "5vh", "width": "97.5vw"})) def generate_summary_dropdowns(): """Generates the dropdowns for the summary dashboard. @@ -88,14 +103,48 @@ def generate_summary_line_chart(): style=merge_dict(BORDER_STYLE, {"height": "40vh", "width": "97.4vw"})) +def generate_summary_pie_chart(): + """Generates the pie chart for the summary dashboard. + This function generates the html elements for the + request type pie chart for summary dashboard. + Return: + Dash html div element containing request type pie chart. + """ + return html.Div( + dcc.Graph( + id="req_type_pie_chart", style={"height": "40vh", + "width": "48.5vw"} + ), style=merge_dict(CHART_OUTLINE_STYLE, {"width": "48.5vw", + "height": "40vh"})) # for border-radius , add stuff later + + +def generate_summary_histogram(): + """Generates the histogram for the summary dashboard. + This function generates the html elements for the + request time to close histogram for summary dashboard. + Return: + Dash html div element containing request time to close histogram. + """ + return html.Div( + dcc.Graph( + id="time_close_histogram", style={"height": "40vh", + "width": "48vw"} + ), style=merge_dict(CHART_OUTLINE_STYLE, {"width": "48vw", + "height": "40vh"})) # LAYOUT. layout = html.Div([ html.Div(children=[ + generate_summary_header(), generate_summary_dropdowns(), html.Div(html.Br(), style={"height": "0.5vh"}), - generate_summary_line_chart() + generate_summary_line_chart(), + html.Div(html.Br(), style=DIVIDER_STYLE), + html.Div(children=[ + generate_summary_pie_chart(), + generate_summary_histogram() + ], style=merge_dict(EQUAL_SPACE_STYLE, {"width": "97.5vw"})) ]), ]) @@ -129,7 +178,7 @@ def generate_dynamic_filter(selected_nc): req_types = sorted(list(set(df["typeName"]))) return req_types, ' ' - +# TODO: Add request type filter if necessary? @callback( Output("nc_avg_comp_line_chart", "figure"), [Input("selected_nc", "value")] @@ -150,13 +199,14 @@ def update_line_chart(selected_nc): """ # If dropdown value is empty, use all data available. if not selected_nc: - df = api_data_df + df = report_json + selected_nc = 'Total' else: - df = api_data_df[api_data_df.council_name == selected_nc] + df = report_json[report_json.council_name == selected_nc] # Calculating the average number of requests throughout the day. neighborhood_sum_df = df.groupby(["created_date"]).agg("sum").reset_index() # noqa - total_sum_df = api_data_df.groupby(["created_date"]).agg("sum").reset_index() + total_sum_df = report_json.groupby(["created_date"]).agg("sum").reset_index() total_sum_df["nc_avg"] = total_sum_df["counts"] / 99 merged_df = neighborhood_sum_df.merge(total_sum_df["nc_avg"].to_frame(), left_index=True, right_index=True) # noqa @@ -179,4 +229,175 @@ def update_line_chart(selected_nc): mode="markers+lines" ) # add markers to lines. - return nc_avg_comp_line_chart \ No newline at end of file + return nc_avg_comp_line_chart + + +@callback( + Output("req_type_pie_chart", "figure"), + Input("selected_nc", "value"), + [Input("selected_request_types", "value")] +) +def generate_req_type_pie_chart(selected_nc, selected_request_types=None): + """Generates the request type pie chart based on selected filters. + This callback function takes the selected neighborhood council (nc) value + from the "selected_nc" dropdown, selected request type from "selected_request_types" + dropdown to output a pie chart showing the share of request types. + Args: + selected_nc: A string argument automatically detected by Dash callback function + when "selected_nc" element is selected in the layout. + selected_request_types: A list of strings automatically detected by Dash callback + function when "selected_request_types" element is selected in the layout, default None. + Returns: + pie chart showing the share of each request type. + """ + df = generate_filtered_dataframe(api_data_df, selected_nc, selected_request_types) + + # Pie Chart for the distribution of Request Types. + print(" * Generating requests types pie chart") + req_type = pd.DataFrame(df["typeName"].value_counts()) + req_type = req_type.reset_index() + req_type_pie_chart = px.pie(req_type, values="typeName", names="index", + title="Share of each Request Type") + req_type_pie_chart.update_layout(margin=dict(l=50, r=50, b=50, t=50), + legend_title=dict(font=dict(size=10)), font=dict(size=9)) + return req_type_pie_chart + + +@callback( + Output("time_close_histogram", "figure"), + Output("data_quality_output", "children"), + Input("selected_nc", "value"), + [Input("selected_request_types", "value")], + Input("data_quality_switch", "value") +) +def generate_time_to_close_histogram(selected_nc, selected_request_types=None, +data_quality_switch=True): + """Generates the request type pie chart based on selected filters. + This callback function takes the selected neighborhood council (nc) + value from the "selected_nc" dropdown, selected request type from "selected_request_types" + dropdown to output a histogram for the time it takes for each request to close. + Args: + selected_nc: A string argument automatically detected by Dash callback + function when "selected_nc" element is selected in the layout. + selected_request_types: A list of strings automatically detected by Dash + callback function when "selected_request_types" element is selected in the + layout, default None. + data_quality_switch: A boolean for data quality filter automatically detected + by Dash callback function when "data_quality_switch" element is selected in + the layout, default True. + Returns: + time_close_histogram: histogram showing the distribution of the request time to close. + data_quality_output: A string stating the status of the data quality filter + ("Quality Filter: On" or "Quality Filter: Off"). + """ + df = generate_filtered_dataframe(api_data_df, selected_nc, selected_request_types) + df, num_bins, data_quality_output = filter_bad_quality_data(df, data_quality_switch) + + # Distribution for the total number of requests. + print(" * Generating requests time to close histogram") + time_close_histogram = px.histogram(df, x="timeToClose", + title="Distribution of Time to Close Request", + nbins=num_bins, range_x=[min( + df.loc[:, "timeToClose"]), max(df.loc[:, "timeToClose"])], + labels={"timeToClose": "Request Duration", + "count": "Frequency"}) + time_close_histogram.update_layout(margin=dict(l=50, r=50, b=50, t=50), font=dict(size=9)) + return time_close_histogram, data_quality_output + +## Helper Functions + +def add_datetime_column(df, colname): + """Adds a datetime column to a dataframe. + This function takes a datetime column 'colname' in string type, remove the last 4 characters, + split date and time by character 'T', and finally combine date and time into a single string again. + The function then converts the string using pandas's to_datetime function. + Args: + df: dataframe to add the datetime column to. + colname: the datetime column that is in string type. + + Return: + A dataframe with new column 'colnameDT' that is in datetime type. + """ + df.loc[:, colname+"DT"] = pd.to_datetime( + df.loc[:, colname].str[:-4].str.split("T").str.join(" ")) + return df + +def generate_filtered_dataframe(api_data_df, selected_nc, selected_request_types): + """Outputs the filtered dataframe based on the selected filters + This function takes the original dataframe "api_data_df", selected neighborhood + council "selected_nc", as well as the selected request type "selected_request_types" + to output a dataframe with matching records. + Args: + api_data_df: full 311-request data directly access from the 311 data API. + selected_nc: A string argument automatically detected by Dash callback + function when "selected_nc" element is selected in the layout. + selected_request_types: A list of strings automatically detected by Dash + callback function when "selected_request_types" element is selected in the + layout, default None. + Returns: + pandas dataframe filtered by selected neighborhood council and request types + """ + print(selected_request_types) + print(type(selected_request_types)) + print(selected_request_types is None) + print(selected_request_types == ' ') + if not selected_nc: + df = api_data_df + else: + df = api_data_df[api_data_df["councilName"] == selected_nc] + # Filter as per selection on Request Type Dropdown. + if not selected_request_types and not selected_nc: + df = api_data_df + elif selected_request_types != ' ': + df = df[df["typeName"].isin(selected_request_types)] + return df + + +def filter_bad_quality_data(df, data_quality_switch=True): + """Filters the dataframe based on pre-defined data quality filters. + This function takes the original dataframe "df" and filters out records + with an outlier amount of request time to close based on the Freedman-Diaconis Rule. + Generally 10% of data is excluded. + Args: + df: 311-request data accessed from the API. + data_quality_switch: A boolean argument automatically detected by Dash. + callback function when "data_quality_switch" toggle element is selected in layout. + + Returns: + df: filtered dataframe excluding outliers. + num_bins: the number of bins that will be used to plot histogram. + data_quality_output: a string being displayed on whether the data quality switch is on or off. + """ + print("* Getting quality data.") + df = add_datetime_column(df, "createdDate") + df = add_datetime_column(df, "closedDate") + df.loc[:, "timeToClose"] = (df.loc[:, "closedDateDT"] - df.loc[:, "createdDateDT"]).dt.days + + # Calculate the Optimal number of bins based on Freedman-Diaconis Rule. + + # Replace empty rows with 0.0000001 To avoid log(0) error later. + df.loc[:, "timeToClose"] = df.loc[:, "timeToClose"].fillna(0.0000001) + + # Replace negative values + df = df[df["timeToClose"] > 0] + if df.shape[0] == 0: + raise PreventUpdate() + + # Data Quality switch to remove outliers as defined by Median +- 1.5*IQR. + if data_quality_switch: + num_bins = len(np.histogram_bin_edges(df.loc[:, "timeToClose"], bins='fd'))-1 + + # Log Transform, Compute IQR, then exclude outliers. + df.loc[:, "logTimeToClose"] = np.log(df.loc[:, "timeToClose"]) + log_q3, log_q1 = np.percentile(df.loc[:, "logTimeToClose"], [75, 25]) + log_iqr = log_q3 - log_q1 + filtered_df = df[(df.loc[:, "logTimeToClose"] > 1.5 * log_iqr - np.median(df.loc[:, "logTimeToClose"])) & + (df.loc[:, "logTimeToClose"] < 1.5 * log_iqr + np.median(df.loc[:, "logTimeToClose"]))] + if filtered_df.shape[0] > 0: + df = filtered_df + data_quality_output = "Quality Filter: On" + else: + num_bins = 10 + + data_quality_output = "Quality Filter: Off" + return df, num_bins, data_quality_output \ No newline at end of file From 1a891738cfc24169065e5dab6a86a70b9ac63996 Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Fri, 30 Sep 2022 13:44:29 -0700 Subject: [PATCH 03/21] Remove print statements --- server/dash/pages/nc_summary_comparison.py | 23 ++++++++-------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index d41c8f5c7..1538ce88c 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -169,7 +169,6 @@ def generate_dynamic_filter(selected_nc): neigbhorhood council. """ # If no neighborhood council is selected, use all data. - print("got new selected_nc ", selected_nc) if not selected_nc: df = api_data_df else: @@ -337,19 +336,14 @@ def generate_filtered_dataframe(api_data_df, selected_nc, selected_request_types Returns: pandas dataframe filtered by selected neighborhood council and request types """ - print(selected_request_types) - print(type(selected_request_types)) - print(selected_request_types is None) - print(selected_request_types == ' ') - if not selected_nc: - df = api_data_df - else: + if (not selected_request_types or selected_request_types == ' ') and not selected_nc: + df = api_data_df + elif selected_nc and (not selected_request_types or selected_request_types == ' '): df = api_data_df[api_data_df["councilName"] == selected_nc] - # Filter as per selection on Request Type Dropdown. - if not selected_request_types and not selected_nc: - df = api_data_df elif selected_request_types != ' ': df = df[df["typeName"].isin(selected_request_types)] + else: + df = api_data_df return df @@ -372,7 +366,6 @@ def filter_bad_quality_data(df, data_quality_switch=True): df = add_datetime_column(df, "createdDate") df = add_datetime_column(df, "closedDate") df.loc[:, "timeToClose"] = (df.loc[:, "closedDateDT"] - df.loc[:, "createdDateDT"]).dt.days - # Calculate the Optimal number of bins based on Freedman-Diaconis Rule. # Replace empty rows with 0.0000001 To avoid log(0) error later. @@ -391,13 +384,13 @@ def filter_bad_quality_data(df, data_quality_switch=True): df.loc[:, "logTimeToClose"] = np.log(df.loc[:, "timeToClose"]) log_q3, log_q1 = np.percentile(df.loc[:, "logTimeToClose"], [75, 25]) log_iqr = log_q3 - log_q1 - filtered_df = df[(df.loc[:, "logTimeToClose"] > 1.5 * log_iqr - np.median(df.loc[:, "logTimeToClose"])) & - (df.loc[:, "logTimeToClose"] < 1.5 * log_iqr + np.median(df.loc[:, "logTimeToClose"]))] + filtered_df = df[(df.loc[:, "logTimeToClose"] > log_q1 - 1.5 * log_iqr) & + (df.loc[:, "logTimeToClose"] < 1.5 * log_iqr + log_q3)] if filtered_df.shape[0] > 0: df = filtered_df data_quality_output = "Quality Filter: On" else: num_bins = 10 - data_quality_output = "Quality Filter: Off" + return df, num_bins, data_quality_output \ No newline at end of file From 4b3c89bbca46d834dbdd594db4b2b784cf04826c Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Fri, 30 Sep 2022 13:54:02 -0700 Subject: [PATCH 04/21] Add comparison filter --- server/dash/pages/nc_summary_comparison.py | 29 ++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index 1538ce88c..17fa4928a 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -133,6 +133,21 @@ def generate_summary_histogram(): "height": "40vh"})) +def generate_council_name_dropdown(output_id): + """Generates the neighborhood council (nc) dropdown for the + comparison dashboard. + This function generates the html elements for the + nc dropdown for the comparison dashboard. + Args: + output_id: the id corresponding to the dash element in the layout. + Return: + Dash html div element containing nc drop down for left pane filtering. + """ + return html.Div(dcc.Dropdown(sorted(list(set(api_data_df["councilName"]))), + value=" ", id=output_id, + placeholder="Select a Neighborhood Council..."), + style=merge_dict(INLINE_STYLE, {"width": "48.5vw"})) + # LAYOUT. layout = html.Div([ html.Div(children=[ @@ -144,8 +159,18 @@ def generate_summary_histogram(): html.Div(children=[ generate_summary_pie_chart(), generate_summary_histogram() - ], style=merge_dict(EQUAL_SPACE_STYLE, {"width": "97.5vw"})) - ]), + ], style=merge_dict(EQUAL_SPACE_STYLE, {"width": "97.5vw"})), + html.Div(html.Br(), style=DIVIDER_STYLE), + # Neighborhood Council Summarization Dashboard. + html.Div(children=[html.H2(COMPARISON_DASHBOARD_TITLE)], + style=merge_dict(CENTER_ALIGN_STYLE, {"height": "5vh"})), + # Comparison Dropdowns. + html.Div(children=[ + generate_council_name_dropdown('nc_comp_dropdown'), + generate_council_name_dropdown('nc_comp_dropdown2') + ], style=merge_dict(EQUAL_SPACE_STYLE, {"width": "97.5vw", "height": "12vh"})), + html.Div(html.Br(), style=DIVIDER_STYLE), + ]), ]) # CALLBACK FUNCTIONS. From 0d449d424ad083fbd9009a4ed0908707ddef0423 Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Fri, 30 Sep 2022 16:21:47 -0700 Subject: [PATCH 05/21] Add indicator visuals --- server/dash/pages/nc_summary_comparison.py | 388 +++++++++++++++------ 1 file changed, 281 insertions(+), 107 deletions(-) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index 17fa4928a..f4ec633e1 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -57,6 +57,119 @@ def merge_dict(d1, d2): SUMMARY_DASHBOARD_TITLE = "LA 311 Requests - Neighborhood Council Summary Dashboard" COMPARISON_DASHBOARD_TITLE = "LA 311 Requests - Neighborhood Council Comparison Dashboard" + +## Helper Functions + +def add_datetime_column(df, colname): + """Adds a datetime column to a dataframe. + This function takes a datetime column 'colname' in string type, remove the last 4 characters, + split date and time by character 'T', and finally combine date and time into a single string again. + The function then converts the string using pandas's to_datetime function. + Args: + df: dataframe to add the datetime column to. + colname: the datetime column that is in string type. + + Return: + A dataframe with new column 'colnameDT' that is in datetime type. + """ + df.loc[:, colname+"DT"] = pd.to_datetime( + df.loc[:, colname].str[:-4].str.split("T").str.join(" ")) + return df + +def generate_filtered_dataframe(api_data_df, selected_nc, selected_request_types): + """Outputs the filtered dataframe based on the selected filters + This function takes the original dataframe "api_data_df", selected neighborhood + council "selected_nc", as well as the selected request type "selected_request_types" + to output a dataframe with matching records. + Args: + api_data_df: full 311-request data directly access from the 311 data API. + selected_nc: A string argument automatically detected by Dash callback + function when "selected_nc" element is selected in the layout. + selected_request_types: A list of strings automatically detected by Dash + callback function when "selected_request_types" element is selected in the + layout, default None. + Returns: + pandas dataframe filtered by selected neighborhood council and request types + """ + if (not selected_request_types or selected_request_types == ' ') and not selected_nc: + df = api_data_df + elif selected_nc and (not selected_request_types or selected_request_types == ' '): + df = api_data_df[api_data_df["councilName"] == selected_nc] + elif selected_request_types != ' ': + df = df[df["typeName"].isin(selected_request_types)] + else: + df = api_data_df + return df + + +def filter_bad_quality_data(df, data_quality_switch=True): + """Filters the dataframe based on pre-defined data quality filters. + This function takes the original dataframe "df" and filters out records + with an outlier amount of request time to close based on the Freedman-Diaconis Rule. + Generally 10% of data is excluded. + Args: + df: 311-request data accessed from the API. + data_quality_switch: A boolean argument automatically detected by Dash. + callback function when "data_quality_switch" toggle element is selected in layout. + + Returns: + df: filtered dataframe excluding outliers. + num_bins: the number of bins that will be used to plot histogram. + data_quality_output: a string being displayed on whether the data quality switch is on or off. + """ + print("* Getting quality data.") + df = add_datetime_column(df, "createdDate") + df = add_datetime_column(df, "closedDate") + df.loc[:, "timeToClose"] = (df.loc[:, "closedDateDT"] - df.loc[:, "createdDateDT"]).dt.days + # Calculate the Optimal number of bins based on Freedman-Diaconis Rule. + + # Replace empty rows with 0.0000001 To avoid log(0) error later. + df.loc[:, "timeToClose"] = df.loc[:, "timeToClose"].fillna(0.0000001) + + # Replace negative values + df = df[df["timeToClose"] > 0] + if df.shape[0] == 0: + raise PreventUpdate() + + # Data Quality switch to remove outliers as defined by Median +- 1.5*IQR. + if data_quality_switch: + num_bins = len(np.histogram_bin_edges(df.loc[:, "timeToClose"], bins='fd'))-1 + + # Log Transform, Compute IQR, then exclude outliers. + df.loc[:, "logTimeToClose"] = np.log(df.loc[:, "timeToClose"]) + log_q3, log_q1 = np.percentile(df.loc[:, "logTimeToClose"], [75, 25]) + log_iqr = log_q3 - log_q1 + filtered_df = df[(df.loc[:, "logTimeToClose"] > log_q1 - 1.5 * log_iqr) & + (df.loc[:, "logTimeToClose"] < 1.5 * log_iqr + log_q3)] + if filtered_df.shape[0] > 0: + df = filtered_df + data_quality_output = "Quality Filter: On" + else: + num_bins = 10 + data_quality_output = "Quality Filter: Off" + + return df, num_bins, data_quality_output + +def generate_comparison_filtered_df(api_data_df, selected_nc): + """Generates the dataframe based on selected neighborhood council. + This function takes the selected neighborhood council (nc) value from + the "selected_nc" dropdown and outputs a dataframe + corresponding to their neighorhood council with additional datetime column. + Args: + api_data_df: full 311-request data directly access from the 311 data API. + selected_nc: A string argument automatically detected by Dash callback + function when "nc_comp_dropdown" element is selected in the layout. + Returns: + Pandas dataframe with requests from the nc selected by nc_comp_dropdown. + """ + if not selected_nc: + df = api_data_df + else: + df = api_data_df[api_data_df["councilName"] == selected_nc] + df = add_datetime_column(df, "createdDate") + return df + +# Layout Helper Functions def generate_summary_header(): """Generates the header for the summary dashboard. This function generates the html elements for the @@ -148,33 +261,41 @@ def generate_council_name_dropdown(output_id): placeholder="Select a Neighborhood Council..."), style=merge_dict(INLINE_STYLE, {"width": "48.5vw"})) -# LAYOUT. -layout = html.Div([ - html.Div(children=[ - generate_summary_header(), - generate_summary_dropdowns(), - html.Div(html.Br(), style={"height": "0.5vh"}), - generate_summary_line_chart(), - html.Div(html.Br(), style=DIVIDER_STYLE), - html.Div(children=[ - generate_summary_pie_chart(), - generate_summary_histogram() - ], style=merge_dict(EQUAL_SPACE_STYLE, {"width": "97.5vw"})), - html.Div(html.Br(), style=DIVIDER_STYLE), - # Neighborhood Council Summarization Dashboard. - html.Div(children=[html.H2(COMPARISON_DASHBOARD_TITLE)], - style=merge_dict(CENTER_ALIGN_STYLE, {"height": "5vh"})), - # Comparison Dropdowns. - html.Div(children=[ - generate_council_name_dropdown('nc_comp_dropdown'), - generate_council_name_dropdown('nc_comp_dropdown2') - ], style=merge_dict(EQUAL_SPACE_STYLE, {"width": "97.5vw", "height": "12vh"})), - html.Div(html.Br(), style=DIVIDER_STYLE), - ]), -]) +def generate_comparison_total_req(output_id): + """Generates the indicator visual for the + total number of requests. + This function generates the html elements for the + indicator visual with matching output_id showing the total number of + requests for the comparison dashboard. + Args: + output_id: the id corresponding to the dash element in the layout. + Return: + Dash html div element containing label and indicator visual. + """ + return html.Div([ + html.H6("Total Number of Requests", style=CENTER_ALIGN_STYLE), + html.H1(id=output_id, style=CENTER_ALIGN_STYLE)], + style=merge_dict(CHART_OUTLINE_STYLE, {"width": "24vw", "height": "16vh"})) -# CALLBACK FUNCTIONS. +def generate_comparison_num_days(output_id): + """Generates the indicator visual for the + total number of days request spans. + This function generates the html elements for the + indicator visual with matching output_id showing the + total number of days request spans for the + comparison dashboard. + Args: + output_id: the id corresponding to the dash element in the layout. + Return: + Dash html div element containing label and indicator visual. + """ + return html.Div([ + html.H6("Number of Days", style=CENTER_ALIGN_STYLE), + html.H1(id=output_id, style=CENTER_ALIGN_STYLE)], + style=merge_dict(CHART_OUTLINE_STYLE, {"width": "24vw", "height": "16vh"})) + +# CALLBACK FUNCTIONS. @callback( [Output("selected_request_types", "options"), @@ -328,94 +449,147 @@ def generate_time_to_close_histogram(selected_nc, selected_request_types=None, time_close_histogram.update_layout(margin=dict(l=50, r=50, b=50, t=50), font=dict(size=9)) return time_close_histogram, data_quality_output -## Helper Functions - -def add_datetime_column(df, colname): - """Adds a datetime column to a dataframe. - This function takes a datetime column 'colname' in string type, remove the last 4 characters, - split date and time by character 'T', and finally combine date and time into a single string again. - The function then converts the string using pandas's to_datetime function. - Args: - df: dataframe to add the datetime column to. - colname: the datetime column that is in string type. - - Return: - A dataframe with new column 'colnameDT' that is in datetime type. - """ - df.loc[:, colname+"DT"] = pd.to_datetime( - df.loc[:, colname].str[:-4].str.split("T").str.join(" ")) - return df - -def generate_filtered_dataframe(api_data_df, selected_nc, selected_request_types): - """Outputs the filtered dataframe based on the selected filters - This function takes the original dataframe "api_data_df", selected neighborhood - council "selected_nc", as well as the selected request type "selected_request_types" - to output a dataframe with matching records. +@callback( + Output("total_req_card", "children"), + Output("total_req_card2", "children"), + Output("num_days_card", "children"), + Output("num_days_card2", "children"), + Input("nc_comp_dropdown", "value"), + Input("nc_comp_dropdown2", "value"), + prevent_initial_call=True +) +def generate_indicator_visuals(nc_comp_dropdown, nc_comp_dropdown2): + """Generates the overlapping line chart based on selected filters. + This function takes the the two neighborhood council (nc) value from + the "nc_comp_dropdown" dropdown and second selected neighborhood + council value from "nc_comp_dropdown2" + dropdown and outputs indicator visuals for the two nc's. Args: - api_data_df: full 311-request data directly access from the 311 data API. - selected_nc: A string argument automatically detected by Dash callback - function when "selected_nc" element is selected in the layout. - selected_request_types: A list of strings automatically detected by Dash - callback function when "selected_request_types" element is selected in the - layout, default None. - Returns: - pandas dataframe filtered by selected neighborhood council and request types + nc_comp_dropdown: A string argument automatically detected by + Dash callback function when "nc_comp_dropdown" element is + selected in the layout. + nc_comp_dropdown2: A string argument automatically detected by + Dash callback function when "nc_comp_dropdown2" element is + selected in the layout. + Returns: + total_req_card: integer for the the total number of request + in first selected neigborhood council. + total_req_card2: integer for the total number of requests + in the second selected neighborhood council. + num_days_card: integer for the total number of days the data + available in first selected neighborhood council span. + num_days_card2: integer for the total number of days the data + available in second selected neighborhood council span. """ - if (not selected_request_types or selected_request_types == ' ') and not selected_nc: - df = api_data_df - elif selected_nc and (not selected_request_types or selected_request_types == ' '): - df = api_data_df[api_data_df["councilName"] == selected_nc] - elif selected_request_types != ' ': - df = df[df["typeName"].isin(selected_request_types)] - else: - df = api_data_df - return df + df_nc1 = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) + df_nc2 = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown2) + # Total number of requests for first neigbhorhood council. + total_req_card = df_nc1.shape[0] + # Total number of requests for second neigbhorhood council. + total_req_card2 = df_nc2.shape[0] -def filter_bad_quality_data(df, data_quality_switch=True): - """Filters the dataframe based on pre-defined data quality filters. - This function takes the original dataframe "df" and filters out records - with an outlier amount of request time to close based on the Freedman-Diaconis Rule. - Generally 10% of data is excluded. - Args: - df: 311-request data accessed from the API. - data_quality_switch: A boolean argument automatically detected by Dash. - callback function when "data_quality_switch" toggle element is selected in layout. - - Returns: - df: filtered dataframe excluding outliers. - num_bins: the number of bins that will be used to plot histogram. - data_quality_output: a string being displayed on whether the data quality switch is on or off. - """ - print("* Getting quality data.") - df = add_datetime_column(df, "createdDate") - df = add_datetime_column(df, "closedDate") - df.loc[:, "timeToClose"] = (df.loc[:, "closedDateDT"] - df.loc[:, "createdDateDT"]).dt.days - # Calculate the Optimal number of bins based on Freedman-Diaconis Rule. + # Total number of days the available requests in first neigbhorhood council span. + num_days_card = np.max(df_nc1["createdDateDT"].dt.day) - \ + np.min(df_nc1["createdDateDT"].dt.day) + 1 - # Replace empty rows with 0.0000001 To avoid log(0) error later. - df.loc[:, "timeToClose"] = df.loc[:, "timeToClose"].fillna(0.0000001) + # Total number of days the available requests in second neigbhorhood council span. + num_days_card2 = np.max(df_nc2["createdDateDT"].dt.day) - \ + np.min(df_nc2["createdDateDT"].dt.day) + 1 - # Replace negative values - df = df[df["timeToClose"] > 0] - if df.shape[0] == 0: - raise PreventUpdate() + return total_req_card, total_req_card2, num_days_card, num_days_card2 - # Data Quality switch to remove outliers as defined by Median +- 1.5*IQR. - if data_quality_switch: - num_bins = len(np.histogram_bin_edges(df.loc[:, "timeToClose"], bins='fd'))-1 - # Log Transform, Compute IQR, then exclude outliers. - df.loc[:, "logTimeToClose"] = np.log(df.loc[:, "timeToClose"]) - log_q3, log_q1 = np.percentile(df.loc[:, "logTimeToClose"], [75, 25]) - log_iqr = log_q3 - log_q1 - filtered_df = df[(df.loc[:, "logTimeToClose"] > log_q1 - 1.5 * log_iqr) & - (df.loc[:, "logTimeToClose"] < 1.5 * log_iqr + log_q3)] - if filtered_df.shape[0] > 0: - df = filtered_df - data_quality_output = "Quality Filter: On" - else: - num_bins = 10 - data_quality_output = "Quality Filter: Off" +def generate_comparison_num_days(output_id): + """Generates the indicator visual for the + total number of days request spans. + This function generates the html elements for the + indicator visual with matching output_id showing the + total number of days request spans for the + comparison dashboard. + Args: + output_id: the id corresponding to the dash element in the layout. + Return: + Dash html div element containing label and indicator visual. + """ + return html.Div([ + html.H6("Number of Days", style=CENTER_ALIGN_STYLE), + html.H1(id=output_id, style=CENTER_ALIGN_STYLE)], + style=merge_dict(CHART_OUTLINE_STYLE, {"width": "24vw", "height": "16vh"})) + +def generate_comparison_req_source_bar(output_id): + """Generates the bar chart visual for the + request source in comparison dashboard. + This function generates the html elements for the + bar chart of request sources with matching output_id + on the comparison dashboard. + Args: + output_id: the id corresponding to the dash element in the layout. + Return: + Dash html div element containing request source bar chart. + """ + return html.Div(dcc.Graph(id=output_id, style={"height": "30vh"}), + style=merge_dict(CHART_OUTLINE_STYLE, { + "width": "48.5vw", "height": "30vh"})) - return df, num_bins, data_quality_output \ No newline at end of file +def generate_comparison_line_chart(): + """Generates the line chart visual for the + number of requests in comparison dashboard. + This function generates the html elements for the + overlapping line chart for number of requests on the + bottom of the comparison dashboard. + Return: + Dash html div element containing overlapping line chart. + """ + return html.Div(dcc.Graph(id="overlay_req_time_line_chart", style={"height": "32vh", + "width": "97.5vw"}), style=merge_dict(BORDER_STYLE, { + "height": "32vh", "width": "97.5vw"})) + +# LAYOUT. +layout = html.Div([ + html.Div(children=[ + generate_summary_header(), + generate_summary_dropdowns(), + html.Div(html.Br(), style={"height": "0.5vh"}), + generate_summary_line_chart(), + html.Div(html.Br(), style=DIVIDER_STYLE), + html.Div(children=[ + generate_summary_pie_chart(), + generate_summary_histogram() + ], style=merge_dict(EQUAL_SPACE_STYLE, {"width": "97.5vw"})) + ]), + html.Div(html.Br(), style=DIVIDER_STYLE), + # Neighborhood Council Summarization Dashboard. + html.Div(children=[html.H2(COMPARISON_DASHBOARD_TITLE)], + style=merge_dict(CENTER_ALIGN_STYLE, {"height": "5vh"})), + # Comparison Dropdowns. + html.Div(children=[ + generate_council_name_dropdown('nc_comp_dropdown'), + generate_council_name_dropdown('nc_comp_dropdown2') + ], style=merge_dict(EQUAL_SPACE_STYLE, {"width": "97.5vw", "height": "12vh"})), + html.Div(html.Br(), style=DIVIDER_STYLE), + html.Div(children=[ + html.Div(children=[ + # Indicator Visuals for Total number of requests and the number of + # days the data spans across. + generate_comparison_total_req("total_req_card"), + generate_comparison_num_days("num_days_card") + ], style=merge_dict(EQUAL_SPACE_STYLE, {"width": "48.5vw"})), + # Indicator Visuals for Total number of requests and the number of days + # the data spans across. + html.Div(children=[ + generate_comparison_total_req("total_req_card2"), + generate_comparison_num_days("num_days_card2") + ], style=merge_dict(EQUAL_SPACE_STYLE, {"width": "48.5vw"})) + ], style=merge_dict(EQUAL_SPACE_STYLE, {"width": "97.5vw"})), + html.Div(html.Br(), style=DIVIDER_STYLE), + # NC Comparison - Request Source Bar Charts. + html.Div(children=[ + generate_comparison_req_source_bar("req_source_bar_chart"), + generate_comparison_req_source_bar("req_source_bar_chart2") + ], style=merge_dict(EQUAL_SPACE_STYLE, {"width": "97.5vw"})), + html.Div(html.Br(), style=DIVIDER_STYLE), + # NC Comparison - Number of Requests per day Overlapping line chart. + generate_comparison_line_chart() + +]) \ No newline at end of file From 65fa500640adb0f4c4497992c299c9437b10d630 Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Fri, 30 Sep 2022 16:24:32 -0700 Subject: [PATCH 06/21] Overlay line chart --- server/dash/pages/nc_summary_comparison.py | 89 ++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index f4ec633e1..43fc9d487 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -499,6 +499,95 @@ def generate_indicator_visuals(nc_comp_dropdown, nc_comp_dropdown2): return total_req_card, total_req_card2, num_days_card, num_days_card2 +@callback( + Output("req_source_bar_chart", "figure"), + Output("req_source_bar_chart2", "figure"), + Input("nc_comp_dropdown", "value"), + Input("nc_comp_dropdown2", "value"), + prevent_initial_call=True +) +def generate_req_source_bar_charts(nc_comp_dropdown, nc_comp_dropdown2): + """Generates a pair of request source bar chart based on the two + neighborhood council dropdown. + This function takes the first selected neighborhood council (nc) + value from the "nc_comp_dropdown" dropdown and second selected + neighborhood council value from "nc_comp_dropdown2" + dropdown and output a pair of request_source bar charts. + Args: + nc_comp_dropdown: A string argument automatically detected + by Dash callback function when "nc_comp_dropdown" element + is selected in the layout. + nc_comp_dropdown2: A string argument automatically detected by + Dash callback function when "nc_comp_dropdown2" element is + selected in the layout. + Returns: + req_source_bar_chart: bar chart showing the number of request + from each source for the first neighborhood council + (e.g. mobile, app, self-report...etc). + req_source_bar_chart2: bar chart showing the number of request + from each source for the second neighborhood council + (e.g. mobile, app, self-report...etc). + """ + df_nc1 = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) + df_nc2 = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown2) + # Bar chart of different Request Type Sources for first selected neigbhorhood council. + req_source = pd.DataFrame(df_nc1["sourceName"].value_counts()) + req_source = req_source.reset_index() + req_source_bar_chart = px.bar(req_source, x="sourceName", y="index", + orientation="h", title="Number of Requests by Source", labels={ + "index": "Request Source", "sourceName": "Frequency"}) + req_source_bar_chart.update_layout(margin=dict(l=25, r=25, b=25, t=50), font=dict(size=9)) + + # Bar chart of different Request Type Sources for second selected neigbhorhood council. + req_source2 = pd.DataFrame(df_nc2["sourceName"].value_counts()) + req_source2 = req_source2.reset_index() + req_source_bar_chart2 = px.bar(req_source2, x="sourceName", y="index", + orientation="h", title="Number of Requests by Source", labels={ + "index": "Request Source", "sourceName": "Frequency"}) + req_source_bar_chart2.update_layout(margin=dict(l=25, r=25, b=25, t=50), font=dict(size=9)) + + return req_source_bar_chart, req_source_bar_chart2 + +@callback( + Output("overlay_req_time_line_chart", "figure"), + Input("nc_comp_dropdown", "value"), + Input("nc_comp_dropdown2", "value"), + prevent_initial_call=True +) +def generate_overlay_line_chart(nc_comp_dropdown, nc_comp_dropdown2): + """Generates the overlapping line chart based on selected filters. + This function takes the the two neighborhood council (nc) value + from the "nc_comp_dropdown" dropdown and second selected + neighborhood council value from "nc_comp_dropdown2" + dropdown and outputs a overlapping line chart. + Args: + nc_comp_dropdown: A string argument automatically detected + by Dash callback function when "nc_comp_dropdown" element + is selected in the layout. + nc_comp_dropdown2: A string argument automatically detected + by Dash callback function when "nc_comp_dropdown2" element + is selected in the layout. + Returns: + Line chart showing the number of requests throughout the + day for both first and second selected neighborhood council. + """ + df_nc1 = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) + df_nc2 = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown2) + # Overlapping line chart for number of request throughout the day + # for both first and second neighborhood council. + req_time = pd.DataFrame(df_nc1.groupby("createdDateDT", as_index=False)["srnumber"].count()) + req_time2 = pd.DataFrame(df_nc2.groupby("createdDateDT", as_index=False)["srnumber"].count()) + overlay_req_time_line_chart = go.Figure() + overlay_req_time_line_chart.add_trace(go.Scatter( + x=req_time["createdDateDT"], y=req_time["srnumber"], mode="lines", name="NC1")) + overlay_req_time_line_chart.add_trace(go.Scatter( + x=req_time2["createdDateDT"], y=req_time2["srnumber"], mode="lines", name="NC2")) + + overlay_req_time_line_chart.update_layout(title="Number of Request Throughout the Day", + margin=dict(l=25, r=25, b=35, t=50), xaxis_range=[min( + min(req_time["createdDateDT"]), min(req_time2["createdDateDT"])), + max(max(req_time["createdDateDT"]), max(req_time2["createdDateDT"]))], font=dict(size=9)) + return def generate_comparison_num_days(output_id): """Generates the indicator visual for the From c63f02ba426e019fdbefb472fd5a6495964efe81 Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Fri, 30 Sep 2022 16:31:57 -0700 Subject: [PATCH 07/21] Structure code by functionality --- server/dash/pages/nc_summary_comparison.py | 96 +++++++++++----------- 1 file changed, 47 insertions(+), 49 deletions(-) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index 43fc9d487..8fdc7dd27 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -58,8 +58,7 @@ def merge_dict(d1, d2): COMPARISON_DASHBOARD_TITLE = "LA 311 Requests - Neighborhood Council Comparison Dashboard" -## Helper Functions - +## DATA WRANGLING HELPER FUNCTIONS. def add_datetime_column(df, colname): """Adds a datetime column to a dataframe. This function takes a datetime column 'colname' in string type, remove the last 4 characters, @@ -169,7 +168,7 @@ def generate_comparison_filtered_df(api_data_df, selected_nc): df = add_datetime_column(df, "createdDate") return df -# Layout Helper Functions +# VISUALS HELPER FUNCTIONS. def generate_summary_header(): """Generates the header for the summary dashboard. This function generates the html elements for the @@ -295,8 +294,52 @@ def generate_comparison_num_days(output_id): html.H1(id=output_id, style=CENTER_ALIGN_STYLE)], style=merge_dict(CHART_OUTLINE_STYLE, {"width": "24vw", "height": "16vh"})) -# CALLBACK FUNCTIONS. +def generate_comparison_num_days(output_id): + """Generates the indicator visual for the + total number of days request spans. + This function generates the html elements for the + indicator visual with matching output_id showing the + total number of days request spans for the + comparison dashboard. + Args: + output_id: the id corresponding to the dash element in the layout. + Return: + Dash html div element containing label and indicator visual. + """ + return html.Div([ + html.H6("Number of Days", style=CENTER_ALIGN_STYLE), + html.H1(id=output_id, style=CENTER_ALIGN_STYLE)], + style=merge_dict(CHART_OUTLINE_STYLE, {"width": "24vw", "height": "16vh"})) + +def generate_comparison_req_source_bar(output_id): + """Generates the bar chart visual for the + request source in comparison dashboard. + This function generates the html elements for the + bar chart of request sources with matching output_id + on the comparison dashboard. + Args: + output_id: the id corresponding to the dash element in the layout. + Return: + Dash html div element containing request source bar chart. + """ + return html.Div(dcc.Graph(id=output_id, style={"height": "30vh"}), + style=merge_dict(CHART_OUTLINE_STYLE, { + "width": "48.5vw", "height": "30vh"})) +def generate_comparison_line_chart(): + """Generates the line chart visual for the + number of requests in comparison dashboard. + This function generates the html elements for the + overlapping line chart for number of requests on the + bottom of the comparison dashboard. + Return: + Dash html div element containing overlapping line chart. + """ + return html.Div(dcc.Graph(id="overlay_req_time_line_chart", style={"height": "32vh", + "width": "97.5vw"}), style=merge_dict(BORDER_STYLE, { + "height": "32vh", "width": "97.5vw"})) + +# CALLBACK FUNCTIONS. @callback( [Output("selected_request_types", "options"), Output("selected_request_types", "value")], @@ -589,51 +632,6 @@ def generate_overlay_line_chart(nc_comp_dropdown, nc_comp_dropdown2): max(max(req_time["createdDateDT"]), max(req_time2["createdDateDT"]))], font=dict(size=9)) return -def generate_comparison_num_days(output_id): - """Generates the indicator visual for the - total number of days request spans. - This function generates the html elements for the - indicator visual with matching output_id showing the - total number of days request spans for the - comparison dashboard. - Args: - output_id: the id corresponding to the dash element in the layout. - Return: - Dash html div element containing label and indicator visual. - """ - return html.Div([ - html.H6("Number of Days", style=CENTER_ALIGN_STYLE), - html.H1(id=output_id, style=CENTER_ALIGN_STYLE)], - style=merge_dict(CHART_OUTLINE_STYLE, {"width": "24vw", "height": "16vh"})) - -def generate_comparison_req_source_bar(output_id): - """Generates the bar chart visual for the - request source in comparison dashboard. - This function generates the html elements for the - bar chart of request sources with matching output_id - on the comparison dashboard. - Args: - output_id: the id corresponding to the dash element in the layout. - Return: - Dash html div element containing request source bar chart. - """ - return html.Div(dcc.Graph(id=output_id, style={"height": "30vh"}), - style=merge_dict(CHART_OUTLINE_STYLE, { - "width": "48.5vw", "height": "30vh"})) - -def generate_comparison_line_chart(): - """Generates the line chart visual for the - number of requests in comparison dashboard. - This function generates the html elements for the - overlapping line chart for number of requests on the - bottom of the comparison dashboard. - Return: - Dash html div element containing overlapping line chart. - """ - return html.Div(dcc.Graph(id="overlay_req_time_line_chart", style={"height": "32vh", - "width": "97.5vw"}), style=merge_dict(BORDER_STYLE, { - "height": "32vh", "width": "97.5vw"})) - # LAYOUT. layout = html.Div([ html.Div(children=[ From aed0645b20cf73304cfabcb42630cb79fa1cca25 Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Fri, 30 Sep 2022 17:47:25 -0700 Subject: [PATCH 08/21] Changes --- server/dash/pages/nc_summary_comparison.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index 8fdc7dd27..88d40eede 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -630,7 +630,7 @@ def generate_overlay_line_chart(nc_comp_dropdown, nc_comp_dropdown2): margin=dict(l=25, r=25, b=35, t=50), xaxis_range=[min( min(req_time["createdDateDT"]), min(req_time2["createdDateDT"])), max(max(req_time["createdDateDT"]), max(req_time2["createdDateDT"]))], font=dict(size=9)) - return + return overlay_req_time_line_chart # LAYOUT. layout = html.Div([ From b571f5dda01de3a96e5646ac62419098fa4e5e2e Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Mon, 10 Oct 2022 20:38:26 -0700 Subject: [PATCH 09/21] No req type selected --- server/dash/pages/nc_summary_comparison.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index 88d40eede..da7d4daaf 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -90,9 +90,10 @@ def generate_filtered_dataframe(api_data_df, selected_nc, selected_request_types Returns: pandas dataframe filtered by selected neighborhood council and request types """ - if (not selected_request_types or selected_request_types == ' ') and not selected_nc: + no_req_type_selected = (not selected_request_types or selected_request_types == ' ') + if no_req_type_selected and not selected_nc: df = api_data_df - elif selected_nc and (not selected_request_types or selected_request_types == ' '): + elif selected_nc and no_req_type_selected: df = api_data_df[api_data_df["councilName"] == selected_nc] elif selected_request_types != ' ': df = df[df["typeName"].isin(selected_request_types)] @@ -255,8 +256,8 @@ def generate_council_name_dropdown(output_id): Return: Dash html div element containing nc drop down for left pane filtering. """ - return html.Div(dcc.Dropdown(sorted(list(set(api_data_df["councilName"]))), - value=" ", id=output_id, + councils = sorted(list(set(api_data_df["councilName"]))) + return html.Div(dcc.Dropdown(councils, councils[0], id=output_id, placeholder="Select a Neighborhood Council..."), style=merge_dict(INLINE_STYLE, {"width": "48.5vw"})) From 0be37528445f00c5a705326e71112a2d428cff4a Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Mon, 10 Oct 2022 20:55:27 -0700 Subject: [PATCH 10/21] Update column name --- server/dash/pages/nc_summary_comparison.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index da7d4daaf..e0cce8536 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -69,11 +69,12 @@ def add_datetime_column(df, colname): colname: the datetime column that is in string type. Return: - A dataframe with new column 'colnameDT' that is in datetime type. + df: A dataframe with new column 'colnameDT' that is in datetime type. + colname: String that represents the new column name. """ - df.loc[:, colname+"DT"] = pd.to_datetime( + df.loc[:, colname+"_dt"] = pd.to_datetime( df.loc[:, colname].str[:-4].str.split("T").str.join(" ")) - return df + return df, colname + "_dt" def generate_filtered_dataframe(api_data_df, selected_nc, selected_request_types): """Outputs the filtered dataframe based on the selected filters @@ -118,15 +119,15 @@ def filter_bad_quality_data(df, data_quality_switch=True): data_quality_output: a string being displayed on whether the data quality switch is on or off. """ print("* Getting quality data.") - df = add_datetime_column(df, "createdDate") - df = add_datetime_column(df, "closedDate") - df.loc[:, "timeToClose"] = (df.loc[:, "closedDateDT"] - df.loc[:, "createdDateDT"]).dt.days + df, create_dt_col_name = add_datetime_column(df, "createdDate") + df, close_dt_col_name = add_datetime_column(df, "closedDate") + df.loc[:, "timeToClose"] = (df.loc[:, close_dt_col_name] - df.loc[:, create_dt_col_name]).dt.days # Calculate the Optimal number of bins based on Freedman-Diaconis Rule. # Replace empty rows with 0.0000001 To avoid log(0) error later. df.loc[:, "timeToClose"] = df.loc[:, "timeToClose"].fillna(0.0000001) - # Replace negative values + # Removing negative request time to close values. df = df[df["timeToClose"] > 0] if df.shape[0] == 0: raise PreventUpdate() @@ -166,7 +167,7 @@ def generate_comparison_filtered_df(api_data_df, selected_nc): df = api_data_df else: df = api_data_df[api_data_df["councilName"] == selected_nc] - df = add_datetime_column(df, "createdDate") + df, _ = add_datetime_column(df, "createdDate") return df # VISUALS HELPER FUNCTIONS. From 9a2ea13a7a15fccb798aa125a445eaddd4dddf98 Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Mon, 10 Oct 2022 20:56:17 -0700 Subject: [PATCH 11/21] Add variable to new col name --- server/dash/pages/nc_summary_comparison.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index e0cce8536..2d2d9937a 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -70,11 +70,12 @@ def add_datetime_column(df, colname): Return: df: A dataframe with new column 'colnameDT' that is in datetime type. - colname: String that represents the new column name. + new_col_name: String that represents the new column name. """ - df.loc[:, colname+"_dt"] = pd.to_datetime( + new_col_name = colname+"_dt" + df.loc[:, new_col_name] = pd.to_datetime( df.loc[:, colname].str[:-4].str.split("T").str.join(" ")) - return df, colname + "_dt" + return df, new_col_name def generate_filtered_dataframe(api_data_df, selected_nc, selected_request_types): """Outputs the filtered dataframe based on the selected filters From e770c0e2cbcc027dda92e0d34d57ea66f1689145 Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Mon, 10 Oct 2022 21:01:47 -0700 Subject: [PATCH 12/21] new column name variable --- server/dash/pages/nc_summary_comparison.py | 41 +++++++++++----------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index 2d2d9937a..020c668fb 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -72,7 +72,7 @@ def add_datetime_column(df, colname): df: A dataframe with new column 'colnameDT' that is in datetime type. new_col_name: String that represents the new column name. """ - new_col_name = colname+"_dt" + new_col_name = colname + "_dt" df.loc[:, new_col_name] = pd.to_datetime( df.loc[:, colname].str[:-4].str.split("T").str.join(" ")) return df, new_col_name @@ -162,14 +162,15 @@ def generate_comparison_filtered_df(api_data_df, selected_nc): selected_nc: A string argument automatically detected by Dash callback function when "nc_comp_dropdown" element is selected in the layout. Returns: - Pandas dataframe with requests from the nc selected by nc_comp_dropdown. + df: Pandas dataframe with requests from the nc selected by nc_comp_dropdown. + create_dt_col_name: String column name of the new datetime column. """ if not selected_nc: df = api_data_df else: df = api_data_df[api_data_df["councilName"] == selected_nc] - df, _ = add_datetime_column(df, "createdDate") - return df + df, create_dt_col_name = add_datetime_column(df, "createdDate") + return df, create_dt_col_name # VISUALS HELPER FUNCTIONS. def generate_summary_header(): @@ -527,8 +528,8 @@ def generate_indicator_visuals(nc_comp_dropdown, nc_comp_dropdown2): num_days_card2: integer for the total number of days the data available in second selected neighborhood council span. """ - df_nc1 = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) - df_nc2 = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown2) + df_nc1, create_dt_col_name = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) + df_nc2, _ = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown2) # Total number of requests for first neigbhorhood council. total_req_card = df_nc1.shape[0] @@ -536,12 +537,12 @@ def generate_indicator_visuals(nc_comp_dropdown, nc_comp_dropdown2): total_req_card2 = df_nc2.shape[0] # Total number of days the available requests in first neigbhorhood council span. - num_days_card = np.max(df_nc1["createdDateDT"].dt.day) - \ - np.min(df_nc1["createdDateDT"].dt.day) + 1 + num_days_card = np.max(df_nc1[create_dt_col_name].dt.day) - \ + np.min(df_nc1[create_dt_col_name].dt.day) + 1 # Total number of days the available requests in second neigbhorhood council span. - num_days_card2 = np.max(df_nc2["createdDateDT"].dt.day) - \ - np.min(df_nc2["createdDateDT"].dt.day) + 1 + num_days_card2 = np.max(df_nc2[create_dt_col_name].dt.day) - \ + np.min(df_nc2[create_dt_col_name].dt.day) + 1 return total_req_card, total_req_card2, num_days_card, num_days_card2 @@ -574,8 +575,8 @@ def generate_req_source_bar_charts(nc_comp_dropdown, nc_comp_dropdown2): from each source for the second neighborhood council (e.g. mobile, app, self-report...etc). """ - df_nc1 = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) - df_nc2 = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown2) + df_nc1, create_dt_col_name = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) + df_nc2, _ = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown2) # Bar chart of different Request Type Sources for first selected neigbhorhood council. req_source = pd.DataFrame(df_nc1["sourceName"].value_counts()) req_source = req_source.reset_index() @@ -617,22 +618,22 @@ def generate_overlay_line_chart(nc_comp_dropdown, nc_comp_dropdown2): Line chart showing the number of requests throughout the day for both first and second selected neighborhood council. """ - df_nc1 = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) - df_nc2 = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown2) + df_nc1, create_dt_col_name = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) + df_nc2, _ = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown2) # Overlapping line chart for number of request throughout the day # for both first and second neighborhood council. - req_time = pd.DataFrame(df_nc1.groupby("createdDateDT", as_index=False)["srnumber"].count()) - req_time2 = pd.DataFrame(df_nc2.groupby("createdDateDT", as_index=False)["srnumber"].count()) + req_time = pd.DataFrame(df_nc1.groupby(create_dt_col_name, as_index=False)["srnumber"].count()) + req_time2 = pd.DataFrame(df_nc2.groupby(create_dt_col_name, as_index=False)["srnumber"].count()) overlay_req_time_line_chart = go.Figure() overlay_req_time_line_chart.add_trace(go.Scatter( - x=req_time["createdDateDT"], y=req_time["srnumber"], mode="lines", name="NC1")) + x=req_time[create_dt_col_name], y=req_time["srnumber"], mode="lines", name="NC1")) overlay_req_time_line_chart.add_trace(go.Scatter( - x=req_time2["createdDateDT"], y=req_time2["srnumber"], mode="lines", name="NC2")) + x=req_time2[create_dt_col_name], y=req_time2["srnumber"], mode="lines", name="NC2")) overlay_req_time_line_chart.update_layout(title="Number of Request Throughout the Day", margin=dict(l=25, r=25, b=35, t=50), xaxis_range=[min( - min(req_time["createdDateDT"]), min(req_time2["createdDateDT"])), - max(max(req_time["createdDateDT"]), max(req_time2["createdDateDT"]))], font=dict(size=9)) + min(req_time[create_dt_col_name]), min(req_time2[create_dt_col_name])), + max(max(req_time[create_dt_col_name]), max(req_time2[create_dt_col_name]))], font=dict(size=9)) return overlay_req_time_line_chart # LAYOUT. From 461fe417aca2037718b62210d8849f3144eb9ee8 Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Mon, 10 Oct 2022 21:08:10 -0700 Subject: [PATCH 13/21] Add # neighborhood councils --- server/dash/pages/nc_summary_comparison.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index 020c668fb..16f1ddb0d 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -56,6 +56,7 @@ def merge_dict(d1, d2): CHART_OUTLINE_STYLE = merge_dict(INLINE_STYLE, BORDER_STYLE) SUMMARY_DASHBOARD_TITLE = "LA 311 Requests - Neighborhood Council Summary Dashboard" COMPARISON_DASHBOARD_TITLE = "LA 311 Requests - Neighborhood Council Comparison Dashboard" +NUM_NEIGHBORHOOD_COUNCILS = 99 ## DATA WRANGLING HELPER FUNCTIONS. @@ -381,7 +382,9 @@ def update_line_chart(selected_nc): This function takes the selected neighborhood council (nc) value from the "selected_nc" dropdown and output a line chart showing the number of requests throughout the day and the average number - of requests throughout the day (total number of requests / all 99 neighborhood councils). + of requests throughout the day (total number of requests / number of + neighborhood councils). + Args: selected_nc: A string argument automatically detected by Dash callback function when "selected_nc" element is selected in the layout. @@ -399,7 +402,7 @@ def update_line_chart(selected_nc): # Calculating the average number of requests throughout the day. neighborhood_sum_df = df.groupby(["created_date"]).agg("sum").reset_index() # noqa total_sum_df = report_json.groupby(["created_date"]).agg("sum").reset_index() - total_sum_df["nc_avg"] = total_sum_df["counts"] / 99 + total_sum_df["nc_avg"] = total_sum_df["counts"] / NUM_NEIGHBORHOOD_COUNCILS merged_df = neighborhood_sum_df.merge(total_sum_df["nc_avg"].to_frame(), left_index=True, right_index=True) # noqa @@ -575,7 +578,7 @@ def generate_req_source_bar_charts(nc_comp_dropdown, nc_comp_dropdown2): from each source for the second neighborhood council (e.g. mobile, app, self-report...etc). """ - df_nc1, create_dt_col_name = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) + df_nc1, _ = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) df_nc2, _ = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown2) # Bar chart of different Request Type Sources for first selected neigbhorhood council. req_source = pd.DataFrame(df_nc1["sourceName"].value_counts()) From 168b3757c01d562a1cb1b75377412a9830f812a5 Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Mon, 10 Oct 2022 21:12:41 -0700 Subject: [PATCH 14/21] update string format --- server/dash/pages/nc_summary_comparison.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index 16f1ddb0d..0ccc9b732 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -384,7 +384,7 @@ def update_line_chart(selected_nc): the number of requests throughout the day and the average number of requests throughout the day (total number of requests / number of neighborhood councils). - + Args: selected_nc: A string argument automatically detected by Dash callback function when "selected_nc" element is selected in the layout. @@ -412,8 +412,7 @@ def update_line_chart(selected_nc): y=["counts", "nc_avg"], color_discrete_sequence=DISCRETE_COLORS, labels=LABELS, - title="Number of " + selected_nc + - " Requests compared with the average Neighborhood Council" + title="Number of {} Requests compared with the average Neighborhood Council".format(selected_nc) ) nc_avg_comp_line_chart.update_xaxes( From 8cc57d6f9a438e2f7209c5ef57fbe0b69c081df5 Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Mon, 10 Oct 2022 21:49:26 -0700 Subject: [PATCH 15/21] add summary statistics helper --- server/dash/pages/nc_summary_comparison.py | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index 0ccc9b732..9f99da8ab 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -498,6 +498,29 @@ def generate_time_to_close_histogram(selected_nc, selected_request_types=None, time_close_histogram.update_layout(margin=dict(l=50, r=50, b=50, t=50), font=dict(size=9)) return time_close_histogram, data_quality_output + +## TODO: Make a helper function that can both indicator callback can call +def generate_summary_statistics(nc_comp_dropdown): + """Generates the summary statistics for neighborhood council + comparisons. + + Args: + nc_comp_dropdown: name of the selected neighborhood council. + + Returns: + total_req_card: integer for the the total number of request + in first selected neigborhood council. + num_days_card: integer for the total number of days the data + available in first selected neighborhood council span. + """ + df_nc1, create_dt_col_name = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) + total_req_card = df_nc1.shape[0] + num_days_card = np.max(df_nc1[create_dt_col_name].dt.day) - \ + np.min(df_nc1[create_dt_col_name].dt.day) + 1 + return total_req_card, num_days_card + +## TODO: Break the callback function into two callbacks, each calling one helper function + @callback( Output("total_req_card", "children"), Output("total_req_card2", "children"), From 147751ade79b772a8cca21ff1e397443a44f36e8 Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Mon, 10 Oct 2022 22:14:57 -0700 Subject: [PATCH 16/21] Break up indicator visual callback --- server/dash/pages/nc_summary_comparison.py | 96 +++++++++++----------- 1 file changed, 47 insertions(+), 49 deletions(-) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index 9f99da8ab..70f9206fb 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -99,7 +99,7 @@ def generate_filtered_dataframe(api_data_df, selected_nc, selected_request_types elif selected_nc and no_req_type_selected: df = api_data_df[api_data_df["councilName"] == selected_nc] elif selected_request_types != ' ': - df = df[df["typeName"].isin(selected_request_types)] + df = api_data_df[api_data_df["typeName"].isin(selected_request_types)] else: df = api_data_df return df @@ -173,6 +173,25 @@ def generate_comparison_filtered_df(api_data_df, selected_nc): df, create_dt_col_name = add_datetime_column(df, "createdDate") return df, create_dt_col_name +def generate_summary_statistics(nc_comp_dropdown): + """Generates the summary statistics for neighborhood council + comparisons. + + Args: + nc_comp_dropdown: name of the selected neighborhood council. + + Returns: + total_req_card: integer for the the total number of request + in first selected neigborhood council. + num_days_card: integer for the total number of days the data + available in first selected neighborhood council span. + """ + df_nc1, create_dt_col_name = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) + total_req_card = df_nc1.shape[0] + num_days_card = np.max(df_nc1[create_dt_col_name].dt.day) - \ + np.min(df_nc1[create_dt_col_name].dt.day) + 1 + return total_req_card, num_days_card + # VISUALS HELPER FUNCTIONS. def generate_summary_header(): """Generates the header for the summary dashboard. @@ -498,78 +517,57 @@ def generate_time_to_close_histogram(selected_nc, selected_request_types=None, time_close_histogram.update_layout(margin=dict(l=50, r=50, b=50, t=50), font=dict(size=9)) return time_close_histogram, data_quality_output - -## TODO: Make a helper function that can both indicator callback can call -def generate_summary_statistics(nc_comp_dropdown): - """Generates the summary statistics for neighborhood council - comparisons. +@callback( + Output("total_req_card", "children"), + Output("num_days_card", "children"), + Input("nc_comp_dropdown", "value"), + prevent_initial_call=True +) +def generate_indicator_visuals_for_nc1(nc_comp_dropdown): + """Generates the overlapping line chart based on selected filters. + This function takes the neighborhood council (nc) value from + the "nc_comp_dropdown" dropdown and outputs indicator + visuals for the nc. Args: - nc_comp_dropdown: name of the selected neighborhood council. + nc_comp_dropdown: A string argument automatically detected by + Dash callback function when "nc_comp_dropdown" element is + selected in the layout. - Returns: + Returns: total_req_card: integer for the the total number of request in first selected neigborhood council. num_days_card: integer for the total number of days the data available in first selected neighborhood council span. """ - df_nc1, create_dt_col_name = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) - total_req_card = df_nc1.shape[0] - num_days_card = np.max(df_nc1[create_dt_col_name].dt.day) - \ - np.min(df_nc1[create_dt_col_name].dt.day) + 1 + total_req_card, num_days_card = generate_summary_statistics(nc_comp_dropdown) return total_req_card, num_days_card -## TODO: Break the callback function into two callbacks, each calling one helper function - @callback( - Output("total_req_card", "children"), Output("total_req_card2", "children"), - Output("num_days_card", "children"), Output("num_days_card2", "children"), - Input("nc_comp_dropdown", "value"), Input("nc_comp_dropdown2", "value"), prevent_initial_call=True ) -def generate_indicator_visuals(nc_comp_dropdown, nc_comp_dropdown2): +def generate_indicator_visuals_for_nc2(nc_comp_dropdown2): """Generates the overlapping line chart based on selected filters. - This function takes the the two neighborhood council (nc) value from - the "nc_comp_dropdown" dropdown and second selected neighborhood - council value from "nc_comp_dropdown2" - dropdown and outputs indicator visuals for the two nc's. + This function takes the neighborhood council (nc) value from + the "nc_comp_dropdown2" dropdown and outputs indicator + visuals for the nc. + Args: - nc_comp_dropdown: A string argument automatically detected by - Dash callback function when "nc_comp_dropdown" element is + nc_comp_dropdown2: A string argument automatically detected by + Dash callback function when "nc_comp_dropdown2" element is selected in the layout. - nc_comp_dropdown2: A string argument automatically detected by - Dash callback function when "nc_comp_dropdown2" element is - selected in the layout. + Returns: total_req_card: integer for the the total number of request - in first selected neigborhood council. - total_req_card2: integer for the total number of requests - in the second selected neighborhood council. + in second selected neigborhood council. num_days_card: integer for the total number of days the data - available in first selected neighborhood council span. - num_days_card2: integer for the total number of days the data available in second selected neighborhood council span. """ - df_nc1, create_dt_col_name = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) - df_nc2, _ = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown2) - # Total number of requests for first neigbhorhood council. - total_req_card = df_nc1.shape[0] - - # Total number of requests for second neigbhorhood council. - total_req_card2 = df_nc2.shape[0] - - # Total number of days the available requests in first neigbhorhood council span. - num_days_card = np.max(df_nc1[create_dt_col_name].dt.day) - \ - np.min(df_nc1[create_dt_col_name].dt.day) + 1 - - # Total number of days the available requests in second neigbhorhood council span. - num_days_card2 = np.max(df_nc2[create_dt_col_name].dt.day) - \ - np.min(df_nc2[create_dt_col_name].dt.day) + 1 - - return total_req_card, total_req_card2, num_days_card, num_days_card2 + total_req_card, num_days_card = generate_summary_statistics(nc_comp_dropdown2) + return total_req_card, num_days_card @callback( Output("req_source_bar_chart", "figure"), From 104f0f15dbd9889bc033bf7cf4449daa71aaa8ac Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Mon, 10 Oct 2022 22:32:50 -0700 Subject: [PATCH 17/21] break up req source callback --- server/dash/pages/nc_summary_comparison.py | 80 ++++++++++++++-------- 1 file changed, 51 insertions(+), 29 deletions(-) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index 70f9206fb..77cad6ad8 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -192,6 +192,28 @@ def generate_summary_statistics(nc_comp_dropdown): np.min(df_nc1[create_dt_col_name].dt.day) + 1 return total_req_card, num_days_card +def req_source_helper(nc_comp_dropdown): + """Generates a request source bar chart based on selected neighborhood council. + + Args: + nc_comp_dropdown: A string argument automatically detected + by Dash callback function when "nc_comp_dropdown" element + is selected in the layout. + + Returns: + req_source_bar_chart: bar chart showing the number of request + from each source for the first neighborhood council + (e.g. mobile, app, self-report...etc). + """ + df_nc1, _ = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) + req_source = pd.DataFrame(df_nc1["sourceName"].value_counts()) + req_source = req_source.reset_index() + req_source_bar_chart = px.bar(req_source, x="sourceName", y="index", + orientation="h", title="Number of Requests by Source", labels={ + "index": "Request Source", "sourceName": "Frequency"}) + req_source_bar_chart.update_layout(margin=dict(l=25, r=25, b=25, t=50), font=dict(size=9)) + return req_source_bar_chart + # VISUALS HELPER FUNCTIONS. def generate_summary_header(): """Generates the header for the summary dashboard. @@ -571,52 +593,52 @@ def generate_indicator_visuals_for_nc2(nc_comp_dropdown2): @callback( Output("req_source_bar_chart", "figure"), - Output("req_source_bar_chart2", "figure"), Input("nc_comp_dropdown", "value"), - Input("nc_comp_dropdown2", "value"), prevent_initial_call=True ) -def generate_req_source_bar_charts(nc_comp_dropdown, nc_comp_dropdown2): - """Generates a pair of request source bar chart based on the two +def generate_req_source_bar_charts(nc_comp_dropdown): + """Generates a request source bar chart based on the neighborhood council dropdown. + This function takes the first selected neighborhood council (nc) - value from the "nc_comp_dropdown" dropdown and second selected - neighborhood council value from "nc_comp_dropdown2" - dropdown and output a pair of request_source bar charts. + value from the "nc_comp_dropdown" dropdown and output a pair + of request_source bar charts. + Args: nc_comp_dropdown: A string argument automatically detected by Dash callback function when "nc_comp_dropdown" element is selected in the layout. - nc_comp_dropdown2: A string argument automatically detected by - Dash callback function when "nc_comp_dropdown2" element is - selected in the layout. Returns: req_source_bar_chart: bar chart showing the number of request from each source for the first neighborhood council (e.g. mobile, app, self-report...etc). - req_source_bar_chart2: bar chart showing the number of request + """ + return req_source_helper(nc_comp_dropdown) + +@callback( + Output("req_source_bar_chart2", "figure"), + Input("nc_comp_dropdown2", "value"), + prevent_initial_call=True +) +def generate_req_source_bar_charts2(nc_comp_dropdown2): + """Generates a request source bar chart based on the + neighborhood council dropdown. + + This function takes the second selected neighborhood council + value from "nc_comp_dropdown2" dropdown and output a pair of + request_source bar charts. + + Args: + nc_comp_dropdown2: A string argument automatically detected by + Dash callback function when "nc_comp_dropdown2" element is + selected in the layout. + Returns: + bar chart showing the number of request from each source for the second neighborhood council (e.g. mobile, app, self-report...etc). """ - df_nc1, _ = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) - df_nc2, _ = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown2) - # Bar chart of different Request Type Sources for first selected neigbhorhood council. - req_source = pd.DataFrame(df_nc1["sourceName"].value_counts()) - req_source = req_source.reset_index() - req_source_bar_chart = px.bar(req_source, x="sourceName", y="index", - orientation="h", title="Number of Requests by Source", labels={ - "index": "Request Source", "sourceName": "Frequency"}) - req_source_bar_chart.update_layout(margin=dict(l=25, r=25, b=25, t=50), font=dict(size=9)) - - # Bar chart of different Request Type Sources for second selected neigbhorhood council. - req_source2 = pd.DataFrame(df_nc2["sourceName"].value_counts()) - req_source2 = req_source2.reset_index() - req_source_bar_chart2 = px.bar(req_source2, x="sourceName", y="index", - orientation="h", title="Number of Requests by Source", labels={ - "index": "Request Source", "sourceName": "Frequency"}) - req_source_bar_chart2.update_layout(margin=dict(l=25, r=25, b=25, t=50), font=dict(size=9)) + return req_source_helper(nc_comp_dropdown2) - return req_source_bar_chart, req_source_bar_chart2 @callback( Output("overlay_req_time_line_chart", "figure"), From d9e0f1737793a470b31ad3c18e6ad6232d2193bd Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Mon, 10 Oct 2022 22:34:57 -0700 Subject: [PATCH 18/21] remove prevent_initial_call --- server/dash/pages/nc_summary_comparison.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index 77cad6ad8..d5c4b29a1 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -543,7 +543,6 @@ def generate_time_to_close_histogram(selected_nc, selected_request_types=None, Output("total_req_card", "children"), Output("num_days_card", "children"), Input("nc_comp_dropdown", "value"), - prevent_initial_call=True ) def generate_indicator_visuals_for_nc1(nc_comp_dropdown): """Generates the overlapping line chart based on selected filters. @@ -569,7 +568,6 @@ def generate_indicator_visuals_for_nc1(nc_comp_dropdown): Output("total_req_card2", "children"), Output("num_days_card2", "children"), Input("nc_comp_dropdown2", "value"), - prevent_initial_call=True ) def generate_indicator_visuals_for_nc2(nc_comp_dropdown2): """Generates the overlapping line chart based on selected filters. @@ -594,7 +592,6 @@ def generate_indicator_visuals_for_nc2(nc_comp_dropdown2): @callback( Output("req_source_bar_chart", "figure"), Input("nc_comp_dropdown", "value"), - prevent_initial_call=True ) def generate_req_source_bar_charts(nc_comp_dropdown): """Generates a request source bar chart based on the @@ -618,7 +615,6 @@ def generate_req_source_bar_charts(nc_comp_dropdown): @callback( Output("req_source_bar_chart2", "figure"), Input("nc_comp_dropdown2", "value"), - prevent_initial_call=True ) def generate_req_source_bar_charts2(nc_comp_dropdown2): """Generates a request source bar chart based on the @@ -644,7 +640,6 @@ def generate_req_source_bar_charts2(nc_comp_dropdown2): Output("overlay_req_time_line_chart", "figure"), Input("nc_comp_dropdown", "value"), Input("nc_comp_dropdown2", "value"), - prevent_initial_call=True ) def generate_overlay_line_chart(nc_comp_dropdown, nc_comp_dropdown2): """Generates the overlapping line chart based on selected filters. From 92bbba1c79572bf29c72510b6938abed67004e82 Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Wed, 12 Oct 2022 16:46:13 -0700 Subject: [PATCH 19/21] Add spacing in docstrings --- server/dash/pages/nc_summary_comparison.py | 67 +++++++++++++++++++++- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index d5c4b29a1..1b71fbe40 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -59,12 +59,14 @@ def merge_dict(d1, d2): NUM_NEIGHBORHOOD_COUNCILS = 99 -## DATA WRANGLING HELPER FUNCTIONS. +# DATA WRANGLING HELPER FUNCTIONS. def add_datetime_column(df, colname): """Adds a datetime column to a dataframe. + This function takes a datetime column 'colname' in string type, remove the last 4 characters, split date and time by character 'T', and finally combine date and time into a single string again. The function then converts the string using pandas's to_datetime function. + Args: df: dataframe to add the datetime column to. colname: the datetime column that is in string type. @@ -79,10 +81,12 @@ def add_datetime_column(df, colname): return df, new_col_name def generate_filtered_dataframe(api_data_df, selected_nc, selected_request_types): - """Outputs the filtered dataframe based on the selected filters + """Outputs the filtered dataframe based on the selected filters. + This function takes the original dataframe "api_data_df", selected neighborhood council "selected_nc", as well as the selected request type "selected_request_types" to output a dataframe with matching records. + Args: api_data_df: full 311-request data directly access from the 311 data API. selected_nc: A string argument automatically detected by Dash callback @@ -90,6 +94,7 @@ def generate_filtered_dataframe(api_data_df, selected_nc, selected_request_types selected_request_types: A list of strings automatically detected by Dash callback function when "selected_request_types" element is selected in the layout, default None. + Returns: pandas dataframe filtered by selected neighborhood council and request types """ @@ -107,9 +112,11 @@ def generate_filtered_dataframe(api_data_df, selected_nc, selected_request_types def filter_bad_quality_data(df, data_quality_switch=True): """Filters the dataframe based on pre-defined data quality filters. + This function takes the original dataframe "df" and filters out records with an outlier amount of request time to close based on the Freedman-Diaconis Rule. Generally 10% of data is excluded. + Args: df: 311-request data accessed from the API. data_quality_switch: A boolean argument automatically detected by Dash. @@ -155,13 +162,16 @@ def filter_bad_quality_data(df, data_quality_switch=True): def generate_comparison_filtered_df(api_data_df, selected_nc): """Generates the dataframe based on selected neighborhood council. + This function takes the selected neighborhood council (nc) value from the "selected_nc" dropdown and outputs a dataframe corresponding to their neighorhood council with additional datetime column. + Args: api_data_df: full 311-request data directly access from the 311 data API. selected_nc: A string argument automatically detected by Dash callback function when "nc_comp_dropdown" element is selected in the layout. + Returns: df: Pandas dataframe with requests from the nc selected by nc_comp_dropdown. create_dt_col_name: String column name of the new datetime column. @@ -177,6 +187,10 @@ def generate_summary_statistics(nc_comp_dropdown): """Generates the summary statistics for neighborhood council comparisons. + This function takes in "nc_comp_dropdown" to compute the + summary statistics for the selected neighborhood council + to be displayed on the dashboard. + Args: nc_comp_dropdown: name of the selected neighborhood council. @@ -195,6 +209,10 @@ def generate_summary_statistics(nc_comp_dropdown): def req_source_helper(nc_comp_dropdown): """Generates a request source bar chart based on selected neighborhood council. + This function takes in "nc_comp_dropdown" and return a bar chart + that shows the number of requests from each source from the + selected neighborhood council. + Args: nc_comp_dropdown: A string argument automatically detected by Dash callback function when "nc_comp_dropdown" element @@ -202,7 +220,7 @@ def req_source_helper(nc_comp_dropdown): Returns: req_source_bar_chart: bar chart showing the number of request - from each source for the first neighborhood council + from each source for the neighborhood council (e.g. mobile, app, self-report...etc). """ df_nc1, _ = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) @@ -217,8 +235,10 @@ def req_source_helper(nc_comp_dropdown): # VISUALS HELPER FUNCTIONS. def generate_summary_header(): """Generates the header for the summary dashboard. + This function generates the html elements for the title and data quality toggle for summary dashboard. + Return: Dash html div element containing title and data quality toggle. @@ -233,8 +253,10 @@ def generate_summary_header(): def generate_summary_dropdowns(): """Generates the dropdowns for the summary dashboard. + This function generates the html elements for the neighborhood council and request type dropdowns for summary dashboard. + Return: Dash html div element containing nc and request type dropdowns. """ @@ -251,8 +273,10 @@ def generate_summary_dropdowns(): def generate_summary_line_chart(): """Generates the line chart for the summary dashboard. + This function generates the html elements for the number of requests line chart for summary dashboard. + Return: Dash html div element containing overlapping line chart. """ @@ -263,8 +287,10 @@ def generate_summary_line_chart(): def generate_summary_pie_chart(): """Generates the pie chart for the summary dashboard. + This function generates the html elements for the request type pie chart for summary dashboard. + Return: Dash html div element containing request type pie chart. """ @@ -278,8 +304,10 @@ def generate_summary_pie_chart(): def generate_summary_histogram(): """Generates the histogram for the summary dashboard. + This function generates the html elements for the request time to close histogram for summary dashboard. + Return: Dash html div element containing request time to close histogram. """ @@ -294,10 +322,13 @@ def generate_summary_histogram(): def generate_council_name_dropdown(output_id): """Generates the neighborhood council (nc) dropdown for the comparison dashboard. + This function generates the html elements for the nc dropdown for the comparison dashboard. + Args: output_id: the id corresponding to the dash element in the layout. + Return: Dash html div element containing nc drop down for left pane filtering. """ @@ -309,11 +340,14 @@ def generate_council_name_dropdown(output_id): def generate_comparison_total_req(output_id): """Generates the indicator visual for the total number of requests. + This function generates the html elements for the indicator visual with matching output_id showing the total number of requests for the comparison dashboard. + Args: output_id: the id corresponding to the dash element in the layout. + Return: Dash html div element containing label and indicator visual. """ @@ -326,12 +360,15 @@ def generate_comparison_total_req(output_id): def generate_comparison_num_days(output_id): """Generates the indicator visual for the total number of days request spans. + This function generates the html elements for the indicator visual with matching output_id showing the total number of days request spans for the comparison dashboard. + Args: output_id: the id corresponding to the dash element in the layout. + Return: Dash html div element containing label and indicator visual. """ @@ -343,12 +380,15 @@ def generate_comparison_num_days(output_id): def generate_comparison_num_days(output_id): """Generates the indicator visual for the total number of days request spans. + This function generates the html elements for the indicator visual with matching output_id showing the total number of days request spans for the comparison dashboard. + Args: output_id: the id corresponding to the dash element in the layout. + Return: Dash html div element containing label and indicator visual. """ @@ -360,11 +400,14 @@ def generate_comparison_num_days(output_id): def generate_comparison_req_source_bar(output_id): """Generates the bar chart visual for the request source in comparison dashboard. + This function generates the html elements for the bar chart of request sources with matching output_id on the comparison dashboard. + Args: output_id: the id corresponding to the dash element in the layout. + Return: Dash html div element containing request source bar chart. """ @@ -375,9 +418,11 @@ def generate_comparison_req_source_bar(output_id): def generate_comparison_line_chart(): """Generates the line chart visual for the number of requests in comparison dashboard. + This function generates the html elements for the overlapping line chart for number of requests on the bottom of the comparison dashboard. + Return: Dash html div element containing overlapping line chart. """ @@ -393,12 +438,15 @@ def generate_comparison_line_chart(): ) def generate_dynamic_filter(selected_nc): """Enables the dashboard to show dynamic filters. + This function takes the selected neighborhood council (nc) value from the "selected_nc" dropdown and output a a list of available request types from that neigbhorhood council. + Args: selected_nc: A string argument automatically detected by Dash callback function when "selected_nc" element is selected in the layout. + Returns: selected_request_types: a list of request types available from the selected neigbhorhood council. @@ -420,6 +468,7 @@ def generate_dynamic_filter(selected_nc): def update_line_chart(selected_nc): """Generates a line chart visualizations for LA 311 requests data based on the two selected neighborhood conucils. + This function takes the selected neighborhood council (nc) value from the "selected_nc" dropdown and output a line chart showing the number of requests throughout the day and the average number @@ -429,6 +478,7 @@ def update_line_chart(selected_nc): Args: selected_nc: A string argument automatically detected by Dash callback function when "selected_nc" element is selected in the layout. + Returns: nc_avg_comp_line_chart: line chart showing the number of requests throughout the day for the selected neighborhood council and average @@ -474,14 +524,17 @@ def update_line_chart(selected_nc): ) def generate_req_type_pie_chart(selected_nc, selected_request_types=None): """Generates the request type pie chart based on selected filters. + This callback function takes the selected neighborhood council (nc) value from the "selected_nc" dropdown, selected request type from "selected_request_types" dropdown to output a pie chart showing the share of request types. + Args: selected_nc: A string argument automatically detected by Dash callback function when "selected_nc" element is selected in the layout. selected_request_types: A list of strings automatically detected by Dash callback function when "selected_request_types" element is selected in the layout, default None. + Returns: pie chart showing the share of each request type. """ @@ -508,9 +561,11 @@ def generate_req_type_pie_chart(selected_nc, selected_request_types=None): def generate_time_to_close_histogram(selected_nc, selected_request_types=None, data_quality_switch=True): """Generates the request type pie chart based on selected filters. + This callback function takes the selected neighborhood council (nc) value from the "selected_nc" dropdown, selected request type from "selected_request_types" dropdown to output a histogram for the time it takes for each request to close. + Args: selected_nc: A string argument automatically detected by Dash callback function when "selected_nc" element is selected in the layout. @@ -520,6 +575,7 @@ def generate_time_to_close_histogram(selected_nc, selected_request_types=None, data_quality_switch: A boolean for data quality filter automatically detected by Dash callback function when "data_quality_switch" element is selected in the layout, default True. + Returns: time_close_histogram: histogram showing the distribution of the request time to close. data_quality_output: A string stating the status of the data quality filter @@ -546,6 +602,7 @@ def generate_time_to_close_histogram(selected_nc, selected_request_types=None, ) def generate_indicator_visuals_for_nc1(nc_comp_dropdown): """Generates the overlapping line chart based on selected filters. + This function takes the neighborhood council (nc) value from the "nc_comp_dropdown" dropdown and outputs indicator visuals for the nc. @@ -571,6 +628,7 @@ def generate_indicator_visuals_for_nc1(nc_comp_dropdown): ) def generate_indicator_visuals_for_nc2(nc_comp_dropdown2): """Generates the overlapping line chart based on selected filters. + This function takes the neighborhood council (nc) value from the "nc_comp_dropdown2" dropdown and outputs indicator visuals for the nc. @@ -643,10 +701,12 @@ def generate_req_source_bar_charts2(nc_comp_dropdown2): ) def generate_overlay_line_chart(nc_comp_dropdown, nc_comp_dropdown2): """Generates the overlapping line chart based on selected filters. + This function takes the the two neighborhood council (nc) value from the "nc_comp_dropdown" dropdown and second selected neighborhood council value from "nc_comp_dropdown2" dropdown and outputs a overlapping line chart. + Args: nc_comp_dropdown: A string argument automatically detected by Dash callback function when "nc_comp_dropdown" element @@ -654,6 +714,7 @@ def generate_overlay_line_chart(nc_comp_dropdown, nc_comp_dropdown2): nc_comp_dropdown2: A string argument automatically detected by Dash callback function when "nc_comp_dropdown2" element is selected in the layout. + Returns: Line chart showing the number of requests throughout the day for both first and second selected neighborhood council. From 5e2f0553408e047474321c3974d967df393fb638 Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Wed, 12 Oct 2022 16:48:32 -0700 Subject: [PATCH 20/21] Remove obsolete var names --- server/dash/pages/nc_summary_comparison.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index 1b71fbe40..45c1a4062 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -196,14 +196,14 @@ def generate_summary_statistics(nc_comp_dropdown): Returns: total_req_card: integer for the the total number of request - in first selected neigborhood council. + in selected neigborhood council. num_days_card: integer for the total number of days the data - available in first selected neighborhood council span. + available in selected neighborhood council span. """ - df_nc1, create_dt_col_name = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) - total_req_card = df_nc1.shape[0] - num_days_card = np.max(df_nc1[create_dt_col_name].dt.day) - \ - np.min(df_nc1[create_dt_col_name].dt.day) + 1 + df_nc, create_dt_col_name = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) + total_req_card = df_nc.shape[0] + num_days_card = np.max(df_nc[create_dt_col_name].dt.day) - \ + np.min(df_nc[create_dt_col_name].dt.day) + 1 return total_req_card, num_days_card def req_source_helper(nc_comp_dropdown): @@ -714,7 +714,7 @@ def generate_overlay_line_chart(nc_comp_dropdown, nc_comp_dropdown2): nc_comp_dropdown2: A string argument automatically detected by Dash callback function when "nc_comp_dropdown2" element is selected in the layout. - + Returns: Line chart showing the number of requests throughout the day for both first and second selected neighborhood council. From f999e71a085dbfa229e37d4787dea9b8573d34f0 Mon Sep 17 00:00:00 2001 From: Yi Heng Joshua Wu Date: Wed, 12 Oct 2022 16:53:17 -0700 Subject: [PATCH 21/21] Removed obsolete var name --- server/dash/pages/nc_summary_comparison.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/dash/pages/nc_summary_comparison.py b/server/dash/pages/nc_summary_comparison.py index 45c1a4062..f4d66f015 100644 --- a/server/dash/pages/nc_summary_comparison.py +++ b/server/dash/pages/nc_summary_comparison.py @@ -206,7 +206,7 @@ def generate_summary_statistics(nc_comp_dropdown): np.min(df_nc[create_dt_col_name].dt.day) + 1 return total_req_card, num_days_card -def req_source_helper(nc_comp_dropdown): +def get_request_source_bar_chart(nc_comp_dropdown): """Generates a request source bar chart based on selected neighborhood council. This function takes in "nc_comp_dropdown" and return a bar chart @@ -223,8 +223,8 @@ def req_source_helper(nc_comp_dropdown): from each source for the neighborhood council (e.g. mobile, app, self-report...etc). """ - df_nc1, _ = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) - req_source = pd.DataFrame(df_nc1["sourceName"].value_counts()) + df_nc, _ = generate_comparison_filtered_df(api_data_df, nc_comp_dropdown) + req_source = pd.DataFrame(df_nc["sourceName"].value_counts()) req_source = req_source.reset_index() req_source_bar_chart = px.bar(req_source, x="sourceName", y="index", orientation="h", title="Number of Requests by Source", labels={ @@ -668,7 +668,7 @@ def generate_req_source_bar_charts(nc_comp_dropdown): from each source for the first neighborhood council (e.g. mobile, app, self-report...etc). """ - return req_source_helper(nc_comp_dropdown) + return get_request_source_bar_chart(nc_comp_dropdown) @callback( Output("req_source_bar_chart2", "figure"), @@ -691,7 +691,7 @@ def generate_req_source_bar_charts2(nc_comp_dropdown2): from each source for the second neighborhood council (e.g. mobile, app, self-report...etc). """ - return req_source_helper(nc_comp_dropdown2) + return get_request_source_bar_chart(nc_comp_dropdown2) @callback(