J'ai un dataframe c
avec beaucoup de colonnes différentes. De plus, arr
est une trame de données qui correspond à un sous-ensemble de c
: arr = c[c['A_D'] == 'A']
.
L'idée principale de mon code est d'itérer sur toutes les lignes du cadre de données c
- et de rechercher tous les cas possibles (dans le cadre de données arr
) où certaines conditions spécifiques doivent se produire:
c['A_D'] == D
Et c['Already_linked'] == 0
hour
dans le cadre de données arr
doit être inférieur au hour_aux
Dans le cadre de données c
Already_linked
De la trame de données arr
doit être nulle: arr.Already_linked == 0
Terminal
et Operator
doivent être les mêmes dans le cadre de données c et arr
À l'heure actuelle, les conditions sont stockées à l'aide de l'indexation booléenne et de groupby get_group:
arr
afin de choisir le même opérateur et terminal: g = groups.get_group((row.Operator, row.Terminal
))c
et où Déjà_lié == 0: vb = g[(g.Already_linked==0) & (g.hour<row.hour_aux)]
Pour chacune des lignes de la trame de données c
qui vérifient toutes les conditions, une trame de données vb
est créée. Naturellement, cette trame de données a des longueurs différentes à chaque itération. Après avoir créé le cadre de données vb
, mon objectif est de choisir l'index du cadre de données vb
qui minimise le temps entre vb.START
Et c [x
]. Le FightID
qui correspond à cet index est ensuite stocké dans le cadre de données c
sur la colonne a
. De plus, l'arrivée étant liée à un départ, la colonne Already_linked
Dans le cadre de données arr
est passée de 0 à 1.
Il est important de noter que la colonne Already_linked
Du cadre de données arr
peut changer à chaque itération (et arr.Already_linked == 0
Est l'une des conditions pour créer le vb
trame de données). Par conséquent, il n'est pas possible de paralléliser ce code.
J'ai déjà utilisé c.itertuples()
pour plus d'efficacité, mais comme c
a des millions de lignes, ce code prend encore trop de temps.
Une autre option serait également d'utiliser pd.apply
Pour chaque ligne. Néanmoins, ce n'est pas vraiment simple car dans chaque boucle il y a des valeurs qui changent à la fois c
et arr
(aussi, je crois que même avec pd.apply
Ce serait extrêmement lent ).
Existe-t-il un moyen possible de convertir cela pour la boucle dans une solution vectorisée (ou de réduire le temps d'exécution de 10X (si possible encore plus))?
Trame de données initiale:
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
Sortie souhaitée (certaines des colonnes ont été exclues dans la trame de données ci-dessous. Seules les colonnes a
et Already_linked
Sont pertinentes):
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
Code:
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'])
Code pour la trame de données initiale 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']
Cette solution utilise pd.DataFrame.isin qui utilise numpy.in1d
Apparemment, "isin" n'est pas nécessairement plus rapide pour les petits ensembles de données (comme cet exemple), mais est beaucoup plus rapide pour les grands ensembles de données. Vous devrez l'exécuter avec vos données pour déterminer les performances.
Développement de l'ensemble de données à l'aide de c = pd.concat([c] * 10000, ignore_index=True)
Nouvelle méthode: utiliser isin et appliquer
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()
Votre question était de savoir s'il existe un moyen de vectoriser la boucle for, mais je pense que cette question cache ce que vous voulez vraiment, ce qui est un moyen facile d'accélérer votre code. Pour les questions de performances, un bon point de départ est toujours le profilage. Cependant, je soupçonne fortement que l'opération dominante dans votre code est .query(row.query_string)
. L'exécuter pour chaque ligne coûte cher si arr
est grand.
Pour les requêtes arbitraires, ce runtime ne peut pas vraiment être amélioré du tout sans supprimer les dépendances entre les itérations et paralléliser l'étape coûteuse. Vous pourriez être un peu plus chanceux cependant. Votre chaîne de requête vérifie toujours deux colonnes différentes pour voir si elles sont égales à quelque chose qui vous tient à cœur. Cependant, pour chaque ligne qui nécessite de parcourir l'intégralité de votre tranche de arr
. Étant donné que la tranche change à chaque fois, cela pourrait poser des problèmes, mais voici quelques idées:
arr
à chaque fois de toute façon, conservez une vue uniquement des lignes arr.Already_Linked==0
Afin de parcourir un objet plus petit.arr
par Terminal
et Operator
. Ensuite, au lieu de parcourir l'ensemble de arr
, sélectionnez d'abord le groupe souhaité, puis effectuez votre découpage et votre filtrage. Cela nécessiterait de repenser un peu l'implémentation exacte de query_string
, Mais l'avantage est que si vous avez beaucoup de terminaux et d'opérateurs, vous travaillerez généralement sur un objet beaucoup plus petit que arr
. De plus, vous n'auriez même pas à interroger cet objet car cela a été implicitement fait par le groupby.aux.hour
Et row.hour_aux
, Vous pouvez apporter des améliorations en triant aux
au début par rapport à hour
. En utilisant simplement l'opérateur d'inégalité, vous ne verriez probablement aucun gain, mais vous pouvez associer cela à une recherche logarithmique du point de coupure, puis simplement couper jusqu'à ce point de coupure.arr
pour chaque ligne offrira beaucoup plus de gains que le simple changement de framework ou vectoriser des morceaux.En développant un peu certains de ces points et en adaptant un peu le code de @ DJK, regardez ce qui se passe lorsque nous avons les changements suivants.
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
Une partie de la raison pour laquelle votre requête est si lente est qu'elle recherche à chaque fois sur arr
. En revanche, la .groupby()
s'exécute à peu près en même temps qu'une requête, mais pour à chaque itération suivante vous pouvez simplement utiliser .get_group()
pour trouver efficacement la petite sous-ensemble des données qui vous intéressent.
Une règle de base utile (extrêmement grossière) lors de l'analyse comparative est qu'un milliard de choses prend une seconde. Si vous voyez des temps beaucoup plus longs que cela pour quelque chose mesuré en millions de choses, comme vos millions de lignes, cela signifie que pour chacune de ces lignes, vous faites des tonnes de choses pour atteindre des milliards d'opérations. Cela laisse une tonne de potentiel pour de meilleurs algorithmes pour réduire le nombre d'opérations, alors que la vectorisation ne donne vraiment que des améliorations constantes des facteurs (et pour de nombreuses opérations de chaîne/requête, pas même de grandes améliorations à cela).
Bien que ce ne soit pas une solution optimisée, cela devrait accélérer les choses rapidement si votre échantillon de données imite votre véritable ensemble de données. Actuellement, vous perdez du temps à boucler sur chaque ligne, mais vous ne vous souciez que de boucler sur les lignes où ['A_D'] == 'D'
et ['Already_linked'] ==0
. Au lieu de cela, supprimez les if et la boucle sur la trame de données tronquée, qui ne représente que 30% de la trame de données initiale
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
Votre problème ressemble à l'un des problèmes les plus courants liés au fonctionnement de la base de données. Je ne comprends pas bien ce que vous voulez obtenir parce que vous n'avez pas formulé la tâche. Passons maintenant à la solution possible - éviter les boucles du tout.
Vous avez un table
très long avec des colonnes time, FlightID, Operator, Terminal, A_D
. Les autres colonnes et dates n'ont pas d'importance si je vous comprends bien. start_time
Et end_time
Sont également les mêmes sur chaque ligne. Par ailleurs, vous pouvez obtenir la colonne time
avec le code table.loc[:, 'time'] = table.loc[:, 'START'].dt.time
.
table = table.drop_duplicates(subset=['time', 'FlightID', 'Operator', 'Terminal'])
. Et votre table
va devenir beaucoup plus court.
Divisez table
en table_arr
Et table_dep
Selon la valeur de A_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']]
On dirait que tout ce que vous avez essayé d'obtenir avec des boucles peut être obtenu avec une seule ligne: table_result = table_arr.merge(table_dep, how='right', on=['Operator', 'Terminal'], suffixes=('_arr', '_dep'))
. Il s'agit essentiellement de la même opération que JOIN
en SQL.
Selon ma compréhension de votre problème et le fait que vous ayez fourni le tout petit morceau de données, vous obtenez exactement la sortie souhaitée (correspondance entre FlightID_dep
Et FlightID_arr
Pour toutes les valeurs FlightID_dep
) Sans n'importe quelle boucle tellement plus vite. table_result
Est:
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
Bien sûr, dans le cas général (avec des données réelles), vous aurez besoin d'une étape supplémentaire - filtrez table_result
À la condition time_arr < time_dep
Ou à toute autre condition que vous avez. Malheureusement, les données que vous avez fournies ne suffisent pas à résoudre complètement votre problème.
Le code complet est:
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)