diff --git a/scripts/_mpl_compat.py b/scripts/_mpl_compat.py new file mode 100644 index 0000000..48687ff --- /dev/null +++ b/scripts/_mpl_compat.py @@ -0,0 +1,34 @@ +"""Matplotlib compatibility helpers for workbook scripts. + +Matplotlib 3.9 renamed the Axes.boxplot keyword argument "labels" to +"tick_labels". The old name is deprecated and scheduled for removal. + +These helpers keep our educational scripts working on Matplotlib 3.8+ +while avoiding deprecation warnings on newer versions. +""" + +from __future__ import annotations + +from typing import Any, Sequence + + +def ax_boxplot( + ax: Any, + *args: Any, + tick_labels: Sequence[str] | None = None, + **kwargs: Any, +): + """Call ``ax.boxplot`` with a 3.8/3.9+ compatible keyword. + + Prefer ``tick_labels`` (Matplotlib >= 3.9). If that keyword is not + supported (Matplotlib <= 3.8), fall back to the legacy ``labels``. + """ + + if tick_labels is None: + return ax.boxplot(*args, **kwargs) + + try: + return ax.boxplot(*args, tick_labels=tick_labels, **kwargs) + except TypeError: + # Older Matplotlib: the new keyword doesn't exist. + return ax.boxplot(*args, labels=tick_labels, **kwargs) diff --git a/scripts/ch14_tutoring_ab.py b/scripts/ch14_tutoring_ab.py index 76f14a3..c355177 100644 --- a/scripts/ch14_tutoring_ab.py +++ b/scripts/ch14_tutoring_ab.py @@ -21,6 +21,7 @@ from scipy import stats from scripts._cli import base_parser, apply_seed +from scripts._mpl_compat import ax_boxplot def cohens_d(x: np.ndarray, y: np.ndarray) -> float: @@ -95,7 +96,7 @@ def main() -> None: # Plot fig, ax = plt.subplots(figsize=(6, 5)) - ax.boxplot([control, tutor], labels=["Control", "Tutor"], patch_artist=True) + ax_boxplot(ax, [control, tutor], tick_labels=["Control", "Tutor"], patch_artist=True) ax.set_title(f"Test Scores: Control vs Tutor (n={len(control)} per group)") ax.set_ylabel("Score") ax.grid(axis="y", linestyle=":", alpha=0.7) diff --git a/scripts/intro_stats_01_descriptives.py b/scripts/intro_stats_01_descriptives.py index 8d599f3..7cf14ec 100644 --- a/scripts/intro_stats_01_descriptives.py +++ b/scripts/intro_stats_01_descriptives.py @@ -59,13 +59,20 @@ def main() -> None: try: import matplotlib.pyplot as plt + # Matplotlib 3.9 renamed `labels` -> `tick_labels` for boxplots. + # Use a small compatibility helper to avoid warnings now and breaks later. + try: + from scripts._mpl_compat import ax_boxplot + except ImportError: # pragma: no cover + from _mpl_compat import ax_boxplot # type: ignore + fig = plt.figure() ax = fig.add_subplot(1, 1, 1) # Keep ordering stable. order = ["control", "treatment"] data = [df.loc[df["group"] == g, "score"].to_numpy() for g in order] - ax.boxplot(data, labels=order) + ax_boxplot(ax, data, tick_labels=order) ax.set_title("Intro Stats: score by group") ax.set_ylabel("score") diff --git a/scripts/intro_stats_03_distributions_outliers.py b/scripts/intro_stats_03_distributions_outliers.py index ce41059..ba08a3e 100644 --- a/scripts/intro_stats_03_distributions_outliers.py +++ b/scripts/intro_stats_03_distributions_outliers.py @@ -21,6 +21,15 @@ import pandas as pd +# Matplotlib 3.9 renamed `labels` -> `tick_labels` for Axes.boxplot. +# This import works when run via `python -m scripts...` (package import), +# and also when run as a plain script from the `scripts/` folder. +try: + from scripts._mpl_compat import ax_boxplot +except ImportError: # pragma: no cover + from _mpl_compat import ax_boxplot # type: ignore + + def _iqr_outliers(df: pd.DataFrame, *, group_col: str, value_col: str) -> pd.DataFrame: """Return a table of IQR outliers per group (may be empty).""" @@ -93,8 +102,8 @@ def main() -> None: # Boxplot order = ["control", "treatment"] data = [df.loc[df["group"] == g, "score"] for g in order if g in set(df["group"])] - labels = [g for g in order if g in set(df["group"])] - axes[1].boxplot(data, labels=labels) + tick_labels = [g for g in order if g in set(df["group"])] + ax_boxplot(axes[1], data, tick_labels=tick_labels) axes[1].set_title("Boxplot (outliers shown)") axes[1].set_ylabel("Score") diff --git a/scripts/psych_ch19_problem_set.py b/scripts/psych_ch19_problem_set.py index 3a74915..d28efac 100644 --- a/scripts/psych_ch19_problem_set.py +++ b/scripts/psych_ch19_problem_set.py @@ -24,6 +24,15 @@ from scipy.stats import chi2_contingency, chisquare, kruskal, mannwhitneyu +# Matplotlib 3.9 renamed `labels` -> `tick_labels` for Axes.boxplot. +# This import works both when run via `python -m scripts...` and when executed +# as a plain script from the `scripts/` folder. +try: + from scripts._mpl_compat import ax_boxplot +except ImportError: # pragma: no cover + from _mpl_compat import ax_boxplot # type: ignore + + PROJECT_ROOT = Path(__file__).resolve().parents[1] SYNTHETIC_DATA_DIR = PROJECT_ROOT / "data" / "synthetic" TRACK_C_OUTPUT_DIR = PROJECT_ROOT / "outputs" / "track_c" @@ -108,7 +117,7 @@ def _plot_boxplot(df: pd.DataFrame, outfile: Path, title: str) -> None: fig, ax = plt.subplots(figsize=(7, 4)) groups = list(df["group"].cat.categories) data = [df.loc[df["group"] == g, "score"].to_numpy() for g in groups] - ax.boxplot(data, labels=[str(g) for g in groups], showfliers=False) + ax_boxplot(ax, data, tick_labels=[str(g) for g in groups], showfliers=False) ax.set_xlabel("Group") ax.set_ylabel("Score") ax.set_title(title)