[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]: