Mark Febrizio
commited on
Alt significance (#17)
Browse files* Update app.py
* fix bugs in new summary layout
* Update plotting.py
account for multiple rule type inputs
- app.py +11 -92
- modules/plotting.py +17 -16
app.py
CHANGED
|
@@ -1,9 +1,6 @@
|
|
| 1 |
# ----- IMPORTS ----- #
|
| 2 |
|
| 3 |
|
| 4 |
-
# ----- IMPORTS ----- #
|
| 5 |
-
|
| 6 |
-
|
| 7 |
import asyncio
|
| 8 |
from datetime import datetime, date, time
|
| 9 |
from pathlib import Path
|
|
@@ -40,14 +37,6 @@ ui.include_css( Path(__file__).parent.joinpath("www") / "style.css")
|
|
| 40 |
# ----- CREATE OBJECTS ----- #
|
| 41 |
|
| 42 |
|
| 43 |
-
# this text appears in the browser tab
|
| 44 |
-
# load css styles from external file
|
| 45 |
-
ui.include_css( Path(__file__).parent.joinpath("www") / "style.css")
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
# ----- CREATE OBJECTS ----- #
|
| 49 |
-
|
| 50 |
-
|
| 51 |
# this text appears in the browser tab
|
| 52 |
TITLE = "CRA Window Tracker - GW Regulatory Studies Center"
|
| 53 |
|
|
@@ -61,15 +50,6 @@ page_header = ui.HTML(
|
|
| 61 |
"""
|
| 62 |
)
|
| 63 |
|
| 64 |
-
# logo at the top of the sidebar
|
| 65 |
-
page_header = ui.HTML(
|
| 66 |
-
f"""
|
| 67 |
-
<div class="header">
|
| 68 |
-
<span>{HEADER}</span>
|
| 69 |
-
</div>
|
| 70 |
-
"""
|
| 71 |
-
)
|
| 72 |
-
|
| 73 |
# logo at the top of the sidebar
|
| 74 |
sidebar_logo = ui.HTML(
|
| 75 |
f"""
|
|
@@ -92,19 +72,6 @@ FOOTER = f"""
|
|
| 92 |
# ----- APP LAYOUT ----- #
|
| 93 |
|
| 94 |
|
| 95 |
-
ui.tags.title(TITLE)
|
| 96 |
-
|
| 97 |
-
# footer at the bottom of the page
|
| 98 |
-
FOOTER = f"""
|
| 99 |
-
-----
|
| 100 |
-
|
| 101 |
-
© 2024 [GW Regulatory Studies Center](https://go.gwu.edu/regstudies). See our page on the [Congressional Review Act](https://regulatorystudies.columbian.gwu.edu/congressional-review-act) for more information.
|
| 102 |
-
"""
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
# ----- APP LAYOUT ----- #
|
| 106 |
-
|
| 107 |
-
|
| 108 |
ui.tags.title(TITLE)
|
| 109 |
|
| 110 |
page_header
|
|
@@ -113,7 +80,6 @@ page_header
|
|
| 113 |
with ui.sidebar(open={"desktop": "open", "mobile": "closed"}):
|
| 114 |
sidebar_logo
|
| 115 |
|
| 116 |
-
#ui.markdown("Estimated CRA window open date: [May 23](https://www.huntonak.com/the-nickel-report/federal-agencies-face-looming-congressional-review-act-deadline)")
|
| 117 |
with ui.tooltip(placement="right", id="window_tooltip"):
|
| 118 |
ui.input_date("start_date", "Select start of window", value=WINDOW_OPEN_DATE, min=START_DATE, max=date.today())
|
| 119 |
"The estimated CRA window open date is May 23. See the notes for more information."
|
|
@@ -124,8 +90,6 @@ with ui.sidebar(open={"desktop": "open", "mobile": "closed"}):
|
|
| 124 |
|
| 125 |
ui.input_select("menu_significant", "Select rule significance", choices=["all", "3f1-significant", "other-significant"], selected="all", multiple=True)
|
| 126 |
|
| 127 |
-
#ui.input_switch("switch", "Stack significant rules in plots", False)
|
| 128 |
-
|
| 129 |
# value boxes with summary data
|
| 130 |
with ui.layout_column_wrap():
|
| 131 |
with ui.value_box():
|
|
@@ -133,8 +97,7 @@ with ui.layout_column_wrap():
|
|
| 133 |
@render.text
|
| 134 |
def count_rules():
|
| 135 |
return f"{filtered_df()['document_number'].count()}"
|
| 136 |
-
ui.input_action_button("filter_all", "Clear Filters", class_="btn-filter", icon=icon_svg("book"))
|
| 137 |
-
ui.input_action_button("filter_all", "Clear Filters", class_="btn-filter", icon=icon_svg("book"))
|
| 138 |
|
| 139 |
with ui.value_box():
|
| 140 |
"Section 3(f)(1) Significant rules *"
|
|
@@ -144,8 +107,7 @@ with ui.layout_column_wrap():
|
|
| 144 |
if GET_SIGNIFICANT:
|
| 145 |
output = f"{filtered_df()['3f1_significant'].sum()}"
|
| 146 |
return output
|
| 147 |
-
ui.input_action_button("filter_3f1", "Filter", class_="btn-filter", icon=icon_svg("book"))
|
| 148 |
-
ui.input_action_button("filter_3f1", "Filter", class_="btn-filter", icon=icon_svg("book"))
|
| 149 |
|
| 150 |
with ui.value_box():
|
| 151 |
"Other Significant rules *"
|
|
@@ -155,8 +117,7 @@ with ui.layout_column_wrap():
|
|
| 155 |
if GET_SIGNIFICANT:
|
| 156 |
output = f"{filtered_df()['other_significant'].sum()}"
|
| 157 |
return output
|
| 158 |
-
ui.input_action_button("filter_other", "Filter", class_="btn-filter", icon=icon_svg("book"))
|
| 159 |
-
ui.input_action_button("filter_other", "Filter", class_="btn-filter", icon=icon_svg("book"))
|
| 160 |
|
| 161 |
# documentation note on significance data
|
| 162 |
ui.markdown(
|
|
@@ -171,7 +132,7 @@ with ui.navset_card_underline(title=""):
|
|
| 171 |
with ui.nav_panel("Rules in detail"):
|
| 172 |
@render.data_frame
|
| 173 |
def table_rule_detail():
|
| 174 |
-
df =
|
| 175 |
df.loc[:, "date"] = df.loc[:, "publication_date"].apply(lambda x: f"{x.date()}")
|
| 176 |
char, limit = " ", 10
|
| 177 |
df.loc[:, "title"] = df["title"].apply(lambda x: x if len(x.split(char)) < (limit + 1) else f"{char.join(x.split(char)[:limit])}...")
|
|
@@ -203,7 +164,7 @@ with ui.navset_card_underline(title=""):
|
|
| 203 |
return plot_tf(
|
| 204 |
grouped,
|
| 205 |
input.frequency(),
|
| 206 |
-
|
| 207 |
)
|
| 208 |
|
| 209 |
with ui.card(full_screen=True):
|
|
@@ -236,16 +197,13 @@ with ui.navset_card_underline(title=""):
|
|
| 236 |
with ui.card(full_screen=True):
|
| 237 |
@render.plot
|
| 238 |
def plot_by_agency():
|
| 239 |
-
grouped = grouped_df_agency()
|
| 240 |
-
#if input.switch():
|
| 241 |
-
# pass
|
| 242 |
-
# # placeholder for stacked bar chart
|
| 243 |
if len(grouped) < 2:
|
| 244 |
return plot_NA()
|
| 245 |
else:
|
| 246 |
plot = plot_agency(
|
| 247 |
grouped.head(10),
|
| 248 |
-
|
| 249 |
)
|
| 250 |
return plot
|
| 251 |
|
|
@@ -346,31 +304,16 @@ def filtered_df():
|
|
| 346 |
return filt_df
|
| 347 |
|
| 348 |
|
| 349 |
-
@reactive.calc
|
| 350 |
-
def filtered_sig():
|
| 351 |
-
filt_df = filtered_df()
|
| 352 |
-
|
| 353 |
-
# filter significance
|
| 354 |
-
if filter_value.get() == "all":
|
| 355 |
-
pass
|
| 356 |
-
elif filter_value.get() == "3f1":
|
| 357 |
-
filt_df = filt_df.loc[filt_df["3f1_significant"] == 1]
|
| 358 |
-
elif filter_value.get() == "other":
|
| 359 |
-
filt_df = filt_df.loc[filt_df["other_significant"] == 1]
|
| 360 |
-
|
| 361 |
-
return filt_df
|
| 362 |
-
|
| 363 |
-
|
| 364 |
@reactive.calc
|
| 365 |
def grouped_df_month():
|
| 366 |
-
filt_df =
|
| 367 |
grouped = groupby_date(filt_df, significant=GET_SIGNIFICANT)
|
| 368 |
return grouped
|
| 369 |
|
| 370 |
|
| 371 |
@reactive.calc
|
| 372 |
def grouped_df_day():
|
| 373 |
-
filt_df =
|
| 374 |
date_col = "publication_date"
|
| 375 |
grouped = groupby_date(filt_df, group_col=date_col, significant=GET_SIGNIFICANT)
|
| 376 |
grouped = pad_missing_dates(
|
|
@@ -387,7 +330,7 @@ def grouped_df_day():
|
|
| 387 |
|
| 388 |
@reactive.calc
|
| 389 |
def grouped_df_week():
|
| 390 |
-
filt_df =
|
| 391 |
filt_df = add_weeks_to_data(filt_df)
|
| 392 |
try:
|
| 393 |
grouped = groupby_date(filt_df, group_col=("week_number", "week_of"), significant=GET_SIGNIFICANT)
|
|
@@ -407,7 +350,7 @@ def grouped_df_week():
|
|
| 407 |
|
| 408 |
@reactive.calc
|
| 409 |
def grouped_df_agency():
|
| 410 |
-
filt_df =
|
| 411 |
grouped = groupby_agency(filt_df, metadata=METADATA, significant=GET_SIGNIFICANT)
|
| 412 |
return grouped
|
| 413 |
|
|
@@ -423,27 +366,3 @@ def get_grouped_data_over_time():
|
|
| 423 |
else:
|
| 424 |
raise ValueError("Only 'daily', 'monthly', or 'weekly' are valid inputs.")
|
| 425 |
return grouped
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
# ----- REACTIVE VALUES ----- #
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
filter_value = reactive.value("all")
|
| 432 |
-
|
| 433 |
-
@reactive.effect
|
| 434 |
-
@reactive.event(input.filter_all)
|
| 435 |
-
def _():
|
| 436 |
-
filter_value.set("all")
|
| 437 |
-
filtered_df()
|
| 438 |
-
|
| 439 |
-
@reactive.effect
|
| 440 |
-
@reactive.event(input.filter_3f1)
|
| 441 |
-
def _():
|
| 442 |
-
filter_value.set("3f1")
|
| 443 |
-
filtered_df()
|
| 444 |
-
|
| 445 |
-
@reactive.effect
|
| 446 |
-
@reactive.event(input.filter_other)
|
| 447 |
-
def _():
|
| 448 |
-
filter_value.set("other")
|
| 449 |
-
filtered_df()
|
|
|
|
| 1 |
# ----- IMPORTS ----- #
|
| 2 |
|
| 3 |
|
|
|
|
|
|
|
|
|
|
| 4 |
import asyncio
|
| 5 |
from datetime import datetime, date, time
|
| 6 |
from pathlib import Path
|
|
|
|
| 37 |
# ----- CREATE OBJECTS ----- #
|
| 38 |
|
| 39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
# this text appears in the browser tab
|
| 41 |
TITLE = "CRA Window Tracker - GW Regulatory Studies Center"
|
| 42 |
|
|
|
|
| 50 |
"""
|
| 51 |
)
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
# logo at the top of the sidebar
|
| 54 |
sidebar_logo = ui.HTML(
|
| 55 |
f"""
|
|
|
|
| 72 |
# ----- APP LAYOUT ----- #
|
| 73 |
|
| 74 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
ui.tags.title(TITLE)
|
| 76 |
|
| 77 |
page_header
|
|
|
|
| 80 |
with ui.sidebar(open={"desktop": "open", "mobile": "closed"}):
|
| 81 |
sidebar_logo
|
| 82 |
|
|
|
|
| 83 |
with ui.tooltip(placement="right", id="window_tooltip"):
|
| 84 |
ui.input_date("start_date", "Select start of window", value=WINDOW_OPEN_DATE, min=START_DATE, max=date.today())
|
| 85 |
"The estimated CRA window open date is May 23. See the notes for more information."
|
|
|
|
| 90 |
|
| 91 |
ui.input_select("menu_significant", "Select rule significance", choices=["all", "3f1-significant", "other-significant"], selected="all", multiple=True)
|
| 92 |
|
|
|
|
|
|
|
| 93 |
# value boxes with summary data
|
| 94 |
with ui.layout_column_wrap():
|
| 95 |
with ui.value_box():
|
|
|
|
| 97 |
@render.text
|
| 98 |
def count_rules():
|
| 99 |
return f"{filtered_df()['document_number'].count()}"
|
| 100 |
+
#ui.input_action_button("filter_all", "Clear Filters", class_="btn-filter", icon=icon_svg("book"))
|
|
|
|
| 101 |
|
| 102 |
with ui.value_box():
|
| 103 |
"Section 3(f)(1) Significant rules *"
|
|
|
|
| 107 |
if GET_SIGNIFICANT:
|
| 108 |
output = f"{filtered_df()['3f1_significant'].sum()}"
|
| 109 |
return output
|
| 110 |
+
#ui.input_action_button("filter_3f1", "Filter", class_="btn-filter", icon=icon_svg("book"))
|
|
|
|
| 111 |
|
| 112 |
with ui.value_box():
|
| 113 |
"Other Significant rules *"
|
|
|
|
| 117 |
if GET_SIGNIFICANT:
|
| 118 |
output = f"{filtered_df()['other_significant'].sum()}"
|
| 119 |
return output
|
| 120 |
+
#ui.input_action_button("filter_other", "Filter", class_="btn-filter", icon=icon_svg("book"))
|
|
|
|
| 121 |
|
| 122 |
# documentation note on significance data
|
| 123 |
ui.markdown(
|
|
|
|
| 132 |
with ui.nav_panel("Rules in detail"):
|
| 133 |
@render.data_frame
|
| 134 |
def table_rule_detail():
|
| 135 |
+
df = filtered_df().copy()
|
| 136 |
df.loc[:, "date"] = df.loc[:, "publication_date"].apply(lambda x: f"{x.date()}")
|
| 137 |
char, limit = " ", 10
|
| 138 |
df.loc[:, "title"] = df["title"].apply(lambda x: x if len(x.split(char)) < (limit + 1) else f"{char.join(x.split(char)[:limit])}...")
|
|
|
|
| 164 |
return plot_tf(
|
| 165 |
grouped,
|
| 166 |
input.frequency(),
|
| 167 |
+
rule_types=input.menu_significant(),
|
| 168 |
)
|
| 169 |
|
| 170 |
with ui.card(full_screen=True):
|
|
|
|
| 197 |
with ui.card(full_screen=True):
|
| 198 |
@render.plot
|
| 199 |
def plot_by_agency():
|
| 200 |
+
grouped = grouped_df_agency()
|
|
|
|
|
|
|
|
|
|
| 201 |
if len(grouped) < 2:
|
| 202 |
return plot_NA()
|
| 203 |
else:
|
| 204 |
plot = plot_agency(
|
| 205 |
grouped.head(10),
|
| 206 |
+
rule_types=input.menu_significant(),
|
| 207 |
)
|
| 208 |
return plot
|
| 209 |
|
|
|
|
| 304 |
return filt_df
|
| 305 |
|
| 306 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
@reactive.calc
|
| 308 |
def grouped_df_month():
|
| 309 |
+
filt_df = filtered_df()
|
| 310 |
grouped = groupby_date(filt_df, significant=GET_SIGNIFICANT)
|
| 311 |
return grouped
|
| 312 |
|
| 313 |
|
| 314 |
@reactive.calc
|
| 315 |
def grouped_df_day():
|
| 316 |
+
filt_df = filtered_df()
|
| 317 |
date_col = "publication_date"
|
| 318 |
grouped = groupby_date(filt_df, group_col=date_col, significant=GET_SIGNIFICANT)
|
| 319 |
grouped = pad_missing_dates(
|
|
|
|
| 330 |
|
| 331 |
@reactive.calc
|
| 332 |
def grouped_df_week():
|
| 333 |
+
filt_df = filtered_df()
|
| 334 |
filt_df = add_weeks_to_data(filt_df)
|
| 335 |
try:
|
| 336 |
grouped = groupby_date(filt_df, group_col=("week_number", "week_of"), significant=GET_SIGNIFICANT)
|
|
|
|
| 350 |
|
| 351 |
@reactive.calc
|
| 352 |
def grouped_df_agency():
|
| 353 |
+
filt_df = filtered_df()
|
| 354 |
grouped = groupby_agency(filt_df, metadata=METADATA, significant=GET_SIGNIFICANT)
|
| 355 |
return grouped
|
| 356 |
|
|
|
|
| 366 |
else:
|
| 367 |
raise ValueError("Only 'daily', 'monthly', or 'weekly' are valid inputs.")
|
| 368 |
return grouped
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/plotting.py
CHANGED
|
@@ -31,20 +31,21 @@ def plot_NA(placeholder_text: str = "Not enough data available to visualize.", p
|
|
| 31 |
)
|
| 32 |
|
| 33 |
|
| 34 |
-
def generate_rule_axis_label(
|
| 35 |
"""Generate axis label for rules, accounting for rule type ("all", "3f1", or "other")."""
|
| 36 |
-
|
| 37 |
-
if
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
|
|
|
| 48 |
"""Plot rules by agency.
|
| 49 |
|
| 50 |
Args:
|
|
@@ -57,7 +58,7 @@ def plot_agency(df, group_col = "acronym", value_col = "rules", color="#033C5A",
|
|
| 57 |
"""
|
| 58 |
order_list = df.loc[:, group_col].to_list()[::-1]
|
| 59 |
|
| 60 |
-
y_lab = generate_rule_axis_label(
|
| 61 |
|
| 62 |
plot = (
|
| 63 |
ggplot(
|
|
@@ -212,7 +213,7 @@ def plot_week(
|
|
| 212 |
return plot
|
| 213 |
|
| 214 |
|
| 215 |
-
def plot_tf(df: DataFrame, frequency: str,
|
| 216 |
"""Plot rules over time by given frequency.
|
| 217 |
|
| 218 |
Args:
|
|
@@ -234,6 +235,6 @@ def plot_tf(df: DataFrame, frequency: str, rule_type: str | None = None, **kwarg
|
|
| 234 |
if plot_freq is None:
|
| 235 |
raise ValueError(f"Frequency must be one of: {', '.join(freq_options.keys())}")
|
| 236 |
|
| 237 |
-
y_lab = generate_rule_axis_label(
|
| 238 |
|
| 239 |
return plot_freq(df, y_lab=y_lab, **kwargs)
|
|
|
|
| 31 |
)
|
| 32 |
|
| 33 |
|
| 34 |
+
def generate_rule_axis_label(rule_types: list | None = None):
|
| 35 |
"""Generate axis label for rules, accounting for rule type ("all", "3f1", or "other")."""
|
| 36 |
+
categories = ""
|
| 37 |
+
if (rule_types is None) or ("all" in rule_types):
|
| 38 |
+
pass
|
| 39 |
+
elif all(True if cat in rule_types else False for cat in ("3f1-significant", "other-significant")):
|
| 40 |
+
categories = "significant"
|
| 41 |
+
elif ("3f1-significant" in rule_types) and ("other-significant" not in rule_types):
|
| 42 |
+
categories = "Section 3(f)(1) Significant"
|
| 43 |
+
elif ("3f1-significant" not in rule_types) and ("other-significant" in rule_types):
|
| 44 |
+
categories = "Other Significant"
|
| 45 |
+
return f"Number of {categories} rules".replace(" ", " ")
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def plot_agency(df, group_col = "acronym", value_col = "rules", color="#033C5A", rule_types: list | None = None):
|
| 49 |
"""Plot rules by agency.
|
| 50 |
|
| 51 |
Args:
|
|
|
|
| 58 |
"""
|
| 59 |
order_list = df.loc[:, group_col].to_list()[::-1]
|
| 60 |
|
| 61 |
+
y_lab = generate_rule_axis_label(rule_types)
|
| 62 |
|
| 63 |
plot = (
|
| 64 |
ggplot(
|
|
|
|
| 213 |
return plot
|
| 214 |
|
| 215 |
|
| 216 |
+
def plot_tf(df: DataFrame, frequency: str, rule_types: str | None = None, **kwargs) -> ggplot:
|
| 217 |
"""Plot rules over time by given frequency.
|
| 218 |
|
| 219 |
Args:
|
|
|
|
| 235 |
if plot_freq is None:
|
| 236 |
raise ValueError(f"Frequency must be one of: {', '.join(freq_options.keys())}")
|
| 237 |
|
| 238 |
+
y_lab = generate_rule_axis_label(rule_types)
|
| 239 |
|
| 240 |
return plot_freq(df, y_lab=y_lab, **kwargs)
|