Корреляция Спирмена Pandas Rolling Window

Я хочу рассчитать корреляцию Спирмена и / или Пирсона между двумя столбцами DataFrame с помощью скользящего окна.

Я пробовал df['corr'] = df['col1'].rolling(P).corr(df['col2'])
(P - размер окна)

но, похоже, я не могу определить метод. (Добавление method='spearman' в качестве аргумента вызывает ошибку:

File "main.py", line 29, in __init__
_df['corr'] = g['col1'].rolling(P).corr(g['col2'], method = corr_function)
File "~\Python36\lib\site-packages\pandas\core\window.py", line 1287, in corr
**kwargs)
File "~\Python36\lib\site-packages\pandas\core\window.py", line 1054, in corr
_get_corr, pairwise=bool(pairwise))
File "~\Python36\lib\site-packages\pandas\core\window.py", line 1866, in _flex_binary_moment
return f(X, Y)
File "~\Python36\lib\site-packages\pandas\core\window.py", line 1051, in _get_corr
return a.cov(b, **kwargs) / (a.std(**kwargs) * b.std(**kwargs))
File "~\Python36\lib\site-packages\pandas\core\window.py", line 1280, in cov
ddof=ddof, **kwargs)
File "~\Python36\lib\site-packages\pandas\core\window.py", line 1020, in cov
_get_cov, pairwise=bool(pairwise))
File "~\Python36\lib\site-packages\pandas\core\window.py", line 1866, in _flex_binary_moment
return f(X, Y)
File "~\Python36\lib\site-packages\pandas\core\window.py", line 1015, in _get_cov
center=self.center).count(**kwargs)
TypeError: count() got an unexpected keyword argument 'method'

Честно говоря, я не ожидал, что это сработает, поскольку читая документацию, нет упоминания о том, что rolling.corr поддерживает методы ...

Любые предложения о том, как это сделать, учитывая, что фрейм данных довольно большой (> 10 миллионов строк)?


person stavrop    schedule 10.01.2018    source источник


Ответы (1)


rolling.corr делает Pearson, так что вы можете использовать его для этого. Для Копейщика используйте что-то вроде этого:

import pandas as pd
from numpy.lib.stride_tricks import as_strided
from numpy.lib import pad
import numpy as np
def rolling_spearman(seqa, seqb, window):
    stridea = seqa.strides[0]
    ssa = as_strided(seqa, shape=[len(seqa) - window + 1, window], strides=[stridea, stridea])
    strideb = seqa.strides[0]
    ssb = as_strided(seqb, shape=[len(seqb) - window + 1, window], strides =[strideb, strideb])
    ar = pd.DataFrame(ssa)
    br = pd.DataFrame(ssb)
    ar = ar.rank(1)
    br = br.rank(1)
    corrs = ar.corrwith(br, 1)
    return pad(corrs, (window - 1, 0), 'constant', constant_values=np.nan)

E.g.:

In [144]: df = pd.DataFrame(np.random.randint(0,1000,size=(10,2)), columns = list('ab'))
In [145]: df['corr'] = rolling_spearman(df.a, df.b, 4)
In [146]: df
Out[146]: 
     a    b  corr
0  429  922   NaN
1  618  382   NaN
2  476  517   NaN
3  267  536  -0.8
4  582  844  -0.4
5  254  895  -0.4
6  583  974   0.4
7  687  298  -0.4
8  697  447  -0.6
9  383   35   0.4

Пояснение: numpy.lib.stride_tricks.as_strided - это хакерский метод, который в этом случае дает нам представление о последовательностях, которые выглядят как 2d-массив с секциями скользящего окна последовательности, на которую мы смотрим.

С этого момента все просто. Корреляция Спирмена эквивалентна преобразованию последовательностей в ранги и взятию коэффициента корреляции Пирсона. К счастью, у Pandas есть быстрые реализации, позволяющие делать это построчно на DataFrames. Затем в конце мы дополняем начало результирующего Series значениями NaN (чтобы вы могли добавить его как столбец в свой фрейм данных или что-то еще).

(Личное примечание: я так долго пытался выяснить, как это сделать эффективно с помощью numpy и scipy, прежде чем я понял, что все, что вам нужно, уже есть в пандах ...!).

Чтобы показать преимущество этого метода в скорости по сравнению с простым циклом по скользящим окнам, я создал небольшой файл с именем srsmall.py, содержащий:

import pandas as pd
from numpy.lib.stride_tricks import as_strided
import scipy.stats
from numpy.lib import pad
import numpy as np

def rolling_spearman_slow(seqa, seqb, window):
    stridea = seqa.strides[0]
    ssa = as_strided(seqa, shape=[len(seqa) - window + 1, window], strides=[stridea, stridea])
    strideb = seqa.strides[0]
    ssb = as_strided(seqb, shape=[len(seqb) - window + 1, window], strides =[strideb, strideb])
    corrs = [scipy.stats.spearmanr(a, b)[0] for (a,b) in zip(ssa, ssb)]
    return pad(corrs, (window - 1, 0), 'constant', constant_values=np.nan)

def rolling_spearman_quick(seqa, seqb, window):
    stridea = seqa.strides[0]
    ssa = as_strided(seqa, shape=[len(seqa) - window + 1, window], strides=[stridea, stridea])
    strideb = seqa.strides[0]
    ssb = as_strided(seqb, shape=[len(seqb) - window + 1, window], strides =[strideb, strideb])
    ar = pd.DataFrame(ssa)
    br = pd.DataFrame(ssb)
    ar = ar.rank(1)
    br = br.rank(1)
    corrs = ar.corrwith(br, 1)
    return pad(corrs, (window - 1, 0), 'constant', constant_values=np.nan)

И сравните производительность:

In [1]: import pandas as pd
In [2]: import numpy as np
In [3]: from srsmall import rolling_spearman_slow as slow
In [4]: from srsmall import rolling_spearman_quick as quick
In [5]: for i in range(6):
   ...:     df = pd.DataFrame(np.random.randint(0,1000,size=(10*(10**i),2)), columns=list('ab'))
   ...:     print len(df), " rows"
   ...:     print "quick: ",
   ...:     %timeit quick(df.a, df.b, 4)
   ...:     print "slow: ",
   ...:     %timeit slow(df.a, df.b, 4)
   ...:     
10  rows
quick: 100 loops, best of 3: 3.52 ms per loop
slow: 100 loops, best of 3: 3.2 ms per loop
100  rows
quick: 100 loops, best of 3: 3.53 ms per loop
slow: 10 loops, best of 3: 42 ms per loop
1000  rows
quick: 100 loops, best of 3: 3.82 ms per loop
slow: 1 loop, best of 3: 430 ms per loop
10000  rows
quick: 100 loops, best of 3: 7.47 ms per loop
slow: 1 loop, best of 3: 4.33 s per loop
100000  rows
quick: 10 loops, best of 3: 50.2 ms per loop
slow: 1 loop, best of 3: 43.4 s per loop
1000000  rows
quick: 1 loop, best of 3: 404 ms per loop
slow:

На миллионе строк (на моей машине) быстрая версия (pandas) выполняется менее чем за полсекунды. Не показано выше, но 10 миллионов заняли 8,43 секунды. Медленный еще работает, но при условии, что линейный рост продолжается, он должен занять около 7 минут для 1M и более часа для 10M.

person LangeHaare    schedule 11.01.2018
comment
Фантастика! Большое спасибо! - person stavrop; 12.01.2018