Pandas:向量化局部范围操作([i:i+2] 行的最大值和总和)

Posted

技术标签:

【中文标题】Pandas:向量化局部范围操作([i:i+2] 行的最大值和总和)【英文标题】:Pandas: vectorize local range operations (max & sum for [i:i+2] rows) 【发布时间】:2019-10-11 19:59:43 【问题描述】:

我希望在本地范围内对数据框中的每一行进行计算,同时避免缓慢的for 循环。例如,对于下面数据中的每一行,我想找出未来 3 天内(包括当天)的最高气温和未来 3 天内的总降雨量:

Day Temperature Rain
0   30          4
1   31          14
2   31          0
3   30          0
4   33          5
5   34          0
6   32          0
7   33          2
8   31          5
9   29          9

理想的输出将是下表中的新列。第 0 天的 TempMax 显示第 0 天和第 2 天之间的最高温度,RainTotal 显示第 0 天和第 2 天之间的降雨总和:

Day  Temperature  Rain  TempMax  RainTotal
0    30           4     31       18
1    31           14    31       14
2    31           0     33       5
3    30           0     34       5
4    33           5     34       5
5    34           0     34       2
6    32           0     33       7
7    33           2     33       16
8    31           5     31       14
9    29           9     29       9

目前我正在使用for 循环:

  # Make empty arrays to store each row's max & sum values
  temp_max = np.zeros(len(df))
  rain_total = np.zeros(len(df))

  # Loop through the df and do operations in the local range [i:i+2]
  for i in range(len(df)):
    temp_max[i] = df['Temperature'].iloc[i:i+2].max()
    rain_total = df['Rain'].iloc[i:i+2].sum()

  # Insert the arrays to df
  df['TempMax'] = temp_max
  df['RainTotal'] = rain_total

for 循环完成了工作,但我的数据框需要 50 分钟。是否有可能通过其他方式对其进行 vecrotized 或使其更快?

非常感谢!

【问题讨论】:

【参考方案1】:

对于Day 连续几天都有数据的情况,我们可以使用快速的 NumPy 和 SciPy 工具来救援 -

from scipy.ndimage.filters import maximum_filter1d

N = 2 # window length
temp = df['Temperature'].to_numpy()
rain = df['Rain'].to_numpy()
df['TempMax'] = maximum_filter1d(temp,N+1,origin=-1,mode='nearest')
df['RainTotal'] = np.convolve(rain,np.ones(N+1,dtype=int))[N:]

样本输出 -

In [27]: df
Out[27]: 
   Day  Temperature  Rain  TempMax  RainTotal
0    0           30     4       31         18
1    1           31    14       31         14
2    2           31     0       33          5
3    3           30     0       34          5
4    4           33     5       34          5
5    5           34     0       34          2
6    6           32     0       33          7
7    7           33     2       33         16
8    8           31     5       31         14
9    9           29     9       29          9

【讨论】:

这也非常快,感谢您的解决方案!但是,我确实更喜欢上一个答案的 .rolling() 方法,因为它完全是 Pandas 并且不需要额外的导入。【参考方案2】:

通过索引将Series.rolling 与变更单一起使用,将maxsum 一起使用:

df['TempMax'] = df['Temperature'].iloc[::-1].rolling(3, min_periods=1).max()
df['RainTotal'] = df['Rain'].iloc[::-1].rolling(3, min_periods=1).sum()
print (df)
   Day  Temperature  Rain  TempMax  RainTotal
0    0           30     4     31.0       18.0
1    1           31    14     31.0       14.0
2    2           31     0     33.0        5.0
3    3           30     0     34.0        5.0
4    4           33     5     34.0        5.0
5    5           34     0     34.0        2.0
6    6           32     0     33.0        7.0
7    7           33     2     33.0       16.0
8    8           31     5     31.0       14.0
9    9           29     9     29.0        9.0

另一个更快的解决方案,在 numpy 中使用 strides 处理二维数组,然后使用 numpy.nanmaxnumpy.nansum

n = 2
t = np.concatenate([df['Temperature'].values, [np.nan] * (n)])
r = np.concatenate([df['Rain'].values, [np.nan] * (n)])

def rolling_window(a, window):
    shape = a.shape[:-1] + (a.shape[-1] - window + 1, window)
    strides = a.strides + (a.strides[-1],)
    return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)

df['TempMax'] = np.nanmax(rolling_window(t, n + 1), axis=1)
df['RainTotal'] = np.nansum(rolling_window(r, n + 1), axis=1)
print (df)
   Day  Temperature  Rain  TempMax  RainTotal
0    0           30     4     31.0       18.0
1    1           31    14     31.0       14.0
2    2           31     0     33.0        5.0
3    3           30     0     34.0        5.0
4    4           33     5     34.0        5.0
5    5           34     0     34.0        2.0
6    6           32     0     33.0        7.0
7    7           33     2     33.0       16.0
8    8           31     5     31.0       14.0
9    9           29     9     29.0        9.0

性能

#[100000 rows x 3 columns]
df = pd.concat([df] * 10000, ignore_index=True)

In [23]: %%timeit
    ...: df['TempMax'] = np.nanmax(rolling_window(t, n + 1), axis=1)
    ...: df['RainTotal'] = np.nansum(rolling_window(r, n + 1), axis=1)
    ...: 
8.36 ms ± 165 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [24]: %%timeit
    ...: df['TempMax'] = df['Temperature'].iloc[::-1].rolling(3, min_periods=1).max()
    ...: df['RainTotal'] = df['Rain'].iloc[::-1].rolling(3, min_periods=1).sum()
    ...: 
20.4 ms ± 1.35 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

【讨论】:

谢谢!这将时间从 50 分钟减少到 3 秒。太棒了! @Tuppitappi 我的解决方案怎么样?

以上是关于Pandas:向量化局部范围操作([i:i+2] 行的最大值和总和)的主要内容,如果未能解决你的问题,请参考以下文章

Pandas之字符串操作

Pandas之字符串操作

pandas DataFrame-向量化运算

向量化前瞻性函数 pandas 数据框

Python / Pandas是否可以向量化与相对类别中所有其他点的比较?

向量化 Pandas 数据帧