2020年7月15日水曜日

DTW(動的時間伸縮法)で時系列データ間の距離を求める

以前にPythonで時系列クラスタリングをするで、DTW(動的時間伸縮法)による時系列データのクラスタリングをやってみた。このときはPythonパッケージのtslearnを使ってクラスタリングをしたが、単にDTWによる時系列データ間の類似度(距離)を求めたいだけならfastdtwも使える。ただ、fastdtwではDTWの近似アルゴリズムを使用しているので、tslearnとfastdtwの結果を比べてみることにした。



環境


Debian Buster(Dockerコンテナ)とJupyter Lab。



時系列データ



PythonでPDFの表からデータを抽出する(その2) で使用した帰国者・接触者相談センター(全相談件数)を使う。これは厚生労働省のホームページ地域ごとの感染状況等の公表についてからダウンロードできる「3.帰国者・接触者相談センターへの相談件数の推移(都道府県別・各日)・帰国者・接触者外来の受診者数の推移・うちPCR検査実施件数の推移(都道府県別・各日) 」のPDFに含まれるデータ。最近EXCELファイルが追加されたのでわざわざPDFからデータを抽出する必要はないのだが、ここではPythonでPDFの表からデータを抽出する(その2) の方法で作成したcsvを使う。都道府県ごとのデータがあるので、都道府県データ間の類似度をDTWで比べてみる。

PDFからcsvにしたデータをExcelで開くと次のような感じになる。


ライブラリなどのインストール


DTWを計算するためにtslearnとfastdtwをインストールする。


インストールしたライブラリのバージョン。


さらに、matplotlibのグラフで日本語表示できるように日本語フォントをインストールしておく。



データの準備


PDFから抽出したデータを保存したcsv(000643758.csv)を読み込んで、このcsvに含まれる都道府県ごとの「帰国者・接触者相談センター(全相談件数)」を使う。期間は5月のひと月分。都道府県により絶対数に差があるので、0-1の範囲にデータを変換する。なお、欠損値があるとDTWによる計算でエラーになるので京都、滋賀、香川のデータは除外。

%matplotlib inline

import pandas as pd
from sklearn import preprocessing

import matplotlib.pyplot as plt

# 日本語フォントの指定
plt.rcParams['font.family'] = 'TakaoGothic'

DT_FROM = '2020-05-01'
DT_TO = '2020-05-30'

def normalize(df):
    cols = df.columns
    idx = df.index
    x = df.values
    min_max_scaler = preprocessing.MinMaxScaler()
    x_scaled = min_max_scaler.fit_transform(x)
    return pd.DataFrame(x_scaled, index=idx, columns=cols)

def _load(csvpath):
    df = pd.read_csv(csvpath, index_col=0, header=[0,1], parse_dates=True, encoding='shift-jis')
 
    # 全国を除く「帰国者・接触者相談センター(全相談件数)」だけを選択
    df_con = df.filter(like='帰国者・接触者相談センター(全相談件数)', axis=1).drop(columns=('全国', '帰国者・接触者相談センター(全相談件数)'))
    
    # カラム名を都道府県のみにする
    df_con.columns = df_con.columns.droplevel(level=1)

    # 0-1の範囲に正規化    
    df_con = normalize(df_con[DT_FROM:DT_TO])

    # 欠損値(NaN)があるとDTWの計算ができないので除外
    # 京都、滋賀、香川のデータを除外
    df_con = df_con.dropna(axis='columns')    

    #print(df_con.info())
    #display(df_con.head())
    ax = df_con.plot(legend=None)
    plt.show()
    
    return df_con

df = _load('000643758.csv')

都道府県ごとのデータのプロット。


DTWの計算


tslearnとfastdtwで都道府県データ間の距離をDTWで計算し、似ている/似ていない都道府県の組み合わせを求めてみる。
%matplotlib inline

from itertools import combinations
import pandas as pd
from sklearn import preprocessing

from fastdtw import fastdtw
from tslearn.metrics import dtw

import matplotlib.pyplot as plt

# 日本語フォントの指定
plt.rcParams['font.family'] = 'TakaoGothic'

DT_FROM = '2020-05-01'
DT_TO = '2020-05-30'

def calc_dtw(l_sr):
    dict_dtw = {}
    for sr1, sr2 in combinations(l_sr, 2):
        distance, path = fastdtw(sr1.to_numpy(), sr2.to_numpy())
        dict_dtw[(sr1.name, sr2.name)] = (dtw(sr1.to_numpy(), sr2.to_numpy()), distance)
    
    return dict_dtw

df = _load('000643758.csv')

# Seriesのリスト
l_sr = [item for l, item in df.items()]

# 都道府県間のDTWを計算
dict_dtw = calc_dtw(l_sr)

print('\n似ている都道府県トップ10(DTW)')
display(sorted(dict_dtw.items(), key=lambda x:x[1][0])[:10])
print('\n似ている都道府県トップ10(fastDTW)')
display(sorted(dict_dtw.items(), key=lambda x:x[1][1])[:10])
print('\n似ていない都道府県トップ10(DTW)')
display(sorted(dict_dtw.items(), key=lambda x:x[1][0], reverse=True)[:10])
print('\n似ていない都道府県トップ10(fastDTW)')
display(sorted(dict_dtw.items(), key=lambda x:x[1][1], reverse=True)[:10])

似ている都道府県の組み合わせトップ10は以下の通り。tslearnとfastdtwで違いはあるものの、最も距離が近い組み合わせの神奈川と富山はtslearnとfastdtwで同じ。

似ていない都道府県の組み合わせトップ10は以下の通り。こちらもtslearnとfastdtwで違いはあるものの、最も距離が遠い組み合わせの岐阜と高知はtslearnとfastdtwで同じ。


最も距離が短い神奈川と富山のプロット。ほぼ一致している。


最も距離が遠い岐阜と高知のプロット。確かにけっこう違う。

tslearnとfastdtwそれぞれで距離が短いトップ5を比較。以下のコードを上記コードのあとに追加する。
(2021.12.16 グラフ作成部のコードを追加)
tslearn_top5 = sorted(dict_dtw.items(), key=lambda x:x[1][0])[:5]
fastdtw_top5 = sorted(dict_dtw.items(), key=lambda x:x[1][1])[:5]

for i, ((prefs1, values1), (prefs2, values2)) in enumerate(zip(tslearn_top5, fastdtw_top5)):
    fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 3))
    df.loc[:, prefs1].plot(ax=axes[0])
    df.loc[:, prefs2].plot(ax=axes[1])
    plt.show()   
左がtslearnで右がfastdtw。どちらが良いのかは判断が難しい・・






2 件のコメント:

  1. 最後のグラフの作り方についてもご教授いただけないでしょうか。

    返信削除
    返信
    1. コメントを見落としていて返信遅くなりました。
      グラフ作成箇所のコードを追記したのでご確認ください。

      削除