DTW(Dynamic Time Warping)で台風軌道をクラスタリングする

はじめに

多次元時系列データのクラスタリングがしたいと思って探していたところ、 ちょうどこちらのブログの題材が台風軌道のクラスタリングという、多次元時系列かつ系列長の異なるデータをクラスタリングするというものだったので、理解を兼ねて同じ内容をpythonで実施してみたのが今回の内容になります。

参考資料

題材と内容を参考にさせていただいたブログ

DTWについてのわかりやすい資料

気象庁の台風データ

tsleanのドキュメント

実装

気象庁から2019年に発生した台風のデータを取得します。 下記のように、各台風に対して6時間毎の緯度経度の時系列データが存在します。

f:id:rmizutaa:20200926085018p:plain

foliumで可視化するとこのような感じ。2019年は全部で29の台風がありました。

import folium

map_osm = folium.Map(location=[35.6,139.7],zoom_start=3)
for num in df["台風番号"].unique():
    point=np.array(df[df["台風番号"]==num][["緯度","経度"]])
    folium.PolyLine(
            locations=point,                   
            ).add_to(map_osm)
map_osm

f:id:rmizutaa:20200926085113p:plain

この台風軌道を3つのグループにクラスタリングします。 tslearnのドキュメントがわかりやすいですが、 系列長が同じかつある特定の時間での比較が重要である場合はユークリッド距離を用いたkmeansでもクラスタリングができますが、 例えば1期分ずれているが、全体的に動きが類似しているような時系列をクラスタリングする場合はDTW(Dynamic Time Warping)が有効になります。

DTWの考え方としては、2つの時系列データに対しすべての点の組み合わせの距離を計算し、系列同士の距離が最短となる経路を探索するものになります。 サンプルとして出てくるものは1次元のデータが多いですが、組み合わせ計算のときはユークリッド距離を使用することが多いので、多次元データにも対応できます。 今回のケースだと緯度と経度という2次元の特徴に対してDTWを用いたクラスタリングを実施する形となります。

調べるとpythonでもいくつかパッケージが見つかるのですが、tsleanというパッケージの信頼性が高そうだったので、今回はこのパッケージを用いてクラスタリング及び結果の可視化を行います。

from tslearn.clustering import TimeSeriesKMeans
from tslearn.utils import to_time_series_dataset
from tslearn.barycenters import dtw_barycenter_averaging

points=[]
for num in df["台風番号"].unique():
    points.append(np.array(df[df["台風番号"]==num][["緯度","経度"]]))
points = to_time_series_dataset(points)

dba_km = TimeSeriesKMeans(n_clusters=3,
                          n_init=5,
                          metric="dtw",
                          verbose=True,
                          max_iter_barycenter=10,
                          random_state=22)

pred = dba_km.fit_predict(points)


colors=["blue","yellow","green","red"]
c0=[]
c1=[]
c2=[]

map_osm = folium.Map(location=[35.6,139.7],zoom_start=3)
for num,c in zip(df["台風番号"].unique(),pred):
    if c==0:
        c0.append(np.array(df[df["台風番号"]==num][["緯度","経度"]]))
    elif c==1:
        c1.append(np.array(df[df["台風番号"]==num][["緯度","経度"]]))
    else:
        c2.append(np.array(df[df["台風番号"]==num][["緯度","経度"]]))
 
    point=np.array(df[df["台風番号"]==num][["緯度","経度"]])
    folium.PolyLine(
            locations=point,     
            color=colors[c],
            ).add_to(map_osm)

map_osm

f:id:rmizutaa:20200926101217p:plain

右下の太平洋上で発生して、そこから東南アジアへ向かうもの、日本の左側を通るもの、日本の東側を通るもの、という別れ方をしているようです。 それほど別れ方に違和感はないように感じます。 元記事ではDTWを使った距離計算をした後にk-medoidsを用いてクラスタリングを行なっているのですが、 今回の手法はDTWで距離計算をした後にDBA(dtw barycenter averaging)で重心計算を行い、その重心を用いてkmeansでクラスタリングを行なっています。

クラスタの重心点をDBAで計算したものを赤線で引いてみます。

dba_c0=dtw_barycenter_averaging(c0)
dba_c1=dtw_barycenter_averaging(c1)
dba_c2=dtw_barycenter_averaging(c2)

folium.PolyLine(
            locations=dba_c0,     
            color=colors[3],
            ).add_to(map_osm)

folium.PolyLine(
            locations=dba_c1,     
            color=colors[3],
            ).add_to(map_osm)

folium.PolyLine(
            locations=dba_c2,     
            color=colors[3],
            ).add_to(map_osm)

map_osm

f:id:rmizutaa:20200926101235p:plain

かなりガタガタしていますが、大まかな特徴を捉えるくらいには使えるでしょうか。

まとめ

DTWを用いて台風軌道のクラスタリングを行いました。 系列長が不定で多次元なデータもクラスタリングを行うことができるので、例えばgpsデータなど探せば応用できる対象が結構あるのではないかなぁと思っています。

使用したコードは下記になります。

blog/dtw_typhoon at master · rmizuta3/blog · GitHub