#python #pandas #dataframe #function #filter
Вопрос:
Проблема и контекст:
Я работаю с электронным оборудованием, которое записывает события, измеренные с помощью геркона. Он записывает время, в течение которого геркон открывается (записывается как 0) и закрывается (записывается как 1). Это оборудование иногда регистрирует ложные измерения в масштабах времени, которые, как мы знаем, нереалистичны. «Состояние по умолчанию» устройства обычно закрыто.
То, что я пытаюсь сделать:
Отфильтруйте изменения в состоянии, когда разница во времени составляет менее 60 секунд. Но…часто бывает множество ложных открытий и закрытий, следующих друг за другом. Кроме того, в результирующем кадре данных должны быть события, упорядоченные как 1,0,1,0,1 и т.д. — Устройство может только открываться, затем закрываться, затем открываться и т.д.
Что я пробовал:
У меня есть кое-какой рабочий код, но он длинный и грязный. Я уверен, что должен быть лучший способ сделать это. Любые предложения были бы замечательны.
Пример исходного кадра данных
import numpy as np
import pandas as pd
df = pd.DataFrame({'date' : ['2019-05-10 12:20:31', '2019-05-10 12:25:43', '2019-05-10 12:25:57', '2019-05-10 12:28:00', '2019-05-10 12:30:01', '2019-05-10 12:35:00', '2019-05-10 12:35:30', '2019-05-10 12:35:45', '2019-05-10 12:37:00', '2019-05-10 12:40:00', '2019-05-10 12:45:00'], 'event' : [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]}, index=range(11))
df['date'] = pd.to_datetime(df['date'], errors='raise', dayfirst=True)
df['event'] = pd.to_numeric(df['event'])
df
Out[21]:
date event
0 2019-05-10 12:20:31 1
1 2019-05-10 12:25:43 0
2 2019-05-10 12:25:57 1
3 2019-05-10 12:28:00 0
4 2019-05-10 12:30:01 1
5 2019-05-10 12:35:00 0
6 2019-05-10 12:35:30 1
7 2019-05-10 12:35:45 0
8 2019-05-10 12:37:00 1
9 2019-05-10 12:40:00 0
10 2019-05-10 12:45:00 1
Созданные функции
def filter_event_filter_dups(input_data, input_variable_name):
"""
This is an 'inside' function for the main filter_event function and iteratively removes duplicates
"""
df_tmp = input_data.copy()
df_tmp = df_tmp.dropna()
df_tmp = df_tmp.reset_index()
df_tmp_final = pd.DataFrame(columns=['date', input_variable_name], index=range(len(df_tmp)))
for index, row in df_tmp.iterrows():
if index == 0:
print('')
df_tmp_final['date'].loc[index] = df_tmp['date'].loc[index]
df_tmp_final[input_variable_name].loc[index] = df_tmp[input_variable_name].loc[index]
else:
if index 1 == len(df_tmp):
print('---------------------------------------------------------------')
print('Index = ', index)
print('End!')
else:
print('---------------------------------------------------------------')
print('Index = ', index)
if df_tmp[input_variable_name].loc[index] == df_tmp[input_variable_name].loc[index-1]:
# keep only first
# delete second
# df_tmp = df_tmp.drop([df_tmp.loc[index 1]])
df_tmp_final['date'].loc[index-1] = df_tmp['date'].loc[index-1]
df_tmp_final[input_variable_name].loc[index-1] = df_tmp[input_variable_name].loc[index-1]
elif df_tmp[input_variable_name].loc[index] != df_tmp[input_variable_name].loc[index-1]:
df_tmp_final['date'].loc[index] = df_tmp['date'].loc[index]
df_tmp_final[input_variable_name].loc[index] = df_tmp[input_variable_name].loc[index]
return(df_tmp_final)
def filter_event(input_data, input_variable_name, input_delta_time_threshold_seconds):
"""
This function is designed to filer out events which are very close together.
"""
df_tmp = input_data.reset_index()
df_tmp[input_variable_name] = pd.to_numeric(df_tmp[input_variable_name])
del df_tmp['index']
# output_df = pd.DataFrame()
output_df = pd.DataFrame(columns=['date', input_variable_name], index=range(len(df_tmp)))
for index, row in df_tmp.iterrows():
if index 1 == len(df_tmp):
print('---------------------------------------------------------------')
print('Index = ', index)
print('End!')
else:
# if df_tmp[input_variable_name].loc[index] == 0:
print('---------------------------------------------------------------')
print('Index = ', index)
if ((df_tmp['date'].iloc[index 1] - df_tmp['date'].iloc[index]).total_seconds())<input_delta_time_threshold_seconds:
# If less than threshold
print('This is LESS than threshold - Threshold = ', input_delta_time_threshold_seconds, 'seconds')
print('Time difference to next 0 = ', df_tmp['date'].loc[index 1]-df_tmp['date'].loc[index])
print('Ignore')
if ((df_tmp['date'].iloc[index 1] - df_tmp['date'].iloc[index]).total_seconds())>input_delta_time_threshold_seconds:
# If more than threshold
print('This is MORE than threshold - Threshold = ', input_delta_time_threshold_seconds, 'seconds')
print('Time difference to next 0 = ', df_tmp['date'].loc[index 1]-df_tmp['date'].loc[index])
output_df['date'].loc[index] = df_tmp['date'].loc[index]
output_df[input_variable_name].loc[index] = df_tmp[input_variable_name].loc[index]
# output_df['date'].loc[index 1] = df_tmp['date'].loc[index 1]
# output_df[input_variable_name].loc[index 1] = df_tmp[input_variable_name].loc[index 1]
output_df = output_df.dropna()
output_df = output_df.reset_index()
output_df[input_variable_name] = pd.to_numeric(output_df[input_variable_name])
#
#
#
output_df_final = filter_event_filter_dups(input_data = output_df, input_variable_name = input_variable_name)
# for i in range(2):
# output_df_final = filter_event_filter_dups(input_data = output_df_final, input_variable_name = input_variable_name)
output_df_final = output_df_final.dropna()
output_df_final[input_variable_name] = pd.to_numeric(output_df_final[input_variable_name])
# output_df_final['date'] = pd.to_datetime(output_df_final, errors='raise', dayfirst=True)
output_df_final['date'] = output_df_final['date'].apply(pd.to_datetime)
return(output_df_final)
test = filter_event(input_data = df
, input_variable_name = 'event'
, input_delta_time_threshold_seconds = 60
)
Как должен выглядеть окончательный DF
final_df
Out[20]:
date event
0 2019-05-10 12:20:31 1
1 2019-05-10 12:28:00 0
2 2019-05-10 12:30:01 1
3 2019-05-10 12:40:00 0
4 2019-05-10 12:45:00 1
Ответ №1:
Вы могли бы использовать маску:
mask = df['date'].diff().lt('60s')
df[~(mask|mask.shift(-1))]
и если вы хотите обеспечить обмен событиями:
mask = df['date'].diff().lt('60s')
df[~(mask|mask.shift(-1))][lambda d: d['event'].ne(d['event'].shift())]
выход:
date event
0 2019-05-10 12:20:31 1
3 2019-05-10 12:28:00 0
4 2019-05-10 12:30:01 1
9 2019-05-10 12:40:00 0
10 2019-05-10 12:45:00 1
Комментарии:
1. Не-меньше-чем, а не больше-равно, чтобы избежать наполнения, это приятный штрих 🙂
2. спасибо @Cimbali , также я понимаю, что забыл условие обмена :p
Ответ №2:
С .diff()
помощью мы можем получить разницу во временных метках и удалить их с интервалом менее 60 секунд:
>>> delay = df['date'].diff().dt.total_seconds()
>>> df_min60s = df[delay.fillna(np.inf).ge(60) amp; delay.shift(-1).fillna(np.inf).ge(60)]
>>> df_min60s
date event
0 2019-05-10 12:20:31 1
3 2019-05-10 12:28:00 0
4 2019-05-10 12:30:01 1
8 2019-05-10 12:37:00 1
9 2019-05-10 12:40:00 0
10 2019-05-10 12:45:00 1
Тогда мы сможем удалить повторяющиеся события:
>>> df_final = df_min60s[df_min60s['event'].ne(df_min60s['event'].shift())]
>>> df_final
date event
0 2019-05-10 12:20:31 1
3 2019-05-10 12:28:00 0
4 2019-05-10 12:30:01 1
9 2019-05-10 12:40:00 0
10 2019-05-10 12:45:00 1
Комментарии:
1. Причина, по которой 0 сохраняется в 12:28 вместо 12:25, заключается в том, что открытие (0) в 12:25:43 продолжается закрытием (1) в 12:25:57, что составляет менее 60 секунд. Следовательно, эти два события являются ложными измерениями. Следовательно, мы сохраняем 12:28, так как это следующее реальное событие открытия. Я надеюсь, что в этом есть смысл.
2. Ах, я вижу @CairanVanRooyen, когда вы удаляете пары событий, а не отдельные события. Я привыкну.
3. Действительно, позиция «по умолчанию» закрыта. Если мы обнаружим, что устройство открывается (0), а затем закрывается (1) менее чем через 60 секунд, то измерения открытия и закрытия являются ложными.
4. ОК @CairanVanRooyen, отмечено. Может ли устройство повторно открыться менее чем через 60 секунд после последнего закрытия? (Значит, разрыв между одной действительной парой открытия-закрытия и следующей будет меньше 60?)
5. Устройство может снова открыться менее чем через 60 секунд после закрытия, и мы тоже хотим это игнорировать. Поэтому иногда устройство будет измерять множество событий открытия, закрытия, открытия, закрытия в течение нескольких секунд друг от друга, и мне нужен код, чтобы игнорировать все события, когда они находятся на расстоянии менее 60 секунд друг от друга.