Объединение двух фреймов данных на основе даты между двумя другими датами без общей колонки

У меня есть два кадра данных, которые мне нужно объединить в зависимости от того, подходит ли значение даты между двумя другими датами. По сути, мне нужно выполнить внешнее соединение, где B.event_date находится между A.start_date и A.end_date. Кажется, что слияние и объединение всегда предполагают общий столбец, которого в данном случае у меня нет.

    A                           B
    start_date  end_date        event_date  price
0   2017-03-27  2017-04-20  0   2017-01-20  100
1   2017-01-10  2017-02-01  1   2017-01-27  200

Result 
    start_date  end_date        event_date  price
0   2017-03-27  2017-04-20  
1   2017-01-10  2017-02-01      2017-01-20  100
2   2017-01-10  2017-02-01      2017-01-27  200

Ответ 1

Создать данные и отформатировать в datetimes:

df_A = pd.DataFrame({'start_date':['2017-03-27','2017-01-10'],'end_date':['2017-04-20','2017-02-01']})
df_B = pd.DataFrame({'event_date':['2017-01-20','2017-01-27'],'price':[100,200]})

df_A['end_date'] = pd.to_datetime(df_A.end_date)
df_A['start_date'] = pd.to_datetime(df_A.start_date)
df_B['event_date'] = pd.to_datetime(df_B.event_date)

Создайте ключи для перекрестного соединения:

df_A = df_A.assign(key=1)
df_B = df_B.assign(key=1)
df_merge = pd.merge(df_A, df_B, on='key').drop('key',axis=1)

Отфильтруйте записи, которые не соответствуют критериям дат событий между датами начала и окончания:

df_merge = df_merge.query('event_date >= start_date and event_date <= end_date')

Присоединиться к исходной таблице диапазона дат и удалить ключевой столбец

df_out = df_A.merge(df_merge, on=['start_date','end_date'], how='left').fillna('').drop('key', axis=1)

print(df_out)

Выход:

              end_date           start_date           event_date price
0  2017-04-20 00:00:00  2017-03-27 00:00:00                           
1  2017-02-01 00:00:00  2017-01-10 00:00:00  2017-01-20 00:00:00   100
2  2017-02-01 00:00:00  2017-01-10 00:00:00  2017-01-27 00:00:00   200

Ответ 2

Это старый ответ, но я сталкивался с этой проблемой несколько раз. Я использую следующий подход, который лучше работает на больших наборах данных, так как не требует сначала создавать декартово произведение.

# prepare some dummy data here
def col_to_timestamp(row):
    row_keys = row.keys()
    if 'event_date' in row_keys:
        row['event_date'] = dateutil.parser.parse(row['event_date'])
    else:
        row['start_date'] = dateutil.parser.parse(row['start_date'])
        row['end_date'] = dateutil.parser.parse(row['end_date'])
    return row
df_A = pd.DataFrame({'start_date':['2017-03-27','2017-01-10'],'end_date':['2017-04-20','2017-02-01']})
df_B = pd.DataFrame({'event_date':['2017-01-20','2017-01-27', '2017-02-02'],'price':[100, 200, 999]})
df_A = df_A.apply(lambda r: col_to_timestamp(r), axis=1)
df_B = df_B.apply(lambda r: col_to_timestamp(r), axis=1)

# concat the actual data along with some "boundary markers" in between
# and sort it in ascending order
time_series = pd.concat([
    pd.DataFrame({'Time': df_B['event_date'], 'Bound': 0}),
    pd.DataFrame({'Time': df_A['start_date'], 'Bound': 1}),
    pd.DataFrame({'Time': df_A['end_date'], 'Bound': -1}),
]).sort_values(by=['Time', 'Bound'])

time_series['CumSum'] = time_series['Bound'].cumsum()
df_B['FilterCol'] = time_series[time_series['Bound'] == 0]['CumSum']
df_B = df_B[df_B['FilterCol'] == 1].drop(columns='FilterCol')

Теперь df_B содержит только записи, которые находятся в пределах одного из интервалов, определенных в df_A. Вы также можете повернуть его, чтобы включить только те данные, которые находятся за пределами одного из этих интервалов, изменив условие во 2-й последней строке на != 0