cs_demean

Defined in fynance.features.cross_section

cs_demean(X, weights=None)[source]

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

Subtracts, from each valid entry of bar t, the (optionally weighted) mean of the valid entries of that bar:

\[cs\_demean_t(i) = X_t(i) - \bar{X}_t, \qquad \bar{X}_t = \frac{\sum_{j \in valid_t} w_t(j) X_t(j)} {\sum_{j \in valid_t} w_t(j)}\]

With weights=None every valid asset gets equal weight (a plain cross-sectional mean) — the usual “dollar-neutral” transform. If the total weight of the valid assets in a bar is zero (e.g. long/short weights that happen to cancel, or all-zero weights), the bar falls back to an equal-weighted mean over its valid assets.

Parameters:
Xarray_like

Panel, shape (T, N).

weightsarray_like, optional

Weights, either (N,) (applied identically at every bar) or (T, N) (time-varying). Weights at positions where X is NaN are ignored. Default None (equal weight).

Returns:
np.ndarray

(T, N) demeaned panel, NaN where X is NaN.

See also

cs_zscore, cs_neutralize

Examples

>>> import numpy as np
>>> X = np.array([[1., 2., 3.]])
>>> cs_demean(X)
array([[-1.,  0.,  1.]])

Weighted demeaning:

>>> cs_demean(np.array([[1., 2., 3.]]), weights=np.array([1., 1., 2.]))
array([[-1.25, -0.25,  0.75]])

NaN-aware: the missing asset is excluded from the bar’s mean and stays NaN:

>>> cs_demean(np.array([[1., np.nan, 3.]]))
array([[-1., nan,  1.]])