Visualization

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.

import pyhf
import json
import numpy as np
import pandas
import altair as alt

Preparing the data

Altair reads the data as a pandas dataframe, so we create one.

with open("data/2-bin_1-channel.json") as serialized:
  spec = json.load(serialized)

workspace = pyhf.Workspace(spec)
model = workspace.model(poi_name="mu")
pars = model.config.suggested_init()
data = workspace.data(model)
muscan = np.linspace(0, 5, 31)
results = [
    pyhf.infer.hypotest(mu, data, model, return_expected_set=True) for mu in muscan
]
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()
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.879600 0.690956 0.786411 0.879601 0.953149 0.990265
2 0.333333 0.761583 0.467167 0.606464 0.761583 0.899718 0.977348
3 0.500000 0.648555 0.308972 0.458403 0.648555 0.840153 0.960587
4 0.666667 0.542811 0.199828 0.339450 0.542811 0.775260 0.939323

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

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