我正在分析和汇总一个数据集(“ 报告”)作为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的初学者,所以我认为自己犯了一个愚蠢的错误。但是我也不确定我的期望是否是错误的,而这仅仅是我应该期望的表现。有没有更好的办法?即使有人可以将我指向正确的方向,我也将不胜感激!
答案 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()
一个接一个:
我们首先检查(对于每一行)指定列中的值是否大于零。这将返回布尔值True
或False
,我们将其转换为整数.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.loc
或df.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的回答显示了一个我认为使用熊猫的好习惯的原则-如果存在可以直接在数据帧上运行的方法(例如sum
,cumsum
),请尝试并利用它们总是更快。
在不存在此类方法的地方,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