cs_rank

Defined in fynance.features.cross_section

cs_rank(X, pct=True)[source]

Per-bar cross-sectional rank, NaN-aware.

At each bar t, the n_valid non-NaN assets are ranked from smallest (rank 1) to largest (rank n_valid); ties get the average of the ranks they span (e.g. two tied values at ranks 2 and 3 both get 2.5). Missing assets are excluded from the ranking and stay NaN.

Parameters:
Xarray_like

Panel, shape (T, N).

pctbool, optional

If True (default), rescale ranks to [0, 1] per bar. If False, return the raw average rank in [1, n_valid].

Returns:
np.ndarray

(T, N) array of ranks, NaN where X is NaN.

See also

cs_zscore, cs_winsorize

Notes

The tie-averaged rank is computed without SciPy: values are sorted with numpy.argsort, then each run of equal values in sorted order is assigned the mean of the 1-based ranks it occupies.

With pct=True the rank is rescaled to [0, 1]:

\[cs\_rank^{pct}_t(i) = \frac{rank_t(i) - 1}{n\_valid_t - 1}\]

so the smallest valid value maps to 0, the largest to 1. When n_valid_t == 1 there is no spread to normalize against, so the single valid entry maps to 0.5 by convention.

Examples

>>> import numpy as np
>>> X = np.array([[3., 1., 2.]])
>>> cs_rank(X, pct=True)
array([[1. , 0. , 0.5]])
>>> cs_rank(X, pct=False)
array([[3., 1., 2.]])

Ties get the average rank, e.g. the two tied 1’s here share ranks 1 and 2:

>>> cs_rank(np.array([[1., 1., 2.]]), pct=False)
array([[1.5, 1.5, 3. ]])

A single valid asset in the bar maps to 0.5 under pct=True:

>>> cs_rank(np.array([[5., np.nan, np.nan]]))
array([[0.5, nan, nan]])