LINUX.ORG.RU

Фильтрование выбросов (аномальных значений) числового ряда с pandas

 , ,


0

2

Помогите реализовать метод отфильтровывания слишком больших значений числового ряда (это котировки акций). Пример значений 2018-09-09, 2018-09-17

               value
timestamp
2018-09-01  0.000206
2018-09-02  0.000217
2018-09-03  0.000212
2018-09-04  0.000209
2018-09-05  0.000212
2018-09-06  0.000235
2018-09-07  0.000267
2018-09-08  0.000271
2018-09-09  0.050000
2018-09-10  0.000277
2018-09-11  0.000252
2018-09-12  0.000243
2018-09-13  0.000261
2018-09-14  0.000291
2018-09-15  0.000303
2018-09-16  0.000292
2018-09-17  0.080000
2018-09-18  0.000352
2018-09-19  0.000389
2018-09-20  0.000359
2018-09-21  0.000350
2018-09-22  0.000350
2018-09-23  0.000350
2018-09-24  0.000335
2018-09-25  0.000341
2018-09-26  0.000355
2018-09-27  0.000358
2018-09-28  0.000357
2018-09-29  0.000352
2018-09-30  0.000333
тут находится полный csv с данными https://file.io/TINyWl существующий код для фильтрования (можете запускать на скачаннном файле)
import pandas as pd

df = pd.read_csv("254.csv")
df["timestamp"] = pd.to_datetime(df["timestamp"])
df.set_index('timestamp', inplace=True)

df["data_pct"] = df["value"].pct_change(1)
df["data_pct_norm"] = (df["data_pct"] - df["data_pct"].mean())/df["data_pct"].std()
df["data_pct_norm"].fillna(0, inplace=True)
index = df["data_pct_norm"].between(-7, 7)
df = df[index]

data_res = df["value"].resample('24H')
data_int = data_res.interpolate(method='linear')

data_int.to_csv("254_cleaned.csv")

Проблемы с существующим кодом: он не может фильтровать два подряд идущих аномальных значения (так как ищет относительное изменение значения в ряде) и иногда отфильтровывает реальные пики в данных. Посоветуйте алгоритмы, альтернативные подходы к подобной задаче (можно использовать scipy, что-либо еще)



Последнее исправление: C (всего исправлений: 1)

сортируй по значениям, отрежь кусок, который больше 0.05

ZERG ★★★★★
()

Построй бегущее среднее и стандартное отклонение, например за последний час (или сутки, или месяц) и выкинь все значения, которые выходят за стандартное отклонение.

yvv ★★☆
()
Ответ на: комментарий от yvv

там в ряду по дням данные. бегущее среднее - это что-то вроде

df['value'].iloc[cur_idx:cur_idx-30].rolling(30).mean()
и 
df['value'].iloc[cur_idx:cur_idx-30].rolling(30).std()
?

C
() автор топика
Ответ на: комментарий от C

Судя по докам, да, что-то типа этого. Я сам pandas не использую, но смысл бегущего среднего в том, что если у тебя есть 1000 значений, ты, например, получаешь 990 значений, каждое из которых равно среднему от i-5 до i+5. Это простейший фильтр, и я предполагаю, что в pandas используется какой-нибудь продвинутый алгоритм. То же самое со стандартным отклонением.

А вообще, бегущее среднее используется в анализе рыночных котировок настолько часто, что я думаю где-то готовые решения есть, чтоб самому не париться.

yvv ★★☆
()
Ответ на: комментарий от yvv

Вот что получается, если вычислить rolling(5).mean() и rolling(5).std() для набора данных. Получается, что возле аномального значения rolling_mean и rolling_std возрастают и не затихают еще 5 значений, как тут определять, что отфильтровывать, непонятно

df['rolling_mean'] = df['value'].rolling(5).mean()
df['rolling_std'] = df['value'].rolling(5).std()
               value  rolling_mean  rolling_std
timestamp
2018-09-01  0.000206      0.000211     0.000006
2018-09-02  0.000217      0.000213     0.000006
2018-09-03  0.000212      0.000213     0.000006
2018-09-04  0.000209      0.000210     0.000004
2018-09-05  0.000212      0.000211     0.000004
2018-09-06  0.000235      0.000217     0.000010
2018-09-07  0.000267      0.000227     0.000024
2018-09-08  0.000271      0.000239     0.000029
2018-09-09  0.050000      0.010197     0.022251
2018-09-10  0.000277      0.010210     0.022243
2018-09-11  0.000252      0.010213     0.022241
2018-09-12  0.000243      0.010209     0.022244
2018-09-13  0.000261      0.010207     0.022245
2018-09-14  0.000291      0.000265     0.000019
2018-09-15  0.000303      0.000270     0.000026
2018-09-16  0.000292      0.000278     0.000025
2018-09-17  0.080000      0.016229     0.035649
2018-09-18  0.000352      0.016248     0.035639
2018-09-19  0.000389      0.016267     0.035628
2018-09-20  0.000359      0.016278     0.035621
2018-09-21  0.000350      0.016290     0.035615
2018-09-22  0.000350      0.000360     0.000017
2018-09-23  0.000350      0.000359     0.000017
2018-09-24  0.000335      0.000349     0.000009
2018-09-25  0.000341      0.000345     0.000007
2018-09-26  0.000355      0.000346     0.000008
2018-09-27  0.000358      0.000348     0.000010
2018-09-28  0.000357      0.000349     0.000011
2018-09-29  0.000352      0.000353     0.000007
2018-09-30  0.000333      0.000351     0.000011
C
() автор топика
Ответ на: комментарий от C

Поиграй с параметрами. Увеличь интервал например. Построй графичек, посмотри, куда у тебя аномальные точки падают, в pandas же это должно быть легко. Тут более конкретный совет тяжело дать, потому что процесс творческий.

П.С. В твоём случае я вижу, что твоё аномальное значение 0.050000 падает явно за пределы стандартного отклонения 0.010197+0.022251. Но оно влияет на среднее в будущих точках, да. Значит, что нужно сделать? Наверное, исключить его, и повторить процедуру опять.

yvv ★★☆
()
Ответ на: комментарий от yvv

добавил еще одну колонку с флагом, попадает ли значение в интервал [mean-std, mean+std]

df['fits_interval']=df['value'].between(df['rolling_mean']-df['rolling_std'], df['rolling_mean']+df['rolling_std'])
результат:
               value  rolling_mean  rolling_std  fits_interval
timestamp
2018-09-01  0.000206      0.000211     0.000006           True
2018-09-02  0.000217      0.000213     0.000006           True
2018-09-03  0.000212      0.000213     0.000006           True
2018-09-04  0.000209      0.000210     0.000004           True
2018-09-05  0.000212      0.000211     0.000004           True
2018-09-06  0.000235      0.000217     0.000010          False
2018-09-07  0.000267      0.000227     0.000024          False
2018-09-08  0.000271      0.000239     0.000029          False
2018-09-09  0.050000      0.010197     0.022251          False
2018-09-10  0.000277      0.010210     0.022243           True
2018-09-11  0.000252      0.010213     0.022241           True
2018-09-12  0.000243      0.010209     0.022244           True
2018-09-13  0.000261      0.010207     0.022245           True
2018-09-14  0.000291      0.000265     0.000019          False
2018-09-15  0.000303      0.000270     0.000026          False
2018-09-16  0.000292      0.000278     0.000025           True
2018-09-17  0.080000      0.016229     0.035649          False
2018-09-18  0.000352      0.016248     0.035639           True
2018-09-19  0.000389      0.016267     0.035628           True
2018-09-20  0.000359      0.016278     0.035621           True
2018-09-21  0.000350      0.016290     0.035615           True
2018-09-22  0.000350      0.000360     0.000017           True
2018-09-23  0.000350      0.000359     0.000017           True
2018-09-24  0.000335      0.000349     0.000009          False
2018-09-25  0.000341      0.000345     0.000007           True
2018-09-26  0.000355      0.000346     0.000008          False
2018-09-27  0.000358      0.000348     0.000010          False
2018-09-28  0.000357      0.000349     0.000011           True
2018-09-29  0.000352      0.000353     0.000007           True
2018-09-30  0.000333      0.000351     0.000011          False
конечно, аномальные значения отфильтровались, но и часть нормальных тоже. Есть идеи, как поправить?

C
() автор топика
Ответ на: комментарий от C

ну какбэ при поиске выбросов через скользящие алгоритмы (неважно, среднее это или какой перцентиль) после нахождения и удаления выброса мы не двигаемся сразу дальше, а «перезапускаем» скользящее с точки i-n где n размер окна.
ну а если возьмешь какой «статичный» алгоритм, то там какбэ тоже крайне рекомендуется после n очищенных выбросов перерасчитывать опорные точки.

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

genryRar ★★
()

Фильтрование выбросов

Robust statistics ©.

Хьюбер П. Робастность в статистике ©.

На пальцах: из вариационного ряда выбрасываешь пару квантилей в евойном хвосте, при условии, что интерквантильный размах больше порога (чтоб не выбросить правильные данные).

quickquest ★★★★★
()

Что за pandas - я х3.

По старинке это делали такими способами:

  • строишь распределение и смотришь на нужную тебе процентовку например, что в 99% случаев результат устраивает
  • Считаешь математическое ожидание, удаляешь всё, что больше и меньше

Если ты делаешь шлюз, то распределение тебе всё равно понадобится.

300us это откуда так долетает, если не секрет?

pon4ik ★★★★★
()

Это просто, вы фильтруете только цены, добавьте еще проверку по коду инструмента, тогда минимально и максимально возможные цены торгового коридора будут вашим фильтром, а не 2 подряд записи.

anonymous
()
Ответ на: комментарий от anonymous

А обычный LPF тут не применим? Да, он повлияет на правильные значения, но, результат его работы будет только проверочным — какраз отсеить резкие скачки.

deep-purple ★★★★★
()
Ответ на: комментарий от C

конечно, аномальные значения отфильтровались, но и часть нормальных тоже. Есть идеи, как поправить?

Увеличить окно

dikiy ★★☆☆☆
()
Ответ на: комментарий от C

Спасибо, а для нестационарных процессов эти методы подходят?

Это зависит от допустимых отклонений от локальной монотонности. Например, для типичных сигналов скачок фильтруется ранговым фильтром без размазывания, характерного для классических фильтров.

quickquest ★★★★★
()
Ответ на: комментарий от dikiy

Увеличивал окно до 15, 30, остановился на 60, вот результат:

               value  rolling_mean  rolling_std  fits_interval
timestamp
2018-09-01  0.000206      0.000264     0.000043          False
2018-09-02  0.000217      0.000264     0.000043          False
2018-09-03  0.000212      0.000263     0.000043          False
2018-09-04  0.000209      0.000262     0.000044          False
2018-09-05  0.000212      0.000261     0.000044          False
2018-09-06  0.000235      0.000261     0.000044           True
2018-09-07  0.000267      0.000260     0.000044           True
2018-09-08  0.000271      0.000259     0.000043           True
2018-09-09  0.050000      0.001087     0.006422          False
2018-09-10  0.000277      0.001087     0.006422           True
2018-09-11  0.000252      0.001086     0.006422           True
2018-09-12  0.000243      0.001085     0.006422           True
2018-09-13  0.000261      0.001084     0.006422           True
2018-09-14  0.000291      0.001084     0.006422           True
2018-09-15  0.000303      0.001084     0.006422           True
2018-09-16  0.000292      0.001084     0.006422           True
2018-09-17  0.080000      0.002413     0.012041          False
2018-09-18  0.000352      0.002413     0.012041           True
2018-09-19  0.000389      0.002415     0.012041           True
2018-09-20  0.000359      0.002415     0.012041           True
2018-09-21  0.000350      0.002416     0.012041           True
2018-09-22  0.000350      0.002417     0.012041           True
2018-09-23  0.000350      0.002417     0.012041           True
2018-09-24  0.000335      0.002417     0.012041           True
2018-09-25  0.000341      0.002417     0.012041           True
2018-09-26  0.000355      0.002417     0.012040           True
2018-09-27  0.000358      0.002418     0.012040           True
2018-09-28  0.000357      0.002419     0.012040           True
2018-09-29  0.000352      0.002420     0.012040           True
2018-09-30  0.000333      0.002421     0.012040           True
зато в ноябре, когда подъем от аномальных значений заказнчивается, такая картина:
               value  rolling_mean  rolling_std  fits_interval
timestamp
2018-11-01  0.000303      0.002467     0.012031           True
2018-11-02  0.000243      0.002467     0.012031           True
2018-11-03  0.000235      0.002468     0.012031           True
2018-11-04  0.000234      0.002468     0.012031           True
2018-11-05  0.000236      0.002468     0.012031           True
2018-11-06  0.000228      0.002468     0.012031           True
2018-11-07  0.000229      0.002467     0.012031           True
2018-11-08  0.000229      0.001637     0.010288           True
2018-11-09  0.000207      0.001636     0.010288           True
2018-11-10  0.000211      0.001636     0.010288           True
2018-11-11  0.000210      0.001635     0.010288           True
2018-11-12  0.000210      0.001634     0.010289           True
2018-11-13  0.000195      0.001633     0.010289           True
2018-11-14  0.000197      0.001631     0.010289           True
2018-11-15  0.000196      0.001629     0.010289           True
2018-11-16  0.000194      0.000299     0.000051          False
2018-11-17  0.000187      0.000296     0.000053          False
2018-11-18  0.000175      0.000293     0.000054          False
2018-11-19  0.000188      0.000290     0.000055          False
2018-11-20  0.000181      0.000287     0.000056          False
2018-11-21  0.000179      0.000284     0.000057          False
2018-11-22  0.000158      0.000281     0.000059          False
2018-11-23  0.000160      0.000278     0.000060          False
2018-11-24  0.000175      0.000275     0.000061          False
2018-11-25  0.000171      0.000272     0.000062          False
2018-11-26  0.000156      0.000269     0.000062          False
2018-11-27  0.000159      0.000266     0.000063          False
2018-11-28  0.000161      0.000262     0.000063          False
2018-11-29  0.000158      0.000260     0.000064          False
2018-11-30  0.000161      0.000257     0.000065          False

C
() автор топика

Примени медианный фильтр.

Он отлично выкидывает аномальные значения.

Еще можешь попробовать робастные варианты фильтра Калмана.

shkolnick-kun ★★★★★
()
Ответ на: комментарий от quickquest

мне по поиску «ранговый фильтр» находится страница wikipedia «Медианный фильтр». Ну в принципе медианный фильтр есть в scipy.signal, вот я его применил (в принципе аномальные значения отфильтровались, но и искажения в сигнале появились)

from scipy import signal
df['medfilt'] = signal.medfilt(df['value'], kernel_size=15)

результат:

               value   medfilt
timestamp
2018-09-01  0.000206  0.000212
2018-09-02  0.000217  0.000212
2018-09-03  0.000212  0.000212
2018-09-04  0.000209  0.000217
2018-09-05  0.000212  0.000220
2018-09-06  0.000235  0.000235
2018-09-07  0.000267  0.000243
2018-09-08  0.000271  0.000252
2018-09-09  0.050000  0.000261
2018-09-10  0.000277  0.000267
2018-09-11  0.000252  0.000271
2018-09-12  0.000243  0.000277
2018-09-13  0.000261  0.000291
2018-09-14  0.000291  0.000292
2018-09-15  0.000303  0.000303
2018-09-16  0.000292  0.000350
2018-09-17  0.080000  0.000335
2018-09-18  0.000352  0.000341
2018-09-19  0.000389  0.000350
2018-09-20  0.000359  0.000350
2018-09-21  0.000350  0.000350
2018-09-22  0.000350  0.000352
2018-09-23  0.000350  0.000352
2018-09-24  0.000335  0.000352
2018-09-25  0.000341  0.000350
2018-09-26  0.000355  0.000350
2018-09-27  0.000358  0.000350
2018-09-28  0.000357  0.000341
2018-09-29  0.000352  0.000335
2018-09-30  0.000333  0.000333
или имелся в виду другой ранговый фильтр?

C
() автор топика
Ответ на: комментарий от pon4ik

Считаешь математическое ожидание

Оно не постоянно для рыночных котировок. И варьируется иногда сумасшедшими темпами.

yvv ★★☆
()
Ответ на: комментарий от C

Я так и не понял, LPF уже пробовал? Он даст сглаженную от скачков херь, которую можно будет применить в качестве проверочной для каждого значения с допуском +/- сколько-то.

dikiy, тыж математик!? (и да, партия баяна когда будет?)

deep-purple ★★★★★
()
Ответ на: комментарий от deep-purple

Я так и не понял, LPF уже пробовал?

Плавающее среднее - это как раз и есть простейший вариант низкочастотного фильтра. Он его пробовал, исходя из сообщения выше. Результатом остался не очень доволен, потому что отфильтровались значения нормальные с его точки зрения. Это творческий процесс.

yvv ★★☆
()
Ответ на: комментарий от yvv

Ну правильно — после фильтра идёт смещение фазы, размер смещения зависит от добротности(?вродебыянепомню) фильтра, плюс, меняется амплитуда, которую нужно подогнать к оригинальным значениям. Пусть построит графиками и посмотрит.

deep-purple ★★★★★
()
Ответ на: комментарий от deep-purple

Пусть построит графиками и посмотрит.

Да! Статистика всегда лучше всего видна на графике.

yvv ★★☆
()
Ответ на: комментарий от C

Я сам торгую. Все библиотеки инструментов, которые встречал, делятся на две категории: одни делали люди, которые не торгуют, другие делали люди, которые сами торгуют с помощью своих инструментов. Догадайся с одного раза, которые из них более результативные )))

yvv ★★☆
()
Ответ на: комментарий от shkolnick-kun

попробовал медианнный фильтр, действительно фильтрует, но и искажает сигнал незначительно. Робастный вариант фильтра Калмана - это интересно, вот нашел реализацию, пример использования: https://github.com/milsto/robust-kalman/blob/master/examples/example_simple.p... Очень уж много у него параметров, попробую paper, по которой автор делал библиотеку, почитать

C
() автор топика
Ответ на: комментарий от shkolnick-kun

скачал твою реализацию

kf = _srkf_ra_lin(x, P, Q, R, psi_func, psidot_func, _scalar_correct)
kf.run(F, B, H, z)
x - как я понимаю, это сигнал, что за параметры P, Q, R, psi_func, psidot_func, _scalar_correct ? и где почитать про то, что такое F, B, H, z? где забирать результат работы фильтра?

C
() автор топика
Ответ на: комментарий от C

Это базовый класс (не полный).

Тебе надо класс robust_lin.


import numpy as np
import matplotlib.pyplot as plt
import numpy.random as random

from sqrtkf import robust_lin as kalman

#Начальное приближение состояния
x = np.array([1.0,0,0,0]).astype('float32')

#Предполагаемая матрица ковариации состояния
P = 0.001*np.eye(4).astype('float32')

#Матрица переходов (в данном случае модель - поступательное движение)
dt = 20.0
F = np.array([[1, dt, 0,  0],
            [0,  1, 0,  0],
            [0,  0, 1, dt],
            [0,  0, 0,  1]]).astype('float32')

#bf = np.array([20.0,0,0,0]).astype('float32')

#Матрица возмущений
B = np.eye(4).astype('float32')

#Матрица ковариации возмущений
Q = 0.0000001*np.eye(4).astype('float32')

#B = np.array([[1., 0],[1, 0],[0, 1],[0, 1]])
#Q = 0.0000001*np.eye(2).astype('float32')

#Матрица наблюдения
H = np.array([[1.0, 0, 0, 0],
              [0, 0, 1, 0]]).astype('float32')

#Вектор смещения наблюдений
bh = np.array([20.0, 0.0]).astype('float32')

def _noise():
    A = 20.1
    B = 20.3
    a = np.array([random.randn()*A, random.randn()*A])
    b = random.randn()*B
    c = np.array([b, -b])
    return a + c

ri = 100.
rj = -0.5*ri

#Матрица ковариации шума наблюдений
R = np.array([[ri,rj],
            [rj, ri]])

kf = kalman(x, P, Q, R)

xs = []
ys = []

xz = []
yz = []

xo = []
yo = []


n = []
e = []
count = 1500
'''
for i in range(count):
    b = 0.0
    c = random.randn()*0.5
    a = 1*i/(1+0.00001*i*i)
    A = 100
    #z = np.array([a*np.cos(0.01*i)+random.randn()*b+c, a*np.sin(0.01*i)+random.randn()*b+c]).transpose().astype(float)
    #z = np.array([a*np.cos(0.01*i*(1+0.0001*i))+random.randn()*b+c, a*np.sin(0.01*i)+random.randn()*b+c]).transpose().astype(float)
    #z = np.array([A*np.cos(0.01*i)+random.randn()*b + c, A*np.sin(0.0101*i*(1+0.0001*i))+random.randn()*b+c]).transpose().astype(float)
    #z = np.array([A*np.cos(0.01*i)+random.randn()*b + c, A*np.sin(0.011*i)+random.randn()*b+c]).transpose().astype(float)
    
    
    p = kf.run(F, bf, B, H, bh, z)
    
    xs.append (p[0])
    ys.append (p[1])
    
    xz.append (z[0])
    yz.append (z[1])
    
    e.append(np.linalg.norm(z - p))
    #e.append(z - p)
    n.append(i)
'''

z = np.array([0, 0]).transpose().astype(float)

for i in range(count//4):
    z += np.array([1.0, 2.0])
    o = z + _noise()
    
    p = kf.run(F, B, H, o, bh = bh)

    xs.append(p[0])
    ys.append(p[1])
    
    xo.append(o[0])
    yo.append(o[1])
    
    xz.append(z[0])
    yz.append(z[1])
    
    e.append(np.linalg.norm(z - p))
    #e.append(z - p)
    n.append(i)

for i in range(count//4):
    z += np.array([-1.0, 3.0])
    o = z + _noise()
    
    p = kf.run(F, B, H, o, bh = bh)

    xs.append(p[0])
    ys.append(p[1])
    
    xo.append(o[0])
    yo.append(o[1])
    
    xz.append(z[0])
    yz.append(z[1])
    
    e.append(np.linalg.norm(z - p))
    #e.append(z - p)
    n.append(i+count//4)
    
for i in range(count//4):
    
    c = random.randn()*1.7
    z += np.array([-5.0+c, -3.0+c])
    o = z + _noise()
    
    p = kf.run(F, B, H, o, bh = bh)

    xs.append(p[0])
    ys.append(p[1])
    
    xo.append(o[0])
    yo.append(o[1])
    
    xz.append(z[0])
    yz.append(z[1])
    
    e.append(np.linalg.norm(z - p))
    #e.append(z - p)
    n.append(i+2*count//4)
    
for i in range(count//4):
    
    c = random.randn()*0.5
    z += np.array([1+c, -2+c]).transpose().astype(float)
    o = z + _noise()
    
    p = kf.run(F, B, H, o, bh = bh)

    xs.append(p[0])
    ys.append(p[1])
    
    xo.append(o[0])
    yo.append(o[1])
    
    xz.append(z[0])
    yz.append(z[1])
    
    e.append(np.linalg.norm(z - p))
    #e.append(z - p)
    n.append(i+3*count//4)


plt.plot(xs, ys)
plt.plot(xz, yz)
plt.show()

plt.plot(xo, yo)
plt.show()

plt.plot(n, e)
plt.show()

print(np.median(e))

Во, попробуй с «НЛО» поиграться поймешь, что как. Только начать лучше с bierman_lin.

shkolnick-kun ★★★★★
()
Последнее исправление: shkolnick-kun (всего исправлений: 2)
Ответ на: комментарий от C

мне по поиску «ранговый фильтр» находится страница wikipedia «Медианный фильтр».

Искать нужно от печки метода максимального правдоподобия.

Если по-простому:
Среднее значение — оптимально только для нормального распределения.
Медиана — робастная оценка для распределений с тяжёлыми хвостами (выбросами).
Ранговые статистики — оптимальны между вышеприведёнными крайностями.

Все они могут быть получены из вариационного ряда.

в принципе аномальные значения отфильтровались, но и искажения в сигнале появились

Это потому, что ты не использовал критерий превышнения порога интерквантильным размахом. Если текущий квантиль локальной выборки не попал в хвост вариационного ряда, то ничего делать НЕНУЖНО™, сохраняется неискажённый сигнал.

quickquest ★★★★★
()
Ответ на: комментарий от C

то что ты мне скинул хорошо фильтруется mean -+std.

А вообще рекомендую взглянуть по другому на сигнал. Может алгоритмы лучше «видят» чем глаза?

Еще как вариант увеличивай окно по мере увеличения количества данных.

dikiy ★★☆☆☆
()
Ответ на: комментарий от C

Вот:

import numpy as np
import pandas as pd
from scipy import signal

df = pd.read_csv("254.csv")
df["timestamp"] = pd.to_datetime(df["timestamp"])
df.set_index('timestamp', inplace=True)

norm = 100.
df['medfilt'] = signal.medfilt(df['value'], kernel_size=5)
df['err'] = np.abs(df['medfilt'] - df['value'])
df['des'] = (df['err'] > norm*df['err'].median()).astype(float)
df['flt'] = df['des']*df['medfilt'] + (1.0 - df['des'])*df['value']

df['flt'].plot()

Аналогичным образом можно поступить не только с медианным фильтром, но и с любым другим.

Да, фильтр Калмана на таких данных без нормальной модели процесса работать не будет.

shkolnick-kun ★★★★★
()
Последнее исправление: shkolnick-kun (всего исправлений: 1)
Ответ на: комментарий от shkolnick-kun

Нульчую адеквата!
Хотя, зависит от задачи.

Задача ТС — котировки акций, если выбросов нету, то и усреднять стоимость акций ненужно, ибо это потеря реальных денег. Поэтому нелинейный критерий фильтрации неизбежен: простейший — непопадание текущего значения в интерквартильный интервал в Ёкселе.

А для профи есть готовые инструменты робастной торговли на бирже ©, где «всё уже украдено до нас» :)

quickquest ★★★★★
()
Ответ на: комментарий от shkolnick-kun

спасибо, определяет аномальные значения флагом в поле 'des', теперь я могу их удалить и проинтерполировать ряд. позже попробую потестировать на других данных

C
() автор топика
Ответ на: комментарий от yvv

Ой, так это всё же не часть таймштампа, ха.

pon4ik ★★★★★
()
Ответ на: комментарий от shkolnick-kun

интерполяция лучшие значения дает чем медианный фильтр. В общем я попробовал на других данных, вот что получается, отфильтровывает нормальные значения

                value    medfilt       err  fits_interval
timestamp
2012-03-01  25.037487  25.029039  0.008448           True
2012-03-02  24.817787  24.817787  0.000000           True
2012-03-03  24.719203  24.719203  0.000000           True
2012-03-04  24.620620  24.620620  0.000000           True
2012-03-05  24.522036  24.522036  0.000000           True
2012-03-06  23.313679  24.522036  1.208357          False
2012-03-07  23.803776  24.226282  0.422506           True
2012-03-08  24.682583  24.189665  0.492918           True
2012-03-09  24.226282  24.189665  0.036617           True
2012-03-10  24.189665  24.189665  0.000000           True
2012-03-11  24.153047  24.189665  0.036618           True
2012-03-12  24.116430  24.153047  0.036617           True
2012-03-13  24.403728  24.153047  0.250681           True
2012-03-14  23.981230  24.276981  0.295751           True
2012-03-15  24.276981  24.403728  0.126747           True
2012-03-16  24.589632  24.589632  0.000000           True
2012-03-17  24.603716  24.603716  0.000000           True
2012-03-18  24.617800  24.617800  0.000000           True
2012-03-19  24.631884  24.617800  0.014084           True
2012-03-20  25.705040  24.617800  1.087240          False
2012-03-21  23.955881  24.107985  0.152104           True
2012-03-22  23.617880  24.107985  0.490105           True
2012-03-23  24.107985  24.107985  0.000000           True
2012-03-24  24.375568  24.375568  0.000000           True
2012-03-25  24.643151  24.395285  0.247866           True
2012-03-26  24.910734  24.395285  0.515449           True
2012-03-27  24.395285  24.395285  0.000000           True
2012-03-28  24.395285  24.395285  0.000000           True
2012-03-29  23.761530  24.395285  0.633755          False
2012-03-30  24.302330  24.395285  0.092955           True
вот файл, на котором тестировалось https://upload.disroot.org/r/-Y1jecT3#vyEfC16lav53S4EaWZ3DFxQLBSzTcCpa/Z0WMWC...

еще один пример отфильтровывания нормальных данных

                value    medfilt       err  fits_interval
timestamp
2014-10-01  38.041538  39.375851  1.334313          False
2014-10-02  38.377380  39.375851  0.998471           True
2014-10-03  39.375851  39.375851  0.000000           True
2014-10-04  40.177648  40.177648  0.000000           True
2014-10-05  40.979445  40.979445  0.000000           True
2014-10-06  41.781242  41.781242  0.000000           True
2014-10-07  42.407558  42.407558  0.000000           True
2014-10-08  42.616325  42.407558  0.208767           True
2014-10-09  42.489250  42.407558  0.081692           True
2014-10-10  40.628471  42.147346  1.518875          False
2014-10-11  41.387909  42.147346  0.759437           True
2014-10-12  42.147346  42.147346  0.000000           True
2014-10-13  42.906784  42.147346  0.759438           True
2014-10-14  42.952171  42.147346  0.804825           True
2014-10-15  40.755554  40.918930  0.163376           True
2014-10-16  39.285088  40.755554  1.470466          False
2014-10-17  40.918930  40.413648  0.505282           True
2014-10-18  40.413648  39.908366  0.505282           True
2014-10-19  39.908366  39.908366  0.000000           True
2014-10-20  39.403084  39.403084  0.000000           True
2014-10-21  37.841839  37.841839  0.000000           True
2014-10-22  37.533234  37.732918  0.199684           True
2014-10-23  36.162601  37.533234  1.370633          False
2014-10-24  37.732918  37.055172  0.677746           True
2014-10-25  37.055172  36.377426  0.677746           True
2014-10-26  36.377426  37.055172  0.677746           True
2014-10-27  35.699680  37.055172  1.355492          False
2014-10-28  37.478767  37.306309  0.172458           True
2014-10-29  37.306309  37.478767  0.172458           True
2014-10-30  38.804005  38.804005  0.000000           True
файл с csv https://upload.disroot.org/r/b9Hl0lPC#8PDsYg69pDcRF2yvi0KKGxSKHrJ3265AozinOqY...

C
() автор топика
Ответ на: комментарий от shkolnick-kun

со следующим изменением в твоем коде

df['des'] = (df['err'] > norm*df['err'].mean()).astype(float)
активы в вышестоящем сообщении фильтруются нормально (т.е. ложных срабатываний нет), а вот это вот опять не фильтруется:
                 value     medfilt       err  fits_interval
timestamp
2018-03-01  115.169250  117.167961  1.998711           True
2018-03-02  117.167961  117.167961  0.000000           True
2018-03-03  117.512680  117.512680  0.000000           True
2018-03-04  117.857399  117.857399  0.000000           True
2018-03-05  118.202118  118.202118  0.000000           True
2018-03-06  119.982063  119.982063  0.000000           True
2018-03-07  120.638344  120.638344  0.000000           True
2018-03-08  120.876999  120.876999  0.000000           True
2018-03-09  123.183960  123.183960  0.000000           True
2018-03-10  123.591657  123.591657  0.000000           True
2018-03-11  123.999354  123.591657  0.407697           True
2018-03-12  124.407051  123.591657  0.815394           True
2018-03-13  122.825981  122.855820  0.029839           True
2018-03-14  122.855820  122.855820  0.000000           True
2018-03-15  122.676834  122.825981  0.149147           True
2018-03-16  123.164078  122.676834  0.487244           True
2018-03-17  122.338743  122.338743  0.000000           True
2018-03-18  121.513407  121.592949  0.079542           True
2018-03-19  120.688072  121.592949  0.904877           True
2018-03-20  121.592949  121.513407  0.079542           True
2018-03-21  121.602898  120.688072  0.914826           True
2018-03-22  118.808693  118.808693  0.000000           True
2018-03-23  115.298515  118.102674  2.804159          False
2018-03-24  116.700594  118.102674  1.402080           True
2018-03-25  118.102674  116.700594  1.402080           True
2018-03-26  119.504753  116.700594  2.804159          False
2018-03-27  116.024422  116.024422  0.000000           True
2018-03-28  113.349533  116.014473  2.664940          False
2018-03-29  116.014473  114.903252  1.111221           True
2018-03-30  114.903252  113.792030  1.111222           True
вот csv-шка (37.csv) https://upload.disroot.org/r/kTmiQ559#0UCXuqZMUIp9pKfgAxOF79Da/0F96b1dbCkGpZc...

C
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.