Объединение двух фреймов данных на основе даты между двумя другими датами без общей колонки
У меня есть два кадра данных, которые мне нужно объединить в зависимости от того, подходит ли значение даты между двумя другими датами. По сути, мне нужно выполнить внешнее соединение, где 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