Pythonで時系列解析をしたい(時系列クラスタリング)

時系列のクラスタリングの場合、tslearnに実装されているのでそれを用います。

時系列と非時系列のクラスタリングの違い

非時系列の場合、1行がidとなり、列が変数でその変数でidをクラスタ化します。対して時系列の場合、(よく見る形は)行が時間であり、列が観測対象で列を時系列する形が多いです。

▼なお、非時系列のクラスタはこれ

esu-ko.hatenablog.com

やりかた

sklearnと同じインターフェイスで扱えます。

from tslearn.clustering import TimeSeriesKMeans

km = TimeSeriesKMeans(n_clusters=3, metric="dtw")
km.fit(データ)

試してみる

テストデータの生成

テストデータをつくって試してみます。

import random
import math
#データを生成する関数(4つ)
base = {
    "a": lambda x : random.random(),
    "b": lambda x : x  * 0.01 + random.random() * 0.1,
    "c": lambda x : math.sin(x + random.random()) + random.random(),
    "d": lambda x : math.sin(x + random.random()) + random.random()
}

#テストデータと種類を格納
test_cls = [] #正解のクラスター
test_X = []
for i in range(20):
  cls,f = random.choice(list(base.items()))
  test_X.append([f(i) for i in range(100)])
  test_cls.append(cls)

aは完全にランダム
bは経過とともに上がっていくデータ
cはsin波
dはcos波

ここからランダムに選んで20のデータをつくります。

import matplotlib.pyplot as plt
for d in test_X:
  plt.plot(d,c='b')

f:id:esu-ko:20200718162942p:plain

クラスタリングしてみる

まずtslearnで扱えるデータに変換します

from tslearn.utils import to_time_series_dataset
ts = to_time_series_dataset(test_X)

#型の確認
type(ts)
#numpy.ndarray

#形の確認
ts.shape
#(20, 100, 1)

専用のクラスとかではなく、numpyのarrayのようです。
長さの異なるデータを入れると最長になるように調整したりするのが目的のようです。

というわけでクラスタリングします
今回は正解が4つなのを知っているので、4つに分類します。

km = TimeSeriesKMeans(n_clusters=4, metric="dtw")
labels = km.fit_predict(ts)

結果の確認

目で見ておきます。

from collections import defaultdict
import matplotlib.pyplot as plt

res = defaultdict(list)

for data,label in zip(test_X,labels):
  res[str(label)].append(data)

for k,v in res.items():
  for row in v:
    plt.plot(row)
  plt.show()
f:id:esu-ko:20200718163553p:plainf:id:esu-ko:20200718164622p:plainf:id:esu-ko:20200718164709p:plainf:id:esu-ko:20200718164733p:plain

目で見る限りうまく分けられているようなかんじがします。
クロス集計でもみておきます。

import pandas as pd
pd.crosstab(pd.Series(test_cls),pd.Series(labels))
col_0    0   1   2   3
row_0               
a   0   0   7   0
b   3   0   0   0
c   0   0   0   2
d   0   2   0   6

かねがねうまく分けられているようです。
ただし、どうやらcとdが分けられていないという結果でした。

と、ここまできちんとコードを読んでいる人は気づいたと思うのですが、実は最初のデータ生成の際にcとdが実は両方sinになっちゃってるんですね。

分けられないのはむしろ正しい結果でした。
クラスターを三つに分けなおして、再度実行すると。

col_0    0   1   2
row_0           
a   7   0   0
b   0   0   3
c   0   2   0
d   0   8   0

ちゃんとc,dはおなじ1というクラスターになりました。