У меня есть DataFrame событий (Имя события - Время) и DataFrame временных окон (Время начала - Время окончания). Я хочу получить DataFrame, содержащий только события, не относящиеся ни к одному из временных окон. Я ищу «питонический» способ фильтрации DataFrame.
Пример: Кадр данных событий:
Время Windows DataFrame:
Результат: Кадр данных отфильтрованных событий:
Настраивать:
import pandas as pd
events_data = {
'Event Name': ['Event1', 'Event2', 'Event3', 'Event4'],
'Event Time': ['02/01/2000 00:00:00', '05/01/2000 10:00:00', '07/01/2000 09:00:00', '10/01/2000 02:00:00']
}
time_windows_data = {
'Time Window Name': ['Window1', 'Window2'],
'Start Time': ['01/01/2000 00:00:00', '10/01/2000 01:00:00'],
'End Time': ['06/01/2000 00:00:00', '10/01/2000 04:00:00']
}
events_df = pd.DataFrame(events_data)
time_windows_df = pd.DataFrame(time_windows_data)
events_df['Event Time'] = pd.to_datetime(events_df['Event Time'], format='%d/%m/%Y %H:%M:%S')
time_windows_df['Start Time'] = pd.to_datetime(time_windows_df['Start Time'], format='%d/%m/%Y %H:%M:%S')
time_windows_df['End Time'] = pd.to_datetime(time_windows_df['End Time'], format='%d/%m/%Y %H:%M:%S')
Вы можете использовать этот подход:
inside_events_df = events_df.merge(time_windows_df, how = "cross")
inside_events_df[["Event Time", "Start Time", "End Time"]] = inside_events_df[
["Event Time", "Start Time", "End Time"]
].apply(pd.to_datetime)
inside_events_df = inside_events_df[
(inside_events_df["Event Time"] > inside_events_df["Start Time"])
& (inside_events_df["Event Time"] < inside_events_df["End Time"])
]
events_df = events_df[~events_df["Event Name"].isin(inside_events_df["Event Name"])]
Event Name Event Time
2 Event3 07/01/2000 09:00:00
Возможно, вы можете выполнить слияние, но я не знаю, есть ли у вас большие фреймы данных. Другой подход был бы примерно таким, хотя я не знаю, считаете ли вы его питоническим.
Настраивать:
import pandas as pd
events_data = {
'Event Name': ['Event1', 'Event2', 'Event3', 'Event4'],
'Event Time': ['02/01/2000 00:00:00', '05/01/2000 10:00:00', '07/01/2000 09:00:00', '10/01/2000 02:00:00']
}
time_windows_data = {
'Time Window Name': ['Window1', 'Window2'],
'Start Time': ['01/01/2000 00:00:00', '10/01/2000 01:00:00'],
'End Time': ['06/01/2000 00:00:00', '10/01/2000 04:00:00']
}
events_df = pd.DataFrame(events_data)
time_windows_df = pd.DataFrame(time_windows_data)
events_df['Event Time'] = pd.to_datetime(events_df['Event Time'], format='%d/%m/%Y %H:%M:%S')
time_windows_df['Start Time'] = pd.to_datetime(time_windows_df['Start Time'], format='%d/%m/%Y %H:%M:%S')
time_windows_df['End Time'] = pd.to_datetime(time_windows_df['End Time'], format='%d/%m/%Y %H:%M:%S')
Подход 1:
# function to check if an event is within any time window
def is_event_outside_windows(event_time, windows_df):
for _, row in windows_df.iterrows():
if row['Start Time'] <= event_time <= row['End Time']:
return False
return True
filtered_events_df = events_df[events_df['Event Time'].apply(lambda x: is_event_outside_windows(x, time_windows_df))]
filtered_events_df.reset_index(drop=True, inplace=True)
print(filtered_events_df)
Подход 2:
# init mask to True for all events
mask = pd.Series([True] * len(events_df))
# update mask for each time window
for _, window in time_windows_df.iterrows():
mask &= ~events_df['Event Time'].between(window['Start Time'], window['End Time'])
filtered_events_df = events_df[mask]
filtered_events_df.reset_index(drop=True, inplace=True)
print(filtered_events_df)
Выход:
Event Name Event Time
0 Event3 2000-01-07 09:00:00
Вы можете создать IntervalIndex , а затем создать логическую маску с помощью переиндекса:
# build IntervalIndex
idx = pd.IntervalIndex.from_arrays(df_time['Start Time'], df_time['End Time'])
# build boolean mask
m = (pd.Series(False, index=idx)
.reindex(df_events['Event Time'],fill_value=True)
.to_numpy()
)
# select non-matching rows
out = df_events[m]
Альтернатива постройке m
:
m = idx.reindex(df_events['Event Time'])[1] == -1
Выход:
Event Name Event Time
2 Event3 2000-01-07 09:00:00
Промежуточные продукты:
# idx
IntervalIndex([(2000-01-01 00:00:00, 2000-01-06 00:00:00],
(2000-01-10 01:00:00, 2000-01-10 04:00:00]],
dtype='interval[datetime64[ns], right]')
# m
array([False, False, True, False])
Воспроизводимые входы:
import pandas as pd
from pandas import Timestamp
df_events = pd.DataFrame({'Event Name': ['Event1', 'Event2', 'Event3', 'Event4'],
'Event Time': [Timestamp('2000-01-02 00:00:00'),
Timestamp('2000-01-05 10:00:00'),
Timestamp('2000-01-07 09:00:00'),
Timestamp('2000-01-10 02:00:00')]})
df_time = pd.DataFrame({'Time Window Name': ['Window1', 'Window2'],
'Start Time': [Timestamp('2000-01-01 00:00:00'), Timestamp('2000-01-10 01:00:00')],
'End Time': [Timestamp('2000-01-06 00:00:00'), Timestamp('2000-01-10 04:00:00')]})
Я получаю нехешируемый тип: 'numpy.ndarray' на out = event_data[m]
@YakirShlezinger, можете ли вы предоставить свои материалы в воспроизводимом формате?
@Yakir проверьте обновление на предмет воспроизводимых входных данных. Также, если это не работает для вас, укажите свою версию pandas.
Я все равно отредактировал пост, попробовал еще раз и вроде работает (может я что-то неправильно написал в первый раз)
Это действительно креативное решение!
Вы можете получить краткое решение с помощью piso
import piso
piso.register_accessors()
result = (
pd.IntervalIndex.from_arrays(
time_windows_df['Start Time'],
time_windows_df['End Time'],
)
.piso.complement()
.piso.contains(events_df.set_index('Event Name')['Event Time'], result = "points")
.pipe(lambda x: x[x]).index
)
Отказ от ответственности: я являюсь автором Piso
Мне нравится этот подход, но он использует много памяти (с большими кадрами данных) из-за перекрестной операции.