[英]How many function calls when I use pandas DataFrame groupby and then apply user-defined function?
[英]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.array
和pd.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.