"""Provides archive_histogram."""
from __future__ import annotations
from collections.abc import Sequence
import matplotlib.colors
import matplotlib.pyplot as plt
from matplotlib.axes import Axes
from matplotlib.typing import ColorType
from pandas import DataFrame
from ribs.archives import ArchiveBase, ArchiveDataFrame
from ribs.visualize._utils import compute_vmin_vmax, retrieve_cmap, validate_df
[docs]
def archive_histogram(
archive: ArchiveBase,
ax: Axes | None = None,
*,
df: DataFrame | ArchiveDataFrame | None = None,
bins: int | Sequence[float] | str | None = 100,
vmin: float | None = None,
vmax: float | None = None,
ylim: float | None = None,
color: ColorType = "#7e57c2",
cmap: str | Sequence[ColorType] | matplotlib.colors.Colormap | None = None,
rasterized: bool = False,
hist_kwargs: dict | None = None,
) -> None:
r"""Plots a histogram of the objective values in an archive.
In short, this function is a wrapper around Matplotlib's
:func:`~matplotlib.pyplot.hist`. It calls ``hist`` with objective values retrieved
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 CDF/CCDF or histogram, see
:func:`~ribs.visualize.aggregate_cdf`.
Examples:
Basic Histogram of a 2D GridArchive
.. plot::
:context: close-figs
import numpy as np
import matplotlib.pyplot as plt
from ribs.archives import GridArchive
from ribs.visualize import archive_histogram
# Populate the archive with the negative sphere function.
archive = GridArchive(solution_dim=2,
dims=[100, 100],
ranges=[(-1, 1), (-1, 1)])
x = np.random.uniform(-1, 1, 10000)
y = np.random.uniform(-1, 1, 10000)
archive.add(solution=np.stack((x, y), axis=1),
objective=-(x**2 + y**2),
measures=np.stack((x, y), axis=1))
# Plot a histogram of the archive.
plt.figure(figsize=(8, 6))
archive_histogram(archive)
plt.xlabel("Objective")
plt.ylabel("Num. Elites")
plt.show()
Histogram Where Bars Are Colored With a Colormap
.. plot::
:context: close-figs
import numpy as np
import matplotlib.pyplot as plt
from ribs.archives import GridArchive
from ribs.visualize import archive_histogram
# Populate the archive with the negative sphere function.
archive = GridArchive(solution_dim=2,
dims=[100, 100],
ranges=[(-1, 1), (-1, 1)])
x = np.random.uniform(-1, 1, 10000)
y = np.random.uniform(-1, 1, 10000)
archive.add(solution=np.stack((x, y), axis=1),
objective=-(x**2 + y**2),
measures=np.stack((x, y), axis=1))
# Plot a histogram of the archive.
plt.figure(figsize=(8, 6))
archive_histogram(archive, cmap="magma")
plt.xlabel("Objective")
plt.ylabel("Num. Elites")
plt.show()
Histogram with More Customizations
.. plot::
:context: close-figs
import numpy as np
import matplotlib.pyplot as plt
from ribs.archives import GridArchive
from ribs.visualize import archive_histogram
# Populate the archive with the negative sphere function.
archive = GridArchive(solution_dim=2,
dims=[100, 100],
ranges=[(-1, 1), (-1, 1)])
x = np.random.uniform(-1, 1, 10000)
y = np.random.uniform(-1, 1, 10000)
archive.add(solution=np.stack((x, y), axis=1),
objective=-(x**2 + y**2),
measures=np.stack((x, y), axis=1))
# Plot a histogram of the archive.
plt.figure(figsize=(8, 6))
archive_histogram(
archive,
bins=50, # Only use 50 bins.
vmin=-2.5, # Minimum objective value.
vmax=0.5, # Maximum objective value.
ylim=400, # Set the top of the y-axis.
)
plt.xlabel("Objective")
plt.ylabel("Num. Elites")
plt.show()
Args:
archive: An archive that can provide its objective values via the
:meth:`~ribs.archives.ArchiveBase.data` method.
ax: Axes on which to plot the histogram. If ``None``, the current axis will be
used.
df: If provided, we will plot data from this argument instead of the data
currently in the archive. 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, the data must contain a column for "objective".
bins: Bins for the histogram. 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 archive is used.
vmax: Maximum objective value to use in the plot. If ``None``, the maximum
objective value in the archive is used.
ylim: If provided, this is used to set the top ylimit (i.e., the upper bound of
the y-axis) for the histogram.
color: Color of the histogram bars. Defaults to the pyribs theme color. Refer to
this `Matplotlib page
<https://matplotlib.org/stable/users/explain/colors/colors.html>`_ for more
info on specifying colors.
cmap: Instead of passing in ``color``, a colormap can be passed in to color the
histogram bars according to their objective value. This parameter can either
be the name of a :class:`~matplotlib.colors.Colormap`, a list of Matplotlib
color specifications (e.g., an :math:`N \times 3` or :math:`N \times 4`
array -- see :class:`~matplotlib.colors.ListedColormap`), or a
:class:`~matplotlib.colors.Colormap` object. For example, "magma" is the
default used in the Pyribs heatmap visualizations. If both ``color`` and
``cmap`` are passed in, the ``cmap`` will take precedence.
rasterized: Whether to rasterize the bars of the histogram. This can be useful
for saving to a vector format like PDF. Essentially, only the bars will be
converted to a raster graphic, so that they will not have to be individually
rendered. Meanwhile, the surrounding axes, particularly text labels, will
remain in vector format.
hist_kwargs: Additional kwargs to pass to :func:`~matplotlib.pyplot.hist`. Note
that since ``hist`` calls :func:`numpy.histogram`, some of these arguments
may ultimately go to ``histogram``. Note that we already pass the following
parameters: ``bins``, ``range`` (via ``vmin`` and ``vmax``), ``color``, and
``rasterized``.
Raises:
AttributeError: The data() method is not implemented on the given archive, which
prevents retrieving its objective values.
"""
# Retrieve archive data.
if df is None:
try:
objectives = archive.data("objective")
except NotImplementedError as e:
raise AttributeError(
"To use archive_histogram, the archive must have the data() method."
) from e
else:
df = validate_df(df)
objectives = df["objective"]
vmin, vmax = compute_vmin_vmax(vmin, vmax, objectives)
# Initialize axis.
ax = plt.gca() if ax is None else ax
# Set axis grid to show up behind the histogram.
# https://stackoverflow.com/questions/1726391/matplotlib-draw-grid-lines-behind-other-graph-elements
ax.set_axisbelow(True)
# Hide spines.
for pos in ["right", "top", "left"]:
ax.spines[pos].set_visible(False)
# Set up grid lines on y-axis.
ax.grid(color="0.9", linestyle="-", axis="y")
# Set the top of the y-axis.
if ylim is not None:
ax.set_ylim(top=ylim)
# Plot the histogram.
hist_kwargs = {} if hist_kwargs is None else hist_kwargs.copy()
hist_kwargs.update(
# If these keys are already in hist_kwargs, they will simply be replaced.
{
"bins": bins,
"range": (vmin, vmax),
"color": color,
# `hist` passes this to Matplotlib's Patch as kwargs.
"rasterized": rasterized,
}
)
_bin_counts, bin_edges, patches = ax.hist(objectives, **hist_kwargs)
# Color the histogram with a cmap. Inspiration from:
# https://stackoverflow.com/questions/51347451/how-to-fill-histogram-with-color-gradient-where-a-fixed-point-represents-the-mid
if cmap is not None:
cmap = retrieve_cmap(cmap)
bin_centers = 0.5 * (bin_edges[:-1] + bin_edges[1:])
norm = plt.Normalize(vmin, vmax)
for c, p in zip(bin_centers, patches, strict=True):
p.set(facecolor=cmap(norm(c)))