当前位置: 首页>>技术教程>>正文


Pandas DataFrame遍历加速/性能优化

遍历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中添加新列。我们注意到速度方面的巨大差异:

如果您从本文中学会了如下两个规则,我将很高兴:

  1. 如果确定需要使用循环,则应始终选择apply方法。
  2. 否则,矢量化始终是可取的,因为它要快得多。

参考资料

本文由《纯净天空》出品。文章地址: https://vimsky.com/article/4327.html,未经允许,请勿转载。