From a72a1c3fb800f0010e8c3612a1b9dd2463cb986b Mon Sep 17 00:00:00 2001 From: Mario Date: Sat, 2 May 2026 18:54:22 +0200 Subject: [PATCH 1/2] Modernize packaging, drop strict pins, add tests and CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves the dependency-resolver failure reported by users on Python 3.10+. The 1.1.1 release pinned numpy==1.19.1, scipy==1.5.3, matplotlib==3.3.2 and iteround==1.0.2; those have no wheels for modern CPython and conflict with any environment that already requires numpy>=2. Changes ------- * Replace setup.py / setup.cfg with PEP 621 pyproject.toml using the hatchling backend. Dependencies are declared with lower bounds only (matplotlib>=3.5, numpy>=1.21, scipy>=1.7); upper bounds are deliberately omitted to avoid the same trap going forward — CI catches breakage. * Move the package to src/ layout (src/baycomp_plotting/) so tests run against the installed wheel instead of the source tree. * Drop iteround as a runtime dependency by inlining a small largest-remainder _safe_round helper. * Bump to 1.2.0. No public API changes: - Color, dens, tern keep the same signatures and behaviour. - dens() figure still exposes add_posterior(); it now defaults ls and color (was already the documented behaviour in README). - __version__ added. * Add a pytest suite (tests/) covering the pure helpers and image- regression tests for dens / tern with pytest-mpl baselines. * Add GitHub Actions workflows: - test.yml: matrix on python 3.9-3.13 + ubuntu / macOS / windows, run on push, PR and weekly cron. - release.yml: builds sdist and wheel on tag push, publishes via PyPI trusted publishing. Pre-release tags (-rc, -a, -b) go to TestPyPI; stable tags go to PyPI with a manual approval gate. * Forward-compat checks: tests pass with -W error::DeprecationWarning -W error::FutureWarning under numpy 2.4, scipy 1.17, matplotlib 3.10. Switched the two-step set_yticks + set_yticklabels to the single-call ax.set_yticks(ticks, labels=...) form (matplotlib 3.5+). Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/release.yml | 67 ++++++ .github/workflows/test.yml | 40 ++++ baycomp_plotting/__init__.py | 1 - baycomp_plotting/plotting.py | 159 ------------ pyproject.toml | 67 ++++++ setup.cfg | 2 - setup.py | 35 --- src/baycomp_plotting/__init__.py | 11 + src/baycomp_plotting/plotting.py | 226 ++++++++++++++++++ tests/__init__.py | 0 .../test_dens_single_posterior.png | Bin 0 -> 23339 bytes .../test_dens_two_posteriors.png | Bin 0 -> 26855 bytes tests/baseline_images/test_tern_basic.png | Bin 0 -> 30001 bytes tests/conftest.py | 30 +++ tests/test_helpers.py | 73 ++++++ tests/test_plots.py | 64 +++++ 16 files changed, 578 insertions(+), 197 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/test.yml delete mode 100644 baycomp_plotting/__init__.py delete mode 100644 baycomp_plotting/plotting.py create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py create mode 100644 src/baycomp_plotting/__init__.py create mode 100644 src/baycomp_plotting/plotting.py create mode 100644 tests/__init__.py create mode 100644 tests/baseline_images/test_dens_single_posterior.png create mode 100644 tests/baseline_images/test_dens_two_posteriors.png create mode 100644 tests/baseline_images/test_tern_basic.png create mode 100644 tests/conftest.py create mode 100644 tests/test_helpers.py create mode 100644 tests/test_plots.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ccc5f2c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,67 @@ +name: release + +on: + push: + tags: + - "v*" + +jobs: + build: + name: Build sdist and wheel + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install build + run: python -m pip install --upgrade build + + - name: Build distribution + run: python -m build + + - uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + + publish-testpypi: + name: Publish to TestPyPI + needs: build + if: contains(github.ref, '-rc') || contains(github.ref, '-a') || contains(github.ref, '-b') + runs-on: ubuntu-latest + environment: + name: testpypi + url: https://test.pypi.org/p/baycomp_plotting + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + publish-pypi: + name: Publish to PyPI + needs: build + # Only stable tags (no -rc, -a, -b suffix) go to real PyPI. + if: ${{ !contains(github.ref, '-') }} + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/baycomp_plotting + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..7a52118 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,40 @@ +name: tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + # Weekly run on Mondays catches breakage from new transitive deps. + - cron: "0 6 * * 1" + +jobs: + test: + name: py${{ matrix.python-version }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + include: + - os: macos-latest + python-version: "3.12" + - os: windows-latest + python-version: "3.12" + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install package with test extras + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[test]" + + - name: Run tests + run: pytest --mpl -W error::DeprecationWarning -W error::FutureWarning diff --git a/baycomp_plotting/__init__.py b/baycomp_plotting/__init__.py deleted file mode 100644 index 7472c3d..0000000 --- a/baycomp_plotting/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .plotting import * diff --git a/baycomp_plotting/plotting.py b/baycomp_plotting/plotting.py deleted file mode 100644 index 217b39c..0000000 --- a/baycomp_plotting/plotting.py +++ /dev/null @@ -1,159 +0,0 @@ -""" -Functions for plotting baycomp's posterior plots. - -``tern`` - A ternary plot. - -``dens`` - A density plot. - -Author: Mario Juez-Gil -""" - -import types -import numpy as np -import matplotlib.patches as patches -import matplotlib.colors as clrs -from matplotlib import pyplot as plt -from matplotlib.lines import Line2D -from matplotlib.path import Path -from matplotlib.colors import ListedColormap -from math import sqrt, sin, cos, pi -from iteround import saferound -from scipy.interpolate import interpn -from scipy import stats - -__all__ = ['Color', 'tern', 'dens'] - -# Custom colormap -> dark blue means high density, and light blue low -BLUES_CMAP = np.ones((256, 4)) -BLUES_CMAP[:, 0] = np.linspace(199/256, 8/256, 256) -BLUES_CMAP[:, 1] = np.linspace(224/256, 64/256, 256) -BLUES_CMAP[:, 2] = np.linspace(252/256, 129/256, 256) -BLUES_CMAP = ListedColormap(BLUES_CMAP) - -# Custom colors -class Color(): - BLUE = clrs.to_hex((0/255, 142/255, 206/255)) - GRAY = clrs.to_hex((77/255, 80/255, 94/255)) - BORDEAUX = clrs.to_hex((208/255, 33/255, 85/255)) - GREEN = clrs.to_hex((5/255, 126/255, 121/255)) - -def project(pts): - SQRT_3 = sqrt(3) - PI_6 = pi / 6 - - p1, p2, p3 = pts.T / SQRT_3 - x = (p2 - p1) * cos(PI_6) + .5 - y = p3 - (p1 + p2) * sin(PI_6) + 1 / (2 * SQRT_3) - - return np.vstack((x, y)).T - -def process_names(names): - for i in range(len(names)): - names[i] = names[i].replace("-", "{-}") - names[i] = names[i].replace(" ", "\\ ") - return names - -def tern(p, names=["L", "R"]): - names = process_names(names) - plt.style.use('classic') - - fig, ax = plt.subplots() - fig.patch.set_alpha(0) - ax.set_aspect('equal', 'box') - ax.axis('off') - ax.set_xlim(-.1, 1.1) - ax.set_ylim(-.1, 1.1) - - # inner lines - cx, cy = project(np.array([[1/3, 1/3, 1/3]]))[0] # central point - for x, y in project(np.array([[.5, .5, 0], [.5, 0, .5], [0, .5, .5]])): - ax.add_line(Line2D([cx, x], [cy, y], color='k', lw=2)) - - # outer border of the triangle - vert_coords = [(0., 0.), (.5, sqrt(3)/2), (1., 0.), (0., 0.)] - codes = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY] - triangle = Path(vert_coords, codes) - patch = patches.PathPatch(triangle, facecolor='none', lw=3) - ax.add_patch(patch) - - # vertices texts - probs = saferound(list(p.probs()), places=4) - # L - ax.text(-.04, -.02, r'$\mathrm{%s}$'%names[0], ha='center', va='top', fontsize=30) - ax.text(-.04, -.12, r'$\mathbf{(%.4f)}$'%probs[0], ha='center', va='top', fontsize=32) - # ROPE - ax.text(.5, 1, r'$\mathrm{ROPE}$', ha='center', va='bottom', fontsize=30) - ax.text(.5, .87, r'$\mathbf{(%.4f)}$'%probs[1], ha='center', va='bottom', fontsize=32) - # R - ax.text(1.04, -.02, r'$\mathrm{%s}$'%names[1], ha='center', va='top', fontsize=30) - ax.text(1.04, -.12, r'$\mathbf{(%.4f)}$'%probs[2], ha='center', va='top', fontsize=32) - - # draw points - tripts = project(p.sample[:, [0, 2, 1]]) - data, xe, ye = np.histogram2d(tripts[:, 0], tripts[:, 1], bins=30) - z = interpn((.5 * (xe[1:] + xe[:-1]), .5 * (ye[1:] + ye[:-1])), data, - np.vstack([tripts[:, 0], tripts[:, 1]]).T, method='splinef2d', - bounds_error=False) - idx = z.argsort() - ax.scatter(tripts[:, 0][idx], tripts[:, 1][idx], c=z[idx], clip_path=patch, - s=50, linewidth=0, cmap=BLUES_CMAP, rasterized=True) - - fig.tight_layout() - return fig - -def dens(p, label, ls='-', color=Color.BLUE): - def add_posterior(ax, p, label, ls, color): - def _update_yticks(): - tick = ax.max_y / 3 - yticks = [0, tick*1, tick*2, tick*3] - ax.set_ylim(0, ax.max_y) - ax.set_yticks(yticks) - ax.set_yticklabels([r'$%.3f$'%round(x, 3) for x in yticks]) - - targs = (p.df, p.mean, np.sqrt(p.var)) - x = np.linspace(min(stats.t.ppf(.005, *targs), -1.05 * p.rope), - max(stats.t.ppf(.995, *targs), 1.05 * p.rope), 100) - y = stats.t.pdf(x, *targs) - y = y / y.sum() # density - ax.plot(x, y, c=color, linestyle=ls, linewidth=2, - label=r'$\mathrm{%s}$'%label, zorder=ax.zo) - ax.fill_between(x, y, facecolor=color, alpha=.1, edgecolor='none', - zorder=ax.zo) - ax.zo = ax.zo - 1 - - if(ax.max_y == None): - ax.max_y = np.amax(y) + np.amax(y)*.02 - _update_yticks() - else: - curr_max_y = np.amax(y) + np.amax(y)*.02 - if(curr_max_y > ax.max_y): - ax.max_y = curr_max_y - _update_yticks() - - plt.style.use('classic') - plt.rc('legend', fontsize=25) - - # figure customization - fig, ax = plt.subplots() - fig.patch.set_alpha(0) - ax.axvline(.01, c='darkorange', linewidth=2, zorder=101) - ax.axvline(-.01, c='darkorange', linewidth=2, zorder=101) - ax.spines['right'].set_visible(False) - ax.spines['top'].set_visible(False) - ax.spines['bottom'].set_visible(False) - ax.yaxis.set_ticks_position('left') - ax.xaxis.set_ticks_position('none') - ax.tick_params(axis='both', which='major', labelsize=30, direction='inout', - width=1, length=5) - ax.set_xticks([]) - - # appending the posterior to the axes - ax.max_y = None - ax.zo = 100 - add_posterior(ax, p, label, ls, color) - fig.add_posterior = types.MethodType(add_posterior, ax) - - fig.tight_layout() - return fig diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..420e7b2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,67 @@ +[build-system] +requires = ["hatchling>=1.24"] +build-backend = "hatchling.build" + +[project] +name = "baycomp_plotting" +version = "1.2.0" +description = "Extra plotting functionality for baycomp's Bayesian classifier comparison posteriors." +readme = "README.md" +license = { file = "LICENSE" } +requires-python = ">=3.9" +authors = [ + { name = "Mario Juez-Gil", email = "mariojg@ubu.es" }, +] +keywords = [ + "bayesian", + "classifier-comparison", + "machine-learning", + "plotting", + "baycomp", +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Visualization", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] +dependencies = [ + "matplotlib>=3.5", + "numpy>=1.21", + "scipy>=1.7", +] + +[project.optional-dependencies] +test = [ + "pytest>=7", + "pytest-mpl>=0.17", + "baycomp>=1.0.3", +] + +[project.urls] +Homepage = "https://github.com/mjuez/baycomp_plotting" +Repository = "https://github.com/mjuez/baycomp_plotting" +Issues = "https://github.com/mjuez/baycomp_plotting/issues" + +[tool.hatch.build.targets.wheel] +packages = ["src/baycomp_plotting"] + +[tool.hatch.build.targets.sdist] +include = [ + "src", + "tests", + "README.md", + "LICENSE", + "pyproject.toml", +] + +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = "-ra" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index b88034e..0000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[metadata] -description-file = README.md diff --git a/setup.py b/setup.py deleted file mode 100644 index d0ac92a..0000000 --- a/setup.py +++ /dev/null @@ -1,35 +0,0 @@ -import setuptools - -with open('README.md') as f: - readme = f.read() - -setuptools.setup( - name='baycomp_plotting', - version='1.1.1', - description='This package provides some extra functionality for plotting baycomp\'s posteriors.', - long_description=readme, - long_description_content_type='text/markdown', - author='Mario Juez-Gil', - author_email='mariojg@ubu.es', - url='https://github.com/mjuez/baycomp_plotting', - download_url='https://github.com/mjuez/baycomp_plotting/archive/v1_1_1.tar.gz', - license='GPLv3', - install_requires=[ - 'matplotlib==3.3.2', - 'numpy==1.19.1', - 'iteround==1.0.2', - 'scipy==1.5.3' - ], - packages=setuptools.find_packages(), - include_package_data=True, - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Science/Research', - 'Topic :: Scientific/Engineering', - 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8' - ] -) diff --git a/src/baycomp_plotting/__init__.py b/src/baycomp_plotting/__init__.py new file mode 100644 index 0000000..a200f31 --- /dev/null +++ b/src/baycomp_plotting/__init__.py @@ -0,0 +1,11 @@ +from importlib.metadata import PackageNotFoundError as _PackageNotFoundError +from importlib.metadata import version as _version + +from .plotting import Color, dens, tern + +try: + __version__ = _version("baycomp_plotting") +except _PackageNotFoundError: + __version__ = "0.0.0+unknown" + +__all__ = ["Color", "dens", "tern", "__version__"] diff --git a/src/baycomp_plotting/plotting.py b/src/baycomp_plotting/plotting.py new file mode 100644 index 0000000..7958d25 --- /dev/null +++ b/src/baycomp_plotting/plotting.py @@ -0,0 +1,226 @@ +""" +Functions for plotting baycomp's posterior distributions. + +``tern`` + A ternary plot for posteriors over (left, rope, right) probabilities. + +``dens`` + A density plot for the posterior of a CorrelatedTTest. + +Author: Mario Juez-Gil +""" +from __future__ import annotations + +import types +from math import cos, pi, sin, sqrt +from typing import Sequence + +import matplotlib.colors as clrs +import matplotlib.patches as patches +import numpy as np +from matplotlib import pyplot as plt +from matplotlib.colors import ListedColormap +from matplotlib.lines import Line2D +from matplotlib.path import Path +from scipy import stats +from scipy.interpolate import interpn + +__all__ = ["Color", "tern", "dens"] + + +_BLUES_CMAP_RGBA = np.ones((256, 4)) +_BLUES_CMAP_RGBA[:, 0] = np.linspace(199 / 256, 8 / 256, 256) +_BLUES_CMAP_RGBA[:, 1] = np.linspace(224 / 256, 64 / 256, 256) +_BLUES_CMAP_RGBA[:, 2] = np.linspace(252 / 256, 129 / 256, 256) +BLUES_CMAP = ListedColormap(_BLUES_CMAP_RGBA) + + +class Color: + """Alternative palette of four colors for matplotlib plots.""" + + BLUE = clrs.to_hex((0 / 255, 142 / 255, 206 / 255)) + GRAY = clrs.to_hex((77 / 255, 80 / 255, 94 / 255)) + BORDEAUX = clrs.to_hex((208 / 255, 33 / 255, 85 / 255)) + GREEN = clrs.to_hex((5 / 255, 126 / 255, 121 / 255)) + + +def _safe_round(values: Sequence[float], places: int) -> list[float]: + """Round ``values`` to ``places`` decimals while preserving their sum. + + Largest-remainder method: distribute the rounding residual to the entries + with the largest fractional parts so that, for probabilities summing to 1, + the rounded triplet still sums to 1.0 to ``places`` decimals. + """ + if not values: + return [] + multiplier = 10 ** places + scaled = [v * multiplier for v in values] + floors = [int(s) for s in scaled] + diff = int(round(sum(scaled))) - sum(floors) + if diff > 0: + order = sorted(range(len(scaled)), key=lambda i: scaled[i] - floors[i], reverse=True) + for i in order[:diff]: + floors[i] += 1 + elif diff < 0: + order = sorted(range(len(scaled)), key=lambda i: scaled[i] - floors[i]) + for i in order[: -diff]: + floors[i] -= 1 + return [f / multiplier for f in floors] + + +def _project(pts: np.ndarray) -> np.ndarray: + """Project barycentric coordinates onto the 2D simplex triangle.""" + sqrt_3 = sqrt(3) + pi_6 = pi / 6 + p1, p2, p3 = pts.T / sqrt_3 + x = (p2 - p1) * cos(pi_6) + 0.5 + y = p3 - (p1 + p2) * sin(pi_6) + 1 / (2 * sqrt_3) + return np.vstack((x, y)).T + + +def _process_names(names: Sequence[str]) -> list[str]: + """Escape characters in ``names`` so they survive matplotlib's mathtext.""" + return [n.replace("-", "{-}").replace(" ", "\\ ") for n in names] + + +def _add_posterior(ax, p, label: str, ls="-", color: str = Color.BLUE) -> None: + """Render a CorrelatedTTest posterior on ``ax`` and update the y-scale. + + Reads and updates two ad-hoc attributes on ``ax``: ``max_y`` (current y-axis + upper bound) and ``zo`` (z-order counter, decremented for each posterior so + earlier ones stay on top). + """ + targs = (p.df, p.mean, np.sqrt(p.var)) + x = np.linspace( + min(stats.t.ppf(0.005, *targs), -1.05 * p.rope), + max(stats.t.ppf(0.995, *targs), 1.05 * p.rope), + 100, + ) + y = stats.t.pdf(x, *targs) + y = y / y.sum() + ax.plot( + x, y, c=color, linestyle=ls, linewidth=2, + label=r"$\mathrm{%s}$" % label, zorder=ax.zo, + ) + ax.fill_between(x, y, facecolor=color, alpha=0.1, edgecolor="none", zorder=ax.zo) + ax.zo -= 1 + + curr_max_y = float(y.max()) * 1.02 + if ax.max_y is None or curr_max_y > ax.max_y: + ax.max_y = curr_max_y + tick = ax.max_y / 3 + yticks = [0, tick, 2 * tick, 3 * tick] + ax.set_ylim(0, ax.max_y) + ax.set_yticks(yticks, labels=[r"$%.3f$" % v for v in yticks]) + + +def tern(p, names: Sequence[str] = ("L", "R")): + """Ternary plot for a posterior over (left, rope, right) probabilities. + + Parameters + ---------- + p + A baycomp posterior with ``probs()`` returning three values that sum to 1 + and ``sample`` of shape ``(n_samples, 3)`` (e.g. ``HierarchicalTest`` or + ``SignedRankTest``). + names + Two-element sequence with labels for the left and right vertices. + """ + names = _process_names(list(names)) + plt.style.use("classic") + + fig, ax = plt.subplots() + fig.patch.set_alpha(0) + ax.set_aspect("equal", "box") + ax.axis("off") + ax.set_xlim(-0.1, 1.1) + ax.set_ylim(-0.1, 1.1) + + cx, cy = _project(np.array([[1 / 3, 1 / 3, 1 / 3]]))[0] + for x, y in _project(np.array([[0.5, 0.5, 0], [0.5, 0, 0.5], [0, 0.5, 0.5]])): + ax.add_line(Line2D([cx, x], [cy, y], color="k", lw=2)) + + vert_coords = [(0.0, 0.0), (0.5, sqrt(3) / 2), (1.0, 0.0), (0.0, 0.0)] + codes = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY] + triangle = Path(vert_coords, codes) + patch = patches.PathPatch(triangle, facecolor="none", lw=3) + ax.add_patch(patch) + + probs = _safe_round(list(p.probs()), places=4) + ax.text(-0.04, -0.02, r"$\mathrm{%s}$" % names[0], ha="center", va="top", fontsize=30) + ax.text(-0.04, -0.12, r"$\mathbf{(%.4f)}$" % probs[0], ha="center", va="top", fontsize=32) + ax.text(0.5, 1, r"$\mathrm{ROPE}$", ha="center", va="bottom", fontsize=30) + ax.text(0.5, 0.87, r"$\mathbf{(%.4f)}$" % probs[1], ha="center", va="bottom", fontsize=32) + ax.text(1.04, -0.02, r"$\mathrm{%s}$" % names[1], ha="center", va="top", fontsize=30) + ax.text(1.04, -0.12, r"$\mathbf{(%.4f)}$" % probs[2], ha="center", va="top", fontsize=32) + + tripts = _project(p.sample[:, [0, 2, 1]]) + data, xe, ye = np.histogram2d(tripts[:, 0], tripts[:, 1], bins=30) + z = interpn( + (0.5 * (xe[1:] + xe[:-1]), 0.5 * (ye[1:] + ye[:-1])), + data, + np.vstack([tripts[:, 0], tripts[:, 1]]).T, + method="splinef2d", + bounds_error=False, + ) + idx = z.argsort() + ax.scatter( + tripts[:, 0][idx], + tripts[:, 1][idx], + c=z[idx], + clip_path=patch, + s=50, + linewidth=0, + cmap=BLUES_CMAP, + rasterized=True, + ) + + fig.tight_layout() + return fig + + +def dens(p, label: str, ls="-", color: str = Color.BLUE): + """Density plot for the posterior of a ``baycomp.CorrelatedTTest``. + + Parameters + ---------- + p + A ``CorrelatedTTest`` posterior with attributes ``df``, ``mean``, ``var`` + and ``rope``. + label + Math-text label shown in the legend (LaTeX-style spacing must be escaped). + ls + Matplotlib line style. + color + Density colour, e.g. one of the :class:`Color` constants. + + Returns + ------- + matplotlib.figure.Figure + Figure with an extra ``add_posterior(p, label, ls, color)`` method that + appends another posterior on top of the existing axes. + """ + plt.style.use("classic") + plt.rc("legend", fontsize=25) + + fig, ax = plt.subplots() + fig.patch.set_alpha(0) + ax.axvline(0.01, c="darkorange", linewidth=2, zorder=101) + ax.axvline(-0.01, c="darkorange", linewidth=2, zorder=101) + ax.spines["right"].set_visible(False) + ax.spines["top"].set_visible(False) + ax.spines["bottom"].set_visible(False) + ax.yaxis.set_ticks_position("left") + ax.xaxis.set_ticks_position("none") + ax.tick_params( + axis="both", which="major", labelsize=30, direction="inout", width=1, length=5, + ) + ax.set_xticks([]) + + ax.max_y = None + ax.zo = 100 + _add_posterior(ax, p, label, ls, color) + fig.add_posterior = types.MethodType(_add_posterior, ax) + + fig.tight_layout() + return fig diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/baseline_images/test_dens_single_posterior.png b/tests/baseline_images/test_dens_single_posterior.png new file mode 100644 index 0000000000000000000000000000000000000000..2cb03671459a214abac2a1fa50df8b209f73d484 GIT binary patch literal 23339 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 {Y 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 np.random.Generator: + return np.random.default_rng(42) + + +@pytest.fixture +def correlated_ttest_posterior(rng): + base = rng.normal(0.85, 0.015, 10) + acc_a = np.clip(base + rng.normal(0.0, 0.005, 10), 0, 1) + acc_b = np.clip(base + rng.normal(0.015, 0.005, 10), 0, 1) + return bc.CorrelatedTTest(acc_a, acc_b, rope=0.01, runs=1) + + +@pytest.fixture +def signed_rank_posterior(rng): + mu = rng.uniform(0.65, 0.92, 25) + mean_a = np.clip(mu + rng.normal(0.0, 0.005, 25), 0, 1) + mean_b = np.clip(mu + rng.normal(0.012, 0.005, 25), 0, 1) + return bc.SignedRankTest(mean_a, mean_b, rope=0.01, random_state=0) diff --git a/tests/test_helpers.py b/tests/test_helpers.py new file mode 100644 index 0000000..75c0c63 --- /dev/null +++ b/tests/test_helpers.py @@ -0,0 +1,73 @@ +"""Unit tests for the pure helper functions.""" +from __future__ import annotations + +import numpy as np +import pytest + +from baycomp_plotting import Color +from baycomp_plotting.plotting import _process_names, _project, _safe_round + + +class TestColor: + def test_palette_has_four_hex_strings(self): + assert len({Color.BLUE, Color.GRAY, Color.BORDEAUX, Color.GREEN}) == 4 + for c in (Color.BLUE, Color.GRAY, Color.BORDEAUX, Color.GREEN): + assert isinstance(c, str) + assert c.startswith("#") and len(c) == 7 + + def test_blue_is_stable(self): + # Pinned for backwards compatibility — used to be public in plots. + assert Color.BLUE == "#008ece" + + +class TestSafeRound: + def test_rounds_to_requested_decimals(self): + out = _safe_round([0.12345, 0.34567, 0.53088], places=4) + assert all(round(v * 10_000) == v * 10_000 for v in out) + + def test_preserves_sum(self): + # Triplet that would round naively to 1.0001 + values = [0.33335, 0.33335, 0.33330] + out = _safe_round(values, places=4) + assert sum(out) == pytest.approx(1.0, abs=1e-9) + + def test_handles_empty(self): + assert _safe_round([], places=4) == [] + + def test_extreme_distribution_sums_to_one(self): + out = _safe_round([0.99996, 1e-5, 3e-5], places=4) + assert sum(out) == pytest.approx(1.0, abs=1e-9) + + +class TestProject: + def test_centroid_maps_to_triangle_center(self): + out = _project(np.array([[1 / 3, 1 / 3, 1 / 3]])) + # Center of the equilateral triangle drawn by tern() is (0.5, 1/(2*sqrt(3))). + np.testing.assert_allclose(out[0], (0.5, 1 / (2 * np.sqrt(3))), atol=1e-9) + + def test_vertex_left_maps_to_left(self): + # (1,0,0) is the "left" axis in the projection; we don't pin the exact + # corner but verify it sits on x<0.5 and y= 3 # 1 density + 2 ROPE verticals + assert hasattr(fig, "add_posterior") + plt.close(fig) + + def test_add_posterior_appends_a_line(self, correlated_ttest_posterior): + fig = bplt.dens(correlated_ttest_posterior, label="A", color=bplt.Color.BLUE) + n_before = len(fig.axes[0].lines) + fig.add_posterior(correlated_ttest_posterior, label="B", color=bplt.Color.BORDEAUX) + assert len(fig.axes[0].lines) == n_before + 1 + plt.close(fig) + + def test_tern_renders_three_vertex_labels(self, signed_rank_posterior): + fig = bplt.tern(signed_rank_posterior, names=["L", "R"]) + ax = fig.axes[0] + text_contents = [t.get_text() for t in ax.texts] + assert any("L" in t for t in text_contents) + assert any("R" in t for t in text_contents) + assert any("ROPE" in t for t in text_contents) + plt.close(fig) From 304478316578c3ad5f1471c623d940a3043d6cfc Mon Sep 17 00:00:00 2001 From: Mario Date: Sat, 2 May 2026 19:03:11 +0200 Subject: [PATCH 2/2] Move strict warning filters to pyproject and ignore pyparsing noise MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI failed on Python 3.9 because matplotlib 3.9.x (the last line that supports 3.9 since matplotlib 3.10 dropped it) emits PyparsingDeprecationWarning during its own initialisation. With -W error::DeprecationWarning that aborted pytest before any test ran. Move the strict-warning filters from the workflow command to filterwarnings in pyproject.toml — that's the canonical place and benefits local runs too — and silence the pyparsing category so the noise from matplotlib's internals doesn't gate our suite. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/test.yml | 2 +- pyproject.toml | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7a52118..a0d8b07 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,4 +37,4 @@ jobs: python -m pip install -e ".[test]" - name: Run tests - run: pytest --mpl -W error::DeprecationWarning -W error::FutureWarning + run: pytest --mpl diff --git a/pyproject.toml b/pyproject.toml index 420e7b2..df09cbb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,3 +65,9 @@ include = [ [tool.pytest.ini_options] testpaths = ["tests"] addopts = "-ra" +filterwarnings = [ + "error::DeprecationWarning", + "error::FutureWarning", + # Emitted by matplotlib's internal use of pyparsing; third-party. + "ignore::pyparsing.warnings.PyparsingDeprecationWarning", +]