来自C的背景(几年前),而且对Python非常陌生,我正在努力优化我的代码。从字面上讲,for循环非常慢。在
在下面的代码中,向Dict中的每个数据帧添加计算列的循环似乎是一个巨大的瓶颈。在
我已经读了很多关于解决这个问题的方法,比如;Vectorisation和Numba,但我认为我对Python了解不够,无法真正理解和使用它们。在
事实上,我对这两种方法的尝试都失败了,可能是不正确的实现,除了我用过的一个测试np.哪里. 这向我展示了我的for循环/计算有多糟糕。在
我将在我的工作示例中省略这些尝试,但如果需要,可以在以后添加:
import pandas as pd
import numpy as np
import datetime as date
import itertools
def points(row):
val = 0
if row['Ob2'] > 0.5:
foo = row['Ob3'] - row['Ob1']
if foo < 0.1:
val = 1 - foo
else:
val = 0
return val
print("Start: "+ str(date.datetime.now()))
print()
player_list = ['player' + str(x) for x in range(1,71)]
data = pd.DataFrame({'Names': player_list*1000,\
'Ob1' : np.random.rand(70000),\
'Ob2' : np.random.rand(70000) ,\
'Ob3' : np.random.rand(70000)})
#create list of unique pairs
comboNames = list(itertools.combinations(data.Names.unique(), 2))
#create a data frame dictionary to store your data frames
DataFrameDict = {elem : pd.DataFrame for elem in comboNames}
for key in DataFrameDict.keys():
DataFrameDict[key] = data[:][data.Names.isin(key)]
DataFrameDict[key] = DataFrameDict[key].sort_values(['Ob1'])
print("DF fill: "+ str(date.datetime.now()))
print()
#Add test calculated column
for tbl in DataFrameDict:
DataFrameDict[tbl]['Test'] = DataFrameDict[tbl].apply(points, axis=1) #Slow loop
#example vectorised, hugh dif is run time
#DataFrameDict[tbl]['Test'] = np.where((DataFrameDict[tbl]['Ob2']>0.5),1,0)
print("Calc'd: "+ str(date.datetime.now()))
print()
headers = ['Player1','Player2','Score','Count']
summary = pd.DataFrame(([tbl[0], tbl[1], DataFrameDict[tbl]['Test'].sum(),
DataFrameDict[tbl]['Test'].astype(bool).sum(axis=0)] for tbl in DataFrameDict),
columns=headers).sort_values(['Score'], ascending=[False])
print("Fin: "+ str(date.datetime.now()))
print()
编辑:该函数添加一列,该列是每个df中两个“players”的比较,因此我们/我无法将其应用于源数据源。很抱歉没有说清楚。
显然,我需要回溯并学习一些Python基础知识,但是我的老板正在等待真正的脚本,它花了3个小时运行一个标准的500个“名称”(125K个数据帧)。在
如果有人能帮我优化它,我将不胜感激!在
EDIT2:更好地表示现实世界中的问题
^{pr2}$我的Solution由于混乱,不想在这里发帖。在
我尽量保留你的代码。我把你的功能改成了np.哪里而不是apply,并在创建dict之前添加了test列,因为正如我在评论中所表达的那样,在那时执行apply没有任何意义。在
使用
%%timeit
时,每个循环得到26.2 s±1.15 s(平均值±标准偏差,7次运行,每个循环1次)编辑:
这是我最快的速度:
^{pr2}$我的目标是不使用循环或dicts来进一步提高速度。在
我的函数ScoreAndCount返回每个玩家的分数和计数。这个帕金森病获取函数的返回值并将其添加到初始df中。在
然后,我使用了itertools组合,并将其作为自己的数据帧,称为summary。然后,我将summary df的player1和player2列与原始df中的names列合并。在
下一步,我把玩家的分数和计数加起来,去掉不必要的列,然后进行排序。我最后每圈157ms。最慢的步骤是concat和merge,但是我想不出办法绕过它们,进一步提高速度。在
编辑3
我们将为两个测试设置一个种子并使用相同的数据df:
接下来我们将使用您的确切代码,并检查player1和player2之间的dict。在
接下来,我们将执行您在摘要中所做的操作,并获取测试列的总和,这将是player1和player2生成的分数
所以我们得到了8.0774。现在如果我说的是真的,如果我们在Edit2中编写代码,那么player1和player2之间的分数将是8.077。在
现在我们将使用player1和player2检查行
如您所见,我通过edit2从player1player2计算出的分数与您在代码中所做的完全相同。在
我可以用numba对函数进行矢量化,结果代码用%%timeit在~8秒内运行。我听从了本·帕普的建议,事先计算了测试柱。我还预先对值进行了排序,并整理了DataFrameDict的创建。在
每个回路8.52 s±204 ms(7次运行的平均值±标准偏差,每个回路1次)
我关注您的函数
point
和调用apply
的for循环。在函数
Point
可以转换成这个条件(a_df
是DataFrameDict
中的每个数据帧):在这种情况下,将值
^{pr2}$1 - x['Ob3'] + x['Ob1']
分配给Test
列。其他所有操作都将0分配给Test
。所以,让我们将新列Test
分配给每个a_df
。然后,只过滤符合上述条件的行,以缩小数据集的范围并为此子集设置新值。最后,将这个子集Test
列值更新回一个_df['Test'],并将其分配回DataFrameDict
字典。所以,你的for循环将变成:这跑得很快
输出:根据指定的条件,
DataFrameDict
列的每个数据帧都填充了Test
列。我从DataFrameDict
随机选取一个最终数据帧来显示输出。在相关问题 更多 >
编程相关推荐