From 78f70fa55fc1c7a20e63bda020f9a8835c4e47fb Mon Sep 17 00:00:00 2001 From: Bryon Tjanaka Date: Wed, 15 Jul 2026 19:27:43 -0700 Subject: [PATCH 01/15] Add to __init__ --- ribs/visualize/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ribs/visualize/__init__.py b/ribs/visualize/__init__.py index 3103ed083..3a9f62325 100644 --- a/ribs/visualize/__init__.py +++ b/ribs/visualize/__init__.py @@ -14,6 +14,7 @@ .. autosummary:: :toctree: + aggregate_cdf archive_ecdf archive_histogram cvt_archive_3d_plot @@ -26,6 +27,7 @@ qdax_repertoire_heatmap """ +from ribs.visualize._aggregate_cdf import aggregate_cdf from ribs.visualize._archive_ecdf import archive_ecdf from ribs.visualize._archive_histogram import archive_histogram from ribs.visualize._cvt_archive_3d_plot import cvt_archive_3d_plot @@ -42,6 +44,7 @@ ) __all__ = [ + "aggregate_cdf", "archive_ecdf", "archive_histogram", "cvt_archive_3d_plot", From dfd2662dbc395a1548f6e70d7774f77ca1e2dd81 Mon Sep 17 00:00:00 2001 From: Bryon Tjanaka Date: Wed, 15 Jul 2026 19:29:53 -0700 Subject: [PATCH 02/15] history --- HISTORY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/HISTORY.md b/HISTORY.md index ef1ea1cb7..3d3ed54d2 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,6 +6,7 @@ #### API +- Add `aggregate_cdf` to visualize CDF of multiple archives ({pr}`722`) - Support fields as a single str for return_type="dict"|"tuple"|"pandas" in archive.data() ({pr}`721`) - Add `archive_ecdf` to visualize ECDF and ECCDF of archives ({pr}`719`) From 7e80e7307ef84b9f915af038ba3fac3cf84f8cef Mon Sep 17 00:00:00 2001 From: Bryon Tjanaka Date: Wed, 15 Jul 2026 23:49:43 -0700 Subject: [PATCH 03/15] Initial draft impl --- ribs/visualize/_aggregate_cdf.py | 113 +++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 ribs/visualize/_aggregate_cdf.py diff --git a/ribs/visualize/_aggregate_cdf.py b/ribs/visualize/_aggregate_cdf.py new file mode 100644 index 000000000..f952e1d88 --- /dev/null +++ b/ribs/visualize/_aggregate_cdf.py @@ -0,0 +1,113 @@ +"""Provides aggregate_cdf.""" + +from __future__ import annotations + +from collections.abc import Collection, Sequence +from typing import Literal + +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.axes import Axes +from pandas import DataFrame + +from ribs.archives import ArchiveBase, ArchiveDataFrame + + +def aggregate_cdf( + archives: Collection[ArchiveBase], + ax: Axes | None = None, + dfs: Collection[DataFrame] | Collection[ArchiveDataFrame] | None = None, + bins: int | Sequence[float] | str | None = 100, + vmin: float | None = None, + vmax: float | None = None, + # estimator: Literal["mean", "median"] = "mean", + # TODO: complementary +) -> None: + """Plots a CDF aggregated over multiple archives. + + .. info:: + + The idea of using a CCDF to evaluate QD algorithms was introduced and formalized + in `Vassiliades + 2018 `_. + + Args: + archives: Archives to aggregate for the CDF. + ax: Axes on which to plot the CDF. If ``None``, the current axis will be used. + dfs: If provided, we will plot data from this sequence of dataframes instead of + the data currently in the archives. This data can be obtained by, for + instance, calling :meth:`ribs.archives.ArchiveBase.data` with + ``return_type="pandas"`` and modifying the resulting + :class:`~ribs.archives.ArchiveDataFrame`. Note that, at a minimum, each + dataframe must contain a column for "objective". + bins: Bins for the CDF. The default of 100 indicates that the histogram will + consist of 100 equally-sized bins. See :func:`~matplotlib.pyplot.hist` for + more info. + vmin: Minimum objective value to use in the plot. If ``None``, the minimum + objective value in the archives is used. + vmax: Maximum objective value to use in the plot. If ``None``, the maximum + objective value in the archives is used. + """ + # TODO: Proper data retrieval. + objectives = [archive.data("objective") for archive in archives] + + # TODO: Computation. + # Compute vmin and vmax. + + # TODO: Use common vmin_vmax util. + if vmin is None: + min_objs = [np.inf if len(objs) == 0 else np.min(objs) for objs in objectives] + # TODO: check for inf? + vmin = np.min(min_objs) + else: + pass + + max_objs = [np.inf if len(objs) == 0 else np.max(objs) for objs in objectives] + + vmax = np.max(max_objs) + + vmin = np.min(objectives) if vmin is None and len(objectives) > 0 else vmin + vmax = np.max(objectives) if vmax is None and len(objectives) > 0 else vmax + + # Initialize axis. + ax = plt.gca() if ax is None else ax + + histograms = [] + for objs in objectives: + hist, bin_edges = np.histogram(objs, bins, range=(vmin, vmax)) + histograms.append(hist) + histograms = np.stack(histograms, axis=0) + + print(histograms) + + # TODO: Complementary + cdf = np.cumsum(histograms, axis=1) + + # TODO: Turn into arg. + estimator = "mean" + if estimator == "mean": + agg_cdf = cdf.mean(axis=0) + elif estimator == "median": + agg_cdf = cdf.median(axis=0) + else: + raise ValueError(f"Unknown estimator {estimator}") + + # TODO: spread parameter. + spread_cdf = cdf.std(axis=0) + + print(histograms.shape) + print(agg_cdf.shape) + print(spread_cdf.shape) + + print(agg_cdf[40:45]) + print(spread_cdf[40:45]) + + patch = ax.stairs(values=agg_cdf, edges=bin_edges) + ax.stairs( + values=agg_cdf + spread_cdf, + edges=bin_edges, + baseline=agg_cdf - spread_cdf, + fill=True, + alpha=0.2, + color=patch.get_edgecolor(), + ) From 0ebe730df43fc68f9b8d05ce58d9de528e22c465 Mon Sep 17 00:00:00 2001 From: Bryon Tjanaka Date: Fri, 17 Jul 2026 02:12:04 -0700 Subject: [PATCH 04/15] Use compute_vmin_vmax --- ribs/visualize/_aggregate_cdf.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/ribs/visualize/_aggregate_cdf.py b/ribs/visualize/_aggregate_cdf.py index f952e1d88..3c9adadcc 100644 --- a/ribs/visualize/_aggregate_cdf.py +++ b/ribs/visualize/_aggregate_cdf.py @@ -11,6 +11,7 @@ from pandas import DataFrame from ribs.archives import ArchiveBase, ArchiveDataFrame +from ribs.visualize._utils import compute_vmin_vmax def aggregate_cdf( @@ -51,23 +52,7 @@ def aggregate_cdf( # TODO: Proper data retrieval. objectives = [archive.data("objective") for archive in archives] - # TODO: Computation. - # Compute vmin and vmax. - - # TODO: Use common vmin_vmax util. - if vmin is None: - min_objs = [np.inf if len(objs) == 0 else np.min(objs) for objs in objectives] - # TODO: check for inf? - vmin = np.min(min_objs) - else: - pass - - max_objs = [np.inf if len(objs) == 0 else np.max(objs) for objs in objectives] - - vmax = np.max(max_objs) - - vmin = np.min(objectives) if vmin is None and len(objectives) > 0 else vmin - vmax = np.max(objectives) if vmax is None and len(objectives) > 0 else vmax + vmin, vmax = compute_vmin_vmax(vmin, vmax, np.concatenate(objectives)) # Initialize axis. ax = plt.gca() if ax is None else ax From 509f4915eb08769170b442edde65ea6ee5870d2c Mon Sep 17 00:00:00 2001 From: Bryon Tjanaka Date: Sat, 18 Jul 2026 19:12:00 -0700 Subject: [PATCH 05/15] Rename to aggregate_histogram --- HISTORY.md | 3 ++- ribs/visualize/__init__.py | 6 +++--- .../{_aggregate_cdf.py => _aggregate_histogram.py} | 11 ++--------- ribs/visualize/_archive_histogram.py | 5 +++++ 4 files changed, 12 insertions(+), 13 deletions(-) rename ribs/visualize/{_aggregate_cdf.py => _aggregate_histogram.py} (94%) diff --git a/HISTORY.md b/HISTORY.md index 91f2ba936..568fa2282 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,7 +6,8 @@ #### API -- Add `aggregate_cdf` to visualize CDF of multiple archives ({pr}`722`) +- Add `aggregate_histogram` to visualize histogram or CDF of multiple archives + ({pr}`722`) - Support fields as a single str for return_type="dict"|"tuple"|"pandas" in archive.data() ({pr}`721`) - Add `archive_ecdf` to visualize ECDF and ECCDF of archives ({pr}`719`) diff --git a/ribs/visualize/__init__.py b/ribs/visualize/__init__.py index 3a9f62325..246912826 100644 --- a/ribs/visualize/__init__.py +++ b/ribs/visualize/__init__.py @@ -14,7 +14,7 @@ .. autosummary:: :toctree: - aggregate_cdf + aggregate_histogram archive_ecdf archive_histogram cvt_archive_3d_plot @@ -27,7 +27,7 @@ qdax_repertoire_heatmap """ -from ribs.visualize._aggregate_cdf import aggregate_cdf +from ribs.visualize._aggregate_histogram import aggregate_histogram from ribs.visualize._archive_ecdf import archive_ecdf from ribs.visualize._archive_histogram import archive_histogram from ribs.visualize._cvt_archive_3d_plot import cvt_archive_3d_plot @@ -44,7 +44,7 @@ ) __all__ = [ - "aggregate_cdf", + "aggregate_histogram", "archive_ecdf", "archive_histogram", "cvt_archive_3d_plot", diff --git a/ribs/visualize/_aggregate_cdf.py b/ribs/visualize/_aggregate_histogram.py similarity index 94% rename from ribs/visualize/_aggregate_cdf.py rename to ribs/visualize/_aggregate_histogram.py index 3c9adadcc..e7d115c42 100644 --- a/ribs/visualize/_aggregate_cdf.py +++ b/ribs/visualize/_aggregate_histogram.py @@ -1,4 +1,4 @@ -"""Provides aggregate_cdf.""" +"""Provides aggregate_histogram.""" from __future__ import annotations @@ -14,7 +14,7 @@ from ribs.visualize._utils import compute_vmin_vmax -def aggregate_cdf( +def aggregate_histogram( archives: Collection[ArchiveBase], ax: Axes | None = None, dfs: Collection[DataFrame] | Collection[ArchiveDataFrame] | None = None, @@ -80,13 +80,6 @@ def aggregate_cdf( # TODO: spread parameter. spread_cdf = cdf.std(axis=0) - print(histograms.shape) - print(agg_cdf.shape) - print(spread_cdf.shape) - - print(agg_cdf[40:45]) - print(spread_cdf[40:45]) - patch = ax.stairs(values=agg_cdf, edges=bin_edges) ax.stairs( values=agg_cdf + spread_cdf, diff --git a/ribs/visualize/_archive_histogram.py b/ribs/visualize/_archive_histogram.py index bf47da798..7d490dbf6 100644 --- a/ribs/visualize/_archive_histogram.py +++ b/ribs/visualize/_archive_histogram.py @@ -35,6 +35,11 @@ def archive_histogram( from the archive and then applies a number of (opinionated) customizations. As such, many of this function's arguments are shared with ``hist``. + .. note:: + This function is intended to plot a single archive, similar to heatmap + functions. To aggregate multiple archives into a single histogram or CDF/CCDF, + see :func:`~ribs.visualize.aggregate_histogram`. + Examples: Basic Histogram of a 2D GridArchive From f7038434c8c5647da6fcde849b314cebb03e069c Mon Sep 17 00:00:00 2001 From: Bryon Tjanaka Date: Sat, 18 Jul 2026 19:12:51 -0700 Subject: [PATCH 06/15] history --- HISTORY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 568fa2282..f45a71b49 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,8 +6,8 @@ #### API -- Add `aggregate_histogram` to visualize histogram or CDF of multiple archives - ({pr}`722`) +- Add `aggregate_histogram` to visualize histogram and CDF/CCDF of multiple + archives ({pr}`722`) - Support fields as a single str for return_type="dict"|"tuple"|"pandas" in archive.data() ({pr}`721`) - Add `archive_ecdf` to visualize ECDF and ECCDF of archives ({pr}`719`) From 24df4396d99da25d837e1a1ca24c3034b2f9cfaf Mon Sep 17 00:00:00 2001 From: Bryon Tjanaka Date: Tue, 21 Jul 2026 01:02:36 -0700 Subject: [PATCH 07/15] Update impl --- ribs/visualize/_aggregate_histogram.py | 90 +++++++++++++++----- tests/visualize/aggregate_histogram_test.py | 91 +++++++++++++++++++++ 2 files changed, 159 insertions(+), 22 deletions(-) create mode 100644 tests/visualize/aggregate_histogram_test.py diff --git a/ribs/visualize/_aggregate_histogram.py b/ribs/visualize/_aggregate_histogram.py index e7d115c42..51e92b126 100644 --- a/ribs/visualize/_aggregate_histogram.py +++ b/ribs/visualize/_aggregate_histogram.py @@ -7,13 +7,16 @@ import matplotlib.pyplot as plt import numpy as np +import scipy.stats from matplotlib.axes import Axes from pandas import DataFrame from ribs.archives import ArchiveBase, ArchiveDataFrame -from ribs.visualize._utils import compute_vmin_vmax +from ribs.visualize._utils import compute_vmin_vmax, validate_df +# TODO: Support more errorbars. +# TODO: None default for estimator and errorbar? def aggregate_histogram( archives: Collection[ArchiveBase], ax: Axes | None = None, @@ -21,10 +24,12 @@ def aggregate_histogram( bins: int | Sequence[float] | str | None = 100, vmin: float | None = None, vmax: float | None = None, - # estimator: Literal["mean", "median"] = "mean", - # TODO: complementary + cumulative: bool | Literal[-1] = False, + estimator: Literal["mean", "median"] = "mean", + errorbar: Literal["se", "sd"] = "sd", + show_edges: bool = True, ) -> None: - """Plots a CDF aggregated over multiple archives. + """Plots a histogram or CDF/CCDF aggregated over multiple archives. .. info:: @@ -40,51 +45,92 @@ def aggregate_histogram( instance, calling :meth:`ribs.archives.ArchiveBase.data` with ``return_type="pandas"`` and modifying the resulting :class:`~ribs.archives.ArchiveDataFrame`. Note that, at a minimum, each - dataframe must contain a column for "objective". + dataframe must contain a column for "objective". The number of dataframes + must be the same as the number of archives. bins: Bins for the CDF. The default of 100 indicates that the histogram will consist of 100 equally-sized bins. See :func:`~matplotlib.pyplot.hist` for more info. vmin: Minimum objective value to use in the plot. If ``None``, the minimum - objective value in the archives is used. + objective value across all the archives is used. vmax: Maximum objective value to use in the plot. If ``None``, the maximum - objective value in the archives is used. + objective value across all the archives is used. + cumulative: Pass False to plot a regular histogram, True to plot a CDF, and -1 + to pass a CCDF. + estimator: Method for aggregating the histogram or CDF/CCDF across the multiple + archives. For example, if "mean" is passed, we count the number of entries + in each histogram bin for each archive, and the final plot shows the mean + number of entries in each bin. + errorbar: Method for computing the errorbar for the histogram or CDF/CCDF. For + example, if "sd" is passed, we display an errorbar showing the standard + deviation of the number of entries in each histogram bin. + show_edges: Whether to show the edges of the histogram or CDF/CCDF. """ - # TODO: Proper data retrieval. - objectives = [archive.data("objective") for archive in archives] + if dfs is None: + objectives = [] + for archive in archives: + try: + objectives.append(archive.data("objective")) + except NotImplementedError as e: + raise AttributeError( + "To use aggregate_histogram, each archive must have the data() method." + ) from e + else: + if len(dfs) != len(archives): + raise ValueError( + "If passed in, the number of dfs must equal the number of archives." + ) + objectives = [] + for df in dfs: + df = validate_df(df) + objectives.append(np.asarray(df["objective"])) vmin, vmax = compute_vmin_vmax(vmin, vmax, np.concatenate(objectives)) # Initialize axis. ax = plt.gca() if ax is None else ax + # Compute histogram for each archive. histograms = [] for objs in objectives: hist, bin_edges = np.histogram(objs, bins, range=(vmin, vmax)) histograms.append(hist) histograms = np.stack(histograms, axis=0) - print(histograms) - - # TODO: Complementary - cdf = np.cumsum(histograms, axis=1) + # Apply the cumulative parameter if needed. + if cumulative > 0: + histograms = np.cumsum(histograms, axis=1) + elif cumulative < 0: + # TODO: Complementary + histograms = ... + else: + # `cumulative` is False or 0, meaning we leave the histogram as is. + ... - # TODO: Turn into arg. - estimator = "mean" + # Aggregate values with `estimator`. if estimator == "mean": - agg_cdf = cdf.mean(axis=0) + agg_hist = histograms.mean(axis=0) elif estimator == "median": - agg_cdf = cdf.median(axis=0) + agg_hist = histograms.median(axis=0) else: raise ValueError(f"Unknown estimator {estimator}") - # TODO: spread parameter. - spread_cdf = cdf.std(axis=0) + # Compute errors/spread with `errorbar`. + if errorbar == "sd": + err_hist = histograms.std(axis=0) + elif errorbar == "se": + err_hist = scipy.stats.sem(histograms, axis=0) + else: + raise ValueError(f"Unknown errorbar {errorbar}") - patch = ax.stairs(values=agg_cdf, edges=bin_edges) + patch = ax.stairs( + values=agg_hist, + edges=bin_edges, + baseline=0 if show_edges else None, + ) ax.stairs( - values=agg_cdf + spread_cdf, + values=agg_hist + err_hist, edges=bin_edges, - baseline=agg_cdf - spread_cdf, + baseline=agg_hist - err_hist, fill=True, alpha=0.2, color=patch.get_edgecolor(), diff --git a/tests/visualize/aggregate_histogram_test.py b/tests/visualize/aggregate_histogram_test.py new file mode 100644 index 000000000..bb85187df --- /dev/null +++ b/tests/visualize/aggregate_histogram_test.py @@ -0,0 +1,91 @@ +"""Tests for aggregate_histogram. + +See README.md for instructions on writing tests. +""" + +import matplotlib.pyplot as plt +import numpy as np +import pytest +from matplotlib.testing.decorators import image_comparison + +from ribs.archives import GridArchive +from ribs.visualize import aggregate_histogram + +# pylint: disable=redefined-outer-name + + +# +# Fixtures +# + + +@pytest.fixture +def simple_archives(): + """Three archives intended to have three bins of objective values.""" + rng = np.random.default_rng(42) + + archives = [] + for obj_count in [ + # Number of items in [0, 1), [1, 2), and [2, 3). + # + # Keep in mind that when plotting a CDF, the CDF does a cumulative sum over the + # number of items in the bins. Hence, there should be a high std in the count in + # [0, 1), then [1, 2) will have lower std (sums are 55, 60, 65), and finally, + # [2, 3) will have zero std (all sums are 100 at that point). + [20, 35, 45], + [30, 30, 40], + [40, 25, 35], + ]: + # Populate the archive with the negative sphere function. + archive = GridArchive(solution_dim=2, dims=[100], ranges=[(0, 1)]) + + objectives = np.concatenate( + ( + 0.0 + rng.uniform(0, 1, size=obj_count[0]), + 1.0 + rng.uniform(0, 1, size=obj_count[1]), + 2.0 + rng.uniform(0, 1, size=obj_count[2]), + ) + ) + + archive.add( + solution=np.zeros((100, 2)), + objective=objectives, + measures=np.arange(0, 1, 0.01)[:, None], + ) + archives.append(archive) + + return archives + + +# +# Tests +# + + +@image_comparison( + baseline_images=["basic_histogram"], + remove_text=False, + extensions=["png"], + style="mpl20", +) +def test_basic_histogram(simple_archives): + plt.figure(figsize=(8, 6)) + aggregate_histogram(simple_archives, bins=3) + + +@image_comparison( + baseline_images=["basic_cdf"], + remove_text=False, + extensions=["png"], + style="mpl20", +) +def test_basic_cdf(simple_archives): + plt.figure(figsize=(8, 6)) + aggregate_histogram(simple_archives, bins=3, cumulative=True) + + +# TODO: vmin and vmax +# TODO: cumulative +# TODO: estimator +# TODO: errorbar +# TODO: show_edges From 68fe75aeb2308d625b7844771aa9355c83d3fcab Mon Sep 17 00:00:00 2001 From: Bryon Tjanaka Date: Tue, 21 Jul 2026 01:10:27 -0700 Subject: [PATCH 08/15] Rename to aggregate_cdf --- HISTORY.md | 3 +-- ribs/visualize/__init__.py | 6 +++--- .../{_aggregate_histogram.py => _aggregate_cdf.py} | 8 ++++---- ribs/visualize/_archive_histogram.py | 4 ++-- ...{aggregate_histogram_test.py => aggregate_cdf_test.py} | 8 ++++---- 5 files changed, 14 insertions(+), 15 deletions(-) rename ribs/visualize/{_aggregate_histogram.py => _aggregate_cdf.py} (95%) rename tests/visualize/{aggregate_histogram_test.py => aggregate_cdf_test.py} (91%) diff --git a/HISTORY.md b/HISTORY.md index f45a71b49..814d83c73 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,8 +6,7 @@ #### API -- Add `aggregate_histogram` to visualize histogram and CDF/CCDF of multiple - archives ({pr}`722`) +- Add `aggregate_cdf` to visualize CDF/CCDF of multiple archives ({pr}`722`) - Support fields as a single str for return_type="dict"|"tuple"|"pandas" in archive.data() ({pr}`721`) - Add `archive_ecdf` to visualize ECDF and ECCDF of archives ({pr}`719`) diff --git a/ribs/visualize/__init__.py b/ribs/visualize/__init__.py index 246912826..3a9f62325 100644 --- a/ribs/visualize/__init__.py +++ b/ribs/visualize/__init__.py @@ -14,7 +14,7 @@ .. autosummary:: :toctree: - aggregate_histogram + aggregate_cdf archive_ecdf archive_histogram cvt_archive_3d_plot @@ -27,7 +27,7 @@ qdax_repertoire_heatmap """ -from ribs.visualize._aggregate_histogram import aggregate_histogram +from ribs.visualize._aggregate_cdf import aggregate_cdf from ribs.visualize._archive_ecdf import archive_ecdf from ribs.visualize._archive_histogram import archive_histogram from ribs.visualize._cvt_archive_3d_plot import cvt_archive_3d_plot @@ -44,7 +44,7 @@ ) __all__ = [ - "aggregate_histogram", + "aggregate_cdf", "archive_ecdf", "archive_histogram", "cvt_archive_3d_plot", diff --git a/ribs/visualize/_aggregate_histogram.py b/ribs/visualize/_aggregate_cdf.py similarity index 95% rename from ribs/visualize/_aggregate_histogram.py rename to ribs/visualize/_aggregate_cdf.py index 51e92b126..7dd1d3609 100644 --- a/ribs/visualize/_aggregate_histogram.py +++ b/ribs/visualize/_aggregate_cdf.py @@ -1,4 +1,4 @@ -"""Provides aggregate_histogram.""" +"""Provides aggregate_cdf.""" from __future__ import annotations @@ -17,7 +17,7 @@ # TODO: Support more errorbars. # TODO: None default for estimator and errorbar? -def aggregate_histogram( +def aggregate_cdf( archives: Collection[ArchiveBase], ax: Axes | None = None, dfs: Collection[DataFrame] | Collection[ArchiveDataFrame] | None = None, @@ -29,7 +29,7 @@ def aggregate_histogram( errorbar: Literal["se", "sd"] = "sd", show_edges: bool = True, ) -> None: - """Plots a histogram or CDF/CCDF aggregated over multiple archives. + """Plots a CDF/CCDF aggregated over multiple archives. .. info:: @@ -72,7 +72,7 @@ def aggregate_histogram( objectives.append(archive.data("objective")) except NotImplementedError as e: raise AttributeError( - "To use aggregate_histogram, each archive must have the data() method." + "To use aggregate_cdf, each archive must have the data() method." ) from e else: if len(dfs) != len(archives): diff --git a/ribs/visualize/_archive_histogram.py b/ribs/visualize/_archive_histogram.py index 7d490dbf6..c430221eb 100644 --- a/ribs/visualize/_archive_histogram.py +++ b/ribs/visualize/_archive_histogram.py @@ -37,8 +37,8 @@ def archive_histogram( .. note:: This function is intended to plot a single archive, similar to heatmap - functions. To aggregate multiple archives into a single histogram or CDF/CCDF, - see :func:`~ribs.visualize.aggregate_histogram`. + functions. To aggregate multiple archives into a CDF/CCDF or histogram, see + :func:`~ribs.visualize.aggregate_cdf`. Examples: Basic Histogram of a 2D GridArchive diff --git a/tests/visualize/aggregate_histogram_test.py b/tests/visualize/aggregate_cdf_test.py similarity index 91% rename from tests/visualize/aggregate_histogram_test.py rename to tests/visualize/aggregate_cdf_test.py index bb85187df..9acf1d518 100644 --- a/tests/visualize/aggregate_histogram_test.py +++ b/tests/visualize/aggregate_cdf_test.py @@ -1,4 +1,4 @@ -"""Tests for aggregate_histogram. +"""Tests for aggregate_cdf. See README.md for instructions on writing tests. """ @@ -9,7 +9,7 @@ from matplotlib.testing.decorators import image_comparison from ribs.archives import GridArchive -from ribs.visualize import aggregate_histogram +from ribs.visualize import aggregate_cdf # pylint: disable=redefined-outer-name @@ -70,7 +70,7 @@ def simple_archives(): ) def test_basic_histogram(simple_archives): plt.figure(figsize=(8, 6)) - aggregate_histogram(simple_archives, bins=3) + aggregate_cdf(simple_archives, bins=3) @image_comparison( @@ -81,7 +81,7 @@ def test_basic_histogram(simple_archives): ) def test_basic_cdf(simple_archives): plt.figure(figsize=(8, 6)) - aggregate_histogram(simple_archives, bins=3, cumulative=True) + aggregate_cdf(simple_archives, bins=3, cumulative=True) # TODO: vmin and vmax From acc65645b4193cdab72fdb3d7d95d0817ffa05d9 Mon Sep 17 00:00:00 2001 From: Bryon Tjanaka Date: Tue, 21 Jul 2026 01:14:20 -0700 Subject: [PATCH 09/15] Prioritize CDF/CCDF in arg descriptions --- ribs/visualize/_aggregate_cdf.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/ribs/visualize/_aggregate_cdf.py b/ribs/visualize/_aggregate_cdf.py index 7dd1d3609..747bda16c 100644 --- a/ribs/visualize/_aggregate_cdf.py +++ b/ribs/visualize/_aggregate_cdf.py @@ -15,6 +15,9 @@ from ribs.visualize._utils import compute_vmin_vmax, validate_df +# TODO: Describe what a CDF/CCDF is and how we aggregate. +# TODO: Review argument descriptions. +# TODO: Examples. # TODO: Support more errorbars. # TODO: None default for estimator and errorbar? def aggregate_cdf( @@ -24,22 +27,26 @@ def aggregate_cdf( bins: int | Sequence[float] | str | None = 100, vmin: float | None = None, vmax: float | None = None, - cumulative: bool | Literal[-1] = False, + cumulative: bool | Literal[-1] = True, estimator: Literal["mean", "median"] = "mean", errorbar: Literal["se", "sd"] = "sd", show_edges: bool = True, ) -> None: """Plots a CDF/CCDF aggregated over multiple archives. + This function also has limited support for plotting histograms by setting the + `cumulative` parameter to False. + .. info:: - The idea of using a CCDF to evaluate QD algorithms was introduced and formalized - in `Vassiliades + The idea of using a CDF/CCDF to evaluate QD algorithms was introduced and + formalized in `Vassiliades 2018 `_. Args: - archives: Archives to aggregate for the CDF. - ax: Axes on which to plot the CDF. If ``None``, the current axis will be used. + archives: Archives to aggregate for the CDF/CCDF. + ax: Axes on which to plot the CDF/CCDF. If ``None``, the current axis will be + used. dfs: If provided, we will plot data from this sequence of dataframes instead of the data currently in the archives. This data can be obtained by, for instance, calling :meth:`ribs.archives.ArchiveBase.data` with @@ -47,23 +54,22 @@ def aggregate_cdf( :class:`~ribs.archives.ArchiveDataFrame`. Note that, at a minimum, each dataframe must contain a column for "objective". The number of dataframes must be the same as the number of archives. - bins: Bins for the CDF. The default of 100 indicates that the histogram will - consist of 100 equally-sized bins. See :func:`~matplotlib.pyplot.hist` for - more info. + bins: Bins for the CDF/CCDF. The default of 100 indicates that there will be 100 + equally-sized bins. vmin: Minimum objective value to use in the plot. If ``None``, the minimum objective value across all the archives is used. vmax: Maximum objective value to use in the plot. If ``None``, the maximum objective value across all the archives is used. - cumulative: Pass False to plot a regular histogram, True to plot a CDF, and -1 - to pass a CCDF. - estimator: Method for aggregating the histogram or CDF/CCDF across the multiple + cumulative: Pass True to plot a CDF, -1 to plot a CCDF, and False to plot a + histogram. + estimator: Method for aggregating the CDF/CCDF or histogram across the multiple archives. For example, if "mean" is passed, we count the number of entries in each histogram bin for each archive, and the final plot shows the mean number of entries in each bin. - errorbar: Method for computing the errorbar for the histogram or CDF/CCDF. For + errorbar: Method for computing the errorbar for the CDF/CCDF or histogram. For example, if "sd" is passed, we display an errorbar showing the standard deviation of the number of entries in each histogram bin. - show_edges: Whether to show the edges of the histogram or CDF/CCDF. + show_edges: Whether to show the edges of the CDF/CCDF or histogram. """ if dfs is None: objectives = [] From d486fb7b5d87943d111e15109aa677b88751fb26 Mon Sep 17 00:00:00 2001 From: Bryon Tjanaka Date: Tue, 21 Jul 2026 22:32:10 -0700 Subject: [PATCH 10/15] More impl and tests --- ribs/visualize/_aggregate_cdf.py | 69 +++++++++++++++++---------- tests/visualize/aggregate_cdf_test.py | 23 ++++++--- 2 files changed, 61 insertions(+), 31 deletions(-) diff --git a/ribs/visualize/_aggregate_cdf.py b/ribs/visualize/_aggregate_cdf.py index 747bda16c..b7b9667f8 100644 --- a/ribs/visualize/_aggregate_cdf.py +++ b/ribs/visualize/_aggregate_cdf.py @@ -15,27 +15,39 @@ from ribs.visualize._utils import compute_vmin_vmax, validate_df -# TODO: Describe what a CDF/CCDF is and how we aggregate. -# TODO: Review argument descriptions. # TODO: Examples. # TODO: Support more errorbars. -# TODO: None default for estimator and errorbar? def aggregate_cdf( archives: Collection[ArchiveBase], ax: Axes | None = None, dfs: Collection[DataFrame] | Collection[ArchiveDataFrame] | None = None, + cumulative: bool | Literal[-1] = True, bins: int | Sequence[float] | str | None = 100, vmin: float | None = None, vmax: float | None = None, - cumulative: bool | Literal[-1] = True, estimator: Literal["mean", "median"] = "mean", - errorbar: Literal["se", "sd"] = "sd", + errorbar: None | Literal["se", "sd"] = "sd", show_edges: bool = True, ) -> None: """Plots a CDF/CCDF aggregated over multiple archives. - This function also has limited support for plotting histograms by setting the - `cumulative` parameter to False. + Generally, the `CDF (cumulative distribution function) + `_ represents the + number of observations that fall below each output value. In the case of archives, a + CDF counts the number of elites that perform worse than or equal to each objective + value. Conversely, a CCDF (*complementary* cumulative distribution function) counts + the number of elites that perform better than or equal to each objective value. + + This function approximates a CDF/CCDF over multiple archives by first computing a + histogram over the objective values in each archive. Then, it performs a cumulative + sum on the histograms to obtain a CDF/CCDF. Finally, it aggregates the CDFs/CCDFs by + aggregating the number of values in each histogram's bin. For example, if Archive + 0's CDF has 10 values in the bin [0, 0.1), and Archive 1's CDF has 20 values in the + bin [0, 0.1), and we aggregate values using the "mean" estimator, then the final + histogram shows 15 values in the bin [0, 0.1). + + This function also supports plotting histograms by setting the `cumulative` + parameter to False. .. info:: @@ -54,22 +66,24 @@ def aggregate_cdf( :class:`~ribs.archives.ArchiveDataFrame`. Note that, at a minimum, each dataframe must contain a column for "objective". The number of dataframes must be the same as the number of archives. + cumulative: Pass True to plot a CDF, -1 to plot a CCDF, and False to plot a + histogram. bins: Bins for the CDF/CCDF. The default of 100 indicates that there will be 100 equally-sized bins. vmin: Minimum objective value to use in the plot. If ``None``, the minimum objective value across all the archives is used. vmax: Maximum objective value to use in the plot. If ``None``, the maximum objective value across all the archives is used. - cumulative: Pass True to plot a CDF, -1 to plot a CCDF, and False to plot a - histogram. estimator: Method for aggregating the CDF/CCDF or histogram across the multiple archives. For example, if "mean" is passed, we count the number of entries in each histogram bin for each archive, and the final plot shows the mean number of entries in each bin. errorbar: Method for computing the errorbar for the CDF/CCDF or histogram. For example, if "sd" is passed, we display an errorbar showing the standard - deviation of the number of entries in each histogram bin. - show_edges: Whether to show the edges of the CDF/CCDF or histogram. + deviation of the number of entries in each histogram bin. Pass None to hide + the errorbar. + show_edges: Whether to show the left and right edges of the CDF/CCDF or + histogram (these show up as vertical lines). """ if dfs is None: objectives = [] @@ -104,13 +118,15 @@ def aggregate_cdf( # Apply the cumulative parameter if needed. if cumulative > 0: + # CDF. histograms = np.cumsum(histograms, axis=1) elif cumulative < 0: - # TODO: Complementary - histograms = ... - else: - # `cumulative` is False or 0, meaning we leave the histogram as is. - ... + # CCDF -- We want to do a cumsum from right to left, so we flip the histograms, + # compute the cumsum from left to right, and then flip back. + histograms = np.flip(np.cumsum(np.flip(histograms, axis=1), axis=1), axis=1) + else: # cumulative is False (i.e., 0). + # Leave the histograms as is. + pass # Aggregate values with `estimator`. if estimator == "mean": @@ -121,7 +137,9 @@ def aggregate_cdf( raise ValueError(f"Unknown estimator {estimator}") # Compute errors/spread with `errorbar`. - if errorbar == "sd": + if errorbar is None: + err_hist = np.zeros_like(histograms) + elif errorbar == "sd": err_hist = histograms.std(axis=0) elif errorbar == "se": err_hist = scipy.stats.sem(histograms, axis=0) @@ -133,11 +151,12 @@ def aggregate_cdf( edges=bin_edges, baseline=0 if show_edges else None, ) - ax.stairs( - values=agg_hist + err_hist, - edges=bin_edges, - baseline=agg_hist - err_hist, - fill=True, - alpha=0.2, - color=patch.get_edgecolor(), - ) + if errorbar is not None: + ax.stairs( + values=agg_hist + err_hist, + edges=bin_edges, + baseline=agg_hist - err_hist, + fill=True, + alpha=0.2, + color=patch.get_edgecolor(), + ) diff --git a/tests/visualize/aggregate_cdf_test.py b/tests/visualize/aggregate_cdf_test.py index 9acf1d518..310e466d2 100644 --- a/tests/visualize/aggregate_cdf_test.py +++ b/tests/visualize/aggregate_cdf_test.py @@ -63,25 +63,36 @@ def simple_archives(): @image_comparison( - baseline_images=["basic_histogram"], + baseline_images=["basic_cdf"], remove_text=False, extensions=["png"], style="mpl20", ) -def test_basic_histogram(simple_archives): +def test_basic_cdf(simple_archives): plt.figure(figsize=(8, 6)) - aggregate_cdf(simple_archives, bins=3) + aggregate_cdf(simple_archives, bins=3, cumulative=True) @image_comparison( - baseline_images=["basic_cdf"], + baseline_images=["basic_ccdf"], remove_text=False, extensions=["png"], style="mpl20", ) -def test_basic_cdf(simple_archives): +def test_basic_ccdf(simple_archives): plt.figure(figsize=(8, 6)) - aggregate_cdf(simple_archives, bins=3, cumulative=True) + aggregate_cdf(simple_archives, bins=3, cumulative=-1) + + +@image_comparison( + baseline_images=["basic_histogram"], + remove_text=False, + extensions=["png"], + style="mpl20", +) +def test_basic_histogram(simple_archives): + plt.figure(figsize=(8, 6)) + aggregate_cdf(simple_archives, bins=3, cumulative=False) # TODO: vmin and vmax From bb642047ecce741aa0d54ebdc20fd52398a5c362 Mon Sep 17 00:00:00 2001 From: Bryon Tjanaka Date: Wed, 22 Jul 2026 00:24:52 -0700 Subject: [PATCH 11/15] Tests --- ribs/visualize/_aggregate_cdf.py | 34 ++- tests/visualize/aggregate_cdf_test.py | 245 +++++++++++++++++- .../aggregate_cdf_test/basic_ccdf.png | Bin 0 -> 10736 bytes .../aggregate_cdf_test/basic_cdf.png | Bin 0 -> 10749 bytes .../aggregate_cdf_test/basic_histogram.png | Bin 0 -> 9978 bytes .../aggregate_cdf_test/errorbar_none.png | Bin 0 -> 10684 bytes .../aggregate_cdf_test/errorbar_se.png | Bin 0 -> 10750 bytes .../full_scale_cdf_mean.png | Bin 0 -> 12704 bytes .../full_scale_cdf_median.png | Bin 0 -> 11516 bytes .../aggregate_cdf_test/full_scale_hist.png | Bin 0 -> 11386 bytes .../full_scale_single_hist.png | Bin 0 -> 17148 bytes .../median_with_iqr_cdf.png | Bin 0 -> 10713 bytes .../median_with_iqr_hist.png | Bin 0 -> 10612 bytes .../aggregate_cdf_test/no_edges.png | Bin 0 -> 12311 bytes .../aggregate_cdf_test/vmin_vmax.png | Bin 0 -> 10745 bytes 15 files changed, 254 insertions(+), 25 deletions(-) create mode 100644 tests/visualize/baseline_images/aggregate_cdf_test/basic_ccdf.png create mode 100644 tests/visualize/baseline_images/aggregate_cdf_test/basic_cdf.png create mode 100644 tests/visualize/baseline_images/aggregate_cdf_test/basic_histogram.png create mode 100644 tests/visualize/baseline_images/aggregate_cdf_test/errorbar_none.png create mode 100644 tests/visualize/baseline_images/aggregate_cdf_test/errorbar_se.png create mode 100644 tests/visualize/baseline_images/aggregate_cdf_test/full_scale_cdf_mean.png create mode 100644 tests/visualize/baseline_images/aggregate_cdf_test/full_scale_cdf_median.png create mode 100644 tests/visualize/baseline_images/aggregate_cdf_test/full_scale_hist.png create mode 100644 tests/visualize/baseline_images/aggregate_cdf_test/full_scale_single_hist.png create mode 100644 tests/visualize/baseline_images/aggregate_cdf_test/median_with_iqr_cdf.png create mode 100644 tests/visualize/baseline_images/aggregate_cdf_test/median_with_iqr_hist.png create mode 100644 tests/visualize/baseline_images/aggregate_cdf_test/no_edges.png create mode 100644 tests/visualize/baseline_images/aggregate_cdf_test/vmin_vmax.png diff --git a/ribs/visualize/_aggregate_cdf.py b/ribs/visualize/_aggregate_cdf.py index b7b9667f8..395423c6f 100644 --- a/ribs/visualize/_aggregate_cdf.py +++ b/ribs/visualize/_aggregate_cdf.py @@ -16,7 +16,6 @@ # TODO: Examples. -# TODO: Support more errorbars. def aggregate_cdf( archives: Collection[ArchiveBase], ax: Axes | None = None, @@ -26,7 +25,7 @@ def aggregate_cdf( vmin: float | None = None, vmax: float | None = None, estimator: Literal["mean", "median"] = "mean", - errorbar: None | Literal["se", "sd"] = "sd", + errorbar: None | Literal["se", "sd", "iqr"] = "sd", show_edges: bool = True, ) -> None: """Plots a CDF/CCDF aggregated over multiple archives. @@ -80,10 +79,16 @@ def aggregate_cdf( number of entries in each bin. errorbar: Method for computing the errorbar for the CDF/CCDF or histogram. For example, if "sd" is passed, we display an errorbar showing the standard - deviation of the number of entries in each histogram bin. Pass None to hide - the errorbar. + deviation of the number of entries in each histogram bin. Options are "sd" + (standard deviation), "se" (standard error of the mean), "iqr" + (interquartile range, i.e., the interval from the 25th to 75th percentile), + and None (no error bar). show_edges: Whether to show the left and right edges of the CDF/CCDF or histogram (these show up as vertical lines). + + Raises: + AttributeError: The data() method is not implemented on one of the archives. + ValueError: Number of dfs passed in is not the same as the number of archives. """ if dfs is None: objectives = [] @@ -130,19 +135,26 @@ def aggregate_cdf( # Aggregate values with `estimator`. if estimator == "mean": - agg_hist = histograms.mean(axis=0) + agg_hist = np.mean(histograms, axis=0) elif estimator == "median": - agg_hist = histograms.median(axis=0) + agg_hist = np.median(histograms, axis=0) else: raise ValueError(f"Unknown estimator {estimator}") # Compute errors/spread with `errorbar`. if errorbar is None: - err_hist = np.zeros_like(histograms) + err_low = histograms + err_high = histograms elif errorbar == "sd": - err_hist = histograms.std(axis=0) + std_hist = histograms.std(axis=0) + err_low = agg_hist - std_hist + err_high = agg_hist + std_hist elif errorbar == "se": - err_hist = scipy.stats.sem(histograms, axis=0) + sem_hist = scipy.stats.sem(histograms, axis=0) + err_low = agg_hist - sem_hist + err_high = agg_hist + sem_hist + elif errorbar == "iqr": + err_low, err_high = np.percentile(histograms, (25, 75), axis=0) else: raise ValueError(f"Unknown errorbar {errorbar}") @@ -153,9 +165,9 @@ def aggregate_cdf( ) if errorbar is not None: ax.stairs( - values=agg_hist + err_hist, + values=err_high, edges=bin_edges, - baseline=agg_hist - err_hist, + baseline=err_low, fill=True, alpha=0.2, color=patch.get_edgecolor(), diff --git a/tests/visualize/aggregate_cdf_test.py b/tests/visualize/aggregate_cdf_test.py index 310e466d2..1a9e0303c 100644 --- a/tests/visualize/aggregate_cdf_test.py +++ b/tests/visualize/aggregate_cdf_test.py @@ -8,7 +8,7 @@ import pytest from matplotlib.testing.decorators import image_comparison -from ribs.archives import GridArchive +from ribs.archives import DensityArchive, GridArchive from ribs.visualize import aggregate_cdf # pylint: disable=redefined-outer-name @@ -19,8 +19,8 @@ # -@pytest.fixture -def simple_archives(): +@pytest.fixture(scope="module") +def three_archives(): """Three archives intended to have three bins of objective values.""" rng = np.random.default_rng(42) @@ -57,20 +57,104 @@ def simple_archives(): return archives +@pytest.fixture(scope="module") +def three_archives_skewed(): + """Same as above, but the distribution for each range is skewed.""" + rng = np.random.default_rng(42) + + archives = [] + for obj_count in [ + # Number of items in [0, 1), [1, 2), and [2, 3). + [30, 20, 50], + [30, 20, 50], + [50, 35, 15], + # Skewed -- e.g., [30, 30, 50] for [0, 1) has mean of 36.67 but median of 30. + ]: + # Populate the archive with the negative sphere function. + archive = GridArchive(solution_dim=2, dims=[100], ranges=[(0, 1)]) + + objectives = np.concatenate( + ( + 0.0 + rng.uniform(0, 1, size=obj_count[0]), + 1.0 + rng.uniform(0, 1, size=obj_count[1]), + 2.0 + rng.uniform(0, 1, size=obj_count[2]), + ) + ) + + archive.add( + solution=np.zeros((100, 2)), + objective=objectives, + measures=np.arange(0, 1, 0.01)[:, None], + ) + archives.append(archive) + + return archives + + # # Tests # +def test_no_data_method_available(): + with pytest.raises( + AttributeError, + match=r"To use aggregate_cdf, each archive must have the data\(\) method\.", + ): + aggregate_cdf([DensityArchive(measure_dim=2) for _ in range(3)]) + + @image_comparison( baseline_images=["basic_cdf"], remove_text=False, extensions=["png"], style="mpl20", ) -def test_basic_cdf(simple_archives): +def test_basic_cdf(three_archives): + """Basic CDF. + + The line should be the mean, which is 30, 60, 100. + + The errorbar should be the std, which is std(20, 30, 40) ~= 8.16, std(55, 60, 65) ~= + 4.08, std(100, 100, 100) = 0. + """ plt.figure(figsize=(8, 6)) - aggregate_cdf(simple_archives, bins=3, cumulative=True) + aggregate_cdf(three_archives, bins=3, cumulative=True) + + +@image_comparison( + baseline_images=["basic_cdf"], + remove_text=False, + extensions=["png"], + style="mpl20", +) +def test_basic_cdf_with_dfs(three_archives): + plt.figure(figsize=(8, 6)) + aggregate_cdf( + three_archives, + dfs=[ + archive.data("objective", return_type="pandas") + for archive in three_archives + ], + bins=3, + cumulative=True, + ) + + +def test_wrong_num_dfs(three_archives): + plt.figure(figsize=(8, 6)) + with pytest.raises( + ValueError, + match=r"If passed in, the number of dfs must equal the number of archives\.", + ): + aggregate_cdf( + three_archives, + # Only provide dfs for two archives. + dfs=[ + archive.data("objective", return_type="pandas") + for archive in three_archives[:2] + ], + ) @image_comparison( @@ -79,9 +163,16 @@ def test_basic_cdf(simple_archives): extensions=["png"], style="mpl20", ) -def test_basic_ccdf(simple_archives): +def test_basic_ccdf(three_archives): + """Basic CCDF. + + The line should be the mean, which is 100, 70, 40. + + The errorbar should be the std, which is std(100, 100, 100) = 0, std(80, 70, 60) ~= + 8.16, std(45, 40, 35) ~= 4.08. + """ plt.figure(figsize=(8, 6)) - aggregate_cdf(simple_archives, bins=3, cumulative=-1) + aggregate_cdf(three_archives, bins=3, cumulative=-1) @image_comparison( @@ -90,13 +181,139 @@ def test_basic_ccdf(simple_archives): extensions=["png"], style="mpl20", ) -def test_basic_histogram(simple_archives): +def test_basic_histogram(three_archives): + """Basic histogram. + + The line should be the mean, which is 30, 30, 40. + + The errorbar should be the std, which is std(20, 30, 40) = 8.16, std(35, 30, 25) ~= + 4.08, std(45, 40, 35) ~= 4.08. + """ + plt.figure(figsize=(8, 6)) + aggregate_cdf(three_archives, bins=3, cumulative=False) + + +@image_comparison( + baseline_images=["vmin_vmax"], + remove_text=False, + extensions=["png"], + style="mpl20", +) +def test_vmin_vmax(three_archives): + """CDF with vmin and vmax. + + On average, the total number of items in the range [1, 2) for the archives is 30, so + the bin on the right should have 30 items. + """ plt.figure(figsize=(8, 6)) - aggregate_cdf(simple_archives, bins=3, cumulative=False) + aggregate_cdf(three_archives, bins=2, vmin=1.0, vmax=2.0) -# TODO: vmin and vmax -# TODO: cumulative -# TODO: estimator -# TODO: errorbar -# TODO: show_edges +@image_comparison( + baseline_images=["errorbar_se"], + remove_text=False, + extensions=["png"], + style="mpl20", +) +def test_errorbar_se(three_archives): + """errorbar should be a bit smaller than in test_basic_cdf.""" + plt.figure(figsize=(8, 6)) + aggregate_cdf(three_archives, bins=3, cumulative=True, errorbar="se") + + +@image_comparison( + baseline_images=["errorbar_none"], + remove_text=False, + extensions=["png"], + style="mpl20", +) +def test_errorbar_none(three_archives): + """errorbar should be a lot smaller than in test_basic_cdf.""" + plt.figure(figsize=(8, 6)) + aggregate_cdf(three_archives, bins=3, cumulative=True, errorbar=None) + + +@image_comparison( + baseline_images=["median_with_iqr_cdf"], + remove_text=False, + extensions=["png"], + style="mpl20", +) +def test_median_with_iqr_cdf(three_archives_skewed): + plt.figure(figsize=(8, 6)) + aggregate_cdf( + three_archives_skewed, + bins=3, + estimator="median", + errorbar="iqr", + cumulative=True, + ) + + +@image_comparison( + baseline_images=["median_with_iqr_hist"], + remove_text=False, + extensions=["png"], + style="mpl20", +) +def test_median_with_iqr_hist(three_archives_skewed): + plt.figure(figsize=(8, 6)) + aggregate_cdf( + three_archives_skewed, + bins=3, + estimator="median", + errorbar="iqr", + cumulative=False, + ) + + +@image_comparison( + baseline_images=["no_edges"], + remove_text=False, + extensions=["png"], + style="mpl20", +) +def test_no_edges(three_archives): + plt.figure(figsize=(8, 6)) + aggregate_cdf(three_archives, bins=3, cumulative=True, show_edges=False) + + +@image_comparison( + baseline_images=[ + "full_scale_single_hist", + "full_scale_hist", + "full_scale_cdf_mean", + "full_scale_cdf_median", + ], + remove_text=False, + extensions=["png"], + style="mpl20", +) +def test_full_scale(): + """Larger-scale test.""" + archives = [] + for i in range(5): + archive = GridArchive( + solution_dim=2, ranges=[(-1, 1), (-1, 1)], dims=[100, 100] + ) + xxs, yys = np.meshgrid(np.linspace(-1, 1, 100), np.linspace(-1, 1, 100)) + xxs, yys = xxs.ravel(), yys.ravel() + coords = np.stack((xxs, yys), axis=1) + archive.add( + solution=coords, + objective=-(xxs**2 + yys**2) + i, # Negative sphere, offset by i. + measures=coords, + ) + archives.append(archive) + + plt.figure(figsize=(8, 6)) + aggregate_cdf(archives[:1], cumulative=False) + + plt.figure(figsize=(8, 6)) + aggregate_cdf(archives, cumulative=False) + + plt.figure(figsize=(8, 6)) + aggregate_cdf(archives, cumulative=True) + + plt.figure(figsize=(8, 6)) + aggregate_cdf(archives, cumulative=True, estimator="median", errorbar="iqr") diff --git a/tests/visualize/baseline_images/aggregate_cdf_test/basic_ccdf.png b/tests/visualize/baseline_images/aggregate_cdf_test/basic_ccdf.png new file mode 100644 index 0000000000000000000000000000000000000000..948054d9a58b685778ec31eb5762efe4ca593de1 GIT binary patch literal 10736 zcmeHt2UL@3n|9Da91Dm_kv>=uL1G3eN>xF0P#Fdlkdl!qLg>;$z%oiFDhfzd&=Hj) zHFPYHfIt9|-jvX*w1k@M{WxcK=iC3^J^wko|DH2vKhNP9$osx|%KcpTRTA>^IlZl$ zL^feCn60Nz>HLDhte3)I)|qVF0Dp<|y6Fo49re^T@icO=^Yp&#Zi_j4+0)g@#nZ{b zO3KUD-NV7fSy2IpQ#d3qb;Z-u)#E4@OZe9-6kOcxu@7m_T;M6+yPh)jz+m_;qaUs` zt>+FHjI8--onz;H5+-{{t``;_y_w&IW7OnH`E%GjMIp6+$+^3&D~`q;U*QdcbSp{LT{{w)1l z@V;N@>@Thq9M>)V0F~bw?Dr{_4X@Jco1+}>yU@#G6ilOSxiFZ!Z*WTzFhW(ojoUC7 zsa;&_Fql2NwK16MHs5W8x5F`T(S3d?3`YCMb>Cqyr%qmnt84yw@&A|A7>%3eTGU!$ zPKJ5+`pWn%j0sp2d)Ba2F&O{j(YDz{)|Bd(-*)uJTCX1DM3F1*@O80lrV+R=>S$qMyA7+E5TjPB?lGC z=0C=&xLB^X1+KN(Us{jB=;fwrYWMmuB7~K$oz9KCxEm+C1RqP{7O||n{Jqxp>?E3kQ&q9lFs&SSj&i2EDi*4%jjVH1L z)|S&UGb6dyZ_v@vArJ`im-6->kdx~wh3Q6m>8%OemC*3h$+9ftEw)=2Yr#2p7n+;s z=$uK`)KGTr7FJSHB3Po~m87yaoicOk$k_|4_{qu4)YR0u@6C<%U3o)MBBG+H+1WVn zlJau4E1CU*{d(?}&-B{w1Ia?OPYlS6{>u9){$wtUo)Xu`4QJ1uH9Y@mH6|LCXPB9P ztzM=lU}e&6JcA-J<<>R9r=}HL)3?&F_&HT;kg(y@i?punZ07UA$_44J*l-I8hos1Og7*a|;|MSn= zxVgC}73Y6IcZX|cdzaHNn9uuNKUHijJ7`xQCtyZ#j(8|%e6o_paju>nthFAhdt_*0 z(mtew!MNt_km7gya$AV{K-S1=ZFynllaWCcdd-~ZC|XO^BS+fSMptzgc`(v*>7h{u znnyyz!mMlV?+wcPwl7IH1^C)dKfn|QIR$xF~EN-_lp>yZ6XU}kUb+Ll1 z=4kX;hvBnkVgi?92$$L@E`70wF2Catw_-428S31d{|Q=u69{is>RT@+Ye`yYS*$S# z*j~X|n@%k(RNZ$rZYO?i%sADtjiYvZ>(;H=o}(X4D9#oSqNAy$zH?Tqi!;6nfC5Ja zjkP;J560jl6&utQ3kY3;xpwuTx%8uQf(qusZ5G}Z&h@=zMC;jsn(V7BFJqMnm*&UY zum=ttu&R$!@=Xx?^AXuJiLfLMB9+AI5dr`X2@d}K_um(XR4R6P_gpj9(fQ9%WlmWQ zgYn#xc|Gr~!}CPJ60b=F%iRTMO|fKB2IYD8_7o=)ZbXtOoEHZ(Gd^0dTY zM$*i=9B#cZdQQ!;t?f_npobWyp40`j*dnSde=+0IL$h2sGs1_j{G7}9@Zp2|$_8Gs z_Wl6Tf5V>gM7AWfYTD;br$n84OOH60Ocund`?{OMz}+VE+o*T8iuO;HOw%(Qu3imI zNlCGzU7POJpkygg`T~gaQ$1>|nVRcdnCo<^z+L4gZt@L zv>7LYcnJlBk;W%8&$ZTMuR1#JmQDfeQFdx?bUJ(C!iAlJg4F7JyUoh_Zr;4PUqQi{ z33Z$J2msHGL9NOPTpMrCvDMenu`w0YS{pS;HAvRPUHz0692~3Uc+sh+Sm|P>`7VC` z67T8A!P*#F-6O?RKr?*mdW?2ZzpM%2^_8ZSw3jbKn^N^?yX+JA0oSwb8AOn zu591Ap^=^vZ-4e&azhm@U~ag9yi3up^7pgxDS3H^so}e#o`=AF*4w3m9;c_vvs#OJ zKK_AE$K!4QmTqJyePdRc*D-nzRj!Ik*UoBi=T%@IEYMjSF&XaMe{aU+2~8Ozd!cQz zhPCmojzhzrU9j;chAVOt$xlp(+kzZ2t+8~eT<^x7&|+~>M^5JP-!S5l=zW>!omFJJ zh=?A^iF(%l#;KR%%t}_0|G{2j#p&yRTqCkZ6gQYnwI4AMl zojZ3-iaj0D%_s^#>FZnfmipoXRu|RJZ2@8zCMQM61IiM6*x?tkz||QXkvZYBGD%@j zMP2G9`%1iB&LpWH0c;Uy{{3tiFeP31mu{N7k8A|zhSm=_Qp#Q)pNVks-yN{ZkfbFX z@sO78?+?R9E-x>4#|26F?QhM(OSZJM;C_8`JvcZmBO^jLOi*4A=p1Lk@p0+K=w zdj&zcT|-ij6Vc7hZ7LAx)=_GNXiOM6IC!-AISpQ?{{H*Ej&uyB@m|roj~n8Z38C56 zG^&w-h5)PE!$6EUkv;PaOOnkjo&C`5s9oUHu^p)IRWI{Dd6-$Zi?y}&VP)kZXU>v< z0N`mj!cz?*voZ`@E==im*FTeZRB7Lt*lm4FbnpI;`q=vaCmck`_Nk?%N-nE(J86X( z5gdH^{hg}HkR1%vY?{xh9sKF1pHeb2GdtYWye6u}`{Mpe#&khPCC0%%xKel7>og>3 zc+Bwg<2*+igBKTX5ApL44i09+^XzDVfx4G3Uk(>LlGCzsaR2_`{{DVDfb+&j-@_92 zhsveAcp)o6ns4eZAWoO5Ab9}y!m}J()7y)$Ii{Nz{U|N%HQg&-8-pc3QnWt_Pt&Br zCh*dXUUwn{0_ZPajIUUVM-;mhb|o9;W0Gy9J2iUYuaqoOZ7P`3>nC@IPf|iZTE1p( z9){tz@vd_5L{6>MhX=BT7cK~B_{|3!ojdZZDvUndkSwWaR~HOjPS?S~A$*tOK7;0U zP(XXMd5^)A8KrY{wUM}T(hBvr?{|FM0=T9poW=DwAtt-E|E_wtc!-F>JSLSIc5O3_ zo|b*QMtYL&U9R_eTj9F@y3j{zHS(~U_f+^1k0BkM%H3r3k52d#Cr-#)6d!KQFcU&7 z-KDowSS!F^llmQ$VU57u6P|+)jj6TR3NZuCC7ayI|5(`#IDjKF;$zNr`80_CIZM8fnK@QaQq^PRvuVL1I6?})zZGF>}S?p|3u6KRUPechJXkCNEEuE*} zX*anyBbJj1=XZVPAEaxDZ}ku1H^l66A)5is__}oy280~V&9HGHcCj}qYmm6}RLY;S zubTh(b^lN*mAXg?T%n-VJoD(sz0%SJ&OIWM0gIQ>Z-VkB+l0(rk3D$sK*z$uBG=k` zqMe_gAIO`-Lb(vuQf#x3LS0X0R+fvK05^uWC1kJeOsc81HH(Khr?DxoWUN?Yrc4GO z#_Dz1E~U4TYgV@S+PENRMv~Ly)P)UPs`Z(xS7F={bq+>wCoTPKH@9;m*T(d&Ke*s+~Ecjh&G#)!2nv*g3s8gm-l+WPATjKnd*1bNdIP3?cy> zO>%5bm+P&E!g?=ox8c{ZPcLSc8Uo9!m`iiRG{7J$=xK(RE{RxE!)xAqA_V*XZo}uN zQRhOV&K-TxLPA*n{f>Q4#Q~uswEM_&hf<6fR z(^dN)u?-Q`)C)y2aV8AEDt-0pRjbNiem5|KXz1i-AlK=cehd|=8d3A1`gkD*joi>& z2~{yu2y{cG*T>p_SgkMQ-flfAv~>xW@+G?jzp)oHKobUACwfX$_8BCJy}FnY>d^eV zZbiTvs3+;xbkitW4WjsNpmNykX^!Ud!gx%H1{^$LH7_TGgzN=8M@d>*!f)h>NkstZ zysN;Sc(%_34EX!x6u7$(d+mWVBO2K*_ADjL7OPZ|w#fstE*CH%(?em@r30x!gA*>9#}W zTsU<4y%*C>_>m(yDJLr%Qe0eYH~trxVG9DxfZqLprby}g3M`icAkBL+sXJg=46)&P|L=E_ZZ9@{yN9MEob=O=lfgCX=vMjY;4>H=Pu&fcq^i? zb@3`#Dp@jlIvY;Udsps(LmmIISn5mV0>bfSQ}yN9#uI#100F7R#YZ=65#Gkn4@I1f zoQL%Ex;fN?aE0sKW7Gfd>W3T!&!M5`fC2P}(L(?sRYY3P!|UXB=1+Aq-I*^ZEj>0q z-sSd@)BL(blc_Wna;Im(5Z|}AX81ddYQirZ&0G#EPrkNu&Ewj|bpwa~w0pXWy{D79 z+Ty@k*KjUdXcg?z+V$h)BsSpgd@~nD%8}X*lSrg3B8T_CeDw+hUty0X zU;UBkB}Vsf&|H!hoy1mUE!~?fwKo++HqDN6s!uGm4D9!5^G40i&iZ_P%M(v{1s2V_ zckhIcxZAv<0h!PNPLWI4yLa!TlGWy_s;crMLpQ!XD?s(zgUKGrk}v2ba*`Y^%ijWG z>*AE;5HUieBAAS=J@^MY?7DsXwr>&`1V=Y;xyOb}XdI0`XeQ_}OpieR;5p5u0dSlm z_v%Lv5Dx?EJfzM&ekkt$G@Sj(pI(AHRX7-Jc1e9Lj5%03j6R=EL=*cq%K)3v`@VcQ z$kO-?-U7$|Yj#ErYgBtAU)#@KnI;EvSb-pY!xW15gOz@*F|hq>uGk6vE{Y2oMp22R zrc8qCadVG)CAUt8=i;twxec14eQYMh%n6@uRmH`b`RNkHxuh8!q{oOt3?kqj7u zi!W%aU{!{oU%jTfci~t2g{*;wHUY$c*X$(`5fL#nm&{R@2w40gtm*I5?M4&lvB2Z; zNWfjmCe8F$85$ZkSz3D?l_bp@s(*d6erCSeKo5#9fyC@^Yq?-d8e_0n6&%v)*C4HM zM2^3HdCw5-TRi#T;lsUg#U3ba0imgeoO*En!}PIbhaqs`^Q&vCU@ySC&IJS@Yp6R@ zhSL)1C>UBCH3$@ODV;gFMNIXOLdEK=>U8NGuU{QXka)BfkEP=)P-_)3w)C|{?Xam* zEL=`1vF@x&&rXE*{!6v^$k5QxLU1drVGcoS!f{>sj>hO(q(zWA1s7L!IKiX8N(it1 z8P`=n2(wQnDLWT_xnm~@V)m5k(mfXd`XYQe^z|Ln6&&_+iMf$fGWF@{sQ$_w2H|KT z5?~m{{%HFX^3q&`mR8fJ#P>T4)cH|`-&oH> zg%3ytyLR8X1YuS$ahi*~1oN`$uMC-Kwfa7w3HU}B&f9hjumC=hB+%hc9Sx{h095qb7P(Pj*wAID~hj>Ig4Z*p)=1U z?}}k5v(quiH-|nQHCzU}3T{mya7ES6I3#Yt>r!QX>V)AVqNg#$cMJ?SPoPX41?;sI zbdC=zDT&Xw3&>rpgp(lFX6|uzbcv~1w_*dHf2)j0AY{z(R*IvZ0F4hEkRkj&OzhC~ZfE7NSn&$o_Jfn;X+?8B@| z@bMXlxB~sEgp}~nbavzwe=fm@T{pGzQwjj4enbG=6gx7^m=a484zUl=hQ0H_OM~)L=dM09j`Q1Jf6uEW$veN z#+y3Z;H+3I36G{BMvd~SshE6Z%0ZC->)V??V1vx6_uO-s?96v-KC4p27x48KV!E#! zd6}^+5<9mhM?SN7>?LycR#%6$v_?8*l6<87m=h3k#8`NlZjn$|MY10uqW_dfvOdoF z`OHFApoZ6Er$B=S%Y1c2Pb(Cq=CFij7AvL!3Q>^&nWs*lJ`HYWetS8@HtWk@eMGf; zNsWxG%Sp$2w&bZ50f=Idy$RytO3bdMc6OGYBOXg$Vzy+MQ3L~J%fG(c%xE#j6Pn|B z8oN=cP*Ej;!&-Z+&+r9!I&RQbeCs4nqSQ^D*>0T;gA)GHrutZ0z$|eMo-2%up9oQv z0>*EN%}c|{k}>A)r&pV`){Nv$UQ5BeT;JsLtQ1YSB?qp1x1w@4L!ypW8s1^5{57>Ar6^2MyUCPL(OlG1}JD3wGNEDX+=Eu@}#C5ODQz*es*SUI(!W zi`Qr}6M(k#o+O<0No9bSi8WSV1Hfg#Y*ez1MTu8ET2h-iuYEjzLg}FE(!oR{E{{IX z1w@6uR@%{KkMN`M$%$NqE-h(^?;v80`Ua=KHGJPF@u`2+_Nv(%GvLetQcwqexchJ* zv_(^}puu`uOYa$;Jd;_$UQP$RLhy>wo2lU0ei9Y=y z9e@~(y$mlAxr^ynm{Ts(Wh_f(6*d6(DlM&=KpcG*lLIohn>DjlRM`gg5kzQUyiUXP z#Evw>)Eo%s_9iB_et-Ae5Y$%#P@~ugVVtxm&PoS;QDmZmvr-YCF$!@YGsA^6emC+< zbC3UAJsPnW{m8HlaLobXhV=0nnshvq;B!>(NU+KfQ@JX){z5-zvTR+ zn8PUSU^Q@^T(A{w`R@U6`I-XI6@Xda9S7q@E9+e-66Eg)6_Cr3Us)uc1@-I&TWyDeWc2Z% z>hpcfOnIjk8RQ1P`H_2SeJfg1Wvt%rIGCE5DOp0podP%(U!SC*HV#w%HbaPdI^Yy1 z63r|%dl+P`BwZb4_kn7`VRl1bK|uTK%N=25mCG?yI#Z@GnRPTbj2Q$GI;YYSZ( zjsJ{`E-5$w3>TZ-C&Q}|h&n+QZwg48ljT1s*8!0D&yb5LcVkST} zy^K+ltX05*b7kp2Q`KJNVUM6BX~*g=*b~K!a<5d_rDD&~?Xc?TYb(02+*^bXNl9yJ zPG^-dFRcRQ4*&8#h{!)>{~CbpVzSqdK)r;avUL1c`>LR&o0TfIqRyT{=?z6;(P^2) z2R6O*e%N`!%;AEy^3_4?u;u4fSO*>OlKLMQovWGT0O8seui_G!lam8rd1`9%u0si6 z3KbD#Xz;;5oVZi@y-Y%*6NjFt#(=6O&oF2zE2hgdEjA9ZZP=K#GX{@RNq>An4||;F i9~H>|l5g*=VtdYWXKuJceGM5d=CtlPoy_CE-uxG0JLH4_ literal 0 HcmV?d00001 diff --git a/tests/visualize/baseline_images/aggregate_cdf_test/basic_cdf.png b/tests/visualize/baseline_images/aggregate_cdf_test/basic_cdf.png new file mode 100644 index 0000000000000000000000000000000000000000..ebe2c7e00c97032f45bb17dcbc1b4786ccf4f0ba GIT binary patch literal 10749 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 Nn8``wydTf~_HSkE05Jdn literal 0 HcmV?d00001 diff --git a/tests/visualize/baseline_images/aggregate_cdf_test/basic_histogram.png b/tests/visualize/baseline_images/aggregate_cdf_test/basic_histogram.png new file mode 100644 index 0000000000000000000000000000000000000000..f3e64fca9271c731286d6af2a10823003feed79e GIT binary patch literal 9978 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 literal 0 HcmV?d00001 diff --git a/tests/visualize/baseline_images/aggregate_cdf_test/errorbar_none.png b/tests/visualize/baseline_images/aggregate_cdf_test/errorbar_none.png new file mode 100644 index 0000000000000000000000000000000000000000..3727826133224b4bee66d84cbb04de22298031a3 GIT binary patch literal 10684 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 literal 0 HcmV?d00001 diff --git a/tests/visualize/baseline_images/aggregate_cdf_test/full_scale_cdf_median.png b/tests/visualize/baseline_images/aggregate_cdf_test/full_scale_cdf_median.png new file mode 100644 index 0000000000000000000000000000000000000000..6aa58c8f46e672dc7d5b504bc481ccae084ac4c8 GIT binary patch literal 11516 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�xWi2XqHWOGYR^?d-ra>k-Xdphby3UU?S0p$6MK65oqq|F*Wp_X(PSg zEmGMZrpS?PSziMN=e)WwgoSSqSl1cb?>oY$0I;?hrB^*xvtw5T_j1S`gtw33LrI#Q zT#vjKYE44nfS<#Ax(QDFpShfLz$q*?g~9HiPlP-v(l6V+pr)+sH5J_*3|wZx*Q{Fc z^u3s?aT@;MYh+?js|)VSBy9il2`?*vdA^*h-~7Z-tIO_Srr8)W8kN9&2sJ2@m#?rP zknLF(7LBMCq6z4QU=>##i%l<4u7b_!k6OW_*82ToRt?W!oYu#=&2_n9!6?L2(k54C zOvo;Bv!4(o(0s>!`{r<6W}!iLA~;C6=xF~aZ~oJ#PvQLyPl)5Y?aY;}$(jlI`BJVE z1bgfw$2`Y=4BU;9Tl$(!0?o9?J{g?uJrpLv*y`tQ1eP#`MQg*G)jXv3MxVS^V-pk3 zVz-Hgw^=nHoy(d3!l%drP!2S~xh+jhYqN2~%3J-F!)h|B%`la}C8@_ZOsdSo%WDu~&D zT2jqtCe@@$qEXK2lqa|f?8EN_rog2x<54R*CCihwBse?+RhOh)NRd$u;|Sr4^%4ha zi9_@C#p9CM)uJU(?A}J8ues$2k=><@8gg GfBYZS$h2Yr literal 0 HcmV?d00001 diff --git a/tests/visualize/baseline_images/aggregate_cdf_test/full_scale_hist.png b/tests/visualize/baseline_images/aggregate_cdf_test/full_scale_hist.png new file mode 100644 index 0000000000000000000000000000000000000000..0b2380c47516f1c8120d31b55c59b4f7afbcdcf9 GIT binary patch literal 11386 zcmeHt1yq!6+wC)e2nfQcw1gmvN{C2Fje>%LfP_ewbO}h;fG>))N=XhPf^;{G2n{A)RgsL05j>mA$F%masVL z8PPlKE&YpnVWO~ngzk2a9_^X=(0j%@_AA;CE>fKD#>EE41>RN5s zyK`8yB-f?gw05QKhALIZ9y z{{PGW2j=91)WXSMeoISBZj+%R#%-|z0>%x2*;T|4N?KA<^6S_b>)uk8eNS&ijPw~I zc}92e)Vq=rp#?h#%0FSRFW?Efe=sd1z%fR+pj}I zL*jO+Pr$o_1$Zm`KmidMgkmhx^_k_-CH`N&1Ei*M%Ep3B!X<^n#Fxqw^>xbZ^mJVY zO&#zBO*k#{L|WsKUL5bs(D!o>A@K^5B*!ISu+ z&aiOb{Ym1|IXph(`Sa&eO)mQtMMZ+k!Uw$C#oV}1=Kj6jp`ja7%V4oC-@Jc3B1fkT zhdo@t;##7vnEY&eE8z6A4IaHoYcwzX|Lq^tz6?m@>5EDs8w>qgJ5}O`5)Km$)x+y$ zPkwwJD7I8vj!DyoAoZ#^zFYPd7J&u_^P1jaqpsuLEA93^g3)|>L*kqcJrCp>6J5s` z!7hZiWMJU-V5bk={rmR?ym!~Dho^Q4d!2C*gc8b{V~jv@-dXSP!fy}RV-Mct=JJ_! zBn*l>Is6Eq$L($`n0$H7g+#7w4%;^^mW{L0>$@4@YxkDi%McJ+TeBN+-rzXPQxzVa zQl4Dw;jV?HQ3tIg$$rNb0fVB{rsvdZA6FOmD=a_yd7HP{LQCb*D9{oztu zo$A$=D*{HJ{~GXIu5mg%@EW3gpOeE2rj9;d*fyxZr0L{G0;IKamIgz|kVj>3zaKqS zIFsawOY&TLyH)}QE_qUHD&i)=N6Hqg%|k8!V-R$n6AiHP3mH2Y;x%w&pokC%faw3W z%Nts*5GM9v-e_@m0VD6^wOMkNP)9uz4qbB$joE(-wDF+#v^YC^6qg|Jf4sw!cW0q9 zJpFIV5$tTgn`uKsv|m`8<>)@CF(xjx4u|S!hP{+CjwQ*cabb5k%FYWO1j}KDLKpt* zI1j1Q|M`7NReJ4{Al+_@vy|0_u1ZaHXNuQb#D5;)gxFDo`IEg}TFWO9UGtDZKs}6h7jn#+Jq4$N>u;c2wu(4shHoTr<->P zFZAbyhlPb19qnlKHN+G^0$W|DcAE9I<6@3?m)N^~SMHbhOp^N~sWNVjPJ}LSg?KYPdV|=i?v9iC` z*>qZ20;#F%aN9n<#JV3hQsFeYmQ3&}E-7g&>Xc%Z-ua?DvAdARpFss;ePtXqdy!dt zkOE)Dd0eQcZ+tZ|wZv5=$&H@vjXdAcVY9h!X$4KJo((1a`pi{}r8ZN z-<_D*`T3Tre|<*08i35od{!O~doYY`4EI@2-ToF++p{;s@3Rstx;vX{Qa0+cAnEd% z{9*$*e3Omz25m=g-{?f_)C?AxH)ZA3GQm@|vo_OIF_SdBHzdAXefq^&NBQJUfY#>A z)-PB+Ec9)67eEA1%O1lnkwBSmUu@m9J%#dFj=}Dm%F0%+e^U@}T^cF>?tfxp%yT(3 zFtD<+GBNtWA~)Z-z~n3C_(M8g?K2{yPt{gx$qff612JCHyI#9Cx2BP(U9s)&&n8>0 z9Oz#9PhxpjSJ$524c-9vtx?z1l#~?LpA#T-gn&Wb@Y$*wZ)EkvBaujI z>HQ~oEaA}M6XN*P=m#vocnb>)?%py|t_RJZ+SpHW3-vQ*NC>f^=y)E{9A|YiOz>L$ zBH*z;yWL6H?<}8=)|vRGz`sa!0mjV{mtk9n!=0HXDv=SIEX};IVSB8k9q2HFfU)eD zEyTd2FliO6g6PfpN!-j^Hgd{lAo!)1-B(jn+h0$Y z4h=X#k4q-(B+EHDIgR-2Ejp|;($n{lwnR;Wc+ zFmI|ZQ551n`7eNhxGxpSMX)8)S_S-pEJ%oZBTUQ8ZJ>_w2o@M=ov#6J`lKzSR9-jUk0k^3?(Ze%rLWJ`8-LL;Qcsk7I z#0CmY6(j%^TlpDpj{^WYF`F9RF}MWySLnrLuWNDfzo{S&hdWmS(+G_54aFy3|%#S0*!M>^9(7^`+b zIq`&R`pmU)$q2E7^={94Iayh71ov0i2o4g2{k58=rJAiV(e*Bw$w)P&mw|z=IOT8# z`;7(-�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 literal 0 HcmV?d00001 diff --git a/tests/visualize/baseline_images/aggregate_cdf_test/median_with_iqr_cdf.png b/tests/visualize/baseline_images/aggregate_cdf_test/median_with_iqr_cdf.png new file mode 100644 index 0000000000000000000000000000000000000000..01f907502a535fd9c5d31764712ee2688e5dbf52 GIT binary patch literal 10713 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 literal 0 HcmV?d00001 diff --git a/tests/visualize/baseline_images/aggregate_cdf_test/median_with_iqr_hist.png b/tests/visualize/baseline_images/aggregate_cdf_test/median_with_iqr_hist.png new file mode 100644 index 0000000000000000000000000000000000000000..2f78aa55a68fa0a8ae6c204a42920874d8feee9f GIT binary patch literal 10612 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 Z#)jCv_|B&fA%#I5HMcY?{L8tk{{f|e>2Lr5 literal 0 HcmV?d00001 diff --git a/tests/visualize/baseline_images/aggregate_cdf_test/no_edges.png b/tests/visualize/baseline_images/aggregate_cdf_test/no_edges.png new file mode 100644 index 0000000000000000000000000000000000000000..56d2298245e743a610b35811494912ef5a2842cc GIT binary patch literal 12311 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 literal 0 HcmV?d00001 From 306f9c5037ff8a751d6dfef85de815f1ba01ebb4 Mon Sep 17 00:00:00 2001 From: Bryon Tjanaka Date: Wed, 22 Jul 2026 00:27:02 -0700 Subject: [PATCH 12/15] Comments --- ribs/visualize/_aggregate_cdf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ribs/visualize/_aggregate_cdf.py b/ribs/visualize/_aggregate_cdf.py index 395423c6f..a2ae5c84d 100644 --- a/ribs/visualize/_aggregate_cdf.py +++ b/ribs/visualize/_aggregate_cdf.py @@ -15,7 +15,6 @@ from ribs.visualize._utils import compute_vmin_vmax, validate_df -# TODO: Examples. def aggregate_cdf( archives: Collection[ArchiveBase], ax: Axes | None = None, @@ -158,11 +157,14 @@ def aggregate_cdf( else: raise ValueError(f"Unknown errorbar {errorbar}") + # Plot the line. patch = ax.stairs( values=agg_hist, edges=bin_edges, baseline=0 if show_edges else None, ) + + # Plot errorbar with same color as the line, but transparent. if errorbar is not None: ax.stairs( values=err_high, From e160971d67894bd01cf16ca75b4b844dc41e3233 Mon Sep 17 00:00:00 2001 From: Bryon Tjanaka Date: Wed, 22 Jul 2026 00:50:44 -0700 Subject: [PATCH 13/15] Add example and return patches --- ribs/visualize/_aggregate_cdf.py | 71 ++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/ribs/visualize/_aggregate_cdf.py b/ribs/visualize/_aggregate_cdf.py index a2ae5c84d..fbd429893 100644 --- a/ribs/visualize/_aggregate_cdf.py +++ b/ribs/visualize/_aggregate_cdf.py @@ -9,6 +9,7 @@ import numpy as np import scipy.stats from matplotlib.axes import Axes +from matplotlib.patches import StepPatch from pandas import DataFrame from ribs.archives import ArchiveBase, ArchiveDataFrame @@ -26,7 +27,7 @@ def aggregate_cdf( estimator: Literal["mean", "median"] = "mean", errorbar: None | Literal["se", "sd", "iqr"] = "sd", show_edges: bool = True, -) -> None: +) -> tuple[StepPatch, StepPatch]: """Plots a CDF/CCDF aggregated over multiple archives. Generally, the `CDF (cumulative distribution function) @@ -53,6 +54,60 @@ def aggregate_cdf( formalized in `Vassiliades 2018 `_. + Examples: + .. plot:: + :context: close-figs + + import numpy as np + import matplotlib.pyplot as plt + from ribs.archives import GridArchive + from ribs.visualize import aggregate_cdf + + # Populate 5 archives with slightly offset versions of the negative sphere + # function. + archives = [] + for i in range(5): + archive = GridArchive( + solution_dim=2, ranges=[(-1, 1), (-1, 1)], dims=[100, 100] + ) + xxs, yys = np.meshgrid(np.linspace(-1, 1, 100), np.linspace(-1, 1, 100)) + xxs, yys = xxs.ravel(), yys.ravel() + coords = np.stack((xxs, yys), axis=1) + archive.add( + solution=coords, + objective=-(xxs**2 + yys**2) + i, # Negative sphere, offset by i. + measures=coords, + ) + archives.append(archive) + + plt.figure(figsize=(8, 6)) + line, _ = aggregate_cdf(archives, cumulative=True, estimator="mean", errorbar="se") + line.set_label("CDF using Mean and SEM") + line, _ = aggregate_cdf(archives, cumulative=True, estimator="median", errorbar="iqr") + line.set_label("CDF using Median and IQR") + plt.title("CDF") + plt.xlabel("Objective Value") + plt.ylabel("Num. Elites") + plt.legend() + + plt.figure(figsize=(8, 6)) + aggregate_cdf(archives, cumulative=-1, estimator="median", errorbar="iqr", vmin=-3, vmax=5) + plt.title("CCDF with Median and IQR, and Using Custom Bounds (vmin/vmax)") + plt.xlabel("Objective Value") + plt.ylabel("Num. Elites") + + plt.figure(figsize=(8, 6)) + aggregate_cdf(archives, cumulative=False) + plt.title("Histogram") + plt.xlabel("Objective Value") + plt.ylabel("Num. Elites") + + plt.figure(figsize=(8, 6)) + aggregate_cdf(archives[:1], cumulative=False) + plt.title("Histogram of Just One Archive") + plt.xlabel("Objective Value") + plt.ylabel("Num. Elites") + Args: archives: Archives to aggregate for the CDF/CCDF. ax: Axes on which to plot the CDF/CCDF. If ``None``, the current axis will be @@ -85,6 +140,10 @@ def aggregate_cdf( show_edges: Whether to show the left and right edges of the CDF/CCDF or histogram (these show up as vertical lines). + Returns: + Tuple of two Matplotlib patches. The first patch is for the line, while the + second is for the errorbar. + Raises: AttributeError: The data() method is not implemented on one of the archives. ValueError: Number of dfs passed in is not the same as the number of archives. @@ -158,7 +217,7 @@ def aggregate_cdf( raise ValueError(f"Unknown errorbar {errorbar}") # Plot the line. - patch = ax.stairs( + line_patch = ax.stairs( values=agg_hist, edges=bin_edges, baseline=0 if show_edges else None, @@ -166,11 +225,15 @@ def aggregate_cdf( # Plot errorbar with same color as the line, but transparent. if errorbar is not None: - ax.stairs( + errorbar_patch = ax.stairs( values=err_high, edges=bin_edges, baseline=err_low, fill=True, alpha=0.2, - color=patch.get_edgecolor(), + color=line_patch.get_edgecolor(), ) + else: + errorbar_patch = None + + return line_patch, errorbar_patch From 6ecdff56a63bbba158bcc563882411e9e0731434 Mon Sep 17 00:00:00 2001 From: Bryon Tjanaka Date: Wed, 22 Jul 2026 00:54:17 -0700 Subject: [PATCH 14/15] Test patches --- tests/visualize/aggregate_cdf_test.py | 19 ++++++++++++++++++ .../aggregate_cdf_test/cdf_with_labels.png | Bin 0 -> 13355 bytes 2 files changed, 19 insertions(+) create mode 100644 tests/visualize/baseline_images/aggregate_cdf_test/cdf_with_labels.png diff --git a/tests/visualize/aggregate_cdf_test.py b/tests/visualize/aggregate_cdf_test.py index 1a9e0303c..f04a5018b 100644 --- a/tests/visualize/aggregate_cdf_test.py +++ b/tests/visualize/aggregate_cdf_test.py @@ -141,6 +141,25 @@ def test_basic_cdf_with_dfs(three_archives): ) +@image_comparison( + baseline_images=["cdf_with_labels"], + remove_text=False, + extensions=["png"], + style="mpl20", +) +def test_cdf_with_labels(three_archives): + """Take advantage of the patches returned from the function.""" + plt.figure(figsize=(8, 6)) + line, errorbar = aggregate_cdf( + three_archives, + bins=3, + cumulative=True, + ) + line.set_label("Mean") + errorbar.set_label("Error Bar") + plt.legend() + + def test_wrong_num_dfs(three_archives): plt.figure(figsize=(8, 6)) with pytest.raises( diff --git a/tests/visualize/baseline_images/aggregate_cdf_test/cdf_with_labels.png b/tests/visualize/baseline_images/aggregate_cdf_test/cdf_with_labels.png new file mode 100644 index 0000000000000000000000000000000000000000..c55c44164210d5bc429b2dfb8501cbce7d92b726 GIT binary patch literal 13355 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 rt}~28QYdvLc;x>J|DTWXMWOObK90D%^ Date: Wed, 22 Jul 2026 00:57:45 -0700 Subject: [PATCH 15/15] Tweak example --- ribs/visualize/_aggregate_cdf.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ribs/visualize/_aggregate_cdf.py b/ribs/visualize/_aggregate_cdf.py index fbd429893..3f08e5296 100644 --- a/ribs/visualize/_aggregate_cdf.py +++ b/ribs/visualize/_aggregate_cdf.py @@ -75,14 +75,14 @@ def aggregate_cdf( coords = np.stack((xxs, yys), axis=1) archive.add( solution=coords, - objective=-(xxs**2 + yys**2) + i, # Negative sphere, offset by i. + objective=-(xxs**2 + yys**2) + 0.2 * i, # Negative sphere, with offset. measures=coords, ) archives.append(archive) plt.figure(figsize=(8, 6)) - line, _ = aggregate_cdf(archives, cumulative=True, estimator="mean", errorbar="se") - line.set_label("CDF using Mean and SEM") + line, _ = aggregate_cdf(archives, cumulative=True) + line.set_label("CDF using Mean and Std") line, _ = aggregate_cdf(archives, cumulative=True, estimator="median", errorbar="iqr") line.set_label("CDF using Median and IQR") plt.title("CDF") @@ -91,7 +91,7 @@ def aggregate_cdf( plt.legend() plt.figure(figsize=(8, 6)) - aggregate_cdf(archives, cumulative=-1, estimator="median", errorbar="iqr", vmin=-3, vmax=5) + aggregate_cdf(archives, cumulative=-1, estimator="median", errorbar="iqr", vmin=-3, vmax=3) plt.title("CCDF with Median and IQR, and Using Custom Bounds (vmin/vmax)") plt.xlabel("Objective Value") plt.ylabel("Num. Elites")