Googleがコミュニティモビリティレポートを公開している。これはGoogleアカウントのロケーション履歴を有効にしているユーザー(サンプルベース)のデータに基づいた移動データで、2020 年 1 ⽉ 3 ⽇〜2 ⽉ 6 ⽇の 5 週間における該当曜⽇の中央値を基準とした増減のデータになっている。詳細はモビリティ レポートの CSV ドキュメントを参照。今回はこのデータを使って、Pythonで時系列クラスタリングをすると同様にPythonのtslearnを使って時系列クラスタリングを行う。
環境
モビリティレポートの可視化
モビリティレポートのcsvには世界中のデータが含まれるが、ここでは日本の都道府県ごとのデータのみを使う。まずはcsvをPandasのDataFrameとして読み込んで中身を確認。sub_region_1が都道府県のカラムで、retail_and_recreation_percent_change_from_baselineからresidential_percent_change_from_baselineが場所カテゴリごとの移動データになっている。
import pandas as pd # Googleモビリティレポートのファイルパス # 2020年1月3日から2月6日の5週間の曜日別中央値を基準とした値 MOBILITY_CSV_GOOGLE = 'Global_Mobility_Report.csv' dfg = pd.read_csv(MOBILITY_CSV_GOOGLE) dfg.info()
7月の都道府県ごとのtransit_stations_percent_change_from_baseline(公共交通機関の拠点)のデータを可視化してみる。
%matplotlib inline import matplotlib.pyplot as plt import pandas as pd # Googleモビリティレポートのファイルパス # 2020年1月3日から2月6日の5週間の曜日別中央値を基準とした値 MOBILITY_CSV_GOOGLE = 'Global_Mobility_Report.csv' # 対象とする場所のカテゴリ # 公共交通機関の拠点(例: 地下鉄、バス、電車の駅)など MOBILITY_CATEGORY = 'transit_stations_percent_change_from_baseline' dfg = pd.read_csv(MOBILITY_CSV_GOOGLE) # 都道府県ごとのデータを取得 dfg_jp = dfg[dfg['country_region_code']=='JP'].dropna(subset=['sub_region_1']) # 都道府県のチェック pref = dfg_jp['sub_region_1'].unique() print(pref) assert len(pref) == 47 # 日付をインデックス、都道府県をカラムとするDataFrameに変換 dfg_cat = dfg_jp.set_index(['date', 'sub_region_1'])[MOBILITY_CATEGORY].unstack() # 2021年7月のデータを取得 dfg_cat_jul = dfg_cat['2021-07-01':'2021-07-31'] # データのプロット ax = dfg_cat_jul.plot(figsize=(12,8), alpha=0.75, rot=90); ax.legend(loc='center left', bbox_to_anchor=(1.0, 0.5)) plt.show()
クラスター数を決める
データをk-means法で時系列クラスタリングするが、k-means法では事前にクラスター数を決める必要がある。クラスタリングの前にシルエット値からクラスター数を決定する。シルエット値はk-means法の最適なクラスター数を選択するでも使用しているが、今回はSelecting the number of clusters with silhouette analysis on KMeans clusteringを参考にシルエット値をプロットして確認してみる。なお、メトリックとしてユークリッド距離を使う。
以下はクラスター数2~5の範囲でシルエット値を算出してプロットするコード。scikit-learnにはデータごとのシルエット値を返してくれるsilhouette_samplesメソッドがあるがtslearnにはないので自作した。平均シルエット値が1に近く、プロット幅がなるべく均等になるクラスター数がよいと言われている。
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import numpy as np
import pandas as pd
from tslearn.clustering import TimeSeriesKMeans, silhouette_score
from tslearn.preprocessing import TimeSeriesScalerMinMax
from tslearn.utils import to_time_series_dataset
from scipy.spatial.distance import cdist
from sklearn.metrics import silhouette_samples as sklearn_silhouette_samples
# Googleモビリティレポートのファイルパス
# 2020年1月3日から2月6日の5週間の曜日別中央値を基準とした値
MOBILITY_CSV_GOOGLE = 'Global_Mobility_Report210814.csv'
# 対象とする場所のカテゴリ
# 公共交通機関の拠点(例: 地下鉄、バス、電車の駅)など
MOBILITY_CATEGORY = 'transit_stations_percent_change_from_baseline'
def silhouette_samples(X, labels, **kwds):
"""シルエット値の計算
ユークリッド距離の計算方法は以下tslernのコードを参照
https://github.com/tslearn-team/tslearn/blob/42a56cc/tslearn/clustering/utils.py#L66-L197
"""
X_ = X.reshape((X.shape[0], -1))
sklearn_X = cdist(X_, X_, metric='euclidean')
# 計算済みのデータを渡すのでmetric='precomputed'にする
return sklearn_silhouette_samples(X=sklearn_X,
labels=labels,
metric='precomputed',
**kwds)
def plot_silhouette_scores(ts_data, silhouette_values, n_clusters, km):
"""シルエット値のプロット
以下のコードをほぼコピー
https://scikit-learn.org/stable/auto_examples/cluster/plot_kmeans_silhouette_analysis.html#sphx-glr-auto-examples-cluster-plot-kmeans-silhouette-analysis-py
"""
cluster_labels = km.labels_
centers = km.cluster_centers_
# Create a subplot with 1 row and 2 columns
fig, (ax1, ax2) = plt.subplots(1, 2)
fig.set_size_inches(18, 7)
ax1.set_xlim([-0.1, 1])
# The (n_clusters+1)*10 is for inserting blank space between silhouette
# plots of individual clusters, to demarcate them clearly.
ax1.set_ylim([0, len(ts_data) + (n_clusters + 1) * 10])
y_lower = 10
for i in range(n_clusters):
# Aggregate the silhouette scores for samples belonging to
# cluster i, and sort them
ith_cluster_silhouette_values = \
silhouette_values[cluster_labels == i]
ith_cluster_silhouette_values.sort()
size_cluster_i = ith_cluster_silhouette_values.shape[0]
y_upper = y_lower + size_cluster_i
color = cm.nipy_spectral(float(i) / n_clusters)
ax1.fill_betweenx(np.arange(y_lower, y_upper),
0, ith_cluster_silhouette_values,
facecolor=color, edgecolor=color, alpha=0.7)
# Label the silhouette plots with their cluster numbers at the middle
ax1.text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))
# Compute the new y_lower for next plot
y_lower = y_upper + 10 # 10 for the 0 samples
ax1.set_title("The silhouette plot for the various clusters.")
ax1.set_xlabel("The silhouette coefficient values")
ax1.set_ylabel("Cluster label")
# The vertical line for average silhouette score of all the values
ax1.axvline(x=np.mean(silhouette_values), color="red", linestyle="--")
ax1.set_yticks([]) # Clear the yaxis labels / ticks
ax1.set_xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1])
# 2nd Plot showing the actual clusters formed
colors = cm.nipy_spectral(cluster_labels.astype(float) / n_clusters)
ax2.scatter(ts_data[:, 0], ts_data[:, 1], marker='.', s=30, lw=0, alpha=0.7,
c=colors, edgecolor='k')
# Labeling the clusters
# centers = clusterer.cluster_centers_
# Draw white circles at cluster centers
ax2.scatter(centers[:, 0], centers[:, 1], marker='o',
c="white", alpha=1, s=200, edgecolor='k')
for i, c in enumerate(centers):
ax2.scatter(c[0], c[1], marker='$%d$' % i, alpha=1,
s=50, edgecolor='k')
ax2.set_title("The visualization of the clustered data.")
ax2.set_xlabel("Feature space for the 1st feature")
ax2.set_ylabel("Feature space for the 2nd feature")
plt.suptitle(("Silhouette analysis for KMeans clustering on sample data "
"with n_clusters = %d" % n_clusters),
fontsize=14, fontweight='bold')
plt.show()
def show_silhouette_scores(ts_data, dates):
# クラスター数2-5を確認
for n_clusters in range(2, 6):
print('クラスター数 =', n_clusters)
km = TimeSeriesKMeans(n_clusters=n_clusters, metric='euclidean', verbose=False, random_state=1).fit(ts_data)
assert len(km.labels_) == 47
print('クラスタリング結果 =', km.labels_)
silhouette_values = silhouette_samples(ts_data, km.labels_)
silhouette_avg = np.mean(silhouette_values)
print('シルエット値平均 =', silhouette_avg)
plot_silhouette_scores(ts_data, silhouette_values, n_clusters, km)
dfg = pd.read_csv(MOBILITY_CSV_GOOGLE)
# 都道府県ごとのデータを取得
dfg_jp = dfg[dfg['country_region_code']=='JP'].dropna(subset=['sub_region_1'])
# 都道府県のチェック
pref = dfg_jp['sub_region_1'].unique()
print(pref)
assert len(pref) == 47
# 日付をインデックス、都道府県をカラムとするDataFrameに変換
dfg_cat = dfg_jp.set_index(['date', 'sub_region_1'])[MOBILITY_CATEGORY].unstack()
# 2021年7月のデータを取得
dfg_cat_jul = dfg_cat['2021-07-01':'2021-07-31']
# データのプロット
ax = dfg_cat_jul.plot(figsize=(12,8), alpha=0.75, rot=90);
ax.legend(loc='center left', bbox_to_anchor=(1.0, 0.5))
plt.show()
# 正規化
ts_data = TimeSeriesScalerMinMax().fit_transform(dfg_cat_jul.values.transpose())
prefs = dfg_cat_jul.columns
dates = dfg_cat_jul.index
show_silhouette_scores(ts_data, dates)
実際にはクラスター数2から5の結果が表示されるが、以下はクラスター数4の結果。平均シルエット値だけをみると他のクラスター数がよさそうだが、ここではクラスター数4を採用する。
時系列クラスタリングの結果を可視化
クラスター数が決まったので、時系列クラスタリングして、クラスターごとの平均を可視化してみる。
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import numpy as np
import pandas as pd
from tslearn.clustering import TimeSeriesKMeans, silhouette_score
from tslearn.preprocessing import TimeSeriesScalerMinMax
from tslearn.utils import to_time_series_dataset
from scipy.spatial.distance import cdist
from sklearn.metrics import silhouette_samples as sklearn_silhouette_samples
# Googleモビリティレポートのファイルパス
# 2020年1月3日から2月6日の5週間の曜日別中央値を基準とした値
MOBILITY_CSV_GOOGLE = 'Global_Mobility_Report210814.csv'
# 対象とする場所のカテゴリ
# 公共交通機関の拠点(例: 地下鉄、バス、電車の駅)など
MOBILITY_CATEGORY = 'transit_stations_percent_change_from_baseline'
## シルエット値プロット用の関数は省略 ##
def tsclustering(ts_data, prefs, n_clusters):
km = TimeSeriesKMeans(n_clusters=n_clusters, metric='euclidean', verbose=False, random_state=1).fit(ts_data)
# クラスタリングの結果
result = pd.DataFrame({'clust': km.labels_}, index=prefs)
for clust, g in result.groupby(by='clust'):
print('Cluster: ', clust)
print(g.index)
# クラスターごとの中心をプロット
for i, c in enumerate(km.cluster_centers_):
plt.plot(c.T[0], label=i)
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))
plt.show()
dfg = pd.read_csv(MOBILITY_CSV_GOOGLE)
# 都道府県ごとのデータを取得
dfg_jp = dfg[dfg['country_region_code']=='JP'].dropna(subset=['sub_region_1'])
# 都道府県のチェック
pref = dfg_jp['sub_region_1'].unique()
print(pref)
assert len(pref) == 47
# 日付をインデックス、都道府県をカラムとするDataFrameに変換
dfg_cat = dfg_jp.set_index(['date', 'sub_region_1'])[MOBILITY_CATEGORY].unstack()
# 2021年7月のデータを取得
dfg_cat_jul = dfg_cat['2021-07-01':'2021-07-31']
# データのプロット
ax = dfg_cat_jul.plot(figsize=(12,8), alpha=0.75, rot=90);
ax.legend(loc='center left', bbox_to_anchor=(1.0, 0.5))
plt.show()
# 正規化
ts_data = TimeSeriesScalerMinMax().fit_transform(dfg_cat_jul.values.transpose())
prefs = dfg_cat_jul.columns
dates = dfg_cat_jul.index
# show_silhouette_scores(ts_data, dates)
n_clusters = 4
tsclustering(ts_data, prefs, n_clusters=n_clusters)
時系列クラスタリングの結果は以下の通り。4連休での差が大きい。4連休で最も移動が減少しているクラスター2には東京、大阪、愛知などの大都市圏が含まれる。逆に4連休に移動が上昇したクラスター1,3には地方の都道府県が含まれている。クラスター2に沖縄県が入っているが、これは台風で天候が悪かった影響と考えられる。




0 件のコメント:
コメントを投稿