如何根据记录中其他 4 个字段的布尔运算符有效地更新数据帧中的字段?



我正在分析和总结一个数据集(">报告")作为Python pandas数据帧。该表指示 4 个不同数据集(">输入")之间的匹配过程的结果,这些数据集应全部匹配在同一键上。

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

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

我已经阅读了一些关于提高数据帧性能(Pandas DataFrame 性能)的文章,但由于我按顺序迭代行,并且 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的初学者,所以我认为我犯了一个愚蠢的错误。但我也不确定我的期望是不是错的,这只是我应该期待的表现。有没有更好的方法?即使有人能为我指出正确的方向,我也会不胜感激!

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中创建一个新的列df['match_count']=,并将结果分配给该列。

我过去在迭代数据帧时遇到过类似的问题 - 由于易于使用,乍一看df.iterrows()似乎是正确的选择,但便利是有代价的。这里有一个有用的博客,概述了熊猫中更有效地迭代的方法。

结果是 - 不要使用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指定); 如果布尔选择row[row > 0]不为空,则np.any返回True,此时布尔选择将减少np.sum以获得最终计数。我们将raw关键字参数作为True,以便传递原始numpy数组,该数组应该用于缩减操作(如总和)以提高性能(请参阅文档)。

在我的机器上运行大约需要 1.2 秒。

编辑

Gio的回答展示了一个原则,我认为在使用熊猫时是很好的做法 - 如果存在可以直接对数据帧进行操作的方法(例如sumcumsum),请尝试利用这些方法,因为它们总是更快。

在不存在此类方法的情况下,如果指定要应用的更复杂的操作,df.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

最新更新