遍历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方法。
- 否则,矢量化始终是可取的,因为它要快得多。