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 есть быстрые реализации, позволяющие делать это построчно на DataFrame
s. Затем в конце мы дополняем начало результирующего 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