Estoy tratando de encontrar una forma más eficiente de encontrar rangos de datos superpuestos (fechas de inicio/finalización proporcionadas por fila) en un marco de datos basado en una columna específica (id). El marco de datos se ordena en la columna 'desde'. Creo que hay una manera de evitar la función de doble apply como lo hice yo:
import pandas as pd from datetime import datetime df = pd.DataFrame(columns=['id','from','to'], index=range(5), \ data=[[878,'2006-01-01','2007-10-01'], [878,'2007-10-02','2008-12-01'], [878,'2008-12-02','2010-04-03'], [879,'2010-04-04','2199-05-11'], [879,'2016-05-12','2199-12-31']]) df['from'] = pd.to_datetime(df['from']) df['to'] = pd.to_datetime(df['to']) id from to 0 878 2006-01-01 2007-10-01 1 878 2007-10-02 2008-12-01 2 878 2008-12-02 2010-04-03 3 879 2010-04-04 2199-05-11 4 879 2016-05-12 2199-12-31Usé la función "aplicar" para recorrer todos los grupos y dentro de cada grupo, uso "aplicar" por fila:
def check_date_by_id(df): df['prevFrom'] = df['from'].shift() df['prevTo'] = df['to'].shift() def check_date_by_row(x): if pd.isnull(x.prevFrom) or pd.isnull(x.prevTo): x['overlap'] = False return x latest_start = max(x['from'], x.prevFrom) earliest_end = min(x['to'], x.prevTo) x['overlap'] = int((earliest_end - latest_start).days) + 1 > 0 return x return df.apply(check_date_by_row, axis=1).drop(['prevFrom','prevTo'], axis=1) df.groupby('id').apply(check_date_by_id) id from to overlap 0 878 2006-01-01 2007-10-01 False 1 878 2007-10-02 2008-12-01 False 2 878 2008-12-02 2010-04-03 False 3 879 2010-04-04 2199-05-11 False 4 879 2016-05-12 2199-12-31 TrueMi código se inspiró en los siguientes enlaces:
Simplemente puede cambiar la columna to y realizar una resta directa de las fechas y horas.
df['overlap'] = (df['to'].shift()-df['from']) > timedelta(0) Aplicar esto mientras se agrupa por id puede parecer
df['overlap'] = (df.groupby('id') .apply(lambda x: (x['to'].shift() - x['from']) > timedelta(0)) .reset_index(level=0, drop=True))Manifestación
>>> df id from to 0 878 2006-01-01 2007-10-01 1 878 2007-10-02 2008-12-01 2 878 2008-12-02 2010-04-03 3 879 2010-04-04 2199-05-11 4 879 2016-05-12 2199-12-31 >>> df['overlap'] = (df.groupby('id') .apply(lambda x: (x['to'].shift() - x['from']) > timedelta(0)) .reset_index(level=0, drop=True)) >>> df id from to overlap 0 878 2006-01-01 2007-10-01 False 1 878 2007-10-02 2008-12-01 False 2 878 2008-12-02 2010-04-03 False 3 879 2010-04-04 2199-05-11 False 4 879 2016-05-12 2199-12-31 TruePuede comparar el tiempo 'desde' con el tiempo 'hasta' anterior:
df['to'].shift() > df['from']Producción:
0 False 1 False 2 False 3 False 4 TrueOtra solución. Esto podría reescribirse para aprovechar Interval.overlaps en pandas 24 y versiones posteriores.
def overlapping_groups(group): if len(group) > 1: for index, row in group.iterrows(): for index2, row2 in group.drop(index).iterrows(): int1 = pd.Interval(row2['start_date'],row2['end_date'], closed = 'both') if row['start_date'] in int1: return row['id'] if row['end_date'] in int1: return row['id'] gcols = ['id'] group_output = df.groupby(gcols,group_keys=False).apply(overlapping_groups) ids_with_overlap = set(group_output[~group_output.isnull()].reset_index(drop = True)) df[df['id'].isin(ids_with_overlap)]