2024-10-22

【備忘録】時系列データをスムージング ~ SciPy

SciPy は Python のための科学的ツールのオープンソース・ライブラリとして開発されています。SciPyは、NumPy をベースにしていて、統計、最適化、積分、線形代数、フーリエ変換、信号・イメージ処理、遺伝的アルゴリズム、ODE (常微分方程式) ソルバ、特殊関数、その他のモジュールを提供しています。

Wikipedia より引用、編集

今回は SciPy パッケージの make_interp_spline の利用例を紹介します。

背景や動機を説明すると長くなるので、手短にまとめました。

背  景 / 動  機
  • ほぼリアルタイムで取得した株価データをデータ解析に活用しています。
  • ある局面で価格が上昇あるいは下降トレンドなのかを把握したい時、秒単位で上下する価格変動がノイズになってしまって解析が難しくなります。
    • 実際のデータ点をきっちり通らなくとも良いので、滑らかな曲線をあてはめたい。
  • SciPy パッケージの make_interp_spline という API がまさにぴったりの機能だったので適用してみました。

下記の OS 環境で動作確認をしています。

Fedora Linux 41 Workstation (beta) x86_64
Python 3.12.7
jupyterlab 4.2.5
matplotlib 3.9.2
numpy 2.1.2
pandas 2.2.3
scipy 1.14.1

以下の作業は JupyterLab 上でおこなっています。

まず、必要なライブラリを読み込んでおきます。

import datetime

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

from matplotlib import dates as mdates
from scipy.interpolate import make_smoothing_spline

サンプルデータ

Github 上の CSV 形式のサンプルデータを、データフレーム df へ読み込みます。

url = 'https://raw.githubusercontent.com/bitwalk123/stock/refs/heads/main/sample/sample_time_series.csv'
df = pd.read_csv(url, index_col=0, parse_dates=True)
df

 

読み込んだデータをプロットしてみます。

fig = plt.figure(figsize=(10, 8))

ax = fig.add_subplot(111)
ax.plot(df)
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
ax.grid()

plt.tight_layout()
# plt.savefig('sample_time_series_raw.png')
plt.show()

プロットで確認できる細かな変動を含んだ値動きを、適度にスムージングした曲線で表現し直すことが今回の狙いです。

make_smoothing_spline によるスプライン曲線

SciPy で扱えるように時系列をタイムスタンプに変換します。タイムスタンプ x と対応する株価 y から、make_smoothing_spline で関数 spl を定義します。

タイムスタンプを時刻に戻すときに、タイムゾーンを明示していないにもかかわらず、タイムスタンプが UTC(世界標準時)に変換されて、そこから日本時間に戻されてしまいます。あとで処理するのが面倒だったので、タイムスタンプにしたときに時差分の秒数を引いています。

lam は何の略かは判らないのですが、正則化パラメータと説明されていて、デフォルトは None になっています。このパラメータに大きな数を指定すればするほど、曲線のフィッティングが大雑把になります。ここでは 106 を指定しています。

delta = 9 * 60 * 60  # 時差
x = np.array([t.timestamp() - delta for t in df.index])
y = df['Price'].values
spl = make_smoothing_spline(x, y, lam=10 ** 6)

元データのデータフレーム df の時間範囲から、1秒間隔のデータ列 xs を作成します。これを関数 spl に適用して、スムージングした株価列 ys を生成します。

なお、なんの断りもなく1秒間隔にしていますが、用途に応じて間隔は調節します。

データ列 xs をインデックスに、ys を Price 列にしたデータフレーム dfs を生成します。

n = len(df)
ts1 = x[0]
ts2 = x[n - 1]
xs = np.linspace(ts1, ts2, int(ts2 - ts1) + 1)
ys = spl(xs)
dt_index = pd.to_datetime([str(datetime.datetime.fromtimestamp(t)) for t in xs])

dfs = pd.DataFrame({'Price': ys}, index=dt_index)
dfs

 

元のデータとスムージングしたデータを重ねてプロットします。

fig = plt.figure(figsize=(10, 8))

ax = fig.add_subplot(111)
ax.plot(df, lw=0.75, label='raw')
ax.plot(dfs, label='smoothing')
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
ax.grid()
ax.legend()

plt.tight_layout()
# plt.savefig('sample_time_series_smoothing.png')
plt.show()

スムージングの程度は make_smoothing_spline のパラメータ lam に指定する値を変えて調整します。

参考サイト

  1. make_smoothing_spline — SciPy Manual
  2. 滑らかな曲線を作成するためのscipy.interpolate.BSpline入門 - MyEnigma [2022-08-19]

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



このエントリーをはてなブックマークに追加

0 件のコメント: