Performing a Combination

We’ll demonstrate how a combination works by combining everything we’ve learned so far.

Loading the Workspace

To do so, we’ll use a simple workspace to demonstrate functionality of combinations.

import json

spec = json.load(open("data/2-bin_1-channel.json"))
import pyhf

workspace = pyhf.Workspace(spec)

Combine Workspaces

Let’s just try to combine naively right now.

pyhf.Workspace.combine(workspace, workspace)
---------------------------------------------------------------------------
InvalidWorkspaceOperation                 Traceback (most recent call last)
<ipython-input-3-4480a7c227be> in <module>
----> 1 pyhf.Workspace.combine(workspace, workspace)

/opt/hostedtoolcache/Python/3.8.5/x64/lib/python3.8/site-packages/pyhf/workspace.py in combine(cls, left, right, join)
    701 
    702         new_version = _join_versions(join, left['version'], right['version'])
--> 703         new_channels = _join_channels(join, left['channels'], right['channels'])
    704         new_observations = _join_observations(
    705             join, left['observations'], right['observations']

/opt/hostedtoolcache/Python/3.8.5/x64/lib/python3.8/site-packages/pyhf/workspace.py in _join_channels(join, left_channels, right_channels)
     98         )
     99         if common_channels:
--> 100             raise exceptions.InvalidWorkspaceOperation(
    101                 f"Workspaces cannot have any channels in common with the same name: {common_channels}. You can also try a different join operation: {Workspace.valid_joins}."
    102             )

InvalidWorkspaceOperation: Workspaces cannot have any channels in common with the same name: {'singlechannel'}. You can also try a different join operation: ['none', 'outer', 'left outer', 'right outer'].

As we can see, we can’t just combine a workspace with itself if it has some channel names in common. We try very hard in pyhf to make sure a combination “makes sense”.

Let’s go ahead and rename the channel (as well as the measurement). Then try to combine.

other_workspace = workspace.rename(channels={'singlechannel': 'othersinglechannel'},
                                   modifiers={'uncorr_bkguncrt': 'otheruncorr_bkguncrt'},
                                   measurements={'Measurement': 'OtherMeasurement'})

combined_workspace = pyhf.Workspace.combine(workspace, other_workspace)

And did we combine?

print(f'    channels: {combined_workspace.channels}')
print(f'       nbins: {combined_workspace.channel_nbins}')
print(f'     samples: {combined_workspace.samples}')
print(f'   modifiers: {combined_workspace.modifiers}')
print(f'  parameters: {combined_workspace.parameters}')
print(f'measurements: {combined_workspace.measurement_names}')
    channels: ['othersinglechannel', 'singlechannel']
       nbins: {'singlechannel': 2, 'othersinglechannel': 2}
     samples: ['background', 'signal']
   modifiers: [('mu', 'normfactor'), ('otheruncorr_bkguncrt', 'shapesys'), ('uncorr_bkguncrt', 'shapesys')]
  parameters: ['mu', 'otheruncorr_bkguncrt', 'uncorr_bkguncrt']
measurements: ['Measurement', 'OtherMeasurement']

Indeed. And at this point, we can just use all the same functionality we expect of pyhf, such as performing a fit:

model = workspace.model()
data = workspace.data(model)

pyhf.infer.hypotest(1.0, data, model, qtilde=True)
array(0.48563995)
other_model = other_workspace.model()
other_data = other_workspace.data(other_model)

pyhf.infer.hypotest(1.0, other_data, other_model, qtilde=True)
array(0.48563995)
combined_model = combined_workspace.model()
combined_data = combined_workspace.data(combined_model)

pyhf.infer.hypotest(1.0, combined_data, combined_model, qtilde=True)
multiple measurements defined. Taking the first measurement.
array(0.35661112)