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

Posted

技术标签:

【中文标题】如何根据记录中其他 4 个字段的布尔运算符有效地更新数据框中的字段?【英文标题】:How do I efficiently update a field within a dataframe based on boolean operators on 4 other fields in the record? 【发布时间】:2019-09-02 22:14:31 【问题描述】:

我正在分析和总结一个数据集(“报告”)作为 Python pandas 数据框。该表显示了 4 个不同数据集(“Inputs”)之间的匹配过程的结果,这些数据集都应该在同一个键上匹配。

报告 中,每个 输入 都有一个字段,其中包含与基本数据集匹配数 (>=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 的初学者,所以我认为我犯了一个愚蠢的错误。但我也不确定我的期望是否错误,这只是我应该期待的表现。有没有更好的办法?即使有人能指出我正确的方向,我也会很感激!

【问题讨论】:

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

【讨论】:

谢谢,Gio,我无法相信解决方案如此简单和高效。我必须做的唯一更改是删除.cumsum(),因为我实际上想要每条记录的总匹配数,而不是所有记录的累积计数(对不起,如果我的问题不清楚)。 您可以(并且应该)更新我的答案。为遇到同样问题的下一个用户明确解决方案总是有用的 好的,谢谢 Gio - 我不确定,以后会记得的。【参考方案2】:

过去我在迭代数据帧时遇到过类似的问题 - df.iterrows() 乍一看似乎是正确的选择,因为它易于使用,但便利是有代价的。这是a helpful blog,它概述了 pandas 中更有效地迭代的方法。

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

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

编辑

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

如果不存在这样的方法,df.apply 在指定要应用的更复杂的操作时会很有用 - 只是对未来的提示!

编辑二

上面的 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

【讨论】:

谢谢,blog post 确实帮助我更好地理解了一些事情! 谢谢,达戈罗迪尔。我也一直在尝试您的 df.apply() 解决方案,但我不断收到错误并且无法解决。错误是:TypeError: ("'>' not supported between instances of 'str' and 'int'", 'occurred at index 0')。会不会是它试图将列标题与 0 进行比较? 可能是您在问题中指定的列之外的其他列属于字符串类型? apply 中的函数将根据其查看的特定行的所有列执行布尔选择,因此如果有字符串,它们将无法在row > 0 中进行比较。但是,您可以在 np.any()np.sum() 方法中使用 Gio 在他的答案中包含的行 - 请参阅最近的编辑

以上是关于如何根据记录中其他 4 个字段的布尔运算符有效地更新数据框中的字段?的主要内容,如果未能解决你的问题,请参考以下文章

在 CloudKit 仪表板中创建布尔字段

如何根据其他记录的值更新字段

MongoDB v2.4.9 按布尔字段排序

MongoDB v2.4.9 按布尔字段排序

如何跳过有效载荷的第一行-常规

如何在 Gatsby 中根据其他字段删除 GraphQL 字段?