【问题标题】:looking for an efficient way to iterate寻找一种有效的迭代方式
【发布时间】:2017-10-02 19:33:03
【问题描述】:

我可以请求建议以更有效(更快)地进行迭代吗? 这是问题所在,我正在寻找一种方法在确定的窗口大小内在 pandas DataFrame 中向下传播零:

import numpy as np
import pandas as pd

A = np.matrix([[ 0.,  1.,  1.,  1.,  1.],
           [ 1.,  0.,  1.,  1.,  1.],
           [ 1.,  1.,  0.,  1.,  1.],
           [ 1.,  1.,  1.,  0.,  1.],
           [ 1.,  1.,  1.,  1.,  0.],
           [ 1.,  1.,  1.,  1.,  1.],
           [ 1.,  1.,  1.,  1.,  1.],
           [ 1.,  1.,  1.,  1.,  1.],
           [ 1.,  1.,  1.,  1.,  0.],
           [ 1.,  1.,  0.,  1.,  1.],
           [ 1.,  1.,  1.,  1.,  1.],
           [ 1.,  1.,  1.,  1.,  1.],
           [ 1.,  1.,  1.,  1.,  1.],
           [ 1.,  1.,  1.,  1.,  1.],
           [ 1.,  1.,  1.,  0.,  1.],
           [ 1.,  1.,  1.,  1.,  1.],
           [ 1.,  1.,  0.,  1.,  1.],
           [ 1.,  1.,  1.,  1.,  0.],
           [ 1.,  0.,  1.,  1.,  1.],
           [ 1.,  1.,  1.,  1.,  1.]])

df = pd.DataFrame(A)

现在我们要填充每 3 行窗口的增量 顶部的值。 3行的每个窗口从window_start开始,定义为:

window_size = 3
window_start = [i for i in range(0, df.shape[0]) 
                if i % window_size == 0]
print(df)
gf = df.copy()
print('\n')

现在制作零传播的DataFrame 该窗口内的前几行:

for i in window_start:
for j in range(1, window_size):
    try: gf.iloc[i + j] = gf.iloc[i + j - 1] * gf.iloc[i + j]
    except: pass

print(gf)

最后一点对于非常大的数据集来说效率很低且耗时,有没有更好的方法来做到这一点?

【问题讨论】:

    标签: python-3.x performance pandas numpy pandas-groupby


    【解决方案1】:

    您应该能够使用groupby 中的累积产品完成此任务。

    df.groupby(np.arange(len(df)) // 3).cumprod()
    
          0    1    2    3    4
    0   0.0  1.0  1.0  1.0  1.0
    1   0.0  0.0  1.0  1.0  1.0
    2   0.0  0.0  0.0  1.0  1.0
    3   1.0  1.0  1.0  0.0  1.0
    4   1.0  1.0  1.0  0.0  0.0
    5   1.0  1.0  1.0  0.0  0.0
    6   1.0  1.0  1.0  1.0  1.0
    7   1.0  1.0  1.0  1.0  1.0
    8   1.0  1.0  1.0  1.0  0.0
    9   1.0  1.0  0.0  1.0  1.0
    10  1.0  1.0  0.0  1.0  1.0
    11  1.0  1.0  0.0  1.0  1.0
    12  1.0  1.0  1.0  1.0  1.0
    13  1.0  1.0  1.0  1.0  1.0
    14  1.0  1.0  1.0  0.0  1.0
    15  1.0  1.0  1.0  1.0  1.0
    16  1.0  1.0  0.0  1.0  1.0
    17  1.0  1.0  0.0  1.0  0.0
    18  1.0  0.0  1.0  1.0  1.0
    19  1.0  0.0  1.0  1.0  1.0
    

    我们可以更好地观察,使用concat 看看它是否在做我们想要的。

    pd.concat([df.iloc[:6, :2], d1.iloc[:6, :2]], axis=1, keys=['Before', 'After'])
    
      Before      After     
           0    1     0    1
    0    0.0  1.0   0.0  1.0
    1    1.0  0.0   0.0  0.0
    2    1.0  1.0   0.0  0.0
    3    1.0  1.0   1.0  1.0
    4    1.0  1.0   1.0  1.0
    5    1.0  1.0   1.0  1.0
    

    我对 numpy 方法的看法
    请参阅 @Divakar 的解决方案,因为我借用了他的一些功能元素

    def prop_zero(df, window_size=3):
        a = df.values
        W = window_size
        m, n = a.shape
    
        pad = np.zeros((W - m % W, n))
        b = np.vstack([a, pad])
    
        return pd.DataFrame(
            b.reshape(-1, W, n).cumprod(1).reshape(-1, n)[:m],
            df.index, df.columns
        )
    
    prop_zero(df)
    

    【讨论】:

    • 请注意,如果值溢出,这将失败,例如[1e200, 1e200, 0],因为 cumprod 会生成 [1e200, inf, nan]。 ;-) #unlikelyfailuremodeoftheday
    • 不错的标签 :-)
    【解决方案2】:

    你可以用cummin 做一个groupby

    In [46]: out = df.groupby(np.arange(len(df))//3).cummin()
    
    In [47]: df.head(6)
    Out[47]: 
         0    1    2    3    4
    0  0.0  1.0  1.0  1.0  1.0
    1  1.0  0.0  1.0  1.0  1.0
    2  1.0  1.0  0.0  1.0  1.0
    3  1.0  1.0  1.0  0.0  1.0
    4  1.0  1.0  1.0  1.0  0.0
    5  1.0  1.0  1.0  1.0  1.0
    
    In [48]: out.head(6)
    Out[48]: 
         0    1    2    3    4
    0  0.0  1.0  1.0  1.0  1.0
    1  0.0  0.0  1.0  1.0  1.0
    2  0.0  0.0  0.0  1.0  1.0
    3  1.0  1.0  1.0  0.0  1.0
    4  1.0  1.0  1.0  0.0  0.0
    5  1.0  1.0  1.0  0.0  0.0
    

    这假设所有值都是 0 和 1。如果你有非 1 值但你仍然想要零后零的行为,你可以做类似的事情

    df.where(~(df == 0).groupby(np.arange(len(df))//3).cummax(), 0)
    

    这不是很漂亮,但不会被 0.5 之类的值(就像将 cummin 直接应用于值那样)或潜在的溢出(就像将 cumprod 直接应用于值那样)混淆。

    【讨论】:

    • 只要值是 1 和 0,它就可以工作。很好的答案!
    【解决方案3】:

    这是一个 NumPy 方法拆分第一个轴给我们一个3D 数组,然后沿第一个轴使用cumprod,然后重新整形为2D。对于行数不能被window_size 整除的情况,我们将保留不属于整形的剩余元素,这些元素将单独处理。

    因此,实现将是 -

    def numpy_cumprod(df, window_size=3):
        a = df.values
        W = window_size
        m,n = a.shape
        N = m//W
        M = N*W
    
        out0 = a[:M].reshape(-1,W,n).cumprod(1).reshape(-1,n)
        out = np.vstack(( out0, a[M:].cumprod(0)))
        return pd.DataFrame(out)
    

    示例运行 -

    In [279]: df
    Out[279]: 
        0  1  2  3  4
    0   2  2  2  0  1
    1   1  2  0  2  2
    2   1  1  0  0  1
    3   2  0  2  0  1
    4   0  0  0  1  0
    5   0  0  1  2  1
    6   1  1  0  0  1
    7   0  0  1  2  1
    8   2  2  2  1  1
    9   2  1  2  1  0
    10  1  1  1  1  2
    11  0  2  2  1  2
    
    In [280]: numpy_cumprod(df, window_size=3)
    Out[280]: 
        0  1  2  3  4
    0   2  2  2  0  1
    1   2  4  0  0  2
    2   2  4  0  0  2
    3   2  0  2  0  1
    4   0  0  0  0  0
    5   0  0  0  0  0
    6   1  1  0  0  1
    7   0  0  0  0  1
    8   0  0  0  0  1
    9   2  1  2  1  0
    10  2  1  2  1  0
    11  0  2  4  1  0
    

    在更大的数据集上进行运行时测试 -

    In [275]: df = pd.DataFrame(np.random.randint(0,3,(10000,5)))
    
    # @piRSquared's soln-1 using pandas groupby
    In [276]: %timeit df.groupby(np.arange(len(df)) // 3).cumprod()
    100 loops, best of 3: 2.49 ms per loop
    
    # @piRSquared's soln-2 using NumPy
    In [261]: %timeit prop_zero(df, window_size=3)
    1000 loops, best of 3: 285 µs per loop
    
    # Proposed in this post
    In [262]: %timeit numpy_cumprod(df, window_size=3)
    1000 loops, best of 3: 262 µs per loop
    

    【讨论】:

    • 这就是我要添加的内容 :-) 我要在最后添加一组填充的零,然后再次删除它们。
    • @piRSquared 可爱的补充,也将其添加到计时中。现在相当快。
    猜你喜欢
    • 2023-04-07
    • 1970-01-01
    • 1970-01-01
    • 2018-03-17
    • 1970-01-01
    • 2018-02-02
    • 1970-01-01
    • 2015-07-16
    • 1970-01-01
    相关资源
    最近更新 更多