pandas - 累计中位数

pandas - cumulative median

我想知道是否有任何 pandas 等同于 cumsum()cummax() 等的中位数:例如cummedian().

所以如果我有,例如这个数据框:

   a
1  5
2  7
3  6
4  4

我想要的是这样的:

df['a'].cummedian()

应该输出:

5
6  
6 
5.5

您可以使用 expanding.median -

df.a.expanding().median()

1    5.0
2    6.0
3    6.0
4    5.5
Name: a, dtype: float64

时间

df = pd.DataFrame({'a' : np.arange(1000000)})

%timeit df['a'].apply(cummedian())
1 loop, best of 3: 1.69 s per loop

%timeit df.a.expanding().median()
1 loop, best of 3: 838 ms per loop

获胜者是 expanding.median 巨大的优势。 Divakar 的方法是内存密集型的,并且在这种输入大小下会出现内存井喷。

特定累积中位数的更快解决方案

In [1]: import timeit

In [2]: setup = """import bisect
   ...: import pandas as pd
   ...: def cummedian():
   ...:     l = []
   ...:     info = [0, True]
   ...:     def inner(n):
   ...:         bisect.insort(l, n)
   ...:         info[0] += 1
   ...:         info[1] = not info[1]
   ...:         median = info[0] // 2
   ...:         if info[1]:
   ...:             return (l[median] + l[median - 1]) / 2
   ...:         else:
   ...:             return l[median]
   ...:     return inner
   ...: df = pd.DataFrame({'a': range(20)})"""

In [3]: timeit.timeit("df['cummedian'] = df['a'].apply(cummedian())",setup=setup,number=100000)
Out[3]: 27.11604686321956

In [4]: timeit.timeit("df['expanding'] = df['a'].expanding().median()",setup=setup,number=100000)
Out[4]: 48.457676260100335

In [5]: 48.4576/27.116
Out[5]: 1.7870482372031273

我们可以使用基于 strides 的函数创建 nan 填充子数组作为行,就像这样 -

def nan_concat_sliding_windows(x):
    n = len(x)
    add_arr = np.full(n-1, np.nan)
    x_ext = np.concatenate((add_arr, x))
    strided = np.lib.stride_tricks.as_strided
    nrows = len(x_ext)-n+1
    s = x_ext.strides[0]
    return strided(x_ext, shape=(nrows,n), strides=(s,s))

样本运行-

In [56]: x
Out[56]: array([5, 6, 7, 4])

In [57]: nan_concat_sliding_windows(x)
Out[57]: 
array([[ nan,  nan,  nan,   5.],
       [ nan,  nan,   5.,   6.],
       [ nan,   5.,   6.,   7.],
       [  5.,   6.,   7.,   4.]])

因此,要获得数组 x 的滑动中值,我们将有一个向量化的解决方案,就像这样-

np.nanmedian(nan_concat_sliding_windows(x), axis=1)

因此,最终的解决方案是 -

In [54]: df
Out[54]: 
a
1  5
2  7
3  6
4  4

In [55]: pd.Series(np.nanmedian(nan_concat_sliding_windows(df.a.values), axis=1))
Out[55]: 
0    5.0
1    6.0
2    6.0
3    5.5
dtype: float64