時系列・スペクトルデータをre-samplingして次元削減【Python: SciPy】

2019年3月26日

PythonでDataFrame中の時系列・スペクトルデータを間引きたい時の方法です。

「pandas.DataFrame.resample」だと引数でD(日次)、W(週次)などの時間設定をしないといけません。日時の列がない時系列データやスペクトルデータの間引きたい時は以下の方法が使えます。

時系列・スペクトルデータとは

時系列・スペクトルデータとは、ある軸に沿って一定間隔ごとに観察されるデータ系列があてはまる。例として、株価の推移や吸光スペクトルなど。

時系列データの例 スペクトルデータの例
・気温や降水状況の遷移
・交通状態の変化
・日々の売り上げ
・株価の推移
・ビットコイン価格の推移
・音声データ
・化合物の吸収スペクトル(IR、UV)
・天体からのスペクトル

それぞれ分類は異なるものの、データとして似たような以下の特徴を持つ。

  1. ある測定点と隣り合っている点は、近い値をとる
  2. 長期(広範囲)でみると、ノイズが含まれる

参照: https://kotobank.jp/word/%E6%99%82%E7%B3%BB%E5%88%97%E3%83%87%E3%83%BC%E3%82%BF-1329677 https://datachemeng.com/preprocessspectratimeseriesdata/

時系列・スペクトルデータのre-sampling

時系列・スペクトルデータを元に予測モデルを作成する際、すべての測定点におけるデータを用いると、膨大な数の特徴量になってしまう。これは過学習につながるので、汎化性能の向上にはダウンサンプリングによる次元削減が有効。

使うもの:SciPy(scipy.signal)

数学・科学・工学用のオープンソースソフトウェアのエコシステム(高度な科学計算ライブラリの詰め合わせ的なもの)。

NumPy より高度な数値演算処理ができ、物理定数,疎行列,確率分布から数値積分,信号処理、最適化、統計などが簡単に実行できるようになる。

scipy.signalは、scipyのうちの波形処理に関するモジュール。

  • scipy.signal.decimate

import numpy as np
from scipy import signal

# 基になる40点の波形データを作成
x = np.linspace(0, 10, 40, endpoint=False)
y = np.cos(-x**2/6)

# データをもとに20点にダウンサンプリング
x_down = np.linspace(0, 10, 20, endpoint=False)
y_down = signal.decimate(y, 2) # 2分の1にダウンサンプリング

# 結果をグラフにプロット
%matplotlib inline
plt.plot(x, y, '.-', label='data')
plt.plot(x_down, y_down, 'rs-', label='down-sampled', alpha=0.5)
plt.legend()
plt.show()
scipy.signal.decimateによるダウンサンプリング
scipy.signal.decimateによるダウンサンプリング

scipy.signal.decimateは、アンチエイリアス (anti-aliasing) 処理を行ってダウンサンプリングする(アンチエイリアス処理とは、連続データから一定間隔にサンプリングするときに生じると歪みをなくすための処理をいう)。そのままデータポイントを減らしたものに近い形でリサンプリングされる。

参照:https://docs.scipy.org/doc/scipy-1.2.1/reference/generated/scipy.signal.decimate.html

  • scipy.signal.resample

# 基になる40点の波形データを作成
x = np.linspace(0, 10, 40, endpoint=False)
y = np.cos(-x**2/6)

# データをもとに2.5倍の100点にアップサンプリング
x_up = np.linspace(0, 10, 100, endpoint=False)
y_up = signal.resample(y, 100)  

# データをもとに半分の20点にダウンサンプリング
x_down = np.linspace(0, 10, 20, endpoint=False)
y_down = signal.resample(y, 20)


# 結果をグラフにプロット
import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(x, y, '.-', label='data')
plt.plot(x_up, y_up, 'go-', label='up-sampled', alpha=0.3)
plt.legend()
plt.show()

plt.plot(x, y, '.-', label='data')
plt.plot(x_down, y_down, 'rs-', label='down-sampled', alpha=0.5)
plt.legend()
plt.show()
scipy.signal.resampleで処理した波形データ
scipy.signal.resampleで処理した波形データ

scipy.signal.resampleはフーリエ変換を用いたリサンプリングのため、信号は周期的であるとの仮定に基づいている。上のデータでいう末端部分など周期性を満たしていない場合は大きく値が外れてしまう。

ちなみにscipy.signal.resampleはup-samplingも可能。
青線が元のデータポイント、緑がアップサンプリング後、赤がダウンサンプリング後のプロット。

 

kaggleの音声解析コンペにも使用例があるので参考になります。

参照:https://docs.scipy.org/doc/scipy-0.18.1/reference/generated/scipy.signal.resample.html

DataFrameへの適用

行が各測定点、列が各サンプルのデータフレームで実施。

import pandas as pd

# データフレームに格納するデータを作成
y1 = np.cos(-x**2/6)*1/2
y2 = np.cos(x)*1/3

df1 = pd.DataFrame({'y':y, 'y1':y1, 'y2':y2})

# ダウンサンプリング
df1_down = signal.decimate(df1, 2, axis=0)
df1_down = pd.DataFrame(df1_down, columns=['y_down','y1_down','y2_down'], index=np.linspace(0, 10, 20, endpoint=False))

# ダウンサンプリングしたデータを表示&プロット
print(df1.head(10)) 
print(df1_down.head(10))
df1.plot(kind='line', marker='.') 
df1_down.plot(kind='line', marker='.')
df1.head(10)  df1_down.head(10)
 df1.head(10)            df1_down.head(10)
ダウンサンプリング前後のデータポイント
ダウンサンプリング前            ダウンサンプリング後

もともとの40点から1/2の20点にダウンサンプリングしても、波形を精度良く維持しており、特徴量とするにも情報のロスは少なそうです。

行が各サンプル、列が各測定点のデータフレームの例
行が各サンプル、列が各測定点の場合

なお、上のような行が各サンプル、列が各測定点のデータフレーム(いわゆる整然データ)の場合は、引数axis=1とすればOK。