[1]:
import pyhf
import pandas
import numpy as np
import altair as alt
Visualization with Altair#
Altair is a python API for generating Vega visuazliation specifications. We demonstracte how to use this to build an interactive chart of pyhf results.
Preparing the data#
Altair reads the data as a pandas dataframe, so we create one.
[2]:
model = pyhf.simplemodels.uncorrelated_background([7], [20], [5])
data = [25] + model.config.auxdata
[3]:
muscan = np.linspace(0, 5, 31)
results = [
pyhf.infer.hypotest(mu, data, model, return_expected_set=True) for mu in muscan
]
[4]:
data = np.concatenate(
[
muscan.reshape(-1, 1),
np.asarray([r[0] for r in results]).reshape(-1, 1),
np.asarray([r[1] for r in results]).reshape(-1, 5),
],
axis=1,
)
df = pandas.DataFrame(data, columns=["mu", "obs"] + [f"exp_{i}" for i in range(5)])
df.head()
[4]:
mu | obs | exp_0 | exp_1 | exp_2 | exp_3 | exp_4 | |
---|---|---|---|---|---|---|---|
0 | 0.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
1 | 0.166667 | 0.885208 | 0.670809 | 0.771258 | 0.870322 | 0.949235 | 0.989385 |
2 | 0.333333 | 0.795986 | 0.438838 | 0.581516 | 0.743696 | 0.890881 | 0.975022 |
3 | 0.500000 | 0.726450 | 0.279981 | 0.428500 | 0.623443 | 0.825621 | 0.956105 |
4 | 0.666667 | 0.672216 | 0.174235 | 0.308524 | 0.512383 | 0.754629 | 0.931866 |
Defining the Chart#
We need to filled areas for the 1,2 sigma bands and two lines for the expected and observed CLs value. For interactivity we add a hovering label of the observed result
[5]:
band1 = (
alt.Chart(df)
.mark_area(opacity=0.5, color="green")
.encode(x="mu", y="exp_1", y2="exp_3")
)
band2 = (
alt.Chart(df)
.mark_area(opacity=0.5, color="yellow")
.encode(x="mu", y="exp_0", y2="exp_4")
)
line1 = alt.Chart(df).mark_line(color="black").encode(x="mu", y="obs")
line2 = (
alt.Chart(df).mark_line(color="black", strokeDash=[5, 5]).encode(x="mu", y="exp_2")
)
nearest = alt.selection_single(
nearest=True, on="mouseover", fields=["mu"], empty="none"
)
point = (
alt.Chart(df)
.mark_point(color="black")
.encode(x="mu", y="obs", opacity=alt.condition(nearest, alt.value(1), alt.value(0)))
.add_selection(nearest)
)
text = line1.mark_text(align="left", dx=5, dy=-5).encode(
text=alt.condition(nearest, "obs", alt.value(" "))
)
band2 + band1 + line1 + line2 + point + text
[5]: