Как я могу удалить резкие скачки в данных? - PullRequest
0 голосов
/ 05 июня 2018

У меня есть некоторые данные о температуре кожи (собранные с частотой 1 Гц), которые я собираюсь проанализировать.

Однако датчики не всегда соприкасались с кожей.Таким образом, у меня есть проблема удаления этих данных о температуре кожи, но при сохранении фактических данных о температуре кожи.У меня есть около 100 файлов для анализа, поэтому мне нужно сделать это автоматизированным.

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

Мои данные выглядят примерно так:

df =

timeStamp                 Temp
2018-05-04 10:08:00       28.63
         .                  . 
         .                  .
2018-05-04 21:00:00       31.63

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

basic threshold filtered data

Чтобы удалить эти скачки, я думал о том, чтобы сделатьподход, при котором я использую дифференциал первого порядка для temp, а затем использую другой набор порогов, чтобы избавиться от данных, которые меня не интересуют.

например

df_diff = df.diff(60) # period of about 60 makes jumps stick out

filter_index = np.nonzero((df.Temp <-1) | (df.Temp>0.5)) # when diff is less than -1 and greater than 0.5, most likely data jumps.

diff data

Однако я застрял здесь.Основная проблема заключается в том, что:

1) Я не знаю, как теперь использовать этот индексный список для удаления данных без скина в df.Как лучше всего это сделать?

Более незначительная проблема заключается в том, что 2) Я думаю, что у меня все еще останутся некоторые остаточные артефакты от скачков данных вблизи краев (например, когда более жесткий порог начнет отслаиваться)хорошие данные).Есть ли лучшая стратегия фильтрации или способ избавиться от этих артефактов?

* Редактировать как предложено Я также рассчитал разницу второго порядка, но, если честно, я думаю, что разница первого порядкадопускаются более жесткие пороговые значения (см. ниже):

enter image description here

* Редактировать 2: Ссылка на данные образца

Ответы [ 2 ]

0 голосов
/ 06 июня 2018

Вот предложение, которое нацелено на ваши проблемы относительно

  1. [...] подхода, в котором я использую разность первого порядка временного значения, а затем использую другой набор пороговых значенийчтобы избавиться от данных, которые меня не интересуют.

  2. [..] Я не знаю, как теперь использовать этот индексный список для удаления данных без скина в df,Как лучше всего это сделать?

с использованием stats.zscore () и pandas.merge ()

Как и раньше, у вас все еще будет небольшая проблема с вашими опасениями относительно

[...] оставшихся с некоторыми остаточными артефактами от скачков данных около краев [...]

Но мы вернемся к этому позже.

Во-первых, вот фрагмент кода для создания кадра данных, который разделяет некоторые проблемы с вашим набором данных:

# Imports
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from scipy import stats

np.random.seed(22)

# A function for noisy data with a trend element
def sample():

    base = 100
    nsample = 50
    sigma = 10

    # Basic df with trend and sinus seasonality 
    trend1 = np.linspace(0,1, nsample)
    y1 = np.sin(trend1)
    dates = pd.date_range(pd.datetime(2016, 1, 1).strftime('%Y-%m-%d'), periods=nsample).tolist()
    df = pd.DataFrame({'dates':dates, 'trend1':trend1, 'y1':y1})
    df = df.set_index(['dates'])
    df.index = pd.to_datetime(df.index)

    # Gaussian Noise with amplitude sigma
    df['y2'] = sigma * np.random.normal(size=nsample)
    df['y3'] = df['y2'] + base + (np.sin(trend1))
    df['trend2'] = 1/(np.cos(trend1)/1.05)
    df['y4'] = df['y3'] * df['trend2']

    df=df['y4'].to_frame()
    df.columns = ['Temp']

    df['Temp'][20:31] = np.nan

    # Insert spikes and missing values
    df['Temp'][19] = df['Temp'][39]/4000
    df['Temp'][31] = df['Temp'][15]/4000

    return(df)

# Dataframe with random data
df_raw = sample()
df_raw.plot()

enter image description here

Как видите, между ними есть два разных пика с пропущенными числами.И это действительно пропущенные числа, которые вызывают проблемы здесь, если вы предпочитаете выделять значения там, где различия велики.Первый всплеск не является проблемой, поскольку вы найдете разницу между очень маленьким числом и числом, которое больше похоже на остальные данные:

enter image description here

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

enter image description here

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

Шаги:

# 1. Get some info about the original data:
firstVal = df_raw[:1]
colName = df_raw.columns

# 2. Take the first difference and 
df_diff = df_raw.diff()

# 3. Remove missing values
df_clean = df_diff.dropna()

# 4. Select a level for a Z-score to identify and remove outliers
level = 3
df_Z = df_clean[(np.abs(stats.zscore(df_clean)) < level).all(axis=1)]
ix_keep = df_Z.index

# 5. Subset the raw dataframe with the indexes you'd like to keep
df_keep = df_raw.loc[ix_keep]

# 6. 
# df_keep will be missing some indexes.
# Do the following if you'd like to keep those indexes
# and, for example, fill missing values with the previous values
df_out = pd.merge(df_keep, df_raw, how='outer', left_index=True, right_index=True)

# 7. Keep only the first column
df_out = df_out.ix[:,0].to_frame()

# 8. Fill missing values
df_complete = df_out.fillna(axis=0, method='ffill')

# 9. Replace first value
df_complete.iloc[0] = firstVal.iloc[0]

# 10. Reset column names
df_complete.columns = colName

# Result
df_complete.plot()

enter image description here

Вот и все, что нужно для легкого копирования:

# Imports
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from scipy import stats

np.random.seed(22)

# A function for noisy data with a trend element
def sample():

    base = 100
    nsample = 50
    sigma = 10

    # Basic df with trend and sinus seasonality 
    trend1 = np.linspace(0,1, nsample)
    y1 = np.sin(trend1)
    dates = pd.date_range(pd.datetime(2016, 1, 1).strftime('%Y-%m-%d'), periods=nsample).tolist()
    df = pd.DataFrame({'dates':dates, 'trend1':trend1, 'y1':y1})
    df = df.set_index(['dates'])
    df.index = pd.to_datetime(df.index)

    # Gaussian Noise with amplitude sigma
    df['y2'] = sigma * np.random.normal(size=nsample)
    df['y3'] = df['y2'] + base + (np.sin(trend1))
    df['trend2'] = 1/(np.cos(trend1)/1.05)
    df['y4'] = df['y3'] * df['trend2']

    df=df['y4'].to_frame()
    df.columns = ['Temp']

    df['Temp'][20:31] = np.nan

    # Insert spikes and missing values
    df['Temp'][19] = df['Temp'][39]/4000
    df['Temp'][31] = df['Temp'][15]/4000

    return(df)

# A function for removing outliers
def noSpikes(df, level, keepFirst):

    # 1. Get some info about the original data:
    firstVal = df[:1]
    colName = df.columns

    # 2. Take the first difference and 
    df_diff = df.diff()

    # 3. Remove missing values
    df_clean = df_diff.dropna()

    # 4. Select a level for a Z-score to identify and remove outliers
    df_Z = df_clean[(np.abs(stats.zscore(df_clean)) < level).all(axis=1)]
    ix_keep = df_Z.index

    # 5. Subset the raw dataframe with the indexes you'd like to keep
    df_keep = df_raw.loc[ix_keep]

    # 6. 
    # df_keep will be missing some indexes.
    # Do the following if you'd like to keep those indexes
    # and, for example, fill missing values with the previous values
    df_out = pd.merge(df_keep, df_raw, how='outer', left_index=True, right_index=True)

    # 7. Keep only the first column
    df_out = df_out.ix[:,0].to_frame()

    # 8. Fill missing values
    df_complete = df_out.fillna(axis=0, method='ffill')

    # 9. Reset column names
    df_complete.columns = colName

    # Keep the first value
    if keepFirst:
        df_complete.iloc[0] = firstVal.iloc[0]

    return(df_complete)

# Dataframe with random data
df_raw = sample()
df_raw.plot()

# Remove outliers
df_cleaned = noSpikes(df=df_raw, level = 3, keepFirst = True)

df_cleaned.plot()
0 голосов
/ 05 июня 2018

Попробуйте код ниже (я использовал функцию тангенса для генерации данных).В комментариях я использовал идею разницы во втором порядке от Mad Physicist.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

df = pd.DataFrame()
df[0] = np.arange(0,10,0.005)
df[1] = np.tan(df[0])

#the following line calculates the absolute value of a second order finite 
#difference (derivative)
df[2] = 0.5*(df[1].diff()+df[1].diff(periods=-1)).abs()

df.loc[df[2] < .05][1].plot() #select out regions of a high rate-of-change 
df[1].plot()                  #plot original data

plt.show()

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

enter image description here

Ваш первый вопрос, на который я полагаю, дан ответ с выбором .loc выше.

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

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

Редактировать:

С помощью этого можно применить конечную разность четвертого порядка:

df[2] = (df[1].diff(periods=1)-df[1].diff(periods=-1))*8/12 - \
    (df[1].diff(periods=2)-df[1].diff(periods=-2))*1/12
df[2] = df[2].abs()

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

Примечание. Приведенные выше уравнения с центральными разностями второго и четвертого порядка не являются правильными первыми производными.Чтобы получить фактическую производную, нужно разделить на длину интервала (в данном случае 0,005).

...