Mark Febrizio
commited on
Commit
•
7da65a3
1
Parent(s):
808d55e
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)
|