如何基于记录中其他4个字段上的布尔运算符有效地更新数据框内的字段?

时间:2019-04-12 07:36:10

标签: python pandas performance dataframe

我正在分析和汇总一个数据集(“ 报告”)作为Python熊猫数据框。该表显示了应该在同一键上匹配的4个不同数据集(“ 输入”)之间匹配过程的结果。

报告中,每个输入都有一个字段,该字段具有与基本数据集匹配数(> = 0)的计数器。我想更新报告中的字段以指示有多少数据集与基本数据匹配(“ matchCounter ”),因此对于任意数量的成功匹配(即> 0), matchCounter 应该以1为增量,最大为4(即所有四个数据集都与基本数据匹配)。

我在Jupyter笔记本上开发了一个大约100'000条记录的小型数据集的过程,尽管我成功地更新了 matchCounter 字段,但我怀疑它花费的时间比原本要长。完整的数据集是10'000'000条记录,根据我的粗略计算,完成当前代码需要8个多小时(我认为这是一个非常简单的操作)。

我已经阅读了一些有关提高数据帧(Pandas DataFrame performance)性能的内容,但是由于我要依次遍历各行,并且if语句是针对行中的项目而不是对数据框,我不知道这是否适用。

这里是代码的摘要版本。第一个for循环是造成瓶颈的一个:

import numpy as np
import pandas as pd

df = pd.read_csv(fileIn, header=0)

df['match_count']= 0
df['exclude']= False

# This for loop takes 300+ seconds to execute 100'000 times     
for index, row in df.iterrows():
    matchCounter = 0
    if row['in_deeds'] > 0:
        matchCounter += 1
    if row['in_valuation'] > 0:
        matchCounter += 1
    if row['in_property'] > 0:
        matchCounter += 1
    if row['in_sg'] > 0:
        matchCounter += 1
    df.loc[index,'match_count'] = matchCounter

# This for loop takes only 11.75 seconds
i=0
for index, row in df.iterrows():
    if "EXCL" in row['stat_deeds'].upper():
        i=i+1
        df.loc[index,'exclude']=True
    elif "EXCL" in row['stat_valuation'].upper():
        i=i+1
        df.loc[index,'exclude']=True
    elif "EXCL" in row['stat_property'].upper():
        i=i+1
        df.loc[index,'exclude']=True
    elif "EXCL" in row['stat_sg'].upper():
        i=i+1
        df.loc[index,'exclude']=True

df = df.query('exclude == False')

这是我第一次与Pandas合作,而且我也是Python的初学者,所以我认为自己犯了一个愚蠢的错误。但是我也不确定我的期望是否是错误的,而这仅仅是我应该期望的表现。有没有更好的办法?即使有人可以将我指向正确的方向,我也将不胜感激!

2 个答案:

答案 0 :(得分:1)

OP评论后更新:

df['match_count']=(df[['in_deeds','in_valuation','in_property','in_sg']]>0).astype(int).sum(axis=1)

以下还将通过获取匹配计数的累积总和来提供每个点(每行)的匹配总数。

df['match_count']=(df[['in_deeds','in_valuation','in_property','in_sg']]>0).astype(int).sum(axis=1).cumsum()

一个接一个

我们首先检查(对于每一行)指定列中的值是否大于零。这将返回布尔值TrueFalse,我们将其转换为整数.astype(int)

df[['in_deeds','in_valuation','in_property','in_sg']]>0).astype(int)

然后,我们对.sum(axis=1)每行的值进行求和。
这将返回单列,在每一行中,我们知道满足多少条件(>0)。

我们最终取各行的累积总和来获得(每行)匹配的总数。

我们最终在原始数据帧df['match_count']=中创建一个新列df,并将结果分配给该列。

答案 1 :(得分:1)

过去,我在遍历数据帧时也遇到过类似的问题-df.iterrows()乍一看由于易于使用而似乎是正确的选择,但便利性是有代价的。 a helpful blog概述了熊猫中可以更有效地进行迭代的方法。

结果是-不要使用iterrows。通常,可以通过使用索引作为迭代器,然后使用df.locdf.iloc来访问数据帧的行,如下所示:

for i in df.index:
  print(df.loc[i, :])

使用df.apply

通过apply方法,您可以将用户定义的函数应用于数据框的所有列或行。尽管此处的用法可能有些不直观,但它是迄今为止最快的方法:

import numpy as np
import pandas as pd

def counter(row):

    if np.any(row[row > 0]):
        return np.sum(row[row > 0])
    else:
        return 0

N = 100000

df = pd.DataFrame({'A': np.random.randint(0, 2, N),
                   'B': np.random.randint(0, 2, N),
                   'C': np.random.randint(0, 2, N),
                   'D': np.random.randint(0, 2, N)})

df['match-count'] = df.apply(counter, axis=1, raw=True)

此处,函数将检查数据帧的每个(由axis=1指定);如果布尔选择np.any不为空,则True返回row[row > 0],这时布尔选择用np.sum减少以获得最终计数。我们将raw关键字参数设为True,以便传递原始numpy数组,该数组应用于归约运算(如sum)中以提高性能(请参见docs

这大约需要1.2秒才能在我的计算机上运行。

编辑

Gio的回答显示了一个我认为使用熊猫的好习惯的原则-如果存在可以直接在数据帧上运行的方法(例如sumcumsum),请尝试并利用它们总是更快。

在不存在此类方法的地方,df.apply在指定要应用的更复杂的操作时很有用-只是将来的提示!

编辑II

上面带有apply的示例假定在布尔选择中使用了数据框内的所有列。如果只有特定的列具有需要用于计数器的数值,请在counter方法中使用Gio的建议:

def counter(row):

    selection = row[['in_deeds', 'in_valuation', 'in_property', 'in_sg']] > 0

    if np.any(selection):
        return np.sum(selection)
    else:
        return 0