Pandas组缓慢应用(嵌套)

rpppsulh  于 2023-03-11  发布在  其他
关注(0)|答案(2)|浏览(123)

我有一个包含“category”和“number”列的 Dataframe 。我想创建一个新列“avg_of_largest_2_from_prev_5”,该列是在按“category”分组并对前5行(不包括当前行)数值中的最高2个值取平均值后计算得出的。

np.random.seed(123)
n_rows = 10000
data = {'category': np.random.randint(1, 1000, n_rows), 'number': np.random.randint(1, n_rows, n_rows)}
df = pd.DataFrame(data)

%timeit df['avg_of_largest_2_from_prev_5'] = df.groupby('category')['number'].apply(lambda x: x.shift(1).rolling(5, min_periods=0).apply(lambda y: pd.Series(y).nlargest(2).mean()))

df = df[df['category'] == df['category'].values[0]]
df

输出:4.55 s ± 34.4 ms/循环(7次运行的平均值±标准差,每次运行1个循环)

category        number   avg_of_largest_2_from_prev_5
0           511         4179    NaN
392         511         2878    4179.0
1292        511         5834    3528.5
1350        511         1054    5006.5
1639        511         8673    5006.5
3145        511         8506    7253.5
4176        511         947     8589.5
4471        511         151     8589.5
4735        511         5326    8589.5
4965        511         4827    8589.5
5046        511         9792    6916.0
5316        511         3772    7559.0
5535        511         1095    7559.0
5722        511         5619    7559.0
5732        511         700     7705.5
6825        511         1156    7705.5
6877        511         7240    4695.5
8100        511         2381    6429.5
8398        511         2376    6429.5

这个操作需要36秒处理10 k行和1 k个类别。当我在1 m+行的 Dataframe 中尝试这个操作时,它需要大约8分钟。我认为应该有一个更快的方法来完成我正在尝试做的事情,我将感谢任何建议。

ni65a41a

ni65a41a1#

另一种解决方案是使用Polars(https://www.pola.rs/)代替Pandas。
在我的测试中,Pandas功能需要5.33秒,而Polars功能需要30.1毫秒,因此快了178倍。
您可以使用pip install polars安装Polars
函数如下:

import polars as pl

# convert the dataframe to Polars
df_polars = pl.from_pandas(df)

# run the calculation
df_polars_result = df_polars.with_columns(
        pl.lit(1).alias('ones')
    ).with_columns(
        i = pl.col('ones').cumsum().over('category').cast(pl.Int32),
        num_shifted = pl.col('number').shift(1).over('category')
        ) \
    .groupby_rolling(index_column = 'i', period='5i', by='category').agg(
        pl.last('number'),    
        avg_of_largest_2_from_prev_5 = 
            pl.col('num_shifted').sort(descending=True).slice(0,2).mean()
)
30.1 ms ± 6.37 ms per loop

# Convert back to Pandas if needed
df_pandas_result = df_polars_result.to_pandas(use_pyarrow_extension_array=True)

# Example of the result
┌──────────┬─────┬────────┬──────────────────────────────┐
│ category ┆ i   ┆ number ┆ avg_of_largest_2_from_prev_5 │
│ ---      ┆ --- ┆ ---    ┆ ---                          │
│ i64      ┆ i32 ┆ i64    ┆ f64                          │
╞══════════╪═════╪════════╪══════════════════════════════╡
│ 511      ┆ 1   ┆ 4179   ┆ null                         │
│ 511      ┆ 2   ┆ 2878   ┆ 4179.0                       │
│ 511      ┆ 3   ┆ 5834   ┆ 3528.5                       │
│ 511      ┆ 4   ┆ 1054   ┆ 5006.5                       │
│ ...      ┆ ... ┆ ...    ┆ ...                          │
│ 511      ┆ 16  ┆ 1156   ┆ 7705.5                       │
│ 511      ┆ 17  ┆ 7240   ┆ 4695.5                       │
│ 511      ┆ 18  ┆ 2381   ┆ 6429.5                       │
│ 511      ┆ 19  ┆ 2376   ┆ 6429.5                       │
└──────────┴─────┴────────┴──────────────────────────────┘
5rgfhyps

5rgfhyps2#

您的样品

import time

start = time.time()
df['avg_of_largest_2_from_prev_5'] = df.groupby('category')['number'].apply(lambda x: x.shift(1).rolling(5, min_periods=0).apply(lambda y: pd.Series(y).nlargest(2).mean()))
end = time.time()
>>> end-start
1.6836352348327637

速度提升

一个二个一个一个
与样品相比,速度提高了3.7%
对于所选的示例,我们无法真正看到agg函数与apply函数相比的效率,但另一方面,如果我们将类别数量减少到10,并相应地将更多行转移到agg函数进行处理,我们会意识到变化非常显著。

import pandas as pd
import numpy as np
import time

np.random.seed(123)
n_rows = 10000
data = {'category': np.random.randint(1, 10, n_rows), 'number': np.random.randint(1, n_rows, n_rows)}
df = pd.DataFrame(data)

start = time.time()
df['avg_of_largest_2_from_prev_5'] = df.groupby('category')['number'].apply(lambda x: x.shift(1).rolling(5, min_periods=0).apply(lambda y: pd.Series(y).nlargest(2).mean()))
end = time.time()

print("First method", end-start)

start = time.time()
df['avg_of_largest_2_from_prev_5'] = \
( df.groupby('category')['number']
    .apply(lambda s: s.shift(1).rolling(5, min_periods=0).agg( lambda s: s.nlargest(2).mean() ))
  )
end = time.time()
print("Second method", end-start)

First method 1.420635461807251
Second method 1.339367151260376
First method 1.420635461807251
Second method 1.339367151260376

在本例中,改进为6%
现在,我测试以下参数:

n_rows = 1000000
data = {'category': np.random.randint(1, 10, n_rows)...

并通过9.5%的改进获得这些结果:

First method 139.25211119651794
Second method 127.48456478118896

结论

数据越多,类别越少,第二种方法的性能就越高

相关问题