首先声明,为了重现问题,我需要大量数据,这也是问题的一部分,因为我无法预测什么时候会出现异常。无论如何,数据太大(约13k行,2列),无法在问题中粘贴,我已在帖子末尾添加了pastebin链接。
我最近几天在使用
pandas.core.window.rolling.Rolling.corr
时遇到了一个奇怪的问题。我有一个数据集,想要计算滚动相关性。以下是问题描述:在计算两列(
a
和b
)的滚动相关性(窗口大小为100)时:一些索引(例如12981)会返回接近0的值(约为1e-10的数量级),但实际上应该返回nan
或inf
(因为其中一列的所有值都是常数)。然而,如果我只计算该索引的独立相关性(即包括该索引的最后100行数据),或者对较少的行进行滚动计算(如300或1000而不是13k),就能得到正确的结果(即nan
或inf
)。
期望结果:
>>> df = pd.read_csv('sample_corr_data.csv') # link at the end, ## columns = ['a', 'b']
>>> df.a.tail(100).value_counts()
0.000000 86
-0.000029 3
0.000029 3
-0.000029 2
0.000029 2
-0.000029 2
0.000029 2
Name: a, dtype: int64
>>> df.b.tail(100).value_counts() # all 100 values are same
6.0 100
Name: b, dtype: int64
>>> df.a.tail(100).corr(df.b.tail(100))
nan # expected, because column 'b' has same value throughout
# Made sure of this using,
# 1. np.corrcoef, because pandas uses this internally to calculate pearson moments
>>> np.corrcoef(df.a.tail(100), df.b.tail(100))[0, 1]
nan
# 2. using custom function
>>> def pearson(a, b):
n = a.size
num = n*np.nansum(a*b) - np.nansum(a)*np.nansum(b)
den = (n*np.nansum((a**2)) - np.nansum(a)**2)*(n*np.nansum(b**2) - np.nansum(b)**2)
return num/np.sqrt(den) if den * np.isfinite(den*num) else np.nan
>>> pearson(df.a.tail(100), df.b.tail(100))
nan
现在,现实情况是:
>>> df.a.rolling(100).corr(df.b).tail(3)
12979 7.761921e-07
12980 5.460717e-07
12981 2.755881e-10 # This should have been NaN/inf !!
## Furthermore!!
>>> debug = df.tail(300)
>>> debug.a.rolling(100).corr(debug.b).tail(3)
12979 7.761921e-07
12980 5.460717e-07
12981 -inf # Got -inf, fine
dtype: float64
>>> debug = df.tail(3000)
>>> debug.a.rolling(100).corr(debug.b).tail(3)
12979 7.761921e-07
12980 5.460717e-07
12981 inf # Got +inf, still acceptable
dtype: float64
这将持续到 9369
行:
>>> debug = df.tail(9369)
>>> debug.a.rolling(100).corr(debug.b).tail(3)
12979 7.761921e-07
12980 5.460717e-07
12981 inf
dtype: float64
# then
>>> debug = df.tail(9370)
>>> debug.a.rolling(100).corr(debug.b).tail(3)
12979 7.761921e-07
12980 5.460717e-07
12981 4.719615e-10 # SPOOKY ACTION IN DISTANCE!!!
dtype: float64
>>> debug = df.tail(10000)
>>> debug.a.rolling(100).corr(debug.b).tail(3)
12979 7.761921e-07
12980 5.460717e-07
12981 1.198994e-10 # SPOOKY ACTION IN DISTANCE!!!
dtype: float64
当前解决方案
>>> df.a.rolling(100).apply(lambda x: x.corr(df.b.reindex(x.index))).tail(3) # PREDICTABLY, VERY SLOW!
12979 7.761921e-07
12980 5.460717e-07
12981 NaN
Name: a, dtype: float64
# again this checks out using other methods,
>>> df.a.rolling(100).apply(lambda x: np.corrcoef(x, df.b.reindex(x.index))[0, 1]).tail(3)
12979 7.761921e-07
12980 5.460717e-07
12981 NaN
Name: a, dtype: float64
>>> df.a.rolling(100).apply(lambda x: pearson(x, df.b.reindex(x.index))).tail(3)
12979 7.761921e-07
12980 5.460717e-07
12981 NaN
Name: a, dtype: float64
据我了解,
series.rolling(n).corr(other_series)
的结果应该与以下内容相匹配:>>> def rolling_corr(series, other_series, n=100):
return pd.Series(
[np.nan]*(n-1) + [series[i-n: i].corr(other_series[i-n:i])
for i in range (n, series.size+1)]
)
>>> rolling_corr(df.a, df.b).tail(3)
12979 7.761921e-07
12980 5.460717e-07
12981 NaN
起初,我认为这是浮点算术问题(因为最初,在某些情况下,通过将列“a”四舍五入到5位小数或转换为float32可以解决此问题),但在这种情况下,无论使用多少样本都会出现。因此,可能存在一些与滚动有关的问题,或者至少滚动会根据数据大小引起浮点问题。我检查了rolling.corr的源代码,但找不到任何可以解释这种不一致性的东西。现在我很担心,有多少过去的代码受到这个问题的困扰。
这是什么原因?如何解决?如果这是因为pandas更注重速度而不是准确性(如here所建议的),那么这是否意味着我永远无法可靠地在大样本上使用pandas.rolling操作?我该如何知道超出哪个大小会出现这种不一致性?
样本相关数据:https://pastebin.com/jXXHSv3r
测试环境
- Windows 10,python 3.9.1,pandas 1.2.2,(IPython 7.20)
- Windows 10,python 3.8.2,pandas 1.0.5,(IPython 7.19)
- Ubuntu 20.04,python 3.7.7,pandas 1.0.5,(GCC 7.3.0,标准REPL)
- CentOS Linux 7(核心),Python 2.7.5,pandas 0.23.4,(IPython 5.8.0)
注意:不同的操作系统在所述索引处返回不同的值,但都是有限且接近于0
。
corr
中,因为它涉及到计算中的std
。NumPy中有类似的问题: https://dev59.com/ab3pa4cB1Zd3GeqPYB9G https://github.com/numpy/numpy/issues/9631 - Quant Christoflex_binary_moment
,它只是在a
和b
都是序列时对给定的函数进行应用。对于corr
,该函数是_get_corr(a, b)
。它使用了cov、var和std
。问题可能出现在这些函数中,但我认为这不是问题,因为它们分别返回正确的结果,你可能已经看到了。 - Sayandip Dutta