簡體   English   中英

在 Pandas/Numpy 中最有效地使用 groupby-apply 和用戶定義的函數

[英]Most efficient use of groupby-apply with user-defined functions in Pandas/Numpy

我缺少有關在 Pandas 或 Numpy 中的 groupby-apply 設置中使用用戶定義函數的最有效(閱讀:最快)方式的信息。 我已經做了一些自己的測試,但想知道是否還有其他我還沒有遇到的方法。

以DataFrame為例:

import numpy as np
import pandas as pd

idx = pd.MultiIndex.from_product([range(0, 100000), ["a", "b", "c"]], names = ["time", "group"])
df = pd.DataFrame(columns=["value"], index = idx)

np.random.seed(12)
df["value"] = np.random.random(size=(len(idx),))

print(df.head())

               value
time group          
0    a      0.154163
     b      0.740050
     c      0.263315
1    a      0.533739
     b      0.014575

我想計算(例如,下面可以是任意用戶定義的函數)每組隨時間變化的百分比。 我可以在純 Pandas 實現中執行此操作,如下所示:

def pct_change_pd(series, num):
    return series / series.shift(num) - 1

out_pd = df.sort_values(['group', 'time']).groupby(["group"]).apply(pct_change_pd, num=1)

但我也可以修改 function 並將其應用於 numpy 數組:

def shift_array(arr, num, fill_value=np.nan):
    if num >= 0:
        return np.concatenate((np.full(num, fill_value), arr[:-num]))
    else:
        return np.concatenate((arr[-num:], np.full(-num, fill_value)))
    
def pct_change_np(series, num):
    idx = series.index

    arr = series.values.flatten()
    arr_out = arr / shift_array(arr, num=num) - 1
    return pd.Series(arr_out, index=idx)

out_np = df.sort_values(['group', 'time']).groupby(["group"]).apply(pct_change_np, num=1)
out_np = out_np.reset_index(level=2, drop=True)

從我的測試來看,numpy 方法似乎更快,即使它在np.arraypd.Series之間轉換的額外開銷也是如此。

Pandas:

%%timeit
out_pd = df.sort_values(['group', 'time']).groupby(["group"]).apply(pct_change_pd, num=1)

113 ms ± 548 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Numpy:

%%timeit
out_np = df.sort_values(['group', 'time']).groupby(["group"]).apply(pct_change_np, num=1)
out_np = out_np.reset_index(level=2, drop=True)

94.7 ms ± 642 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

隨着索引的增長和用戶定義的 function 變得更加復雜,Numpy 實現將繼續超越 Pandas 實現。 但是,我想知道是否有其他方法可以更快地獲得類似結果。 我特別關注另一種(更有效的)groupby-apply 方法,它允許我使用任意用戶定義的 function,而不僅僅是計算百分比變化的所示示例。 很高興聽到它們是否存在!

通常游戲的名稱是嘗試使用工具箱中的任何功能(通常經過優化和 C 編譯),而不是應用您自己的純 Python ZC1C425268E68385D1AB5074C17A94F。 例如,一種替代方法是:

def f1(df, num=1):
    grb_kwargs = dict(sort=False, group_keys=False)  # avoid redundant ops
    z = df.sort_values(['group', 'time'])
    return z / z.groupby('group', **grb_kwargs).transform(pd.Series.shift, num) - 1

這比.groupby('group').apply(pct_change_pd, num=1)快大約 32%。 在您的系統上,它將產生大約 85 毫秒。

然后,有一個技巧是對整個df進行“昂貴的”計算,但掩蓋了其他組溢出的部分:

def f2(df, num=1):
    grb_kwargs = dict(sort=False, group_keys=False)  # avoid redundant ops
    z = df.sort_values(['group', 'time'])
    z2 = z.shift(num)
    gid = z.groupby('group', **grb_kwargs).ngroup()
    z2.loc[gid != gid.shift(num)] = np.nan
    return z / z2 - 1

那個速度要快 2.1 倍(在您的系統上大約是 52.8 毫秒)。

最后,當沒有辦法找到一些向量化的 function 直接使用時,你可以使用 numba 來加速你的代碼(然后可以用循環編寫你的心臟內容)......一個經典的例子是帶有大寫字母的累積和,就像在這個 SO 帖子這個帖子中一樣。

你的第一個 function 和 using.apply() 給了我這個結果:

In [42]: %%timeit
    ...: out_pd = df.sort_values(['group', 'time']).groupby(["group"]).apply(pct_change_pd, num=1)
155 ms ± 887 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

使用組,時間達到 56 毫秒。

%%timeit
num=1
outpd_list = []
for g in dfg.groups.keys():
    gc = dfg.get_group(g)
    outpd_list.append(gc['value'] / gc['value'].shift(num) - 1)
out_pd = pd.concat(outpd_list, axis=0)

56 ms ± 821 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

如果您將上述代碼中的這一行更改為使用內置 function 您可以節省更多時間

outpd_list.append(gc['value'].pct_change(num))
41.2 ms ± 283 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

暫無
暫無

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

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