如何删除数据中的急剧跳转?

时间:2018-06-05 11:21:41

标签: python python-3.x pandas dataframe filtering

我有一些我打算分析的皮肤温度数据(以1Hz收集)。

然而,传感器并不总是与皮肤接触。因此,我有一个挑战,即在保留实际皮肤温度数据的同时去除这些非皮肤温度数据。我有大约100个要分析的文件,所以我需要自动化。

我知道已经有this similar post,但是我无法用它来解决我的问题。

我的数据大致如下:

df =

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

我采取的第一步是简单地应用最小阈值 - 这已经摆脱了大部分非皮肤数据。然而,我留下了急剧的跳跃,传感器被移除或连接:

basic threshold filtered data

为了消除这些跳跃,我正在考虑采用一种方法,我使用temp的一阶微分,然后使用另一组阈值来摆脱我不感兴趣的数据。

e.g。

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:Link to sample data

2 个答案:

答案 0 :(得分:2)

尝试下面的代码(我使用切线函数生成数据)。我在评论中使用了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()

认为它可能有所帮助是合理的。上述系数可以计算出来或从以下链接导出,以获得更高的订单。 Finite Difference Coefficients Calculator

注意:上述二阶和四阶中心差分方程不是正确的一阶导数。必须除以间隔长度(在这种情况下为0.005)才能得到实际导数。

答案 1 :(得分:0)

以下是针对您的问题

的建议
  
      
  1. [...]一种方法,我使用临时的一阶微分,然后使用另一组阈值来摆脱我不感兴趣的数据。

    < / LI>   
  2. [..]我不知道如何使用此索引列表删除df中的非皮肤数据。怎么做到最好?

  3.   

使用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()