Python-For循环数百万行

时间:2018-09-11 15:44:58

标签: python python-3.x pandas performance vectorization

我有一个数据框c,其中包含许多不同的列。另外,arr是与carr = c[c['A_D'] == 'A']的子集相对应的数据帧。

我的代码的主要思想是遍历c数据框中的所有行,并搜索应该发生某些特定条件的所有可能情况(在arr数据框中):

  • 只需要遍历c['A_D'] == Dc['Already_linked'] == 0的行
  • hour数据框中的arr必须小于hour_aux数据框中的c
  • Already_linked数据帧的列arr必须为零:arr.Already_linked == 0
  • 在c和Terminal数据帧中,Operatorarr必须相同

现在,条件使用布尔索引和groupby get_group进行存储:

  • arr数据帧分组以选择相同的操作员和终端:g = groups.get_group((row.Operator, row.Terminal))
  • 仅选择小时小于c数据帧中的小时并且到达Already_linked == 0的到达:vb = g[(g.Already_linked==0) & (g.hour<row.hour_aux)]

对于c数据框中验证所有条件的每一行,都会创建一个vb数据框。自然地,该数据帧在每次迭代中具有不同的长度。创建vb数据帧之后,我的目标是选择vb数据帧的索引,以最小化vb.START和c [{x]之间的时间。然后,与此索引对应的FightID存储在列c的{​​{1}}数据框中。此外,由于到达已与出发相关联,因此a数据框中的列Already_linked从0更改为1。

请务必注意,arr数据框的列Already_linked可能在每次迭代中都发生变化(并且arr是创建arr.Already_linked == 0数据框的条件之一)。因此,无法并行处理此代码。

我已经使用vb来提高效率,但是由于c.itertuples()具有数百万行,因此此代码仍然很耗时。

其他选择还可以是对每一行使用c。但是,这并不是很简单,因为在每个循环中,pd.applyc中的值都发生变化(而且,我相信即使使用arr也会非常慢)。 / p>

是否有可能在向量化解决方案中将此for循环转换(或将运行时间减少10倍(如果可能,甚至更多))?

初始数据框:

pd.apply

所需的输出(下面的数据框中不包括某些列。仅START END A_D Operator FlightID Terminal TROUND_ID tot 0 2017-03-26 16:55:00 2017-10-28 16:55:00 A QR QR001 4 QR002 70 1 2017-03-26 09:30:00 2017-06-11 09:30:00 D DL DL001 3 " " 84 2 2017-03-27 09:30:00 2017-10-28 09:30:00 D DL DL001 3 " " 78 3 2017-10-08 15:15:00 2017-10-22 15:15:00 D VS VS001 3 " " 45 4 2017-03-26 06:50:00 2017-06-11 06:50:00 A DL DL401 3 " " 9 5 2017-03-27 06:50:00 2017-10-28 06:50:00 A DL DL401 3 " " 19 6 2017-03-29 06:50:00 2017-04-19 06:50:00 A DL DL401 3 " " 3 7 2017-05-03 06:50:00 2017-10-25 06:50:00 A DL DL401 3 " " 32 8 2017-06-25 06:50:00 2017-10-22 06:50:00 A DL DL401 3 " " 95 9 2017-03-26 07:45:00 2017-10-28 07:45:00 A DL DL402 3 " " 58 a列是相关的)

Already_linked

代码:

    START                    END             A_D  Operator  a   Already_linked
0   2017-03-26 16:55:00 2017-10-28 16:55:00 A   QR  0               1
1   2017-03-26 09:30:00 2017-06-11 09:30:00 D   DL  DL402           1
2   2017-03-27 09:30:00 2017-10-28 09:30:00 D   DL  DL401           1
3   2017-10-08 15:15:00 2017-10-22 15:15:00 D   VS  No_link_found   0
4   2017-03-26 06:50:00 2017-06-11 06:50:00 A   DL  0               0
5   2017-03-27 06:50:00 2017-10-28 06:50:00 A   DL  0               1
6   2017-03-29 06:50:00 2017-04-19 06:50:00 A   DL  0               0
7   2017-05-03 06:50:00 2017-10-25 06:50:00 A   DL  0               0
8   2017-06-25 06:50:00 2017-10-22 06:50:00 A   DL  0               0
9   2017-03-26 07:45:00 2017-10-28 07:45:00 A   DL  0               1

初始groups = arr.groupby(['Operator', 'Terminal']) for row in c[(c.A_D == "D") & (c.Already_linked == 0)].itertuples(): try: g = groups.get_group((row.Operator, row.Terminal)) vb = g[(g.Already_linked==0) & (g.hour<row.hour_aux)] aux = (vb.START - row.x).abs().idxmin() c.loc[row.Index, 'a'] = vb.loc[aux].FlightID arr.loc[aux, 'Already_linked'] = 1 continue except: continue c['Already_linked'] = np.where((c.a != 0) & (c.a != 'No_link_found') & (c.A_D == 'D'), 1, c['Already_linked']) c.Already_linked.loc[arr.Already_linked.index] = arr.Already_linked c['a'] = np.where((c.Already_linked == 0) & (c.A_D == 'D'),'No_link_found',c['a']) 数据帧的代码:

c

2 个答案:

答案 0 :(得分:3)

您的问题是,是否有一种向量化for循环的方法,但是我认为这个问题隐藏了您真正想要的东西,这是一种加速代码的简便方法。对于性能问题,始终要进行概要分析。但是,我强烈怀疑代码中的主要操作是.query(row.query_string)。如果arr很大,则为每一行运行该代码将很昂贵。

对于任意查询,如果不消除迭代之间的依赖性并并行化昂贵的步骤,则根本无法真正改善运行时间。不过,您可能会更幸运。您的查询字符串始终检查两个不同的列,以查看它们是否等于您关心的内容。但是,对于需要遍历整个arr的每一行。由于切片每次都会更改,因此可能会引起问题,但是这里有一些建议:

  • 由于无论如何每次都要切片arr,因此请仅维护arr.Already_Linked==0行的视图,以便遍历较小的对象。
  • 更好的是,在执行任何循环之前,应首先按arrTerminalOperator进行分组。然后,而不是遍历所有arr,首先选择所需的组,然后进行切片和过滤。这将需要重新考虑query_string的确切实现,但是这样做的好处是,如果您有许多终端和运算符,则通常要处理比arr小得多的对象。而且,您甚至不必查询该对象,因为那是由groupby隐式完成的。
  • 根据aux.hour通常与row.hour_aux的关系,您可以通过在开头将aux相对于hour进行排序来进行改进。仅仅使用不等式运算符,您可能看不到任何收益,但您可以将其与对数搜索边界点配对,然后将其切成该边界点。
  • 以此类推。同样,我怀疑以任何方式重组您在所有arr 上为每一行进行的查询,都将获得比转换框架或向量化更多的收益点点滴滴。

稍微扩展其中的一些内容,并稍微修改@DJK的代码,看看当我们进行以下更改时会发生什么。

groups = arr.groupby(['Operator', 'Terminal'])

for row in c[(c.A_D == 'D') & (c.Already_linked == 0)].itertuples():
    g = groups.get_group((row.Operator, row.Terminal))
    vb = g[(g.Already_linked==0) & (g.hour<row.hour_aux)]
    try:
        aux = (vb.START - row.x).abs().idxmin()
        print(row.x)
        c.loc[row.Index, 'a'] = vb.loc[aux,'FlightID']
        g.loc[aux, 'Already_linked'] = 1
        continue
    except:
        continue

查询如此之慢的部分原因是因为它每次都在arr的所有内容中进行搜索。相反,.groupby()的执行时间几乎与一个查询相同,但是对于以后的每次迭代,您只需使用.get_group()即可高效地找到数据的一小部分你在乎。

基准测试时的一个有用的(极其粗略的)经验法则是十亿个事物需要一秒钟。如果您看到的时间要比以百万计的事物(例如数百万行)所度量的时间长得多,则意味着对于每一行,您要进行大量的工作来完成数十亿次操作。这就为更好的算法减少操作数量留下了巨大的潜力,而矢量化实际上只能带来恒定的因子改进(对于许多字符串/查询操作,甚至还没有很大的改进)。

答案 1 :(得分:3)

虽然这不是一个专门的解决方案,但是如果您的样本数据集模仿您的真实数据集,它应该可以加快速度。当前,您正在浪费时间遍历每一行,但是您只关心遍历['A_D'] == 'D'['Already_linked'] ==0的行。取而代之的是删除if,然后在截短的数据帧(仅占初始数据帧的30%)上循环

for row in c[(c.A_D == 'D') & (c.Already_linked == 0)].itertuples():

    vb = arr[(arr.Already_linked == 0) & (arr.hour < row.hour_aux)].copy().query(row.query_string)
    try:
        aux = (vb.START - row.x).abs().idxmin()
        print(row.x)
        c.loc[row.Index, 'a'] = vb.loc[aux,'FlightID']
        arr.loc[aux, 'Already_linked'] = 1
        continue
    except:
        continue