Repository: CodeForPhilly/chime
Pull request #191 (2020-03-23 03:52:36)

17 chart language
See notes in #17. This is in draft for now.


Patch:
diff --git a/src/app.py b/src/app.py
index 9527acb..a7ce992 100644
--- a/src/app.py
+++ b/src/app.py
@@ -19,11 +19,10 @@ from penn_chime.presentation import (
 )
 from penn_chime.settings import DEFAULTS
 from penn_chime.models import sim_sir_df, build_admissions_df, build_census_df
-from penn_chime.charts import (
-    additional_projections_chart,
-    admitted_patients_chart,
-    new_admissions_chart,
-)
+from penn_chime.charts import (additional_projections_chart,
+                               admitted_patients_chart,
+                               new_admissions_chart,
+                               chart_descriptions)
 
 # This is somewhat dangerous:
 # Hide the main menu with "Rerun", "run on Save", "clear cache", and "record a screencast"
@@ -51,20 +50,24 @@ census_df = build_census_df(admissions_df, parameters=p)
 
 st.subheader("New Admissions")
 st.markdown("Projected number of **daily** COVID-19 admissions at Penn hospitals")
+new_admit_chart = new_admissions_chart(alt, admissions_df, parameters=p, as_date=as_date)
 st.altair_chart(
-    new_admissions_chart(alt, admissions_df, parameters=p, as_date=as_date),
-    use_container_width=True,
+    new_admit_chart, use_container_width=True
 )
+
+st.markdown(chart_descriptions(new_admit_chart))
+
 if st.checkbox("Show Projected Admissions in tabular form"):
     draw_projected_admissions_table(st, admissions_df, as_date=as_date)
 st.subheader("Admitted Patients (Census)")
 st.markdown(
     "Projected **census** of COVID-19 patients, accounting for arrivals and discharges at Penn hospitals"
 )
+census_chart = admitted_patients_chart(alt=alt, census=census_df, parameters=p, as_date=as_date)
 st.altair_chart(
-    admitted_patients_chart(alt=alt, census=census_df, parameters=p, as_date=as_date),
-    use_container_width=True,
+    census_chart, use_container_width=True
 )
+st.markdown(chart_descriptions(census_chart, suffix=" Census"))
 if st.checkbox("Show Projected Census in tabular form"):
     draw_census_table(st, census_df, as_date=as_date)
 st.markdown(
diff --git a/src/penn_chime/charts.py b/src/penn_chime/charts.py
index 22afe4c..4509a00 100644
--- a/src/penn_chime/charts.py
+++ b/src/penn_chime/charts.py
@@ -1,3 +1,6 @@
+
+from math import ceil
+import datetime
 from altair import Chart  # type: ignore
 import pandas as pd  # type: ignore
 import numpy as np  # type: ignore
@@ -112,3 +115,43 @@ def additional_projections_chart(
         )
         .interactive()
     )
+
+
+def chart_descriptions(chart: Chart, suffix: str = ""):
+    """
+
+    :param chart: Chart: The alt chart to be used in finding max points
+    :param suffix: str: The assumption is that the charts have similar column names.
+                   The census chart adds " Census" to the column names.
+                   Make sure to include a space or underscore as appropriate
+    :return: str: Returns a multi-line string description of the results
+    """
+    messages = []
+
+    cols = ["Hospitalized", "ICU", "Ventilated"]
+    asterisk = False
+    day = "date" if "date" in chart.data.columns else "day"
+
+    for col in cols:
+        if chart.data[col].idxmax() + 1 == len(chart.data):
+            asterisk = True
+
+        on = chart.data[day][chart.data[col].idxmax()]
+        if day == "date":
+            on = datetime.datetime.strftime(on, "%b %d")  # todo: bring this to an optional arg / i18n
+        else:
+            on += 1  # 0 index issue
+
+        messages.append(
+            "{}{} peaks at {:,} on day {}{}".format(
+                col,
+                suffix,
+                ceil(chart.data[col].max()),
+                on,
+                "*" if asterisk else "",
+            )
+        )
+
+    if asterisk:
+        messages.append("_* The max is at the upper bound of the data, and therefore may not be the actual max_")
+    return "\n\n".join(messages)
diff --git a/src/penn_chime/presentation.py b/src/penn_chime/presentation.py
index ca20281..b78cb9a 100644
--- a/src/penn_chime/presentation.py
+++ b/src/penn_chime/presentation.py
@@ -404,7 +404,6 @@ def draw_projected_admissions_table(
         admits_table = add_date_column(
             admits_table, drop_day_column=True, date_format=DATE_FORMAT
         )
-
     st.table(admits_table)
     return None
 


Test patch:
diff --git a/tests/test_app.py b/tests/test_app.py
index 5dcd023..c613b36 100644
--- a/tests/test_app.py
+++ b/tests/test_app.py
@@ -1,11 +1,13 @@
 """Tests."""
 
+from math import ceil  # type: ignore
+import datetime  # type: ignore
 import pytest  # type: ignore
 import pandas as pd  # type: ignore
 import numpy as np  # type: ignore
 import altair as alt  # type: ignore
 
-from src.penn_chime.charts import new_admissions_chart, admitted_patients_chart
+from src.penn_chime.charts import new_admissions_chart, admitted_patients_chart, chart_descriptions
 from src.penn_chime.models import sir, sim_sir, build_admissions_df
 from src.penn_chime.parameters import Parameters
 from src.penn_chime.presentation import display_header
@@ -254,3 +256,41 @@ def test_parameters():
         0.05 * 0.05 * (param.infected_v[1:-1] + param.recovered_v[1:-1]) - 100
     )
     assert (diff.abs() < 0.1).all()
+    assert len(param.susceptible_v) == len(param.infected_v) == len(param.recovered_v) == param.n_days + 1 == 3
+
+
+def test_chart_descriptions():
+    # new admissions chart
+    projection_admits = pd.read_csv('tests/projection_admits.csv')
+    projection_admits = projection_admits.rename(columns={'hosp': 'Hospitalized', 'icu': 'ICU', 'vent': 'Ventilated'})
+    chart = new_admissions_chart(alt, projection_admits, PARAM)
+    description = chart_descriptions(chart)
+
+    hosp, icu, vent, asterisk = description.split("\n\n")  # break out the description into lines
+
+    max_hosp = chart.data['Hospitalized'].max()
+    assert str(ceil(max_hosp)) in hosp
+
+    max_icu_ix = chart.data['ICU'].idxmax()
+    assert max_icu_ix + 1 == len(chart.data)
+    assert "*" in icu
+
+    # test asterisk
+    param = PARAM
+    param.n_days = 600
+
+    projection_admits = pd.read_csv('tests/projection_admits.csv')
+    projection_admits = projection_admits.rename(columns={'hosp': 'Hospitalized', 'icu': 'ICU', 'vent': 'Ventilated'})
+    chart = new_admissions_chart(alt, projection_admits, PARAM)
+    description = chart_descriptions(chart)
+    assert "*" not in description
+
+    # census chart
+    census_df = pd.read_csv('tests/census_df.csv')
+    census_df = census_df.rename(columns={'hosp': 'Hospitalized', 'icu': 'ICU', 'vent': 'Ventilated'})
+    chart = admitted_patients_chart(alt, census_df, PARAM, as_date=True)
+    description = chart_descriptions(chart)
+
+    assert str(ceil(chart.data['Ventilated'].max())) in description
+    assert str(chart.data['ICU'].idxmax()) not in description
+    assert datetime.datetime.strftime(chart.data.iloc[chart.data['ICU'].idxmax()].date, '%b %d') in description