web-dev-qa-db-ja.com

Python-ループの数百万行

データフレームcにはさまざまな列があります。また、arrは、cのサブセットに対応するデータフレームです:_arr = c[c['A_D'] == 'A']_。

私のコードの主なアイデアは、c- dataframe内のすべての行を反復処理し、特定の条件が発生する可能性のあるすべてのケース(arr dataframe内)を検索することです。

  • _c['A_D'] == D_および_c['Already_linked'] == 0_であった行のみを反復処理する必要があります
  • hourデータフレームのarrは、cデータフレームの_hour_aux_より小さくなければなりません
  • arrデータフレームの_Already_linked_列はゼロでなければなりません:_arr.Already_linked == 0_
  • TerminalOperatorは、cおよびarrデータフレームで同じである必要があります

現在、条件はブールインデックスと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.START_とc [vb]の間の時間を最小にするxデータフレームのインデックスを選択することです。このインデックスに対応するFightIDは、列caデータフレームに保存されます。さらに、到着は出発にリンクされていたため、arrデータフレームの列_Already_linked_は0から1に変更されます。

arrデータフレームの列_Already_linked_は、繰り返しごとに変化する可能性があることに注意することが重要です(_arr.Already_linked == 0_はvbデータフレームを作成する条件の1つです) 。したがって、このコードを並列化することはできません。

効率のために既にc.itertuples()を使用しましたが、cには数百万の行があるため、このコードは依然として時間がかかりすぎます。

他のオプションは、すべての行に_pd.apply_を使用することです。それにもかかわらず、各ループでcarrの両方で値が変化するため、これは本当に簡単ではありません(また、_pd.apply_でも非常に遅いと思います) 。

ベクトル化されたソリューションでこれをforループに変換する方法はありますか(または実行時間を10倍(可能であればさらに)短縮してください)?

初期データフレーム:

_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データフレームのコード:

_import numpy as np
import pandas as pd
import io

s = '''
 A_D     Operator     FlightID    Terminal   TROUND_ID   tot
 A   QR  QR001   4   QR002       70
 D   DL  DL001   3   "        "  84
 D   DL  DL001   3   "        "  78
 D   VS  VS001   3   "        "  45
 A   DL  DL401   3   "        "  9
 A   DL  DL401   3   "        "  19
 A   DL  DL401   3   "        "  3
 A   DL  DL401   3   "        "  32
 A   DL  DL401   3   "        "  95
 A   DL  DL402   3   "        "  58
'''

data_aux = pd.read_table(io.StringIO(s), delim_whitespace=True)
data_aux.Terminal = data_aux.Terminal.astype(str)
data_aux.tot= data_aux.tot.astype(str)

d = {'START': ['2017-03-26 16:55:00', '2017-03-26 09:30:00','2017-03-27 09:30:00','2017-10-08 15:15:00',
           '2017-03-26 06:50:00','2017-03-27 06:50:00','2017-03-29 06:50:00','2017-05-03 06:50:00',
           '2017-06-25 06:50:00','2017-03-26 07:45:00'], 'END': ['2017-10-28 16:55:00' ,'2017-06-11 09:30:00' ,
           '2017-10-28 09:30:00' ,'2017-10-22 15:15:00','2017-06-11 06:50:00' ,'2017-10-28 06:50:00', 
           '2017-04-19 06:50:00' ,'2017-10-25 06:50:00','2017-10-22 06:50:00' ,'2017-10-28 07:45:00']}    

aux_df = pd.DataFrame(data=d)
aux_df.START = pd.to_datetime(aux_df.START)
aux_df.END = pd.to_datetime(aux_df.END)
c = pd.concat([aux_df, data_aux], axis = 1)
c['A_D'] = c['A_D'].astype(str)
c['Operator'] = c['Operator'].astype(str)
c['Terminal'] = c['Terminal'].astype(str)

c['hour'] = pd.to_datetime(c['START'], format='%H:%M').dt.time
c['hour_aux'] = pd.to_datetime(c['START'] - pd.Timedelta(15, unit='m'), 
format='%H:%M').dt.time
c['start_day'] = c['START'].astype(str).str[0:10]
c['end_day'] = c['END'].astype(str).str[0:10]
c['x'] = c.START -  pd.to_timedelta(c.tot.astype(int), unit='m')
c["a"] = 0
c["Already_linked"] = np.where(c.TROUND_ID != "        ", 1 ,0)

arr = c[c['A_D'] == 'A']
_
17
Miguel Lambelho

このソリューションは、 numpy.in1dを使用するpd.DataFrame.isinを使用します

明らかに、「isin」は小さなデータセット(このサンプルのような)では必ずしも高速ではありませんが、大きなデータセットではかなり高速です。パフォーマンスを判断するには、データに対して実行する必要があります。

flight_record_linkage.ipynb

c = pd.concat([c] * 10000, ignore_index=True)を使用してデータセットを展開しました

  • データセットの長さを3桁増やします(合計10000行)。
    • 元の方法:ウォールタイム:8.98s
    • 新しい方法:ウォール時間:16.4秒
  • データセットの長さを4桁増やします(合計100000行)。
    • 元の方法:所要時間:8分17秒
    • 新しい方法:ウォール時間:1分14秒
  • データセットの長さを5桁増やします(合計1000000行)。
    • 新しい方法:ウォール時間:11分33秒

新しい方法:isinとapplyを使用する

def apply_do_g(it_row):
    """
    This is your function, but using isin and apply
    """

    keep = {'Operator': [it_row.Operator], 'Terminal': [it_row.Terminal]}  # dict for isin combined mask

    holder1 = arr[list(keep)].isin(keep).all(axis=1)  # create boolean mask
    holder2 = arr.Already_linked.isin([0])  # create boolean mask
    holder3 = arr.hour < it_row.hour_aux  # create boolean mask

    holder = holder1 & holder2 & holder3  # combine the masks

    holder = arr.loc[holder]

    if not holder.empty:

        aux = np.absolute(holder.START - it_row.x).idxmin()

        c.loc[it_row.name, 'a'] = holder.loc[aux].FlightID  # use with apply 'it_row.name'

        arr.loc[aux, 'Already_linked'] = 1


def new_way_2():
    keep = {'A_D': ['D'], 'Already_linked': [0]}
    df_test = c[c[list(keep)].isin(keep).all(axis=1)].copy()  # returns the resultant df
    df_test.apply(lambda row: apply_do_g(row), axis=1)  # g is multiple DataFrames"


#call the function
new_way_2()
1
Trenton_M

あなたの質問は、forループをベクトル化する方法があるかどうかでしたが、その質問はあなたの本当に欲しいものを隠していると思います。これはコードを高速化する簡単な方法です。パフォーマンスに関する質問については、常にプロファイリングから始めるのが適切です。ただし、コードの主要な操作は.query(row.query_string)であるという強い疑いがあります。 arrが大きい場合、すべての行で実行するとコストがかかります。

任意のクエリの場合、反復間の依存関係を削除し、高価なステップを並列化しない限り、そのランタイムを実際に改善することはできません。あなたは少し幸運かもしれませんが。クエリ文字列は、常に2つの異なる列をチェックして、それらが関心のあるものと等しいかどうかを確認します。ただし、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()は1つのクエリとほぼ同時に実行されますが、以降の反復ごとにを使用すると、.get_group()を使用して効率的に検索できます気になるデータの小さなサブセット。

ベンチマークの際に役立つ(非常に粗雑な)経験則は、10億ものものに1秒かかるということです。数百万の行など、数百万ものもので測定されたものよりもはるかに長い時間を見ている場合、それらの行のそれぞれに対して、何十億もの操作を行うために大量のことをしていることを意味します。これにより、操作の数を減らすためのより良いアルゴリズムの可能性がたくさん残されますが、ベクトル化は実際には一定のファクターの改善のみをもたらします(そして多くの文字列/クエリ操作ではそれほど改善されません)。

3
Hans Musgrave

これはベクトル化されたソリューションではありませんが、サンプルデータセットが実際のデータセットを模倣している場合は、物事を迅速にスピードアップする必要があります。現在、すべての行でループする時間を無駄にしていますが、['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
3
DJK

問題は、データベース操作で最も一般的な問題の1つのように見えます。あなたがタスクを定式化していないので、私はあなたが何を得たいかを完全には理解していません。考えられる解決策については、-ループをまったく避けてください

time, FlightID, Operator, Terminal, A_D列の非常に長いtableがあります。私があなたを正しく理解していれば、他の列と日付は関係ありません。また、start_timeend_timeはすべての行で同じです。ところで、コードtable.loc[:, 'time'] = table.loc[:, 'START'].dt.timetime列を取得できます。

  1. table = table.drop_duplicates(subset=['time', 'FlightID', 'Operator', 'Terminal'])。そして、tableが大幅に短くなります。

  2. tabletable_arrの値に従ってtable_depA_Dに分割します:table_arr = table.loc[table.loc[:, 'A_D'] == 'A', ['FlightID', 'Operator', 'Terminal', 'time']]table_dep = table.loc[table.loc[:, 'A_D'] == 'D', ['FlightID', 'Operator', 'Terminal', 'time']]

  3. 1行で取得できるループで取得しようとしたすべてのように思えます:table_result = table_arr.merge(table_dep, how='right', on=['Operator', 'Terminal'], suffixes=('_arr', '_dep'))。基本的に、SQLのJOINと同じ操作です。

あなたの問題の私の理解とあなたが提供した小さなデータを持っていると、非常に高速にループすることなく、目的の出力(すべてのFlightID_dep値のFlightID_arrFlightID_depの対応)を得ることができます。 table_resultは:

  FlightID_arr Operator  Terminal  time_arr FlightID_dep  time_dep
0        DL401       DL         3  06:50:00        DL001  09:30:00
1        DL402       DL         3  07:45:00        DL001  09:30:00
2          NaN       VS         3       NaN        VS001  15:15:00

もちろん、一般的な場合(実際のデータを使用する場合)、もう1つのステップが必要になります-条件table_resultまたはその他の条件でtime_arr < time_depをフィルタリングします。残念ながら、提供されたデータでは問題を完全に解決するには不十分です。

完全なコードは次のとおりです。

import io
import pandas as pd

data = '''
START,END,A_D,Operator,FlightID,Terminal,TROUND_ID,tot
2017-03-26 16:55:00,2017-10-28 16:55:00,A,QR,QR001,4,QR002,70
2017-03-26 09:30:00,2017-06-11 09:30:00,D,DL,DL001,3,,84
2017-03-27 09:30:00,2017-10-28 09:30:00,D,DL,DL001,3,,78
2017-10-08 15:15:00,2017-10-22 15:15:00,D,VS,VS001,3,,45
2017-03-26 06:50:00,2017-06-11 06:50:00,A,DL,DL401,3,,9
2017-03-27 06:50:00,2017-10-28 06:50:00,A,DL,DL401,3,,19
2017-03-29 06:50:00,2017-04-19 06:50:00,A,DL,DL401,3,,3
2017-05-03 06:50:00,2017-10-25 06:50:00,A,DL,DL401,3,,32
2017-06-25 06:50:00,2017-10-22 06:50:00,A,DL,DL401,3,,95
2017-03-26 07:45:00,2017-10-28 07:45:00,A,DL,DL402,3,,58
'''

table = pd.read_csv(io.StringIO(data), parse_dates=[0, 1])
table.loc[:, 'time'] = table.loc[:, 'START'].dt.time
table = table.drop_duplicates(subset=['time', 'FlightID', 'Operator', 'Terminal'])
table_arr = table.loc[table.loc[:, 'A_D'] == 'A', ['FlightID', 'Operator', 'Terminal', 'time']]
table_dep = table.loc[table.loc[:, 'A_D'] == 'D', ['FlightID', 'Operator', 'Terminal', 'time']]

table_result = table_arr.merge(
    table_dep,
    how='right',
    on=['Operator', 'Terminal'],
    suffixes=('_arr', '_dep'))
print(table_result)
1
Poolka