时序Python
在处理数据的时候,经常会遇到一些非连续的散点时间序列数据:
Tsmoothie—将数据平滑化并找到异常点 - 图1
有些时候,这样的散点数据是不利于进行数据的聚类和预测的。因此需要把它们平滑化,如下图所示:
Tsmoothie—将数据平滑化并找到异常点 - 图2
如果将散点及其范围区间都去除,平滑后的效果如下:
Tsmoothie—将数据平滑化并找到异常点 - 图3
这样的时序数据是不是看起来舒服多了?此外,使用平滑后的时序数据去做聚类或预测或许有令人惊艳的效果,因为它去除了一些偏差值并细化了数据的分布范围。
如果自己开发一个这样的平滑工具,会耗费不少的时间。因为平滑的技术有很多种,需要一个个地去研究,找到最合适的技术并编写代码,这是一个非常耗时的过程。平滑技术包括但不限于:

  • 指数平滑
  • 具有各种窗口类型(常数、汉宁、汉明、巴特利特、布莱克曼)的卷积平滑
  • 傅立叶变换的频谱平滑
  • 多项式平滑
  • 各种样条平滑(线性、三次、自然三次)
  • 高斯平滑
  • 二进制平滑

所幸,有大佬已经实现好了时间序列的这些平滑技术,并在GitHub上开源了这份模块的代码——它就是 Tsmoothie 模块。

1、准备

开始之前,要确保Python和pip已经成功安装在电脑上。
请选择以下任一种方式输入命令安装依赖:

  1. Windows 环境 打开 Cmd (开始-运行-CMD)。
  2. MacOS 环境 打开 Terminal (command+空格输入Terminal)。
  3. 如果用的是 VSCode编辑器 或 Pycharm,可以直接使用界面下方的Terminal。
    1. pip install tsmoothie
    (PS) Tsmoothie 仅支持Python 3.6 及以上的版本。

    2、Tsmoothie 基本使用

    为了尝试Tsmoothie的效果,需要生成随机数据: ```python import numpy as np import matplotlib.pyplot as plt from tsmoothie.utils_func import sim_randomwalk from tsmoothie.smoother import LowessSmoother

生成 3 个长度为200的随机数据组

np.random.seed(123) data = sim_randomwalk(n_series=3, timesteps=200, process_noise=10, measure_noise=30)

  1. 然后使用Tsmoothie执行平滑化:
  2. ```python
  3. # 平滑
  4. smoother = LowessSmoother(smooth_fraction=0.1, iterations=1)
  5. smoother.smooth(data)

通过 smoother.smooth_data 就可以获取平滑后的数据:

  1. print(smoother.smooth_data)
  2. # [[ 5.21462928 3.07898076 0.93933646 -1.19847767 -3.32294934
  3. # -5.40678762 -7.42425709 -9.36150892 -11.23591897 -13.05271523
  4. # ....... ....... ....... ....... ....... ]]

绘制效果图:

  1. # 生成范围区间
  2. low, up = smoother.get_intervals('prediction_interval')
  3. plt.figure(figsize=(18,5))
  4. for i in range(3):
  5. plt.subplot(1,3,i+1)
  6. plt.plot(smoother.smooth_data[i], linewidth=3, color='blue')
  7. plt.plot(smoother.data[i], '.k')
  8. plt.title(f"timeseries {i+1}"); plt.xlabel('time')
  9. plt.fill_between(range(len(smoother.data[i])), low[i], up[i], alpha=0.3)

Tsmoothie—将数据平滑化并找到异常点 - 图4

3、基于Tsmoothie的极端异常值检测

事实上,基于smoother生成的范围区域,可以进行异常值的检测:
Tsmoothie—将数据平滑化并找到异常点 - 图5
可以看到,在蓝色范围以外的点,都属于异常值。可以轻易地将这些异常值标红或记录,以便后续的处理。

  1. _low, _up = smoother.get_intervals('sigma_interval', n_sigma=2)
  2. series['low'] = np.hstack([series['low'], _low[:,[-1]]])
  3. series['up'] = np.hstack([series['up'], _up[:,[-1]]])
  4. is_anomaly = np.logical_or(
  5. series['original'][:,-1] > series['up'][:,-1],
  6. series['original'][:,-1] < series['low'][:,-1]
  7. ).reshape(-1,1)

假设蓝色范围interval的最大值为up、最小值为low,如果存在 data > up 或 data < low 则表明此数据是异常点。
使用以下代码通过滚动数据点进行平滑化和异常检测,就能保存得到上方的GIF动图。

  1. # Origin: https://github.com/cerlymarco/MEDIUM_NoteBook/blob/master/Anomaly_Detection_RealTime/Anomaly_Detection_RealTime.ipynb
  2. import numpy as np
  3. import matplotlib.pyplot as plt
  4. from celluloid import Camera
  5. from collections import defaultdict
  6. from functools import partial
  7. from tqdm import tqdm
  8. from tsmoothie.utils_func import sim_randomwalk, sim_seasonal_data
  9. from tsmoothie.smoother import *
  10. def plot_history(ax, i, is_anomaly, window_len, color='blue', **pltargs):
  11. posrange = np.arange(0,i)
  12. ax.fill_between(posrange[window_len:],
  13. pltargs['low'][1:], pltargs['up'][1:],
  14. color=color, alpha=0.2)
  15. if is_anomaly:
  16. ax.scatter(i-1, pltargs['original'][-1], c='red')
  17. else:
  18. ax.scatter(i-1, pltargs['original'][-1], c='black')
  19. ax.scatter(i-1, pltargs['smooth'][-1], c=color)
  20. ax.plot(posrange, pltargs['original'][1:], '.k')
  21. ax.plot(posrange[window_len:],
  22. pltargs['smooth'][1:], color=color, linewidth=3)
  23. if 'ano_id' in pltargs.keys():
  24. if pltargs['ano_id'].sum()>0:
  25. not_zeros = pltargs['ano_id'][pltargs['ano_id']!=0] -1
  26. ax.scatter(not_zeros, pltargs['original'][1:][not_zeros],
  27. c='red', alpha=1.)
  28. np.random.seed(42)
  29. n_series, timesteps = 3, 200
  30. data = sim_randomwalk(n_series=n_series, timesteps=timesteps,
  31. process_noise=10, measure_noise=30)
  32. window_len = 20
  33. fig = plt.figure(figsize=(18,10))
  34. camera = Camera(fig)
  35. axes = [plt.subplot(n_series,1,ax+1) for ax in range(n_series)]
  36. series = defaultdict(partial(np.ndarray, shape=(n_series,1), dtype='float32'))
  37. for i in tqdm(range(timesteps+1), total=(timesteps+1)):
  38. if i>window_len:
  39. smoother = ConvolutionSmoother(window_len=window_len, window_type='ones')
  40. smoother.smooth(series['original'][:,-window_len:])
  41. series['smooth'] = np.hstack([series['smooth'], smoother.smooth_data[:,[-1]]])
  42. _low, _up = smoother.get_intervals('sigma_interval', n_sigma=2)
  43. series['low'] = np.hstack([series['low'], _low[:,[-1]]])
  44. series['up'] = np.hstack([series['up'], _up[:,[-1]]])
  45. is_anomaly = np.logical_or(
  46. series['original'][:,-1] > series['up'][:,-1],
  47. series['original'][:,-1] < series['low'][:,-1]
  48. ).reshape(-1,1)
  49. if is_anomaly.any():
  50. series['ano_id'] = np.hstack([series['ano_id'], is_anomaly*i]).astype(int)
  51. for s in range(n_series):
  52. pltargs = {k:v[s,:] for k,v in series.items()}
  53. plot_history(axes[s], i, is_anomaly[s], window_len,
  54. **pltargs)
  55. camera.snap()
  56. if i>=timesteps:
  57. continue
  58. series['original'] = np.hstack([series['original'], data[:,[i]]])
  59. print('CREATING GIF...') # it may take a few seconds
  60. camera._photos = [camera._photos[-1]] + camera._photos
  61. animation = camera.animate()
  62. animation.save('animation1.gif', codec="gif", writer='imagemagick')
  63. plt.close(fig)
  64. print('DONE')

注意,异常点并非都是负面作用,在不同的应用场景下,它们可能代表了不同的意义。
Tsmoothie—将数据平滑化并找到异常点 - 图6
比如在股票中,它或许可以代表着震荡行情中某种趋势反转的信号。
或者在家庭用电量分析中,它可能代表着某个时刻的用电峰值,根据这个峰值可以此时此刻开启了什么样的电器。
所以异常点的作用需要根据不同应用场景进行不同的分析,才能找到它真正的价值。
Tsmoothie—将数据平滑化并找到异常点 - 图7
总而言之,Tsmoothie 不仅可以使用多种平滑技术平滑化时序数据,让模型训练更加有效,还可以根据平滑结果找出数据中的离群点,是做数据分析和研究的一个好帮手,非常有价值。