Skip to content

runs

runs ¤

Vectorized run-length helpers.

Many detectors collapse consecutive equal values -- a boolean condition mask or a state / identifier column -- into runs and then compute something per run. The pandas idiom df.groupby((s != s.shift()).cumsum()) followed by a Python loop over the groups costs tens of microseconds per run; these helpers find the run boundaries with one comparison and aggregate per run with ufunc.reduceat.

:func:run_bounds returns every run of equal consecutive values, :func:true_runs only the runs where a boolean mask is True, and :func:run_agg aggregates a value array per run.

Run boundaries follow the pandas idiom (s != s.shift()).cumsum() exactly: a missing value (NaN, None, NaT, pd.NA) never equals anything, so by default each missing value is a run of its own. Pass nan_equal=True to merge consecutive missing values into one run instead. (For nullable extension dtypes the idiom compares to <NA> next to a missing value and groupby then drops those rows; :func:run_bounds keeps them as runs of their own.)

Runs ¤

Bases: NamedTuple

Positional bounds of runs.

Attributes:

Name Type Description
starts _Ints

Position of the first element of each run.

ends _Ints

Position one past the last element of each run (exclusive, like a slice stop), so values[starts[k]:ends[k]] is run k and the last element is values[ends[k] - 1].

lengths _Ints

Number of elements per run (ends - starts, always >= 1).

last property ¤

last: _Ints

Position of the last element of each run (inclusive end).

n_runs property ¤

n_runs: int

Number of runs.

run_bounds ¤

run_bounds(values: Any, *, nan_equal: bool = False) -> Runs

Find the runs of equal consecutive values.

Parameters:

Name Type Description Default
values Any

A 1-d array, Series or Index of any dtype (numeric, boolean, datetime, string, object, categorical, nullable extension types). The order is taken as given; sort first if needed.

required
nan_equal bool

False (default) treats every missing value as unequal to everything, including another missing value -- exactly what (s != s.shift()).cumsum() does -- so each missing value is a run of length 1. True merges consecutive missing values into one run.

False

Returns:

Type Description
Runs

class:Runs with starts / ends (exclusive) / lengths as

Runs

int64 arrays, one entry per run in order. Empty input gives empty

Runs

arrays.

true_runs ¤

true_runs(mask: Any) -> Runs

Runs where a boolean mask is True.

Parameters:

Name Type Description Default
mask Any

Boolean array or Series. Missing values (nullable boolean) count as False.

required

Returns:

Type Description
Runs

class:Runs of the True stretches only, in order.

run_labels ¤

run_labels(runs: Runs) -> _Ints

Run number (0, 1, ...) per element covered by runs, in order.

For runs that tile the array (from :func:run_bounds) this is one label per element; for :func:true_runs only the True elements are covered (see :func:run_positions for their positions).

run_agg ¤

run_agg(
    values: Any, runs: Runs, funcs: str | Iterable[str]
) -> Any

Aggregate values per run.

Parameters:

Name Type Description Default
values Any

1-d array or Series aligned with the array the runs were found on (positionally).

required
runs Runs

Runs from :func:run_bounds or :func:true_runs. Runs must not overlap and must be in increasing order.

required
funcs str | Iterable[str]

One aggregation name or several, from :data:RUN_FUNCS:

  • first / last: the first / last element (any dtype).
  • size: run length; count: non-NaN elements.
  • sum / mean / min / max: NaN propagates, like numpy.
  • nansum / nanmean / nanmin / nanmax: NaN skipped, like pandas Series.sum() / mean() / min() / max(); an all-NaN run gives 0.0 (nansum) or NaN.
  • any / all: logical reductions.

Numeric aggregations work in float64. sum / mean (and their nan variants) are bit-identical to numpy's pairwise ndarray.sum() -- and so to pandas -- on each run's slice.

required

Returns:

Type Description
Any

One array (one entry per run) for a single name, else a dict of

Any

name -> array.

run_positions ¤

run_positions(runs: Runs) -> _Ints

Positions of all elements covered by runs, in order.

Aligned with :func:run_labels: element i of both belongs together.

run_argmax ¤

run_argmax(values: Any, runs: Runs) -> _Ints

Position of the first maximum of values in each run.

Like Series.idxmax() per run (NaN skipped), but returns positions. A run whose values are all NaN gives its first position.

total_seconds ¤

total_seconds(deltas: Any) -> npt.NDArray[np.float64]

Seconds per timedelta, bit-identical to scalar Timedelta.total_seconds().

The scalar method computes days * 86400 + seconds + microseconds / 1e6 (dropping sub-microsecond precision); the vectorized TimedeltaIndex.total_seconds() divides nanoseconds by 1e9, which can differ in the last bit. Use this when replacing a per-row (end - start).total_seconds() loop. NaT gives NaN.