Pandas循環(huán)提速7萬多倍!Python數(shù)據(jù)分析哪種更快?
本文經(jīng)AI新媒體量子位(公眾號ID:QbitAI)授權(quán)轉(zhuǎn)載,轉(zhuǎn)載請聯(lián)系出處。
用Python和Pandas進行數(shù)據(jù)分析,很快就會用到循環(huán)。
但在這其中,就算是較小的DataFrame,使用標(biāo)準(zhǔn)循環(huán)也比較耗時。
遇到較大的DataFrame時,需要的時間會更長,會讓人更加頭疼。
現(xiàn)在,有人忍不了了。他是一位來自德國的數(shù)據(jù)分析師,名叫Benedikt Droste。
他說,當(dāng)自己花了大半個小時等待代碼執(zhí)行的時候,決定尋找速度更快的替代方案。
在給出的替代方案中,使用Numpy向量化,與使用標(biāo)準(zhǔn)循環(huán)相比,速度提升了71803倍。

他是怎么實現(xiàn)的?我們一起來看看~
標(biāo)準(zhǔn)循環(huán)處理3年足球賽數(shù)據(jù):20.7秒
DataFrame是具有行和列的Pandas對象。如果使用循環(huán),需要遍歷整個對象。
Python不能利用任何內(nèi)置函數(shù),而且速度很慢。在Benedikt Droste的提供的示例中,是一個包含65列和1140行的Dataframe,包含了2016-2019賽季的足球賽結(jié)果。
需要解決的問題是:創(chuàng)建一個新的列,用于指示某個特定的隊是否打了平局。可以這樣開始:
- 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'

在這個案例中是阿森納,在實現(xiàn)目標(biāo)之前要確認(rèn)阿森納參加了哪些場比賽,是主隊還是客隊。但使用標(biāo)準(zhǔn)循環(huán)非常慢,執(zhí)行時間為20.7秒。
那么,怎么才能更有效率?
Pandas 內(nèi)置函數(shù): iterrows ()ー快321倍
在第一個示例中,循環(huán)遍歷了整個DataFrame。iterrows()為每一行返回一個Series,它以索引對的形式遍歷DataFrame,以Series的形式遍歷感興趣的列。這使得它比標(biāo)準(zhǔn)循環(huán)更快:
- 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毫秒,比標(biāo)準(zhǔn)循環(huán)快321倍。但是,許多人建議不要使用它,因為仍然有更快的選項,而且iterrows()不能跨行保存dtype。
這意味著,如果你在DataFrame dtypes上使用iterrows(),可以更改它,但這會導(dǎo)致很多問題。
一定要保存dtypes的話,你還可以使用itertuples()。這里我們不詳細(xì)討論 ,你可以在這里找到官方文件:
https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.itertuples.html
apply ()方法ー快811倍
apply 本身并不快,但與DataFrame結(jié)合使用時,它具有優(yōu)勢。這取決于 apply 表達(dá)式的內(nèi)容。如果可以在 Cython 空間中執(zhí)行,那么apply要快得多,這里的示例就是這種情況。
大家可以在Lambda函數(shù)中使用apply。所要做的就是指定這個軸。在本文的示例中,想要執(zhí)行按列操作,要使用 axis 1:

這段代碼甚至比之前的方法更快,完成時間為27毫秒。
Pandas向量化—快9280倍
此外,也可以利用向量化的優(yōu)點來創(chuàng)建非常快的代碼。
重點是避免像之前的示例中的Python級循環(huán),并使用優(yōu)化后的C語言代碼,這將更有效地使用內(nèi)存。只需要稍微修改一下函數(shù):
- 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'
現(xiàn)在,可以用 Pandas 列作為輸入創(chuàng)建新列:

在這種情況下,甚至不需要循環(huán)。所要做的就是調(diào)整函數(shù)的內(nèi)容。現(xiàn)可以直接將Pandas 列傳遞給函數(shù),從而獲得巨大的速度增益。
Numpy向量化—快71803倍
在上面的示例中,將將Pandas 列傳遞給函數(shù)。通過添加.values,可以得到一個Numpy數(shù)組:

因為引用了局部性的好處,Numpy數(shù)組的速度非常快,代碼運行時間僅為0.305毫秒,比一開始使用的標(biāo)準(zhǔn)循環(huán)快71803倍。
誰更強一目了然
最后,Benedikt Droste對上述方案進行了總結(jié)。
他說,如果你使用Python、Pandas和Numpy進行數(shù)據(jù)分析,總會有改進代碼的空間。
在對上述五種方法進行比較之后,哪個更快一目了然:

從這個圖中,可以得出兩個結(jié)論:
1、如果要使用循環(huán),則應(yīng)始終選擇apply方法。
2、否則,使用向量化是最好的,因為它更快!


































