遍曆Pandas DataFrames可能非常慢,本文將向您展示一些加速辦法,可將性能提升成千上萬倍!
如果您使用Python和Pandas進行數據分析,即使對於小型DataFame,使用標準Python循環也是很費時間的,而對於大型DataFrame則需要花費特別長的時間。有什麽方法可以優化呢?西麵來看看不同遍曆方法的性能
標準循環
DataFrame(數據幀)是具有行和列的Pandas對象(objects)。如果使用循環,則將遍曆整個對象。 Python無法利用任何內置函數,而且速度非常慢。在我們的示例中,我們獲得了一個具有65列和1140行的DataFrame(數據框)。它包含2016-2019賽季的足球成績。我們要創建一個新列,以指示特定球隊是否參加過平局。我們可以這樣開始:
def soc_loop(leaguedf,TEAM,):
leaguedf['Draws'] = 99999
for row in range(0, len(leaguedf)):
if ((leaguedf['HomeTeam'].iloc[row] == TEAM) & (leaguedf['FTR'].iloc[row] == 'D')) | \
((leaguedf['AwayTeam'].iloc[row] == TEAM) & (leaguedf['FTR'].iloc[row] == 'D')):
leaguedf['Draws'].iloc[row] = 'Draw'
elif ((leaguedf['HomeTeam'].iloc[row] == TEAM) & (leaguedf['FTR'].iloc[row] != 'D')) | \
((leaguedf['AwayTeam'].iloc[row] == TEAM) & (leaguedf['FTR'].iloc[row] != 'D')):
leaguedf['Draws'].iloc[row] = 'No_Draw'
else:
leaguedf['Draws'].iloc[row] = 'No_Game'
由於我們在DataFrame(數據框架)中獲得了英超聯賽的每場比賽,因此我們必須檢查感興趣的球隊(阿森納)是否參加過比賽,以及這是否適用,他們是主隊還是客隊。如您所見,此循環非常慢,執行時間為20.7秒。讓我們看看如何提高效率。
Pandas內置函數: iterrows() —快321倍
在第一個示例中,我們遍曆了整個DataFrame。iterrows()
為每行返回一個Series,因此將DataFrame迭代為一對索引,將感興趣的列作為Series進行迭代。這使其比標準循環更快:
def soc_iter(TEAM,home,away,ftr):
#team, row['HomeTeam'], row['AwayTeam'], row['FTR']
if [((home == TEAM) & (ftr == 'D')) | ((away == TEAM) & (ftr == 'D'))]:
result = 'Draw'
elif [((home == TEAM) & (ftr != 'D')) | ((away == TEAM) & (ftr != 'D'))]:
result = 'No_Draw'
else:
result = 'No_Game'
return result
該代碼花了68毫秒來運行,比標準循環快321倍。但是,許多人建議不要使用它,原因是:還有更快的選擇和iterrows()
不跨行保留dtype。這意味著如果您使用iterrows()
可以更改DataFrame上的dtypes,這可能會導致很多問題。要保留的話,您也可以使用的dtypesitertuples()
。在這裏我們將不做詳細介紹,因為我們要關注效率。您可以在這裏找到官方文檔:pandas.DataFrame.itertuples-pandas 0.25.1文檔
apply()方法-快811倍
apply
本身並不快,但與DataFrames結合使用時具有優勢。這取決於內容的apply
表達。如果可以在Cython空間執行apply則
快得多(這裏就是這種情況)。
我們可以用apply
與一個Lambda
功能。我們要做的就是指定軸。在這種情況下,我們必須使用axis=1
因為我們要執行按列(column-wise)操作:
此代碼比以前的方法更快,需要27毫秒完成。
Pandas 向量化—快9280倍
我們利用向量化的優勢來創建真正快速的代碼。關鍵是要避免像之前的示例中那樣的Python級別的循環,而要使用優化的C代碼,該代碼可以更有效地使用內存。隻需要稍微修改一下函數:
def soc_iter(TEAM,home,away,ftr):
df['Draws'] = 'No_Game'
df.loc[((home == TEAM) & (ftr == 'D')) | ((away == TEAM) & (ftr == 'D')), 'Draws'] = 'Draw'
df.loc[((home == TEAM) & (ftr != 'D')) | ((away == TEAM) & (ftr != 'D')), 'Draws'] = 'No_Draw'
現在我們可以用Pandas係列創建新的列:
在這種情況下,我們甚至不需要循環。我們要做的就是調整函數的內容。現在我們可以直接將Pandas係列傳遞給我們的函數,這會帶來巨大的速度提升。
Numpy 向量化 —快71,803倍
在前麵的示例中,我們將Pandas係列傳遞給了函數。通過添加.values,我們得到一個Numpy數組:
numpy數組之所以如此快,是因為我們獲得了引用局部性的好處。我們的代碼運行了0,305毫秒,比前文使用的標準循環快71803倍。
結論
如果您使用Python,Pandas和Numpy進行數據分析,那麽總會有一些空間可以改進您的代碼性能。我們比較了五種不同的方法,它們根據一些計算在DataFrame中添加新列。我們注意到速度方麵的巨大差異:
如果您從本文中學會了如下兩個規則,我將很高興:
- 如果確定需要使用循環,則應始終選擇apply方法。
- 否則,矢量化始終是可取的,因為它要快得多。