簡體   English   中英

提高大熊貓的表現

[英]Improving the performance of pandas groupby

我有一個用Python編寫的機器學習應用程序,它包括一個數據處理步驟。 當我編寫它時,我最初在Pandas DataFrames上進行數據處理,但是當這導致糟糕的性能時,我最終使用vanilla Python重寫了它,使用for循環而不是矢量化操作以及列表和dicts而不是DataFrames和Series。 令我驚訝的是,用vanilla Python編寫的代碼的性能最終遠遠高於使用Pandas編寫的代碼。

由於我的手寫代碼數據處理代碼比原始的Pandas代碼更大更亂,我還沒有完全放棄使用Pandas,而我正在嘗試優化Pandas代碼而不會取得太大成功。

數據處理步驟的核心包括以下內容:我首先將行分成幾組,因為數據包含數千個時間序列(每個“個體”一個),然后我對每個組執行相同的數據處理:很多摘要,將不同的列組合成新的列等。

我使用Jupyter Notebook的lprun描述了我的代碼,大部分時間花在了以下和其他類似的行上:

grouped_data = data.groupby('pk')
data[[v + 'Diff' for v in val_cols]] = grouped_data[val_cols].transform(lambda x: x - x.shift(1)).fillna(0)
data[[v + 'Mean' for v in val_cols]] = grouped_data[val_cols].rolling(4).mean().shift(1).reset_index()[val_cols]
(...)

...矢量化和非矢量化處理的混合。 我知道非矢量化操作不會比我的手寫循環更快,因為這基本上是他們在幕后的東西,但他們怎么會這么慢 我們談論的是我的手寫代碼和Pandas代碼之間的性能下降了10-20倍。

我做的非常非常錯嗎?

不,我認為你不應該放棄大熊貓。 肯定有更好的方法來做你想做的事情。 訣竅是盡可能避免以任何形式apply / transform 像瘟疫一樣避免它們。 它們基本上實現為for循環,所以你也可以直接使用python for循環,它以C速度運行並為你提供更好的性能。

真正的速度增益是你擺脫循環並使用pandas的函數隱藏矢量化操作的地方。 例如,正如我很快向您展示的那樣,您的第一行代碼可以大大簡化。

在這篇文章中,我概述了設置過程,然后,對於問題中的每一行,提供一個改進,以及時間和正確性的並排比較。

設定

data = {'pk' : np.random.choice(10, 1000)} 
data.update({'Val{}'.format(i) : np.random.randn(1000) for i in range(100)})

df = pd.DataFrame(data)
g = df.groupby('pk')
c = ['Val{}'.format(i) for i in range(100)]

transform + sub + shiftdiff

您的第一行代碼可以替換為簡單的diff語句:

v1 = df.groupby('pk')[c].diff().fillna(0)

完整性檢查

v2 = df.groupby('pk')[c].transform(lambda x: x - x.shift(1)).fillna(0)

np.allclose(v1, v2)
True

性能

%timeit df.groupby('pk')[c].transform(lambda x: x - x.shift(1)).fillna(0)
10 loops, best of 3: 44.3 ms per loop

%timeit df.groupby('pk')[c].diff(-1).fillna(0)
100 loops, best of 3: 9.63 ms per loop

刪除冗余索引操作

就你的第二行代碼而言,我沒有看到太大的改進空間,但如果你的groupby語句不考慮pk作為索引,你可以擺脫reset_index() + [val_cols]調用:

g = df.groupby('pk', as_index=False)

然后你的第二行代碼減少到:

v3 = g[c].rolling(4).mean().shift(1)

完整性檢查

g2 = df.groupby('pk')
v4 = g2[c].rolling(4).mean().shift(1).reset_index()[c]

np.allclose(v3.fillna(0), v4.fillna(0))
True

性能

%timeit df.groupby('pk')[c].rolling(4).mean().shift(1).reset_index()[c]
10 loops, best of 3: 46.5 ms per loop

%timeit df.groupby('pk', as_index=False)[c].rolling(4).mean().shift(1)
10 loops, best of 3: 41.7 ms per loop

請注意,不同計算機上的計時時間會有所不同,因此請確保徹底測試您的代碼,以確保數據確實有所改進。

雖然這次的差異不是很大,但你可以欣賞這樣一個事實:你可以做出改進! 這可能會對更大的數據產生更大的影響。


后記

總之,大多數操作都很慢,因為它們可以加快速度。 關鍵是要擺脫任何不使用矢量化的方法。

為此,走出大熊貓空間並步入笨拙的空間有時是有益的。 numpy數組或使用numpy的操作往往比pandas等效快得多(例如, np.sumpd.DataFrame.sum快,而np.wherepd.DataFrame.where快,依此類推)。

有時,循環無法避免。 在這種情況下,您可以創建一個基本的循環函數,然后您可以使用numba或cython進行矢量化。 這方面的例子就是提高性能 ,直接來自馬口。

在其他情況下,您的數據太大而無法合理地適應numpy數組。 在這種情況下,是時候放棄並切換到daskspark ,這兩者都提供了高性能的分布式計算框架來處理大數據。

暫無
暫無

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

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