通勤手段で都道府県ごとにクラスタリング分析してみる。

# 目的

通勤手段で都道府県ごとにクラスタリング分析してみる。

前回の国勢調査データを使ったクラスタリング分析では人口データを使ったが、今回は、通勤手段を使ってみたい。

前回記事 [年齢別人口の男女比を都道府県ごとにクラスタリング分析してみる。](https://qiita.com/DekaGodzilla/items/634ea456060ba610fa28) を適宜参照してください。

# 国勢調査データのダウンロード

[従業地・通学地による人口・就業状態等集計](https://www.e-stat.go.jp/stat-search/files?page=1&query=%E5%88%A9%E7%94%A8%E4%BA%A4%E9%80%9A%E6%89%8B%E6%AE%B5%EF%BC%8C%E5%B8%B8%E4%BD%8F%E5%9C%B0%E5%8F%88%E3%81%AF%E5%BE%93%E6%A5%AD%E5%9C%B0%E3%83%BB%E9%80%9A%E5%AD%A6%E5%9C%B0%E5%88%A5%E9%80%9A%E5%8B%A4%E8%80%85%E3%83%BB%E9%80%9A%E5%AD%A6%E8%80%85%E6%95%B0&layout=dataset&stat_infid=000032214691&metadata=1&data=1) からダウンロードするのだが、ダウンロードできるファイルのサイズ制限があり、csvは、項目を絞らないとダウンロードできないため、エクセルファイルでダウンロードする。


# 国勢調査データの読み込み

エクセルでみると、１シートしかなく、ヘッダは８行目から４行あるため、
skiprows=8, header=[0,1,2,3] をパラメータ指定して読み込む。

```python
import pandas as pd
# 読み込み
excel_file = pd.ExcelFile('./data/e18.xlsx')
for sheet_name in excel_file.sheet_names:
  df = excel_file.parse(sheet_name, index_col=0, skiprows=8, header=[0,1,2,3])
print(df.head())
```

```
        Unnamed: 1_level_0 Unnamed: 2_level_0 Unnamed: 3_level_0 0_常住地による人口  \
        Unnamed: 1_level_1 Unnamed: 2_level_1 Unnamed: 3_level_1          人   
地域識別コード               都道府県                地域名             利用交通手段              
a                    00_全国           00000_全国              00_総数   57152761   
a                    00_全国           00000_全国            01_徒歩のみ    3999367   
a                    00_全国           00000_全国           02_鉄道・電車   14575628   
a                    00_全国           00000_全国            03_乗合バス    3890283   
a                    00_全国           00000_全国       04_勤め先・学校のバス     684451   
a                    00_全国           00000_全国            05_自家用車   28315168
```
# 国勢調査データの前処理

## ヘッダ整形

４行あるヘッダのうち、前半は３行目の 地域識別コード/都道府県/地域名/利用交通手段 を採用し、後半は、１行目の 0_常住地による人口 （以降）を採用する。

```python
# ヘッダ整形
front_col = [col for col in df.columns.get_level_values(2) if not col.startswith('Unnamed')]
back_col = [col for col in df.columns.get_level_values(0) if not col.startswith("Unnamed:")]
front_col = [col for col in front_col if not col.startswith(" ")]
new_col = front_col + back_col
df.columns = new_col
```
```
都道府県	地域名	利用交通手段	0_常住地による人口	001_従業も通学もしていない	002_自市区町村で従業・通学	0021_自宅で従業	0022_自宅外の自市区町村で従業・通学	003_他市区町村で従業・通学	0031_自市内他区で従業・通学	...	0034_従業・通学市区町村「不詳・外国」	004_従業地・通学地「不詳」	0R1_（再掲）流出人口	1_従業地・通学地による人口	101_うち他市区町村に常住	1011_自市内他区に常住	1012_県内他市町村に常住	1013_他県に常住	102_うち従業地・通学地「不詳」又は従業・通学市区町村「不詳・外国」で当地に常住している者	1R1_（再掲）流入人口
a	00_全国	00000_全国	01_徒歩のみ	3999367	-	3736864	-	3736864	220806	102950	...	4553	41697	-	3999367	216253	102950	75030	38273	46250	-
a	00_全国	00000_全国	02_鉄道・電車	14575628	-	1666279	-	1666279	12801798	3729954	...	178316	107551	-	14575628	12623482	3729954	4866967	4026561	285867	-
a	00_全国	00000_全国	03_乗合バス	3890283	-	1265371	-	1265371	2586104	820524	...	39057	38808	-	3890283	2547047	820524	1069016	657507	77865	-
a	00_全国	00000_全国	04_勤め先・学校のバス	684451	-	290836	-	290836	387212	39815	...	6063	6403	-	684451	381149	39815	261742	79592	12466	-
```

## 必要なデータのみ残す

文字列⇒数値変換した後、必要なデータのみを残し、不要なデータは削除する。
* 都道府県単位で分析したいので、市区町村と全国のデータを削除
* 各利用交通手段を合計しても、利用交通手段の総数とならなかったため、総数を削除（総数は各利用交通手段を合計して別途計算することにする）
* 従業地による人口は、大きく分けて、定住地人口、流出人口、流入人口とあるが、今回は定住地人口を採用

また、カラム名を使いやすいアルファベットに変換し、不要な都道府県コードも削除する。
```python
# 文字列⇒数値変換
for col in df.loc[:, '0_常住地による人口':].columns:
  df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0).astype(int)
# 都道府県レベルのみ（市区町村レベルと全国を削除する）
pref_level = df['地域名'].map(lambda x: '000_' in x and (not '00000_全国' in x ) )
df_pref = df[pref_level]
# 00_総数は一旦削除
df_pref = df_pref[df_pref['利用交通手段']!='00_総数']
# 必要な列のみ（都道府県/利用交通手段/0_常住地による人口）
df_pref = df_pref.loc[:,['都道府県','利用交通手段','0_常住地による人口']]
df_pref.rename(columns={'都道府県': 'pref','利用交通手段':'commute by','0_常住地による人口':'population'},inplace=True)
# 都道府県のコードを削除
df_pref['pref'] = df_pref['pref'].map(lambda x: x[3:] )
print(df_pref.head())
```
```
	pref	commute by	population
a	北海道	01_徒歩のみ	256106
a	北海道	02_鉄道・電車	352288
a	北海道	03_乗合バス	205581
a	北海道	04_勤め先・学校のバス	44902
a	北海道	05_自家用車	1308686
```

## ２次元データ化

通勤手段毎の人数は、縦１次元のデータフレームなので、都道府県別に２次元化する。
都道府県のリストを作って、１都道府県ずつ処理する。
都道府県名(pref)が一致するデータのみを抽出し、転置することで縦⇒横のデータに変換する。
それをリストにアペンドすることで２次元のデータフレームができあがる。
ついでに、通勤手段名称をカラム名称として設定する。
```python
import numpy as np

# 通勤手段毎の人数
population_list = []
# 都道府県のリストを作成
pref_list = df_pref['pref'].unique()
# 都道府県のリストのループ
for pref in pref_list:
  # 対象の都道府県のデータのみ
  df_temp = df_pref[df_pref['pref']==pref]
  # 人数を取得し、転置（縦⇒横）
  df_population = df_temp['population'].T
  # numpy配列としてリストに追加
  np_population = np.array(df_population)
  population_list.append(np_population)
# 通勤手段毎の人数
np_population_list = np.array(population_list)
# 通勤手段の名称リスト
col = ['walk','train','public bus','private bus','car','taxi','motorcycle', 'bicycle','other','unknown']
# 通勤手段のデータフレーム
df_pref = pd.DataFrame(np_population_list,index=pref_list,columns=col)
print(df_pref.head())
```

```
	walk	train	public bus	private bus	car	taxi	motorcycle	bicycle	other	unknown
北海道	256106	352288	205581	44902	1308686	10774	8294	217597	57288	62412
青森県	44465	15912	24012	13319	420743	1548	2920	47789	10087	12004
岩手県	41249	25940	22785	9194	437679	1113	4132	47516	9408	9932
宮城県	84357	186933	73377	11622	657838	2357	17559	109607	19764	25621
秋田県	26546	14318	10544	3571	350972	441	1394	34717	4974	7588
```
## 合計と比率を計算
各都道府県で人口に差があるので、通勤手段別人口にも差があるため、差ではなく比率を計算する。
前述の通り、ダウンロードしたデータの総数は数が合わないので、自分で合計を計算する。

```python
# 合計を求める
df_sum = df_pref.sum(axis=1)
# 各通勤手段の比率を計算
df_pref_ratio = ( df_pref.T / df_sum * 100 )
print(df_pref_ratio.head())
```

```
                   北海道        青森県        岩手県        宮城県        秋田県        山形県  \
walk         10.147120   7.500856   6.773813   7.094577   5.833452   5.487031   
train        13.957926   2.684215   4.259805  15.721404   3.146364   3.076838   
public bus    8.145280   4.050614   3.741699   6.171139   2.317032   1.284219   
private bus   1.779052   2.246799   1.509817   0.977431   0.784723   0.901730   
car          51.851162  70.975660  71.874610  55.325369  77.125685  78.084589   
```
ようやく前処理が終わったので可視化する。

# 可視化（都道府県別）

都道府県別でグラフ表示する。

```python
import matplotlib.pyplot as plt
import japanize_matplotlib

fig_size_x = 12
fig_size_y = 5
fig = plt.figure(figsize=(fig_size_x, fig_size_y))
plt.plot(df_pref_ratio)
plt.title( '通勤手段比率(都道府県別)')
plt.xlabel('通勤手段')
plt.ylabel('比率(%)')
plt.grid(axis='y')
plt.grid(axis='x')
plt.show()
```
![output1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570565/893cd058-e809-1f6f-68a9-19fdd7a5c05f.png)

車＞電車＞自転車の順にばらついているようだが、線が多くてわかりにくいので、クラスタリング分析してみる

# クラスタリング分析

```python
from scipy.cluster import hierarchy
import japanize_matplotlib
fig = plt.figure(figsize=(fig_size_x, fig_size_y))
method = 'ward'
Z = hierarchy.linkage(df_pref_ratio.T, method=method)
hierarchy.dendrogram(Z, color_threshold=0,labels=df_pref_ratio.columns)
plt.ylabel('distance')
plt.title( '通勤手段比率')
plt.xticks(rotation=90)
plt.show()
```
![output2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570565/b5d7dd27-9757-8c56-4fcb-8fd56dff5967.png)

ぱっとみ、都会と田舎グループでクラスタリングされているように見えるが、５クラスタとして分類し、クラスタ毎の平均を可視化する。

# 可視化（クラスタ別平均）

```python
from sklearn.cluster import AgglomerativeClustering
n_clusters = 5
clustering = AgglomerativeClustering(linkage=method, n_clusters=n_clusters)
clustering.fit(df_pref_ratio.T)
df_clustering_result = pd.DataFrame( clustering.labels_, index=df_pref_ratio.columns, columns=['label'])
fig = plt.figure(figsize=(fig_size_x, fig_size_y))
# クラスタ毎に処理
for i in range(n_clusters):
  df_work = pd.DataFrame()
  for j, rec in df_clustering_result.iterrows():
    # 対象クラスタに分類された結果のみ抽出
    if rec.label==i:
      # インデックス（＝都道府県名称）
      name = j
      df_work[name] = df_pref_ratio[name]
  # クラスタ毎の平均をプロット
  plt.plot(df_work.mean(axis=1),label=f'#{i}クラスタ')
plt.title( '通勤手段比率(クラスタ別)')
plt.xlabel('通勤手段')
plt.ylabel('比率(%)')
plt.grid(axis='y')
plt.grid(axis='x')
plt.legend()
plt.show()
```
![output3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570565/1afeb1ca-d265-8b7c-4c11-c9639114d03a.png)

やはり、ばらつきの大きい、車、電車、自転車の影響が強そうだ。
今度はクラスタ毎に可視化する。
理解しやすいように、都会から表示してみる。
（クラスタ番号はクラスタリング分析で付与されるものなので、意味はありません。都会順に表示できるように、あらかじめ確認しておき、表示順番をリスト化しました。）

# 可視化（クラスタ別）

```python
# クラスタ毎に処理
#for i in range(n_clusters):
for i in [1,3,2,0,4]: # 都会順に表示できるようにリスト化
  fig = plt.figure(figsize=(fig_size_x, fig_size_y))
  for j, rec in df_clustering_result.iterrows():
    # 対象クラスタに分類された結果のみ抽出
    if rec.label==i:
      plt.plot(df_pref_ratio[j],label=f'{j}')
  plt.title( f'通勤手段比率(#{i}クラスタ)')
  plt.xlabel('通勤手段')
  plt.ylabel('比率(%)')
  plt.grid(axis='y')
  plt.grid(axis='x')
  plt.ylim(0, 80)
  plt.legend(loc='upper right')
  plt.show()
```

# #1 クラスタ
電車が多く(40%超)、車が少ない(10%台）大都会型。
唯一、電車＞車となるクラスタ。東京、神奈川、大阪。
![output4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570565/28b7c5d1-5979-4ef3-16b5-f3982df2ad61.png)

# #3 クラスタ
電車(30%)、車(30%)ほぼ同じとなる、都会型。埼玉、千葉など。
![output5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570565/15c3931f-7c08-f861-eb1b-b4aeae499dcd.png)

# #2 クラスタ
電車(20%台)、車(50%台)となり、車が多くなるが、電車利用が多い準都会型。愛知、福岡など。
![output6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570565/d898b914-591b-e26e-f45e-f5928edf8c13.png)

# #0 クラスタ
電車(10%以下)、車(60%超)となり、車がないと不便な田舎型。静岡、和歌山など。
![output7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570565/7560eaaf-f943-fe97-e89b-a18f0700f100.png)

# #4 クラスタ
電車(10%以下)、車(70%超)となり、車がないと生活できないど田舎型。約半数の都道府県。
![output8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570565/fdf16fb4-5f6d-ffe8-a4db-b7b55300d1ae.png)

# 可視化（通勤手段別比率）

通勤手段別に比率を棒グラフに表示する。（インタラクティブなグラフにて）
まずは、転置し表示用の都道府県名の列を作成する
```python
df_pref_ratio_T = df_pref_ratio.T
# x軸表示用に都道府県名称の列を追加
df_pref_ratio_T['pref'] = df_pref_ratio_T.index
```

次にpanelに通勤手段別の棒グラフを表示する。

```python
import plotly.express as px
import panel as pn
from typing import List
import itertools
pn.extension('plotly', sizing_mode="stretch_width")

# 各エリアのデータ観察（棒グラフ）

# ①4列目以降の列名のリストを作成（都道府県名称、他、不明を除く）
cols_list = list(df_pref_ratio_T.columns[:-3].values)

# ②グラフ作成の関数を定義
def show_chart(col_name):
    '''col_nameで指定された列名で指定されたデータ
       をソートし棒グラフを返す'''
    fig = px.bar(df_pref_ratio_T.sort_values(col_name,ascending=False),
                 x='pref',
                 y=col_name,
                 title=f'交通手段の比率: {col_name} (%)'
                 )
    return fig

# ③列名を選択するウィジェットを作成
col_sel = pn.widgets.Select(name='bar_sel', options=cols_list, value='walk')
# ④ウィジェットの値が更新されるとグラフが更新されるよう設定
interactive_chart = pn.bind(show_chart, col_sel)

# ⑤ウィジェットとグラフが並んで配置されるよう、レイアウトを作成
panel1 = pn.Column(
    col_sel,
    interactive_chart
)
panel1.save('commute-ratio.html',embed=True)
panel1
```
以下、傾向がわかりやすい/面白いグラフのみを表示する

## 徒歩
![output9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570565/27fae14e-5b29-3ff0-691b-eb18e24077d5.png)
長崎県、北海道の比率が高く、健康志向が高いと考えられる。

## タクシー
![output10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570565/83cc8a31-dc9c-370d-f913-47124ad6b136.png)
全体的に割合が低く、1%未満ではあるものの、その中でも、沖縄県、東京都、北海道が突出している。この３都道県には富裕層が多く住んでいるのではないかと考えられる。

## バイク
![output11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570565/768dac58-2d15-ae8f-3ab0-f1ab9fa6f987.png)
愛媛県、和歌山県、京都府、沖縄県など、近畿以西の暖かい府県が多い。
逆に、秋田県、北海道、富山県など、北海道、東北、北陸はバイク利用が少ない。やはり冬寒く雪が多いとバイク通勤はできないようだ。

## 自転車
![output12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570565/fe2dc90e-c8b3-d1d8-c8b7-cd3d149a7da7.png)
大阪府の自転車通勤が突出して高い。大阪府民は健康志向が高いのか、節約志向が高いのか。大阪府にも富裕層は多く住んでいそうだが、タクシー利用の割合が高くないことも考えると、後者ではないかと考えてしまう。
逆に、長崎県、沖縄県は自転車利用の割合が突出して低い。長崎県は坂が多いので自転車は利用しにくいか。そう考えると、長崎県は徒歩が多いことも納得できる。
沖縄県はどうしてだろう。台風が多いため、自転車が飛ばされてしまうからではないだろうか。

# まとめ
通勤手段で都道府県ごとにクラスタリング分析したところ、都会か田舎かというグループ化になった。
田舎度≒車がないと生活できない、ということと考えられるが、以下の道県は、（個人的な）イメージ以上に都会になっている。
* 奈良県が、京都府、兵庫県と型を並べる都会型
* 北海道、宮城県、滋賀県が、愛知県、福岡県と型を並べる準都会型

通勤手段別でみてみると、さらに面白いことがわかった。
* 沖縄県、東京都、北海道にはタクシーを利用する富裕層が多く住んでいる。
* バイクは、暖かい地域での利用が多く、寒い地域での利用は少ない。
* 長崎県は坂が多いため、自転車利用は少なく、徒歩が多い。
* 大阪府は節約志向のため、自転車利用が極めて多い。

大阪府の富裕層は、タクシーを利用せず、自転車で通勤しているのではないか、と考えてしまいます。
（大阪府は節約志向です。いい意味で！！大阪府をディスっているわけではありません。）