【问题标题】:Pandas expanding/rolling window correlation calculation with p-value使用 p 值计算 Pandas 扩展/滚动窗口相关性
【发布时间】:2019-11-06 01:53:06
【问题描述】:

假设我有一个 DataFrame,我想在其上计算两列之间的滚动或扩展 Pearson 相关性

import numpy as np
import pandas as pd
import scipy.stats as st


df = pd.DataFrame({'x': np.random.rand(10000), 'y': np.random.rand(10000)})

借助内置的pandas 功能,计算速度非常快

expanding_corr = df['x'].expanding(50).corr(df['y'])
rolling_corr = df['x'].rolling(50).corr(df['y'])

但是,如果我希望获得与这些相关性相关的 p 值,我能做的最好的事情就是定义一个自定义滚动函数并将 apply 传递给 groupby 对象

def custom_roll(df, w, **kwargs):

    v = df.values
    d0, d1 = v.shape
    s0, s1 = v.strides
    a = np.lib.stride_tricks.as_strided(v, (d0 - (w - 1), w, d1), (s0, s0, s1))
    rolled_df = pd.concat({
        row: pd.DataFrame(values, columns=df.columns)
        for row, values in zip(df.index[(w-1):], a)
    })
    return rolled_df.groupby(level=0, **kwargs)

c_df = custom_roll(df, 50).apply(lambda df: st.pearsonr(df['x'], df['y']))

c_df 现在包含适当的相关性,重要的是它们的相关 p 值。

但是,与内置的 pandas 方法相比,此方法非常慢,这意味着它不适合,因为实际上我在优化过程中计算了数千次这些相关性。此外,我不确定如何扩展 custom_roll 函数以用于扩展窗口。

谁能指出我利用 numpy 以矢量化速度在扩展窗口上获得 p 值的方向?

【问题讨论】:

    标签: python pandas numpy optimization vectorization


    【解决方案1】:

    我想不出在 pandas 中直接使用 rolling 的聪明方法,但请注意,您可以在给定相关系数的情况下计算 p 值。

    Pearson 的相关系数遵循学生的 t 分布,您可以通过将其插入由不完全 beta 函数 scipy.special.betainc 定义的 cdf 来获得 p 值。这听起来很复杂,但可以在几行代码中完成。下面是一个在给定相关系数corr 和样本大小n 的情况下计算p 值的函数。它实际上是基于你一直在使用的scipy's implementation

    import pandas as pd
    from scipy.special import betainc
    
    def pvalue(corr, n=50):
        df = n - 2
        t_squared = corr**2 * (df / ((1.0 - corr) * (1.0 + corr)))
        prob = betainc(0.5*df, 0.5, df/(df+t_squared))
        return prob
    

    然后您可以将此函数应用于您已有的相关值。

    rolling_corr = df['x'].rolling(50).corr(df['y'])
    pvalue(rolling_corr)
    

    它可能不是完美的向量化 numpy 解决方案,但应该比一遍又一遍地计算相关性快几十倍。

    【讨论】:

    • 谢谢。我了解从相关性中剔除 p 值的统计数据。我想虽然我想要的真正答案允许我绕过缓慢的apply 方法将一般统计函数传递到滚动/扩展窗口中
    • 我明白这一点,这确实是一个非常好的问题。似乎您可以使用 rolling.apply 传递任何函数,但我找不到将其应用于两列的方法。
    • 是的,这是一个有趣的问题,我感谢您尝试解决方案。不幸的是,apply 只是引擎盖下的一个循环,因此当DataFrame 变大时会出现性能问题。我希望有一个numpy 解决方案,或者也许能够对.corr 本身进行一些修改
    • 但是rolling函数在后台也只是一个for循环吧?我想说减慢您当前解决方案的并不是真正的apply,而是每次都必须创建一个新的数据框。另外,我更新了我的答案,假设你有相关系数,你真的不需要apply
    【解决方案2】:

    方法#1

    corr2_coeff_rowwise 列出了如何在行之间进行元素关联。我们可以将其分解为两列之间元素相关的情况。所以,我们最终会得到一个使用corr2_coeff_rowwise 的循环。然后,我们将尝试对其进行矢量化,并查看其中是否有可以矢量化的片段:

    1. 使用mean 获取平均值。这可以使用统一过滤器进行矢量化。
    2. 接下来是获取这些平均值与输入数组中的滑动元素之间的差异。要移植到矢量化的,我们将使用broadcasting

    Rest 保持不变,以从 pearsonr 的两个输出中获取第一个。

    要获得第二个输出,我们返回source code。考虑到第一个系数输出,这应该是直截了当的。

    因此,考虑到这些,我们最终会得到这样的结果 -

    import scipy.special as special
    from scipy.ndimage import uniform_filter
    
    def sliding_corr1(a,b,W):
        # a,b are input arrays; W is window length
    
        am = uniform_filter(a.astype(float),W)
        bm = uniform_filter(b.astype(float),W)
    
        amc = am[W//2:-W//2+1]
        bmc = bm[W//2:-W//2+1]
    
        da = a[:,None]-amc
        db = b[:,None]-bmc
    
        # Get sliding mask of valid windows
        m,n = da.shape
        mask1 = np.arange(m)[:,None] >= np.arange(n)
        mask2 = np.arange(m)[:,None] < np.arange(n)+W
        mask = mask1 & mask2
        dam = (da*mask)
        dbm = (db*mask)
    
        ssAs = np.einsum('ij,ij->j',dam,dam)
        ssBs = np.einsum('ij,ij->j',dbm,dbm)
        D = np.einsum('ij,ij->j',dam,dbm)
        coeff = D/np.sqrt(ssAs*ssBs)
    
        n = W
        ab = n/2 - 1
        pval = 2*special.btdtr(ab, ab, 0.5*(1 - abs(np.float64(coeff))))
        return coeff,pval
    

    因此,从 pandas 系列的输入中获得最终输出 -

    out = sliding_corr1(df['x'].to_numpy(copy=False),df['y'].to_numpy(copy=False),50)
    

    方法#2

    Approach #1 非常相似,但我们将使用numba 来提高内存效率,以取代之前方法中的步骤#2。

    from numba import njit
    import math
    
    @njit(parallel=True)
    def sliding_corr2_coeff(a,b,amc,bmc):
        L = len(a)-W+1
        out00 = np.empty(L)
        for i in range(L):
            out_a = 0
            out_b = 0
            out_D = 0
            for j in range(W):
                d_a = a[i+j]-amc[i]
                d_b = b[i+j]-bmc[i]
                out_D += d_a*d_b
                out_a += d_a**2
                out_b += d_b**2
            out00[i] = out_D/math.sqrt(out_a*out_b)
        return out00
    
    def sliding_corr2(a,b,W):
        am = uniform_filter(a.astype(float),W)
        bm = uniform_filter(b.astype(float),W)
    
        amc = am[W//2:-W//2+1]
        bmc = bm[W//2:-W//2+1]
    
        coeff = sliding_corr2_coeff(a,b,amc,bmc)
    
        ab = W/2 - 1
        pval = 2*special.btdtr(ab, ab, 0.5*(1 - abs(np.float64(coeff))))
        return coeff,pval
    

    方法#3

    与上一个非常相似,除了我们将所有系数工作推到numba -

    @njit(parallel=True)
    def sliding_corr3_coeff(a,b,W):
        L = len(a)-W+1
        out00 = np.empty(L)
        for i in range(L):
            a_mean = 0.0
            b_mean = 0.0
            for j in range(W):
                a_mean += a[i+j]
                b_mean += b[i+j]
            a_mean /= W
            b_mean /= W
    
            out_a = 0
            out_b = 0
            out_D = 0
            for j in range(W):
                d_a = a[i+j]-a_mean
                d_b = b[i+j]-b_mean
                out_D += d_a*d_b
                out_a += d_a*d_a
                out_b += d_b*d_b
            out00[i] = out_D/math.sqrt(out_a*out_b)
        return out00
    
    def sliding_corr3(a,b,W):    
        coeff = sliding_corr3_coeff(a,b,W)
        ab = W/2 - 1
        pval = 2*special.btdtr(ab, ab, 0.5*(1 - np.abs(coeff)))
        return coeff,pval
    

    时间安排 -

    In [181]: df = pd.DataFrame({'x': np.random.rand(10000), 'y': np.random.rand(10000)})
    
    In [182]: %timeit sliding_corr2(df['x'].to_numpy(copy=False),df['y'].to_numpy(copy=False),50)
    100 loops, best of 3: 5.05 ms per loop
    
    In [183]: %timeit sliding_corr3(df['x'].to_numpy(copy=False),df['y'].to_numpy(copy=False),50)
    100 loops, best of 3: 5.51 ms per loop
    

    注意:

    • sliding_corr1 似乎在这个数据集上花费了很长时间,很可能是因为它的第 2 步需要内存。

    • 使用numba funcs后的瓶颈,然后用special.btdtr转移到p-val计算。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-08
      • 2018-05-23
      • 1970-01-01
      相关资源
      最近更新 更多