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