Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pypesto/visualize/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
projection_scatter_umap,
projection_scatter_umap_original,
)
from .ensemble import ensemble_identifiability
from .ensemble import ensemble_identifiability, ensemble_parameters_plot
from .misc import process_offset_y, process_result_list, process_y_limits
from .observable_mapping import (
plot_linear_observable_mappings_from_pypesto_result,
Expand Down
116 changes: 116 additions & 0 deletions pypesto/visualize/ensemble.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from __future__ import annotations

import matplotlib.axes
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from matplotlib import colormaps
from matplotlib.collections import PatchCollection
from matplotlib.patches import Rectangle

Expand Down Expand Up @@ -408,3 +412,115 @@ def _create_patches(
)

return patches_both_hit, patches_lb_hit, patches_ub_hit, patches_none_hit


def ensemble_parameters_plot(
ensemble: Ensemble,
ax: plt.Axes | None = None,
parameter_ids: list[int] | None = None,
size: tuple[float] | None = (6, 12),
cmap=colormaps["Greys"],
):
"""
Visualize a parameter ensemble.

For each parameter, this shows the full range of values taken across
all vectors contained in ``ensemble``: a rectangle spanning the
minimum to the maximum value, one scatter point per vector, and
dashed vertical lines marking the parameter bounds. Vectors are
colored by their position in ``ensemble.x_vectors``, under the
assumption that they are ordered from best to worst (as is the case,
e.g., if ``ensemble`` was created via
:meth:`Ensemble.from_optimization_endpoints
<pypesto.ensemble.Ensemble.from_optimization_endpoints>`, which
orders vectors by ascending objective value): the first (best)
vector is highlighted in red, all other vectors are shaded using
`cmap`.

As this always visualizes every vector currently contained in
``ensemble``, the resulting range does not by itself represent a
confidence interval or credible region. To visualize such an
interval, construct ``ensemble`` such that it only contains the
corresponding vectors (e.g. via the ``rel_cutoff``/``percentile``
arguments of
:meth:`Ensemble.from_optimization_endpoints
<pypesto.ensemble.Ensemble.from_optimization_endpoints>`, or the
``ci_level`` argument of
:meth:`Ensemble.from_sample <pypesto.ensemble.Ensemble.from_sample>`).

Parameters
----------
ensemble:
Ensemble of parameter vectors (from pypesto.ensemble).
ax:
Axes object to use.
parameter_ids:
Indices of the parameters to plot. Defaults to all parameters in
`ensemble`.
size:
Figure size (width, height) in inches. Is only applied when no ax
object is specified.
cmap:
Colormap used to color all vectors except the first one (which
is always shown in red).

Returns
-------
ax: matplotlib.Axes
The plot axes.
"""

if ax is None:
_, ax = plt.subplots(figsize=size, layout="constrained")

if parameter_ids:
x_vectors = ensemble.x_vectors[parameter_ids]
n_x = len(parameter_ids)
else:
parameter_ids = np.arange(ensemble.n_x)
x_vectors = ensemble.x_vectors
n_x = ensemble.n_x

y_rect = -0.4
h_rect = 0.8 # rectangle height
rectangles = []
colors = np.flip(
cmap(np.linspace(0.3, 0.8, (ensemble.n_vectors - 1))), axis=0
)
colors = np.insert(colors, 0, [1.0, 0.0, 0.0, 1.0], axis=0)

for par_values in x_vectors:
w_rect = np.max(par_values) - np.min(par_values) # rectangle width
rectangles.append(
Rectangle((np.min(par_values), y_rect), w_rect, h_rect)
)
y_rect += h_rect + 0.2

ax.add_collection(
PatchCollection(
rectangles, facecolors=[1.0, 1.0, 1.0, 1.0], edgecolors="dimgrey"
)
)

for i, v in enumerate(x_vectors):
ax.scatter(
x=v, y=[i] * ensemble.n_vectors, s=40, color=colors, alpha=0.6
)
# plot the best parameter values
ax.scatter(
x_vectors[:, 0], np.arange(n_x), s=40, color=[1.0, 0.0, 0.0, 1.0]
)

ax.plot(
ensemble.lower_bound[parameter_ids], np.arange(n_x), "--", color="grey"
)
ax.plot(
ensemble.upper_bound[parameter_ids], np.arange(n_x), "--", color="grey"
)
ax.set_xlim(
np.min(ensemble.lower_bound) * 1.1, np.max(ensemble.upper_bound) * 1.1
)
ax.set_yticks(np.arange(n_x))
ax.set_yticklabels(np.asarray(ensemble.x_names)[parameter_ids])
ax.set_xlabel("Parameter value")
return ax
19 changes: 19 additions & 0 deletions test/visualize/test_visualize.py
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,25 @@ def test_ensemble_identifiability():
visualize.ensemble_identifiability(my_ensemble)


@close_fig
def test_ensemble_parameters_plot():
# creates a test problem
problem = create_problem(n_parameters=100)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

such a big one necessary for the test? 🙈


my_ensemble = [
(1 + np.cos(ix) ** 2) * np.random.rand(500) - 1.0 + np.sin(ix)
for ix in range(100)
]
my_ensemble = ensemble.Ensemble(
np.array(my_ensemble), lower_bound=problem.lb, upper_bound=problem.ub
)

visualize.ensemble_parameters_plot(my_ensemble)
visualize.ensemble_parameters_plot(
my_ensemble, parameter_ids=[0, 5, 8, 13, 17, 33, 45, 76, 82, 88, 90]
)


@close_fig
def test_profiles():
# create the necessary results
Expand Down
Loading