You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Faster moving-window ops (win/mavg/msum/mdev/mdowndev/mmin/mmax). The rolling engine now realises the column once into a primitive double[] + a present-mask and computes each trailing window with primitive arithmetic — no per-window vector allocation or boxing (the previous hot path boxed every value in every window). Results are identical (same nil/NaN/±Inf and :min-periods/:ddof semantics). On a real 2.1M-row × 45k-firm transform (40 moving averages) the window computation dropped ~3× (21 s → 7 s) and the full :set :by pass ~2.1× (27 s → 13 s).
BREAKING: :set + :by window mode now preserves input row order. Previously a window-mode :set with :by returned rows reordered into grouped + :within-order-sorted blocks (an artifact of the per-group split + concat). Now the output keeps the original input row order — passthrough columns are untouched and each derivation's per-group results are scattered back to their original positions. :within-order consequently governs only the per-group computation order (so win/lag/win/cumsum/etc. walk in the right order within a group), not the output order; use :order-by to sort output. Both the fast and general :by paths agree. (Window-mode :setwithout:by is unchanged — it still sorts by :within-order.)
Added
prepare-grouping + the :grouping option — amortise grouping across multi-pass :set :by.(prepare-grouping ds [:gvkey] [(asc :datadate)]) computes the grouping + within-order permutation once; pass the result as :grouping to dt (in place of :by/:within-order) and many :set passes reuse it, skipping the per-call grouping + sort. Valid for any dataset with the same rows in the same order (adding columns between passes is fine, since :set :by preserves input row order); dt checks the row count and rejects :grouping alongside :by/:within-order. On a real 2.1M-row × 45k-firm 10-pass transform the grouping was the per-pass bottleneck once the window compute was sped up — reusing it dropped the run from 48.6 s to 18.4 s (~2.6×), identical results. Like coalesce, but skips NaN and ±Inf as well as nil, returning the first finite value (#dt/e (coalesce-finite :a :b 0.0)). This is the right primitive for fallback chains over computed columns, where an arithmetic step can yield NaN/±Inf (e.g. (+ x nil) → NaN) that plain coalesce would treat as present — previously expressed verbosely as (coalesce (nonfin2na :a) (nonfin2na :b) …). Non-numeric and non-finite arguments count as absent; if no argument is finite the result is nil. A #dt/e special form like coalesce, so it's also element-wise (works in off-heap :set :by).
win/lag / win/lead:fill option. A trailing options map fills the boundary positions (those without enough history/future) with a given value: #dt/e (win/lag :price 1 {:fill 0}) → [0 …] instead of [nil …]. Collapses the common (coalesce (win/lag …) 0) two-step into one op. Source nils that get shifted stay nil; default (no map) is unchanged.
win/ema accepts an options map. Alongside the numeric shorthand (>= 1 → period, < 1 → smoothing factor alpha), win/ema now takes {:alpha 0.18} or {:period 10} — a self-documenting alternative that makes the alpha-vs-period intent explicit at the call site. Same results as the equivalent numeric form.