Utilities for SBND CCnue cross-section analysis.
This package is designed for notebook and script workflows where CAF-derived dataframes
have already been produced (typically via cafpyana) and you want to run selection,
plotting, and uncertainty studies.
- Selection helpers for signal and sideband studies.
- Signal-category definitions for MC truth labeling.
- Histogram utilities with overflow handling.
- Plotting helpers for stacked MC, data overlays, and data/MC ratios.
- Systematic uncertainty tools (covariance, correlation, universe handling, detector-variation helpers).
- I/O helpers for split HDF5 dataframe files (output of cafpyana).
analysis.py: The single file to edit for a new analysis. Signal/background category dicts, physics constants, flux values, cut sequences, truth-labeling functions, and analysis variable definitions.classes.py: Core dataclasses —CutSpec,VariableConfig,SystematicsInput,SystematicsOutput,PlottingConfig.config.py: Global paths and environment setup (cafpyana path, flux file, detvar files).preprocess.py: DataFrame preprocessing — column fixes, pi0 kinematics, secondary shower energy corrections. Contains hard-coded CAF column names specific to Gen1 nuecc; update for a different analyzer.selection.py: Event selection pipeline (select,select_sideband) and cut helpers (drop_cuts,modify_cut).plotting.py: Stacked MC, data overlay, data/MC ratio, and systematics breakdown plots.funcs.py: High-level systematics driver — total covariance calculation and custom uncertainty helpers.syst.py: Low-level systematics — universe histograms, covariance matrices, detector variations.utils.py: DataFrame helpers for MultiIndex sorting, header merging, event masking, and histograms.io.py: HDF5 dataframe loading — split-file primitives (load_dfs) and high-level loaders (load_mc,load_data).detvar/: Detector variation (DetVar) subpackage. Seedetvar/README.md.
There are four things to update when adapting this package to a different signal or
selection: the file paths in config.py, the signal categories, the cut sequences,
and the analysis variables — the latter three all in analysis.py.
Set the paths for your environment:
# In config.py
CAFPYANA_PATH = "/path/to/your/cafpyana"
FLUX_FILE = "/path/to/your/flux.root"
INTIME_FILE = "/path/to/your/mc_intime.df"
DETVAR_DICT_DIR = "/path/to/your/detvars/"
DETVAR_DICT_SIGNAL = DETVAR_DICT_DIR + "detvars_signal.h5"
DETVAR_DICT_CONTROL = DETVAR_DICT_DIR + "detvars_sideband.h5"Note:
FLUX_FILEis read at import time byanalysis.py, so it must point to a valid file beforeimport nueanais called. The detvar and intime paths are only used when running systematics and can be left as placeholders until then.
signal_categories drives the integer labels written by define_signal() and the
colors/labels used by plot_mc_data(). Each entry needs a "value" (integer ID),
"label" (legend text), and "color". By convention, the signal topology has ID 0.
# In analysis.py — replace or extend signal_categories with your own channels
signal_categories = {
"mySignal": {"value": 0, "label": r"My Signal", "color": "steelblue"},
"background1":{"value": 1, "label": "Background 1","color": "tomato"},
"nonFV": {"value": 10, "label": "Non-FV", "color": "gray"},
"dirt": {"value": 11, "label": "Dirt", "color": "peru"},
"cosmic": {"value": 12, "label": "Cosmic", "color": "orchid"},
"offbeam": {"value": 13, "label": "Off-beam", "color": "silver"},
}
signal_dict = {k: v["value"] for k, v in signal_categories.items()}Then update define_signal() in analysis.py to assign your integer IDs.
DEFAULT_CUTS and SIDEBAND_CUTS are defined in analysis.py. Pass cut overrides
directly to select() without touching the source, or modify the sequences in place:
import nueana as nue
from nueana.selection import DEFAULT_CUTS, modify_cut, drop_cuts, CutSpec
# Override individual cut thresholds
cuts = nue.modify_cut(nue.DEFAULT_CUTS, "dedx", min=1.5, max=3.0)
df_sel = nue.select(df, cuts=cuts)
# Drop a cut that doesn't apply to your topology
cuts = nue.drop_cuts(nue.DEFAULT_CUTS, "muon_rejection")
# Add a custom cut on top of the standard pipeline
my_cut = nue.CutSpec("my_cut", fn=lambda df: df.x > 10)
cuts = nue.DEFAULT_CUTS + [my_cut]select() can return all intermediate stages for cut-flow studies:
stages = nue.select(df, savedict=True)
# Or stop at a specific stage
df_preflash = nue.select(df, stage="flash_pe")preprocess_mc and preprocess_data apply fixes that may be analysis specific, or
MC/data version specific. For a different analyzer, update the individual fix functions
(fix_flash_pe_scale, fix_flash_time, fix_prim_shw_energy, fix_sec_shw_energy,
add_phi) with your variables, or replace the functions entirely.
Add a factory function returning a VariableConfig for each variable you want to plot or unfold:
from nueana.classes import VariableConfig
import numpy as np
def my_variable() -> VariableConfig:
return VariableConfig(
var_save_name = "my_var",
var_plot_name = r"$p_T$",
var_unit = "GeV",
bins = np.array([0.0, 0.2, 0.5, 1.0, 2.0]),
bin_labels = np.array([0.0, 0.2, 0.5, 1.0, 2.0]),
var_evt_reco_col = ("primshw", "shw", "my_reco_col", "", "", ""),
var_evt_truth_col = ("slc", "truth", "e", "my_truth_col"),
var_nu_col = ("e", "my_truth_col"),
)import numpy as np
import nueana as nue
# load_mc: preprocess → select (cuts, optional) → merge_hdr → define_signal
# load_data: preprocess → merge_hdr → select (cuts, optional) → stamp offbeam signal label
mc_df, mc_pot, mc_ngen = nue.load_mc ("/path/to/mc.df", keys=["nuecc","hdr"], cuts=nue.DEFAULT_CUTS)
data_df, data_pot, ngates = nue.load_data("/path/to/data.df", keys=["nuecc","hdr"], onbeam=True, cuts=nue.DEFAULT_CUTS)
# Make a stacked MC + data plot
cfg = nue.PlottingConfig(scale=data_pot/mc_pot, ylabel=f"Events [{data_pot:.2e} POT]")
output = nue.plot_mc_data(
mc_df=mc_df, data_df=data_df,
var=nue.electron_energy().var_evt_reco_col,
bins=nue.electron_energy().bins,
xlabel="Reco shower energy [GeV]",
config=cfg,
)See examples/signal_plots.ipynb for a complete worked example of signal-region plots, and examples/sideband_plots.ipynb for the sideband equivalent.
analysis.pyreads the flux ROOT file at import time. If the file is unavailable, importingnueanawill fail.- Several utilities assume pandas MultiIndex columns with CAF-style naming.
- Many routines expect a
signalcolumn to already be present — calldefine_signal()ordefine_generic()before plotting or applying event masks. - Overflow is enabled by default (
overflow=True) and folds out-of-range values into the first/last bin.