Pandas 滚动 window 斯皮尔曼相关
Pandas Rolling window Spearman correlation
我想计算 DataFrame 两列之间的 Spearman and/or Pearson 相关性,使用滚动 window。
我试过了df['corr'] = df['col1'].rolling(P).corr(df['col2'])
(P 是 window 尺码)
但我似乎无法定义该方法。 (添加 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
支持方法...
考虑到数据框很大(>1000 万行),关于如何执行此操作的任何建议?
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)
例如:
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
是一种 hacky 方法,在这种情况下,它为我们提供了一个看起来像二维数组的序列视图,其中包含我们正在查看的序列的滚动 window 部分.
从此以后,就简单了。斯皮尔曼相关相当于将序列转化为秩,取皮尔逊相关系数。 Pandas 有帮助地快速实现了在 DataFrame
上按行执行此操作。然后在最后我们用 NaN 值填充结果 Series
的开头(这样你就可以将它作为列添加到你的数据框或其他任何东西)。
(个人说明:我花了很长时间试图弄清楚如何使用 numpy 和 scipy 有效地做到这一点,然后才意识到你需要的一切都已经在 pandas 中了......!)。
为了展示这种方法相对于循环滑动 windows 的速度优势,我制作了一个名为 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) 版本运行不到半秒。上面没有显示,但 1000 万花费了 8.43 秒。慢的仍然是 运行,但假设线性增长继续下去,1M 大约需要 7 分钟,10M 需要一个多小时。
我想计算 DataFrame 两列之间的 Spearman and/or Pearson 相关性,使用滚动 window。
我试过了df['corr'] = df['col1'].rolling(P).corr(df['col2'])
(P 是 window 尺码)
但我似乎无法定义该方法。 (添加 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
支持方法...
考虑到数据框很大(>1000 万行),关于如何执行此操作的任何建议?
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)
例如:
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
是一种 hacky 方法,在这种情况下,它为我们提供了一个看起来像二维数组的序列视图,其中包含我们正在查看的序列的滚动 window 部分.
从此以后,就简单了。斯皮尔曼相关相当于将序列转化为秩,取皮尔逊相关系数。 Pandas 有帮助地快速实现了在 DataFrame
上按行执行此操作。然后在最后我们用 NaN 值填充结果 Series
的开头(这样你就可以将它作为列添加到你的数据框或其他任何东西)。
(个人说明:我花了很长时间试图弄清楚如何使用 numpy 和 scipy 有效地做到这一点,然后才意识到你需要的一切都已经在 pandas 中了......!)。
为了展示这种方法相对于循环滑动 windows 的速度优势,我制作了一个名为 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) 版本运行不到半秒。上面没有显示,但 1000 万花费了 8.43 秒。慢的仍然是 运行,但假设线性增长继续下去,1M 大约需要 7 分钟,10M 需要一个多小时。