Skip to content

Commit 7e8f630

Browse files
Sensitivity: Minor plotting fixes (#819)
## Summary Minor improvements in the sensitivity posterior-marginal plots ## Detailed description - **Prior reference instead of the mean:** the continuous panels drop the posterior-mean line and instead overlay the uniform prior as a flat reference plus a shaded 5–95% band. - **Shared y-axis across a vector variation's components:** share a y-axis so their densities are directly comparable; --------- Signed-off-by: Clemens Volk <cvolk@nvidia.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
1 parent edc98f3 commit 7e8f630

1 file changed

Lines changed: 35 additions & 9 deletions

File tree

isaaclab_arena/analysis/sensitivity/plotting.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import math
99
import matplotlib.pyplot as plt
1010
import numpy as np
11+
import re
1112
from pathlib import Path
1213
from scipy.stats import gaussian_kde
1314
from typing import TYPE_CHECKING
@@ -19,7 +20,7 @@
1920

2021
_CONTINUOUS_COLOR = "steelblue"
2122
_CATEGORICAL_COLOR = "steelblue"
22-
_MEAN_COLOR = "firebrick"
23+
_PRIOR_COLOR = "grey"
2324

2425

2526
def plot_marginals(
@@ -32,7 +33,8 @@ def plot_marginals(
3233
3334
A pure renderer: it draws already-sampled posterior draws and does not run inference.
3435
One panel per factor — a density curve for continuous factors, a probability bar chart
35-
for categorical ones, wrapped into a grid.
36+
for categorical ones, wrapped into a grid. Panels for components of the same vector
37+
variation share a y-axis, so their densities compare directly.
3638
3739
Args:
3840
samples: ``(num_samples, num_factors)`` posterior draws in the dataset's factor
@@ -52,17 +54,30 @@ def plot_marginals(
5254
num_rows = math.ceil(len(factors) / num_columns)
5355
figure, axes = plt.subplots(num_rows, num_columns, figsize=(6.0 * num_columns, 4.5 * num_rows), squeeze=False)
5456
flat_axes = axes.flatten()
57+
continuous_axes_by_variation: dict[str, list] = {}
5558
for axis_index, factor in enumerate(factors):
5659
ax = flat_axes[axis_index]
5760
factor_samples = samples[:, dataset.factor_columns[factor.name]].squeeze(-1)
5861
if factor.type == "continuous":
5962
_draw_continuous_marginal(ax, factor, factor_samples)
63+
# Components of one vector variation (name[0], name[1], ...) share a scale.
64+
variation_name = re.sub(r"\[\d+\]$", "", factor.name)
65+
continuous_axes_by_variation.setdefault(variation_name, []).append(ax)
6066
else:
6167
_draw_categorical_marginal(ax, factor, factor_samples)
6268
ax.set_title(factor.name, fontsize=11)
6369
for unused_index in range(len(factors), len(flat_axes)):
6470
flat_axes[unused_index].axis("off")
6571

72+
# Give the components of a vector variation a common y-axis so their densities compare directly.
73+
# A standalone scalar factor keeps its own scale, since unrelated factors can differ in magnitude.
74+
for grouped_axes in continuous_axes_by_variation.values():
75+
if len(grouped_axes) < 2:
76+
continue
77+
shared_top = max(grouped_ax.get_ylim()[1] for grouped_ax in grouped_axes)
78+
for grouped_ax in grouped_axes:
79+
grouped_ax.set_ylim(0, shared_top)
80+
6681
observation_label = ", ".join(
6782
f"{name}={value:g}" for name, value in zip(dataset.outcome_names, observation.tolist())
6883
)
@@ -80,21 +95,32 @@ def plot_marginals(
8095

8196

8297
def _draw_continuous_marginal(ax, factor: FactorSpec, factor_samples: np.ndarray) -> None:
83-
"""Smooth posterior density (filled KDE curve) of a continuous factor, with a mean line.
98+
"""Posterior density of a continuous factor over its swept range.
8499
85-
A KDE line over the posterior samples reads the shape of a continuous posterior better
86-
than a binned histogram. Falls back to a single line at the mean when the samples have
87-
no spread (KDE bandwidth is then undefined).
100+
Draws the KDE of the posterior samples, the uniform prior as a flat reference, and shades
101+
the central 5-95% of the posterior. Reading the posterior against the prior shows whether
102+
conditioning on the outcome concentrated the factor, which a mean alone would miss for a
103+
factor swept symmetrically around its nominal value.
88104
"""
89105
range_low, range_high = factor.range
90-
sample_mean = float(np.mean(factor_samples))
106+
span = range_high - range_low
107+
91108
if float(np.std(factor_samples)) >= 1e-9:
92109
grid = np.linspace(range_low, range_high, 200)
93110
density = gaussian_kde(factor_samples)(grid)
94-
ax.plot(grid, density, color=_CONTINUOUS_COLOR, linewidth=2)
111+
ax.plot(grid, density, color=_CONTINUOUS_COLOR, linewidth=2, label="posterior")
95112
ax.fill_between(grid, 0, density, color=_CONTINUOUS_COLOR, alpha=0.2)
96113
ax.set_ylim(bottom=0)
97-
ax.axvline(sample_mean, color=_MEAN_COLOR, linestyle="--", linewidth=2, label=f"mean = {sample_mean:.3g}")
114+
low_percentile, high_percentile = np.percentile(factor_samples, [5, 95])
115+
ax.axvspan(low_percentile, high_percentile, color=_CONTINUOUS_COLOR, alpha=0.15, label="5-95%")
116+
else:
117+
ax.axvline(float(np.mean(factor_samples)), color=_CONTINUOUS_COLOR, linewidth=2, label="constant")
118+
ax.set_ylim(bottom=0)
119+
120+
if span > 0:
121+
# The uniform prior is the "no effect" reference the posterior is read against.
122+
ax.axhline(1.0 / span, color=_PRIOR_COLOR, linestyle="--", linewidth=1.5, label="prior (uniform)")
123+
98124
ax.set_xlim(range_low, range_high)
99125
ax.set_xlabel(factor.name)
100126
ax.set_ylabel("posterior density")

0 commit comments

Comments
 (0)