簡體   English   中英

Python Pandas中的一般Groupby:快速方式

[英]General Groupby in Python Pandas: Fast way

終極問題

有沒有辦法做一個不依賴 pd.groupby 的通用的、高性能的 groupby 操作?

輸入

pd.DataFrame([[1, '2020-02-01', 'a'], [1, '2020-02-10', 'b'], [1, '2020-02-17', 'c'], [2, '2020-02-02', 'd'], [2, '2020-03-06', 'b'], [2, '2020-04-17', 'c']], columns=['id', 'begin_date', 'status'])`
   id  begin_date status
0   1  2020-02-01      a
1   1  2020-02-10      b
2   1  2020-02-17      c
3   2  2020-02-02      d
4   2  2020-03-06      b

所需 Output

   id status  count  uniquecount
0   1      a      1            1
1   1      b      1            1
2   1      c      1            1
3   2      b      1            1
4   2      c      1            1

問題

現在,在 Python 中有一個簡單的方法可以做到這一點,使用 Pandas。

df = df.groupby(["id", "status"]).agg(count=("begin_date", "count"), uniquecount=("begin_date", lambda x: x.nunique())).reset_index()
# As commented, omitting the lambda and replacing it with "begin_date", "nunique" will be faster. Thanks!

對於較大的數據集,此操作很慢,我猜測並說 O(n²)。

缺乏所需普遍適用性的現有解決方案

現在,經過一番谷歌搜索,StackOverflow 上有一些替代解決方案,或者使用 numpy、iterrows 或其他不同的方式。

執行 pandas groupby 操作的更快替代方案

Pandas 快速加權隨機選擇來自 groupby

還有一個很棒的:

Groupby 在 python pandas:快速方式

在我的示例中,這些解決方案通常旨在創建“計數”或“唯一計數”,基本上是聚合值。 但是,不幸的是,總是只有一個聚合,而不是多個 groupby 列。 此外,不幸的是,他們從未解釋如何將它們合並到分組的 dataframe 中。

Is there a way to use itertools (Like this answer: Faster alternative to perform pandas groupby operation , or even better this answer: Groupby in python pandas: Fast Way ) that do not only return the series "count", but the whole dataframe in分組形式?

終極問題

有沒有辦法做一個不依賴 pd.groupby 的通用的、高性能的 groupby 操作?

這看起來像這樣:

from typing import List
def fastGroupby(df, groupbyColumns: List[str], aggregateColumns):
    # numpy / iterrow magic
    return df_grouped

df = fastGroupby(df, ["id", "status"], {'status': 'count',
                             'status': 'count'}

並返回所需的 output。

在放棄groupby之前,我建議首先評估您是否真正利用了groupby所提供的功能。

取消lambda以支持內置的pd.DataFrameGroupBy方法。

許多SeriesDataFrame方法都實現為pd.DataFrameGroupBy方法。 您應該直接使用它們,而不是使用groupby + apply(lambda x: ...)調用它們

此外,對於許多計算,您可以將問題重新定義為對整個 DataFrame 進行一些矢量化操作,然后使用在 cython 中實現的 groupby 方法。 這會很快。

一個常見的例子是在一個組中找到'Y'答案的比例。 一個直接的方法是檢查每個組內的條件,然后得到比例:

N = 10**6
df = pd.DataFrame({'grp': np.random.choice(range(10000), N),
                   'answer': np.random.choice(['Y', 'N'], N)})

df.groupby('grp')['answer'].apply(lambda x: x.eq('Y').mean())

以這種方式思考問題需要lambda ,因為我們在 groupby 內部做了兩個操作; 檢查條件然后平均。 這個完全相同的計算可以被認為是首先檢查整個 DataFrame 的條件,然后計算組內的平均值:

df['answer'].eq('Y').groupby(df['grp']).mean()

這是一個很小的變化,但后果是巨大的,隨着組數的增加,收益會越來越大。

%timeit df.groupby('grp')['answer'].apply(lambda x: x.eq('Y').mean())
#2.32 s ± 99.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit df['answer'].eq('Y').groupby(df['grp']).mean()
#82.8 ms ± 995 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

添加sort=False作為參數

默認情況下, groupby對鍵上的 output 進行排序。 如果沒有理由對 output 進行排序,則指定sort=False可以獲得輕微的收益


添加observed=True作為參數

如果分組鍵是分類的,它將重新索引到所有可能的組合,即使對於從未出現在 DataFrame 中的組也是如此。 如果這些不重要,從 output 中刪除它們將大大提高速度。


對於您的示例,我們可以檢查差異。 切換到pd.DataFrameGroupBy.nunique可以獲得巨大的收益,並且刪除排序會增加一點額外的速度。 兩者的結合提供了一個“相同”的解決方案(直到排序),並且對於許多組來說快了近 100 倍。

import perfplot
import pandas as pd
import numpy

def agg_lambda(df):
    return df.groupby(['id', 'status']).agg(uniquecount=('Col4', lambda x: x.nunique()))
    
def agg_nunique(df):
    return df.groupby(['id', 'status']).agg(uniquecount=('Col4', 'nunique'))

def agg_nunique_nosort(df):
    return df.groupby(['id', 'status'], sort=False).agg(uniquecount=('Col4', 'nunique'))

perfplot.show(
    setup=lambda N: pd.DataFrame({'Col1': range(N),
                       'status': np.random.choice(np.arange(N), N),
                       'id': np.random.choice(np.arange(N), N),
                       'Col4': np.random.choice(np.arange(N), N)}),
    kernels=[
        lambda df: agg_lambda(df),
        lambda df: agg_nunique(df),
        lambda df: agg_nunique_nosort(df),
    ],
    labels=['Agg Lambda', 'Agg Nunique', 'Agg Nunique, No sort'],
    n_range=[2 ** k for k in range(20)],
    # Equality check same data, just allow for different sorting
    equality_check=lambda x,y: x.sort_index().compare(y.sort_index()).empty,
    xlabel="~ Number of Groups"
)

在此處輸入圖像描述

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM