tips - Python-Para bucle millones de filas
tips para mysql (4)
Tengo un marco de datos c
con muchas columnas diferentes. Además, arr
es un marco de datos que corresponde a un subconjunto de c
: arr = c[c[''A_D''] == ''A'']
.
La idea principal de mi código es iterar sobre todas las filas en el marco de datos c
y buscar todos los casos posibles (en el marco de datos de arr
) donde deben ocurrir algunas condiciones específicas:
- Solo es necesario iterar sobre las filas donde
c[''A_D''] == D
yc[''Already_linked''] == 0
- La
hour
en el marco de datosarr
debe ser menor que lahour_aux
en el marco de datosc
- La columna
Already_linked
delAlready_linked
de datosarr
debe ser cero:arr.Already_linked == 0
- La
Terminal
y elOperator
deben ser iguales en el marco de datos c yarr
En este momento, las condiciones se almacenan utilizando tanto la indexación booleana como el grupo by get_group:
- Agrupe el marco de datos
arr
para elegir el mismo operador y terminal:g = groups.get_group((row.Operator, row.Terminal
)) - Elija solo las llegadas donde la hora es más pequeña que la hora en el marco de datos
c
y donde Ya está enlazado == 0:vb = g[(g.Already_linked==0) & (g.hour<row.hour_aux)]
Para cada una de las filas en el marco de datos c
que verifica todas las condiciones, se crea un marco de datos vb
. Naturalmente, este marco de datos tiene diferentes longitudes en cada iteración. Después de crear el marco de datos vb
, mi objetivo es elegir el índice del marco de datos vb
que minimiza el tiempo entre vb.START
yc [ x
]. El FightID
que corresponde a este índice se almacena en el marco de datos c
en la columna a
. Además, dado que la llegada estaba vinculada a una partida, la columna Already_linked
en el marco de datos arr
se cambia de 0 a 1.
Es importante tener en cuenta que la columna Already_linked
del Already_linked
de datos arr
puede cambiar en cada iteración (y arr.Already_linked == 0
es una de las condiciones para crear el marco de datos vb
). Por lo tanto, no es posible paralelizar este código.
Ya he usado c.itertuples()
para c.itertuples()
eficiencia, sin embargo, dado que c
tiene millones de filas, este código aún requiere demasiado tiempo.
Otra opción también sería usar pd.apply
para cada fila. No obstante, esto no es realmente sencillo, ya que en cada ciclo hay valores que cambian tanto en c
como en arr
(también creo que incluso con pd.apply
sería extremadamente lento).
¿Hay alguna forma posible de convertir esto para un bucle en una solución vectorizada (o disminuir el tiempo de ejecución en 10X (si es posible aún más))?
Marco de datos inicial:
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
Salida deseada (algunas de las columnas se excluyeron en el marco de datos a continuación. Solo las columnas a y Already_linked
son relevantes):
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
Código:
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ódigo para el marco de datos inicial 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'']
Si bien esta no es una solución virtual, debería acelerar las cosas rápidamente si su conjunto de datos de muestra imita su verdadero conjunto de datos. Actualmente, está perdiendo el tiempo en cada fila, pero solo le importa hacer un ciclo en las filas donde [''A_D''] == ''D''
y [''Already_linked''] ==0
. En su lugar, elimine los if''s y haga un bucle sobre el marco de datos truncado, que es solo el 30% del marco de datos inicial
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
Su pregunta fue si hay una forma de vectorizar el bucle for, pero creo que esa pregunta oculta lo que realmente quiere, lo cual es una manera fácil de acelerar su código . Para preguntas de desempeño, un buen punto de partida es siempre perfilar. Sin embargo, tengo una fuerte sospecha de que la operación dominante en su código es .query(row.query_string)
. Ejecutar eso para cada fila es costoso si arr
es grande.
Para consultas arbitrarias, ese tiempo de ejecución no se puede mejorar en absoluto sin eliminar las dependencias entre iteraciones y paralelizar el paso costoso. Aunque podrías ser un poco más afortunado. La cadena de consulta siempre verifica dos columnas diferentes para ver si son iguales a algo que le importa. Sin embargo, para cada fila que requiere pasar por toda su porción de arr
. Dado que la división cambia cada vez, eso podría causar problemas, pero aquí hay algunas ideas:
- Ya que estás cortando
arr
cada vez de todas formas, mantén una vista de las filasarr.Already_Linked==0
para que estés iterando sobre un objeto más pequeño. - Mejor aún, antes de hacer un bucle, primero debe agrupar
arr
porTerminal
yOperator
. Luego, en lugar de ejecutar todo elarr
, primero seleccione el grupo que desea y luego haga su rebanado y filtrado. Esto requeriría repensar un poco la implementación exacta dequery_string
, pero la ventaja es que si tiene muchos terminales y operadores, normalmente trabajará con un objeto mucho más pequeño quearr
. Además, ni siquiera tendría que consultar ese objeto, ya que el grupo lo hizo implícitamente. - Dependiendo de cómo
aux.hour
se relacione típicamente conrow.hour_aux
, puede tener mejoras al clasificaraux
al principio con respecto a lahour
. Si solo utiliza el operador de desigualdad, probablemente no verá ninguna ganancia, pero puede emparejarlo con una búsqueda logarítmica del punto de corte y luego cortarlo hasta ese punto de corte. - Y así. De nuevo, sospecho que cualquier forma de reestructurar la consulta que está haciendo en todos los
arr
para cada fila ofrecerá sustancialmente más ganancias que solo cambiar los marcos o vectorizar bits y piezas.
Ampliando algunos de esos puntos un poco y adaptando un poco el código de @ DJK, observa lo que sucede cuando tenemos los siguientes cambios.
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
Parte de la razón por la que su consulta es tan lenta es porque está buscando todos los arr
cada vez. En contraste, .groupby()
ejecuta aproximadamente al mismo tiempo que una consulta, pero luego para cada iteración subsiguiente puede usar .get_group()
para encontrar de manera eficiente el pequeño subconjunto de los datos que le interesan.
Una regla de oro útil (extremadamente cruda) cuando se hace una evaluación comparativa es que mil millones de cosas toman un segundo. Si está viendo tiempos mucho más largos que eso para algo medido en millones de cosas, como sus millones de filas, eso significa que para cada una de esas filas está haciendo toneladas de cosas para obtener miles de millones de operaciones. Eso deja un montón de potencial para que los mejores algoritmos reduzcan el número de operaciones, mientras que la vectorización en realidad solo produce mejoras de factores constantes (y para muchas operaciones de cadena / consulta ni siquiera grandes mejoras en eso).
Su problema parece uno de los problemas más comunes en la operación de la base de datos. No entiendo completamente lo que quiere obtener porque no ha formulado la tarea. Ahora a la posible solución - evitar bucles en absoluto .
Tiene una table
muy larga con columnas de time, FlightID, Operator, Terminal, A_D
. Otras columnas y fechas no importan si te entiendo correctamente. También start_time
y end_time
son iguales en cada fila. Por cierto, puede obtener la columna de time
con el código table.loc[:, ''time''] = table.loc[:, ''START''].dt.time
.
table = table.drop_duplicates(subset=[''time'', ''FlightID'', ''Operator'', ''Terminal''])
. Y tutable
volverá significativamente más corta.Dividir
table
entable_arr
ytable_dep
según el valorA_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'']]
Parece que todo lo que intentaste obtener con los bucles puedes obtener con una sola línea:
table_result = table_arr.merge(table_dep, how=''right'', on=[''Operator'', ''Terminal''], suffixes=(''_arr'', ''_dep''))
. Es básicamente la misma operación queJOIN
en SQL.
De acuerdo con mi comprensión de su problema y con la pequeña cantidad de datos que nos ha proporcionado, obtiene la salida deseada (la correspondencia entre FlightID_dep
y FlightID_arr
para todos los valores de FlightID_dep
) sin ningún bucle mucho más rápido. table_result
es:
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
Por supuesto, en el caso general (con datos reales) necesitará un paso más: filtre table_result
en la condición time_arr < time_dep
o cualquier otra condición que tenga. Desafortunadamente, los datos que ha proporcionado no son suficientes para resolver completamente su problema.
El código completo es:
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)
Esta solución usa pd.DataFrame.isin que usa numpy.in1d
Al parecer, ''isin'' no es necesariamente más rápido para conjuntos de datos pequeños (como este ejemplo), pero es significativamente más rápido para conjuntos de datos grandes. Tendrá que ejecutarlo contra sus datos para determinar el rendimiento.
Expandió el conjunto de datos usando c = pd.concat([c] * 10000, ignore_index=True)
- Aumente la longitud del conjunto de datos en 3 órdenes de magnitud (10000 filas en total).
- Método original: Tiempo de pared: 8.98s
- Nuevo método: tiempo de pared: 16.4s
- Aumente la longitud del conjunto de datos en 4 órdenes de magnitud (100000 filas en total).
- Método original: Tiempo de pared: 8min 17s
- Nuevo método: tiempo de pared: 1min 14s
- Aumente la longitud del conjunto de datos en 5 órdenes de magnitud (1000000 filas en total).
- Nuevo método: tiempo de pared: 11min 33s
Nuevo método: usar isin y aplicar
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()