Skip to content

Commit fb72978

Browse files
plots
1 parent ebe5377 commit fb72978

2 files changed

Lines changed: 112 additions & 28 deletions

File tree

cybench/evaluation/aggregated_metrics.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010

1111
from cybench.config import KEY_LOC, KEY_TARGET, KEY_YEAR
1212

13+
# Minimum points per slice for median view metrics (and temporal panel regional lines).
14+
MIN_SLICE_YEARS = 3
15+
MIN_SLICE_REGIONS = 3
16+
1317

1418
def calc_r_r2(
1519
y_true: npt.ArrayLike,
@@ -50,7 +54,7 @@ def calc_median_yearly_r2(
5054
*,
5155
loc_col: str = KEY_LOC,
5256
year_col: str = KEY_YEAR,
53-
min_regions: int = 3,
57+
min_regions: int = MIN_SLICE_REGIONS,
5458
) -> float:
5559
"""Median of per-year R², where each year's R² is computed across regions."""
5660
yearly_r2: list[float] = []
@@ -74,7 +78,7 @@ def calc_median_regional_r2(
7478
*,
7579
loc_col: str = KEY_LOC,
7680
year_col: str = KEY_YEAR,
77-
min_years: int = 3,
81+
min_years: int = MIN_SLICE_YEARS,
7882
) -> float:
7983
"""Median of per-region R², where each region's R² is computed across years."""
8084
del year_col # API symmetry with yearly helper; years come from row groups.
@@ -98,7 +102,7 @@ def calc_median_regional_r2_res(
98102
model_col: str,
99103
*,
100104
loc_col: str = KEY_LOC,
101-
min_years: int = 3,
105+
min_years: int = MIN_SLICE_YEARS,
102106
) -> float:
103107
"""Median of per-region R² on location-de-meaned yields (anomaly view)."""
104108
loc_means = df.groupby(loc_col)[target_col].mean()

cybench/runs/viz/visualize_results_aggregated.py

Lines changed: 105 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from cybench.util.geo import get_shapes_from_polygons, world_shape_path
2222
from cybench.config import KEY_LOC, KEY_TARGET
2323
from cybench.evaluation.aggregated_metrics import (
24+
MIN_SLICE_REGIONS,
2425
calc_r_r2,
2526
compute_report_metrics,
2627
get_metrics_dict,
@@ -520,6 +521,64 @@ def generate_local_report_html(stats_list: List[dict], pdf_filename: str) -> str
520521
# -----------------------------
521522
# Plotting
522523
# -----------------------------
524+
def _yearly_cross_region_stats(
525+
df: pd.DataFrame,
526+
value_col: str,
527+
*,
528+
year_col: str = "year",
529+
min_regions: int = MIN_SLICE_REGIONS,
530+
) -> pd.DataFrame:
531+
"""Per-year mean and std of *value_col* across regions."""
532+
grouped = df.groupby(year_col)[value_col]
533+
out = pd.DataFrame(
534+
{
535+
"mean": grouped.mean(),
536+
"std": grouped.std(ddof=1),
537+
"n_regions": grouped.count(),
538+
}
539+
).sort_index()
540+
out.loc[out["n_regions"] < min_regions, "std"] = np.nan
541+
return out
542+
543+
544+
def _plot_mean_std_band(
545+
ax: Axes,
546+
stats: pd.DataFrame,
547+
*,
548+
color: str,
549+
mean_linestyle: str,
550+
label: str,
551+
zorder: int = 2,
552+
) -> None:
553+
years = stats.index.to_numpy()
554+
mean = stats["mean"].to_numpy(dtype=float)
555+
std = stats["std"].to_numpy(dtype=float)
556+
ax.plot(
557+
years,
558+
mean,
559+
mean_linestyle,
560+
color=color,
561+
linewidth=2.0,
562+
markersize=5,
563+
marker="o",
564+
label=label,
565+
zorder=zorder + 1,
566+
)
567+
lower = mean - std
568+
upper = mean + std
569+
has_band = np.isfinite(std)
570+
if np.any(has_band):
571+
ax.fill_between(
572+
years,
573+
lower,
574+
upper,
575+
color=color,
576+
alpha=0.18,
577+
linewidth=0,
578+
zorder=zorder,
579+
)
580+
581+
523582
def _plot_scatter_panel(
524583
ax: Axes,
525584
df_filtered: pd.DataFrame,
@@ -575,17 +634,12 @@ def _plot_scatter_panel(
575634
ax.set_xlabel("Actual yield")
576635
ax.set_ylabel("Predicted yield")
577636

578-
def fmt_line(label, m):
579-
return (
580-
f"{label}: r = {m['r']:.2f}, R² = {m['r2']:.2f} | "
581-
f"r_res = {m['r_res']:.2f}, R²_res = {m['r2_res']:.2f}"
582-
)
637+
def fmt_region_year(m: dict) -> str:
638+
return f"r = {m['r']:.2f}, R² = {m['r2']:.2f}, NRMSE = {m['nrmse']:.2f}"
583639

584-
txt_lines = [f"N = {n_samples:,}", fmt_line("Model", metrics_model)]
640+
txt_lines = [f"N = {n_samples:,}", f"Model: {fmt_region_year(metrics_model)}"]
585641
if metrics_base:
586-
txt_lines.append(fmt_line("Base", metrics_base))
587-
else:
588-
txt_lines.append("(Baseline not found)")
642+
txt_lines.append(f"Baseline: {fmt_region_year(metrics_base)}")
589643

590644
ax.text(
591645
0.05,
@@ -751,27 +805,53 @@ def process_dataset(
751805
n_samples=n_samples,
752806
)
753807
elif panel_name == "temporal":
754-
ax.plot(ts.index, ts[KEY_TARGET], "-o", linewidth=2, label="Actual")
755-
ax.plot(
756-
ts.index,
757-
ts[model],
758-
"--o",
759-
linewidth=2,
760-
label=f"Model ($r={r_time_model:.2f}$)",
808+
r2_med_reg = report["temporal"]["r2_typical_region"]
809+
actual_yr = _yearly_cross_region_stats(df_filtered, KEY_TARGET)
810+
model_yr = _yearly_cross_region_stats(df_filtered, model)
811+
_plot_mean_std_band(
812+
ax,
813+
actual_yr,
814+
color="#1f2328",
815+
mean_linestyle="-",
816+
label="Actual (mean ± std across regions)",
817+
)
818+
model_label = (
819+
f"Model (mean ± std, R² med/reg = {r2_med_reg:.2f})"
820+
if pd.notnull(r2_med_reg)
821+
else "Model (mean ± std across regions)"
822+
)
823+
_plot_mean_std_band(
824+
ax,
825+
model_yr,
826+
color="#2166ac",
827+
mean_linestyle="--",
828+
label=model_label,
761829
)
762830
if has_baseline:
763-
ax.plot(
764-
ts.index,
765-
ts[base_col_name],
766-
":o",
767-
color="green",
768-
alpha=0.7,
769-
label=f"Base ($r={r_time_base:.2f}$)",
831+
base_yr = _yearly_cross_region_stats(df_filtered, BASELINE_MODEL)
832+
_plot_mean_std_band(
833+
ax,
834+
base_yr,
835+
color="#1a7f37",
836+
mean_linestyle=":",
837+
label="Baseline (mean ± std)",
770838
)
771-
ax.set_title(f"Spatial Mean over Time ($N_{{yrs}}={n_years}$)")
772-
ax.legend()
839+
ax.set_title(
840+
f"Cross-region spread by year ($N_{{reg}}$={n_regions}, "
841+
f"$N_{{yrs}}$={n_years})"
842+
)
843+
ax.legend(fontsize=9)
773844
ax.grid(alpha=0.3)
774845
ax.set_xlabel("Year")
846+
ax.text(
847+
0.02,
848+
0.02,
849+
f"Band: ±1 std across regions (years with ≥{MIN_SLICE_REGIONS} regions)",
850+
transform=ax.transAxes,
851+
fontsize=8,
852+
color="#555555",
853+
verticalalignment="bottom",
854+
)
775855

776856
return fig, stats, panel_axes
777857

0 commit comments

Comments
 (0)