都営地下鉄の最短経路を図化
以前に虎ノ門ヒルズ駅開業時の経路(周辺の駅のみ)をNetworkxで図化したのですが、今回は範囲を広げて都営地下鉄全体でチャレンジしたいと思います。
引き続き、オーム社『Pythonで学ぶネットワーク分析 ColaboratoryとNetworkXを使った実践入門』も参照しています。特にコードの組み方について参考にしました。
▼以前の記事です
まずは座標の作成
前回は簡易的な路線図を使いましたが、今回は緯度経度を使用して位置関係をわかりやすく表現したいと思います。位置情報は『駅データ.jp』のデータを利用させてもらいます。これだけのデータを無料に利用できるのはありがたいです。
Python(Pandas)を使って必要なデータを抽出します。Excelでもいいのですが、エンコードが「UTF-8」なので、読み込みにひと手間かかります。
あらかじめ調べておいた路線コードで都営地下鉄の行と必要なデータ項目(駅名と座標)の列を抽出します。
修正が必要な項目として押上駅の副駅名(スカイツリー前)の削除と、重複データ(乗り換え駅)の削除を行います。
最後にcsvファイルとして保存します。
import pandas as pd
# 駅データ.jpの駅データを読み込み
df = pd.read_csv('station20200316free.csv')
# 都営地下鉄を抽出
df = df[df['line_cd'].isin([99301, 99302, 99303, 99304])][['station_name', 'lon', 'lat']]
# 押上を置き換え
df = df.replace('押上(スカイツリー前)', '押上')
# 重複データを削除
df = df.drop_duplicates()
# 出力
df.to_csv('pos.csv', encoding='shift-jis', index=None)
路線別のキロ程データを作成
各駅間の距離(=エッジの重み)を作成します。駅間を何キロと一個ずつ入力してもいいのですが、Pythonで計算させればよいので、起点からのキロ程、例えば浅草線であれば西馬込からのキロ程を入力します。そうすれば公開されている運賃表などの「西馬込の列」を参照すればよいので確認もしやすくなります。
Networkxで描画&最短経路算出
区切ると面倒なので、一旦コードを最初から最後まで以下に表示します。最短経路を求めたい駅の例を「両国から御成門」としました。
import openpyxl
import csv
import networkx as nx
import matplotlib.pyplot as plt
# 読み込み
lines = ['浅草線', '三田線', '新宿線', '大江戸線']
edge=[]
for line in lines:
wb = openpyxl.load_workbook('distance.xlsx')
sheet = wb[line]
data = [[sheet['A' + str(i)].value, sheet['B' + str(i)].value] for i in range(1, sheet.max_row + 1)]
for i in range(len(data)-1):
edge.append([data[i][0], data[i + 1][0], abs(data[i][1]*1000 - data[i + 1][1]*1000)])
# 名前の違う接続駅を追加(重みは0)
edge.append(['東日本橋', '馬喰横山', 0])
# 座標データ読み込み
with open('pos.csv', 'r', encoding='shift-jis') as f:
reader = csv.reader(f)
next(reader)
pos={row[0]: (float(row[1]), float(row[2])) for row in reader}
# グラフ作成
G = nx.Graph() # 無向グラフ
G.add_weighted_edges_from((e[0], e[1], e[2]) for e in edge)
# 描画
fig = plt.figure(figsize=(20, 20))
ax = fig.add_subplot(111)
nx.draw_networkx(G, ax=ax, pos=pos, font_family='MS Gothic', node_size=50)
plt.savefig('全駅表示.png')
# 検索経路
start = '両国'
goal = '御成門'
path = nx.dijkstra_path(G, start, goal)
edge_list = [(path[i], path[i+1]) for i in range(len(path)-1)]
weight = nx.dijkstra_path_length(G, start, goal)
# 描画
fig = plt.figure(figsize=(20, 20))
ax = fig.add_subplot(111)
nx.draw_networkx(G, ax=ax, pos=pos, font_family='MS Gothic', node_size=50, node_color='gray', with_labels=False)
nx.draw_networkx_nodes(G, pos, [start, goal ],node_color='red', node_size=100)
nx.draw_networkx_edges(G, pos, edge_list, edge_color='pink', width=8, alpha=0.8)
nx.draw_networkx_labels(G, pos, {start: start, goal: goal}, font_family='MS Gothic', font_size=24)
nx.draw_networkx_labels(G, pos, {'蔵前': '蔵前', '東日本橋': '東日本橋\n馬喰横山', '大門': '大門', '三田': '三田',
'神保町': '神保町', '春日': '春日', '新宿': '新宿', '森下': '森下', '都庁前': '都庁前'},
font_family='MS Gothic', font_size=16, alpha=0.5)
plt.savefig('{}-{}.png'.format(start, goal))
東日本橋駅と馬喰横山の接続は重みが0のエッジを追加します。それ以外の接続駅は同じ名前なので気にする必要はありません。
最後の描画部分は見やすいようにエッジのカラーを重ねたり、発着ノードの色を変えたりしています。
描画した図は以下のようになりました。
どのルートを通っているか確認できるように図化することができるようになりました!
応用して
上記コードを少し改良して、押上駅から大江戸線全線(接続駅を除く:蔵前、春日など)を図化してみました。築地市場と汐留で経路が変わるところなどが確認できます。
まとめ
今回はNetworkxを使って都営地下鉄の最短経路を図化してみました。納得できるところまでできたかなと思っています。結構簡単にできるので、東京メトロや東急などでもやってみようかと思います。