Download src/utils/plotting.py from rmbielski/RHTP: direct link, hf CLI and curl.
- Browser
- Download file 10.4 kB
-
https://huggingface.co/spaces/rmbielski/RHTP/resolve/main/src/utils/plotting.py
- Command line
-
hf download hf://spaces/rmbielski/RHTP/src/utils/plotting.py
-
curl -L -o plotting.py https://huggingface.co/spaces/rmbielski/RHTP/resolve/main/src/utils/plotting.py
10.4 kB
| """Plotly helpers tuned for the RHTP dashboard (dark theme).""" | |
| from __future__ import annotations | |
| from typing import Iterable, Optional | |
| import numpy as np | |
| import pandas as pd | |
| import plotly.express as px | |
| import plotly.graph_objects as go | |
| import plotly.io as pio | |
| from .styling import PALETTE | |
| # Build a custom dark template once at import time so every plot | |
| # inherits the same surface / type / grid styling. | |
| _template = go.layout.Template() | |
| _template.layout = go.Layout( | |
| paper_bgcolor=PALETTE["bg"], | |
| plot_bgcolor=PALETTE["bg"], | |
| font=dict(family="Inter, system-ui, sans-serif", | |
| size=12, color=PALETTE["text"]), | |
| xaxis=dict( | |
| gridcolor=PALETTE["border"], zeroline=False, | |
| linecolor=PALETTE["border"], | |
| tickfont=dict(color=PALETTE["text_dim"]), | |
| title=dict(font=dict(color=PALETTE["text_dim"])), | |
| ), | |
| yaxis=dict( | |
| gridcolor=PALETTE["border"], zeroline=False, | |
| linecolor=PALETTE["border"], | |
| tickfont=dict(color=PALETTE["text_dim"]), | |
| title=dict(font=dict(color=PALETTE["text_dim"])), | |
| ), | |
| legend=dict( | |
| bgcolor="rgba(0,0,0,0)", | |
| font=dict(color=PALETTE["text"], size=11), | |
| ), | |
| hoverlabel=dict( | |
| bgcolor=PALETTE["surface2"], | |
| bordercolor=PALETTE["border"], | |
| font_color=PALETTE["text"], | |
| font_size=12, | |
| ), | |
| colorway=[ | |
| PALETTE["secondary"], PALETTE["primary"], PALETTE["good"], | |
| PALETTE["accent"], "#A487E0", "#D679A4", "#5BCFA9", "#E08F5B", | |
| ], | |
| ) | |
| pio.templates["rhtp_dark"] = _template | |
| PLOT_TEMPLATE = "rhtp_dark" | |
| def _layout(fig: go.Figure, height: int = 380, **kwargs) -> go.Figure: | |
| default_title = dict( | |
| font=dict(size=14, color=PALETTE["text"], family="Inter, system-ui, sans-serif"), | |
| x=0.0, xanchor="left", | |
| ) | |
| if "title" in kwargs: | |
| t = kwargs.pop("title") | |
| if isinstance(t, dict): | |
| default_title.update(t) | |
| else: | |
| default_title["text"] = t | |
| fig.update_layout( | |
| template=PLOT_TEMPLATE, | |
| height=height, | |
| margin=dict(t=46, l=12, r=12, b=42), | |
| title=default_title, | |
| legend=dict(orientation="h", yanchor="bottom", y=1.02, | |
| xanchor="left", x=0.0, font=dict(size=11)), | |
| **kwargs, | |
| ) | |
| return fig | |
| def line_by_group( | |
| df: pd.DataFrame, x: str, y: str, group: str, *, | |
| title: str = "", y_label: Optional[str] = None, | |
| treatment_band: Optional[tuple[float, float]] = None, | |
| hover_extra: Iterable[str] = (), | |
| height: int = 380, | |
| ) -> go.Figure: | |
| fig = px.line( | |
| df, x=x, y=y, color=group, | |
| hover_data=list(hover_extra), | |
| markers=True, | |
| ) | |
| if treatment_band: | |
| x0, x1 = treatment_band | |
| fig.add_vrect( | |
| x0=x0, x1=x1, fillcolor=PALETTE["accent"], opacity=0.10, | |
| line_width=0, | |
| annotation_text="RHTP rollout", | |
| annotation_position="top left", | |
| annotation_font_size=10, | |
| annotation_font_color=PALETTE["accent"]) | |
| fig.update_yaxes(title=y_label or y) | |
| fig.update_xaxes(title=x) | |
| return _layout(fig, height=height, title=dict(text=title)) | |
| def scatter_with_trend( | |
| df: pd.DataFrame, x: str, y: str, *, | |
| color: Optional[str] = None, | |
| size: Optional[str] = None, | |
| title: str = "", height: int = 380, | |
| log_x: bool = False, | |
| ) -> go.Figure: | |
| fig = px.scatter( | |
| df, x=x, y=y, color=color, size=size, | |
| opacity=0.80, trendline="ols", | |
| trendline_color_override=PALETTE["accent"], | |
| log_x=log_x, | |
| ) | |
| fig.update_traces(marker=dict(line=dict(width=0.4, color=PALETTE["bg"]))) | |
| return _layout(fig, height=height, title=dict(text=title)) | |
| def event_study_plot( | |
| coefs: pd.DataFrame, *, | |
| title: str = "Event-study: dynamic treatment effect", | |
| y_label: str = "Estimated effect (vs. period -1)", | |
| height: int = 420, | |
| ) -> go.Figure: | |
| fig = go.Figure() | |
| fig.add_trace(go.Scatter( | |
| x=coefs["event_time"], y=coefs["coef"], | |
| error_y=dict(type="data", | |
| array=coefs["ci_high"] - coefs["coef"], | |
| arrayminus=coefs["coef"] - coefs["ci_low"], | |
| color=PALETTE["secondary"], thickness=1.4, width=4), | |
| mode="markers+lines", | |
| line=dict(color=PALETTE["secondary"], width=2), | |
| marker=dict(size=8, color=PALETTE["secondary"], | |
| line=dict(color=PALETTE["bg"], width=1.5)), | |
| name="point estimate (95% CI)", | |
| )) | |
| fig.add_hline(y=0, line=dict(color=PALETTE["text_dim"], | |
| dash="dash", width=1)) | |
| fig.add_vline( | |
| x=-0.5, line=dict(color=PALETTE["primary"], dash="dot", width=1.6), | |
| annotation_text="implementation", | |
| annotation_position="top right", | |
| annotation_font_color=PALETTE["primary"]) | |
| fig.update_xaxes(title="Event time (periods relative to implementation)", | |
| dtick=1) | |
| fig.update_yaxes(title=y_label) | |
| return _layout(fig, height=height, title=dict(text=title)) | |
| def coef_forest( | |
| coef_df: pd.DataFrame, *, | |
| title: str = "Coefficient estimates", | |
| height: int = 380, | |
| ) -> go.Figure: | |
| coef_df = coef_df.copy().iloc[::-1] | |
| fig = go.Figure() | |
| fig.add_trace(go.Scatter( | |
| x=coef_df["coef"], | |
| y=coef_df["term"], | |
| error_x=dict( | |
| type="data", | |
| array=coef_df["ci_high"] - coef_df["coef"], | |
| arrayminus=coef_df["coef"] - coef_df["ci_low"], | |
| color=PALETTE["secondary"], thickness=1.5, width=6, | |
| ), | |
| mode="markers", | |
| marker=dict(size=10, color=PALETTE["secondary"], | |
| line=dict(color=PALETTE["bg"], width=1)), | |
| showlegend=False, | |
| )) | |
| fig.add_vline(x=0, line=dict(color=PALETTE["text_dim"], | |
| dash="dash", width=1)) | |
| fig.update_xaxes(title="Estimated coefficient (95% CI)") | |
| fig.update_yaxes(title="") | |
| return _layout(fig, height=height, title=dict(text=title)) | |
| def montana_county_choropleth( | |
| counties: pd.DataFrame, *, color: str, color_label: str, | |
| title: str = "", height: int = 540, | |
| ) -> go.Figure: | |
| fig = px.choropleth_mapbox( | |
| counties, | |
| geojson="https://raw.githubusercontent.com/plotly/datasets/master/geojson-counties-fips.json", | |
| locations="fips", | |
| color=color, | |
| color_continuous_scale="Cividis", | |
| mapbox_style="carto-darkmatter", | |
| center={"lat": 47.0, "lon": -110.5}, | |
| zoom=5.2, | |
| hover_name="county_name", | |
| hover_data={ | |
| "fips": False, "rurality": True, | |
| "population_2020": ":,", color: ":.2f", | |
| }, | |
| labels={color: color_label}, | |
| ) | |
| fig.update_layout( | |
| template=PLOT_TEMPLATE, | |
| margin=dict(l=0, r=0, t=42, b=0), | |
| height=height, | |
| paper_bgcolor=PALETTE["bg"], | |
| title=dict(text=title, x=0.0, xanchor="left", | |
| font=dict(size=14, color=PALETTE["text"])), | |
| ) | |
| return fig | |
| def montana_with_hospitals( | |
| counties: pd.DataFrame, hospitals: pd.DataFrame, *, | |
| height: int = 620, | |
| facility_type_filter: Optional[list[str]] = None, | |
| ) -> go.Figure: | |
| cdf = counties.copy() | |
| cdf["rurality_code"] = cdf["rurality"].map({"Urban": 0, "Rural": 1, "Tribal": 2}) | |
| if facility_type_filter: | |
| hdf = hospitals[hospitals["facility_type"].isin(facility_type_filter)].copy() | |
| else: | |
| hdf = hospitals.copy() | |
| hdf["marker_size"] = np.clip(hdf["staffed_beds"] / 4 + 6, 6, 32) | |
| type_color = { | |
| "Tertiary": "#5BA3DA", | |
| "PPS": "#7AB6E0", | |
| "Sole Community": "#9CC8E8", | |
| "CAH": "#E0B458", | |
| "Tribal/IHS": "#E15A63", | |
| } | |
| fig = go.Figure() | |
| # Choropleth base — subtle differentiation between rurality strata. | |
| fig.add_choroplethmapbox( | |
| geojson="https://raw.githubusercontent.com/plotly/datasets/master/geojson-counties-fips.json", | |
| locations=cdf["fips"], | |
| z=cdf["rurality_code"], | |
| colorscale=[[0, "#1A2030"], [0.5, "#2A2438"], [1, "#3A2030"]], | |
| marker_opacity=0.9, | |
| marker_line_width=0.5, | |
| marker_line_color="#0B0E13", | |
| showscale=False, | |
| text=cdf["county_name"] + " — pop " + | |
| cdf["population_2020"].astype(int).map("{:,}".format) + | |
| " (" + cdf["rurality"] + ")", | |
| hovertemplate="<b>%{text}</b><extra></extra>", | |
| name="Counties", | |
| ) | |
| for ftype, sub in hdf.groupby("facility_type"): | |
| fig.add_scattermapbox( | |
| lat=sub["lat"], lon=sub["lon"], | |
| mode="markers", | |
| marker=dict(size=sub["marker_size"], | |
| color=type_color.get(ftype, "#9CA3AF"), | |
| opacity=0.92), | |
| text=sub["facility_name"] | |
| + "<br>" + sub["county_name"] + " County" | |
| + "<br>" + ftype + " — " + sub["staffed_beds"].astype(str) | |
| + " staffed beds<br>CCN " + sub["ccn"].astype(str) | |
| + "<br>" + sub["ownership"], | |
| hovertemplate="<b>%{text}</b><extra></extra>", | |
| name=ftype, | |
| ) | |
| fig.update_layout( | |
| mapbox=dict( | |
| style="carto-darkmatter", | |
| center={"lat": 47.0, "lon": -110.0}, | |
| zoom=5.4, | |
| ), | |
| paper_bgcolor=PALETTE["bg"], | |
| plot_bgcolor=PALETTE["bg"], | |
| font=dict(color=PALETTE["text"]), | |
| margin=dict(l=0, r=0, t=10, b=0), | |
| height=height, | |
| legend=dict( | |
| orientation="h", yanchor="bottom", y=0.0, | |
| xanchor="left", x=0.0, | |
| bgcolor="rgba(11,14,19,0.85)", | |
| bordercolor=PALETTE["border"], borderwidth=1, | |
| font=dict(color=PALETTE["text"]), | |
| ), | |
| ) | |
| return fig | |
| def stacked_bar(df: pd.DataFrame, x: str, ys: list[str], *, | |
| title: str = "", y_label: str = "", height: int = 360) -> go.Figure: | |
| fig = go.Figure() | |
| palette = [PALETTE["secondary"], "#7AB6E0", "#9CC8E8", | |
| PALETTE["primary"], PALETTE["accent"], "#A487E0"] | |
| for i, y in enumerate(ys): | |
| fig.add_bar(x=df[x], y=df[y], name=y, | |
| marker_color=palette[i % len(palette)]) | |
| fig.update_layout(barmode="stack") | |
| fig.update_yaxes(title=y_label) | |
| fig.update_xaxes(title=x) | |
| return _layout(fig, height=height, title=dict(text=title)) | |