How to apply corrections#
Coffea integrates naturally with correctionlib and other lookup tools so that scale factors and systematic variations flow through your processor. This guide demonstrates typical patterns.
Load a correction set once#
import correctionlib
from coffea import processor
class MuonSFProcessor(processor.ProcessorABC):
def __init__(self, payload: str):
self.cset = correctionlib.CorrectionSet.from_file(payload)
self.sf = self.cset["muon_sf"]
...
Load the JSON once in
__init__, coffea knows how to distribute it with the Processor onto a cluster.Keep references to individual
Correctionobjects you will call frequently.
Evaluate per-object scale factors#
import awkward as ak
def process(self, events):
dataset = events.metadata["dataset"]
muons = events.Muon[(events.Muon.tightId) & (events.Muon.pt > 20)]
sf = self.sf.evaluate(
muons.eta,
muons.pt,
"nominal",
)
event_weight = ak.prod(sf, axis=1)
...
Correctionlib broadcasts over Awkward arrays automatically; the output matches the shape of the inputs.
Handle systematic variations#
sf_up = self.sf.evaluate(muons.eta, muons.pt, "syst_up")
sf_down = self.sf.evaluate(muons.eta, muons.pt, "syst_down")
event_weight = ak.prod(sf, axis=1)
event_weight_up = ak.prod(sf_up, axis=1)
event_weight_down = ak.prod(sf_down, axis=1)
Store alternative weights in your output dictionary so downstream steps can build envelopes.
Combine multiple corrections#
trig = self.cset["trigger_sf"].evaluate(muons.pt, muons.eta, "nominal")
iso = self.cset["iso_sf"].evaluate(muons.pt, muons.eta, "nominal")
per_muon = sf * trig * iso
event_weight = ak.prod(per_muon, axis=1)
Multiply per-object corrections before reducing across the event dimension.
Apply event-level weights#
Not all weights depend on per-object kinematics. Use metadata for global factors.
xsec = events.metadata["cross_section"]
luminosity = 35.9
event_weight *= xsec * luminosity / events.metadata["n_events"]
Keep bookkeeping inputs (sum of generator weights, number of events) in the fileset metadata.
Report weights in the output#
return {
dataset: {
"cutflow": {
"weighted": float(ak.sum(event_weight)),
},
"systematics": {
"muon_sf_up": float(ak.sum(event_weight_up)),
"muon_sf_down": float(ak.sum(event_weight_down)),
},
}
}
Return systematic variations in your output dictionary so they can be merged across chunks.
Tips & tricks#
Persist helper arrays (like absolute eta) on the events object if multiple corrections need them; this avoids recomputing inside every evaluation.
Record the correction versions you used in the output dictionary or metadata to streamline reproducibility and cross-checks.
Use the
Weightsclass fromcoffea.analysis_toolsto manage multiple corrections and their systematics together (see the applying_corrections notebook for examples).