--- name: awkward description: >- Use when working with jagged or variable-length arrays in Python HEP analysis: building event records with ak.zip, filtering nested arrays, computing combinatorics (cartesian products or combinations), flattening ragged arrays, using argmin/argmax with keepdims, broadcasting per-event weights to per-object, or debugging OptionType/None-padding issues in Awkward 2.x workflows. --- # Awkward Array ## Overview Awkward Array provides NumPy-like idioms for variable-length, nested, and record-structured data — the natural shape of HEP event data. Version 2.x (current) has a substantially different API from 1.x; do not mix patterns. ## When to Use - Reading TTrees via uproot (returns `ak.Array` automatically) - Building physics-object records and applying per-object selection - Computing pair quantities (invariant mass, deltaR) via combinatorics - Broadcasting per-event scalars to per-object arrays for weighted fills - Preparing arrays for histogram filling or NumPy interop ## Key Concepts | Concept | Notes | | -------------- | ------------------------------------------------------------ | | `ak.type(arr)` | Inspect type — e.g., `var * {pt: float64}` | | `OptionType` | `None` values from `pad_none` or `firsts` on empty events | | Behaviors | `vector.register_awkward()` adds 4-vector methods to records | | `ak.num(arr)` | Per-event count (number of jets per event, etc.) | ## Canonical Patterns **Build a record from columns**: ```python import awkward as ak jets = ak.zip({"pt": events["jet_pt"], "eta": events["jet_eta"], "phi": events["jet_phi"], "mass": events["jet_m"]}) ``` **Filter objects, then events**: ```python good_jets = jets[jets.pt > 25_000] # per-object mask, MeV events_ok = good_jets[ak.num(good_jets) >= 2] # per-event mask ``` **All unique pairs per event**: ```python combos = ak.combinations(jets, 2, axis=1) j1, j2 = ak.unzip(combos) ``` **Leading object per event (keepdims is required for slicing)**: ```python lead_idx = ak.argmax(jets.pt, axis=1, keepdims=True) lead = ak.firsts(jets[lead_idx]) # None for empty events ``` **Add a derived field**: ```python jets = ak.with_field(jets, jets.pt / 1000, "pt_gev") ``` **Broadcast per-event weight to per-object for weighted fills**: ```python flat_w = ak.flatten(ak.broadcast_arrays(events.weight, jets.pt)[0]) flat_pt = ak.flatten(jets.pt) h.fill(pt=ak.to_numpy(flat_pt), weight=ak.to_numpy(flat_w)) ``` ## Gotchas - **`firsts` vs `[:, 0]`**: `firsts` returns `None` for empty events; `[:, 0]` raises. Use `firsts` whenever collections may be empty. - **`flatten` depth**: Default `axis=1` flattens one level. `axis=None` flattens everything — rarely correct. - **`option` propagation**: Once `OptionType` enters, operations propagate `None`. Call `ak.drop_none` or `ak.fill_none` before NumPy interop. - **`to_numpy` on ragged**: Fails unless regular. Always flatten or select a fixed depth first. - **Behavior registration is global**: Call `vector.register_awkward()` once at module level; it mutates the global dict. - **2.x breaking changes from 1.x**: Many top-level functions were renamed or removed; behavior registration changed. Do not copy 1.x examples verbatim. ## Interop - **uproot**: `tree.arrays()` returns `ak.Array` — no conversion needed - **vector**: `register_awkward()` gives records with `pt/phi/eta/mass` Lorentz methods - **hist**: Flatten arrays before `Hist.fill()`; broadcast weights first - **coffea**: NanoEvents columns are `ak.Array`; all coffea processors consume awkward natively ## Docs https://awkward-array.org/doc/main/