Skip to content

Plotting

neureptrace.plot_time_decode

plot_time_decode_results(results_csv, out_path, *, metrics=METRIC_COLUMNS, chance=None, title=None)

Plot time-resolved decoding metrics from a raw or aggregated CSV file.

Source code in src/neureptrace/plot_time_decode.py
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def plot_time_decode_results(
    results_csv: Path,
    out_path: Path,
    *,
    metrics: tuple[str, ...] = METRIC_COLUMNS,
    chance: float | None = None,
    title: str | None = None,
) -> Path:
    """Plot time-resolved decoding metrics from a raw or aggregated CSV file."""
    metrics = _validate_metrics(metrics)
    summary = _summary_from_csv(results_csv, metrics=metrics)
    group_columns = _group_columns(summary)
    plot_groups = list(summary.groupby(group_columns, sort=True, dropna=False)) if group_columns else [(None, summary)]
    n_metrics = len(metrics)
    n_cols = 2 if n_metrics > 1 else 1
    n_rows = (n_metrics + n_cols - 1) // n_cols
    fig, axes = plt.subplots(n_rows, n_cols, figsize=(6 * n_cols, 3.6 * n_rows), squeeze=False)
    axes_flat = axes.ravel()

    for index, metric in enumerate(metrics):
        ax = axes_flat[index]
        for group_name, group in plot_groups:
            mean = group[f"{metric}_mean"]
            sem = group[f"{metric}_sem"].fillna(0.0)
            label = _group_label(group_name) if group_name is not None else metric
            ax.plot(group["time"], mean, label=label)
            ax.fill_between(group["time"], mean - sem, mean + sem, alpha=0.2)
        if metric == "accuracy" and chance is not None:
            ax.axhline(chance, color="0.4", linestyle="--", linewidth=1.0, label="chance")
        ax.axvline(0.0, color="0.6", linestyle=":", linewidth=1.0)
        ax.set_xlabel("Time (s)")
        ax.set_ylabel(metric)
        ax.set_title(metric.replace("_", " ").title())
        ax.legend(loc="best")

    for index in range(n_metrics, len(axes_flat)):
        axes_flat[index].axis("off")

    if title:
        fig.suptitle(title)
    fig.tight_layout()
    out_path.parent.mkdir(parents=True, exist_ok=True)
    fig.savefig(out_path, dpi=150)
    plt.close(fig)
    return out_path

neureptrace.plot_calibration

plot_reliability_diagram(reliability_bins_csv, out_path, *, time_window=None, title=None)

Plot a reliability diagram from aggregated reliability-bin CSV output.

Source code in src/neureptrace/plot_calibration.py
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def plot_reliability_diagram(
    reliability_bins_csv: Path,
    out_path: Path,
    *,
    time_window: tuple[float, float] | None = None,
    title: str | None = None,
) -> Path:
    """Plot a reliability diagram from aggregated reliability-bin CSV output."""
    curve = summarize_reliability_curve(pd.read_csv(reliability_bins_csv), time_window=time_window)
    groups = list(curve[["decoder", "emission_mode"]].drop_duplicates().itertuples(index=False, name=None))
    if not groups:
        raise ValueError("No reliability-bin rows available to plot.")

    n_groups = len(groups)
    n_cols = min(3, n_groups)
    n_rows = (n_groups + n_cols - 1) // n_cols
    fig, axes = plt.subplots(n_rows, n_cols, figsize=(4.2 * n_cols, 4.0 * n_rows), squeeze=False)
    axes_flat = axes.ravel()

    for index, (decoder, emission_mode) in enumerate(groups):
        ax = axes_flat[index]
        group = curve[(curve["decoder"] == decoder) & (curve["emission_mode"] == emission_mode) & (curve["n_samples"] > 0)].sort_values("bin")
        ax.plot([0.0, 1.0], [0.0, 1.0], color="0.5", linestyle="--", linewidth=1.0, label="perfect")
        ax.plot(group["confidence"], group["accuracy"], color="tab:blue", linewidth=1.5)
        sizes = 25 + 125 * group["n_samples"] / max(group["n_samples"].max(), 1)
        ax.scatter(group["confidence"], group["accuracy"], s=sizes, color="tab:blue", edgecolor="white", linewidth=0.6)
        ax.set_xlim(0.0, 1.0)
        ax.set_ylim(0.0, 1.0)
        ax.set_xlabel("Mean confidence")
        ax.set_ylabel("Empirical accuracy")
        ax.set_title(_display_label({"decoder": decoder, "emission_mode": emission_mode}))
        ax.grid(True, color="0.9", linewidth=0.8)

    for index in range(n_groups, len(axes_flat)):
        axes_flat[index].axis("off")

    if title:
        fig.suptitle(title)
    fig.tight_layout()
    out_path.parent.mkdir(parents=True, exist_ok=True)
    fig.savefig(out_path, dpi=160)
    plt.close(fig)
    return out_path

summarize_reliability_curve(reliability_bins, *, time_window=None)

Aggregate reliability bins over an optional time window for plotting.

Source code in src/neureptrace/plot_calibration.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def summarize_reliability_curve(
    reliability_bins: pd.DataFrame,
    *,
    time_window: tuple[float, float] | None = None,
) -> pd.DataFrame:
    """Aggregate reliability bins over an optional time window for plotting."""
    required = {"time", "bin", "bin_left", "bin_right", "n_samples", "accuracy", "confidence"}
    missing = sorted(required.difference(reliability_bins.columns))
    if missing:
        raise ValueError(f"Reliability bins are missing required columns: {missing}")

    bins = _window(reliability_bins.copy(), time_window)
    if "decoder" not in bins.columns:
        bins["decoder"] = "overall"
    else:
        bins["decoder"] = bins["decoder"].astype("object").where(bins["decoder"].notna(), "overall")
    if "emission_mode" not in bins.columns:
        bins["emission_mode"] = "calibrated"
    else:
        bins["emission_mode"] = bins["emission_mode"].astype("object").where(bins["emission_mode"].notna(), "calibrated")

    mass_column = _SAMPLE_WEIGHT_COLUMN if _SAMPLE_WEIGHT_COLUMN in bins.columns else "n_samples"
    rows = []
    group_columns = ["decoder", "emission_mode", "bin", "bin_left", "bin_right"]
    for keys, group in bins.groupby(group_columns, sort=True):
        n_samples = int(group["n_samples"].sum())
        aggregation_mass = group[mass_column].astype(float)
        max_mass = float(aggregation_mass.max())
        if max_mass > 0.0:
            scaled_mass = aggregation_mass / max_mass
            weights = scaled_mass / float(scaled_mass.sum())
            accuracy = float((group["accuracy"].fillna(0.0) * weights).sum())
            confidence = float((group["confidence"].fillna(0.0) * weights).sum())
        else:
            accuracy = float("nan")
            confidence = float("nan")
        rows.append(
            {
                **dict(zip(group_columns, keys)),
                "n_samples": n_samples,
                "accuracy": accuracy,
                "confidence": confidence,
                "gap": accuracy - confidence if max_mass > 0.0 else float("nan"),
            }
        )
    return pd.DataFrame(rows)