作ったもの
東海道線に乗っている時に、自分はどの駅でテンションがあがるのだろう?
そんな疑問に答えるため1、東海道線の名古屋-藤沢間を乗車中に心拍数を測り、それをビジュアライズしてみました。ただそれだけです。
こうみると、ところどころ心拍数が上がっている駅がありますね。自分の感情と一致しているところもあれば、なんでここで?と思うところもあります。心拍数変化の考察も後述しています(もしかして:誰も興味ない)。
身体情報のビジュアライズって意外と面白いかも。
きっかけ・背景
これまで名古屋⇄横浜間の移動には新幹線や夜行バスを使うことが多かったのですが、今回初めて青春18きっぷを使ってみることにしました。せっかく鈍行で行くのなら、その間に何かデータを取ったら遊べるのではと思ったのがきっかけです。
どんなデータを取ったら面白いかを先輩とあれこれ話していたのですが、属人的なデータの方が楽しいよねと思い、心拍数を計測することにしました。
心拍数変化の考察
せっかくなので、心拍数と実際の状況の照らし合わせを行い、何の要因が私の心拍数を変化させていたのかを考察してみました。
-
名古屋駅
出発地点です。これから始まる長旅にどきどきしていたのですが、思いのほか心拍数としては高くなかったです。愛知を去る寂しさで心拍数が上がらなかったのでしょうか。 -
安城駅
意外にも序盤に心拍数の最大がきました。特に風景で感動したり、思い入れのある場所ではありませんが、なぜこんなにも心拍数が高かったのでしょうか。振り返ってみるとこの時、車窓をタイムラプスで記録してみようと思い立ち、撮影をしていました。しかし、充電とメモリ残量がどんどん減っていく様子を見て不安になり、ワタワタしていました。その様子を反映しているのかもしれません。 -
新所原駅
ちょうど愛知県から静岡県に入ったタイミングです。結構ワクワクしていたと記憶しているのですが、心拍数は全然変わっておらず、リラックスしていました。記憶とデータ、どちらが正しいのでしょうか。 -
浜松駅・島田駅
両駅とも、乗り換えのタイミングです。乗り換えが反対のホームだったので、階段を上り下りする必要がありました。そのために心拍数が上がっていたと思われます。至極真っ当で最も説明しやすいデータなのですが、なんだかロマンに欠けるというか、面白くないですね。 -
原駅
この近辺では車窓から非常に美しい富士山が見え、興奮していました。ブレブレながらも写真まで撮っていました。若干ではありますが、その様子が心拍数に反映されているのかもしれません。 -
根府川駅・早川駅
あまり感動する駅では無かったのですが(注:個人の意見です)、なぜか結構心拍数が高かったです。そういえば、この時映画「セッション」を見ていました。J.K.シモンズの名演技に圧倒されて心拍数が高くなったのかもしれません。もはや東海道線とは何も関係ないのですが。
まとめるとこんな感じです。適当な考察すぎますが、まとめてみると結構面白いですね。旅の振り返りにもなって楽しいです。ブレブレの風景写真とるくらいなら心拍数とった方がいいかもしれません。
手法
手法は至って簡単です。心拍数は、ヨドバシカメラで3500円程度で売られている安価なスマートウォッチで計測しました。継時的に測定するモードがあるので、乗車中にやることは何もありません。
また、日本の電車は時間に正確です。そのため乗った電車の時刻表を控えていれば、時刻と位置(駅)の対応が大体分かります。
あとは家に帰ってから、心拍数と時刻、時刻と駅情報を対応させればデータセットが完成します。実は、この過程が意外に大変でした。購入するまで気づかなかったのですが、このスマートウォッチ、データを外部にエクスポートできない仕様だったのです。
データが書かれているバイナリファイルを解読することも考えましたが、今回のように少ないデータ点数では手動でやった方が早いということで、気合いで打ち込みました。今後も同じようなことをすることを考えると、このフローをもっと最適化できるといいなーと思っています(多分しないけど)。
あとはビジュアライズのために色々やるだけです。
ビジュアライズのソースコード
大まかには、1.東海道線の路線と駅をプロットする。2.県境が書かれた日本地図を描画する。3.心拍数に応じて駅のプロット点の色と大きさを変化させる。 の3段階です。
全く同じことをやりたい人は殆どいないと思いますが、もし参考になれば幸いです。
1.東海道線の路線と駅をプロットする。
今回は、国土交通省の国土数値情報ダウンロードサイトから、鉄道時系列データ(ここではN05-19_GML.zip)をダウンロードして使いました。ちなみに、令和2年までのデータと令和3年以降のデータでは仕様が異なるようなので、使用の際は注意が必要です。
N05-19_RailroadSection2.geojson
に路線情報、N05-19_Station2.geojson
に駅情報が載っています。
# 路線図と駅の位置情報を読み込み
json_open = open('N05-19_RailroadSection2.geojson', 'r', encoding = 'utf_8_sig')
json_load = json.load(json_open)
json_open2 = open('N05-19_Station2.geojson', 'r', encoding = 'utf_8_sig',errors='ignore')
json_station = json.load(json_open2)
東海道線の路線を書きます。今回は路線名
に東海道線
と入っている座標データを取得し、その中で目的の経度(min_longitude
~max_longitude
)に入っている座標をプロットするようにしています。東海道線が東西に伸びている路線のため、今回はこのようにすることで目的の描画ができました。一般化するのは大変そう。また、路線感を出すために、白黒の点線で表現しています。
# 東海道線をプロット
for railway_data in json_load['features']:
if railway_data['properties']['路線名'] == '東海道線':
segments = railway_data['geometry']['coordinates']
x2, y2 = [], []
for xy in segments:
if xy[0]>= min_longitude and xy[0]<=max_longitude:
x2.append(xy[0])
y2.append(xy[1])
ax.plot(x2, y2, color='gray', linewidth=6)
ax.plot(x2, y2, color='black', linewidth=5, linestyle='--', gapcolor='white')
東海道線の駅もプロットできます。今回は最終的にこのコードは使いませんでしたが、何かに役立つかと思い供養します。
# 東海道線の駅をプロット
x, y =[],[]
for station_data in json_station['features']:
if station_data['properties']['路線名'] == '東海道線' and
station_data['geometry']['coordinates'][0]>=min_longitude and
station_data['geometry']['coordinates'][0]<=max_longitude:
segments = station_data['geometry']['coordinates']
name = station_data['properties']['駅名']
x.append(segments[0])
y.append(segments[1])
ax.scatter(np.array(x), np.array(y),s=10,c='blue')
2.県境が書かれた日本地図を描画する。
次に、県境が書かれた日本地図を描画します。正確な日本地図は国土地理院のデータを使うのが良いと思います。しかし、このデータでは、県境だけでなく、市区町村境界も描画されてしまいます。
そこで今回は、こちらでちずぞうさんが公開されている県境データを用いました。
# 日本の県境データを読み込み
jp_prefecture_boundaries = gpd.read_file("prefectures.geojson")
# 都道府県の境界をポリゴンでプロット
jp_prefecture_boundaries.plot(ax=ax, facecolor="#d1deb0", edgecolor='black', linewidth=1.5, zorder=1)
# 富士山をプロット
# 富士山の座標
fuji_latitude = 35.2164
fuji_longitude = 138.4364
ax.plot(fuji_longitude, fuji_latitude, marker='^', markersize=60, color='skyblue', label='富士山')
# 富士山の横にラベルを追加
ax.text(fuji_longitude, fuji_latitude, ' 富士山', color='black', fontsize=40, ha='left', va='center')
ついでに富士山もプロットしています。ChatGPTに「山をプロットしたい」とお願いしたら、「marker='^'」と提案してくれました。確かに山っぽい。
3.心拍数に応じて駅のプロット点の色と大きさを変化させる。
最後に最も肝心な、心拍数のプロットです。先に述べたように、心拍数データは手動でcsvファイルにまとめてあります。
作成したcsvファイルがこんな感じです。
駅名 (Station) | 経度 (Longitude) | 緯度 (Latitude) | 時刻(time) | 乗り換え | 心拍数(BPM) | コメント |
---|---|---|---|---|---|---|
名古屋 | 136.882201 | 35.171313 | 09:01 | NaN | 71.0 | 出発 |
金山 | 136.900736 | 35.142837 | 09:05 | NaN | 62.0 | NaN |
共和 | 136.954580 | 35.035365 | 09:15 | NaN | 71.0 | NaN |
刈谷 | 137.009710 | 34.990805 | 09:22 | NaN | 78.0 | NaN |
安城 | 137.086348 | 34.960473 | 09:28 | NaN | 105.0 | タイムラプス撮影してた |
今回は、心拍数に対応して色が変化し、かつ駅のプロットサイズも変化するようにしました。心拍数は正規化を行い、大きさを調整しています
# 心拍数ファイルの読み込み
heart = pd.read_csv("station_ore_heartrate.csv",encoding="shift_jis")
heart_drop= heart.dropna(subset="心拍数(BPM)")
# 心拍数の散布図をプロット
normalized_heart = (heart_drop["心拍数(BPM)"] - heart_drop["心拍数(BPM)"].min()) / (heart_drop["心拍数(BPM)"].max() - heart_drop["心拍数(BPM)"].min())
heart_size = 80**(normalized_heart + 1)
ax.scatter(heart_drop["経度 (Longitude)"], heart_drop["緯度 (Latitude)"],
c=heart_drop["心拍数(BPM)"], cmap='bwr', zorder=3, s=heart_size,linewidths=1, ec="black",alpha=0.8)
コードのまとめ
駅名を足したり色々微調整した後です。今回のメインの図を作ったコード全文です。
import json
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
import geopandas as gpd
import cartopy.crs as ccrs
from cartopy.io import shapereader
# 路線図の位置情報を読み込み
json_open = open('N05-19_RailroadSection2.geojson', 'r', encoding = 'utf_8_sig')
json_load = json.load(json_open)
# 心拍数ファイルの読み込み
heart = pd.read_csv("station_ore_heartrate.csv",encoding="shift_jis")
heart_drop= heart.dropna(subset="心拍数(BPM)")
# 今回の地図範囲の設定
min_longitude = min(heart_drop["経度 (Longitude)"]-0.1)
max_longitude = max(heart_drop["経度 (Longitude)"]+0.1)
min_latitude = min(heart_drop["緯度 (Latitude)"]-0.1)
max_latitude = max(heart_drop["緯度 (Latitude)"]+0.1)
# プロット領域の設定
fig, ax = plt.subplots(figsize=(30, 20), subplot_kw={'projection': ccrs.PlateCarree()},facecolor='#f6c555')
ax.set_extent([min_longitude, max_longitude, min_latitude, max_latitude])
# 心拍数の散布図をプロット
normalized_heart = (heart_drop["心拍数(BPM)"] - heart_drop["心拍数(BPM)"].min()) / (heart_drop["心拍数(BPM)"].max() - heart_drop["心拍数(BPM)"].min())
heart_size = 80**(normalized_heart + 1)
scatter = ax.scatter(heart_drop["経度 (Longitude)"], heart_drop["緯度 (Latitude)"],
c=heart_drop["心拍数(BPM)"], cmap='bwr', zorder=3, s=heart_size,linewidths=1, ec="black",alpha=0.8)
# 東海道線をプロット
for railway_data in json_load['features']:
if railway_data['properties']['路線名'] == '東海道線':
segments = railway_data['geometry']['coordinates']
x2, y2 = [], []
for xy in segments:
if xy[0]>= min_longitude and xy[0]<=max_longitude:
x2.append(xy[0])
y2.append(xy[1])
ax.plot(x2, y2, color='gray', linewidth=6)
ax.plot(x2, y2, color='black', linewidth=5, linestyle='--', gapcolor='white')
# 日本の県境データを読み込み
jp_prefecture_boundaries = gpd.read_file("prefectures.geojson")
# 都道府県の境界をポリゴンでプロット
jp_prefecture_boundaries.plot(ax=ax, facecolor="#d1deb0", edgecolor='black', linewidth=1.5, zorder=1)
# 富士山をプロット
# 富士山の座標
fuji_latitude = 35.2164
fuji_longitude = 138.4364
ax.plot(fuji_longitude, fuji_latitude, marker='^', markersize=60, color='skyblue', label='富士山')
# 富士山の横にラベルを追加
ax.text(fuji_longitude, fuji_latitude, ' 富士山', color='black', fontsize=40, ha='left', va='center')
# カラーバーの追加
cbar = plt.colorbar(scatter, ax=ax, orientation='horizontal', fraction=0.03, pad=0.04)
cbar.set_label('心拍数(BPM)',fontsize=40)
cbar.ax.tick_params(labelsize=30)
# 駅名の位置を微調整
adjustments = {
'名古屋': {'x': 0.06, 'y': 0.03},
'安城': {'x': 0.00, 'y': 0.08},
'豊橋': {'x': 0.04, 'y': 0.03},
'新所原': {'x': 0.00, 'y': 0.03},
'浜松': {'x': -0.02, 'y': 0.03},
'島田': {'x': 0.00, 'y': 0.05},
'原': {'x': 0.00, 'y': 0.03},
'熱海': {'x': 0.06, 'y': 0.00},
'湯河原': {'x': 0.11, 'y': 0.00},
'藤沢': {'x': 0.00, 'y': 0.03},
}
# 駅名をプロット
for i in range(len(heart_drop)):
if not pd.isnull(heart_drop['コメント'].iloc[i]) or not pd.isnull(heart_drop['乗り換え'].iloc[i]):
station_name = heart_drop['駅名 (Station)'].iloc[i]
adjust_x = adjustments[station_name]['x']
adjust_y = adjustments[station_name]['y']
color = 'deeppink' if pd.notnull(heart_drop['乗り換え'].iloc[i]) else 'black'
ax.text(heart_drop['経度 (Longitude)'].iloc[i] + adjust_x, heart_drop['緯度 (Latitude)'].iloc[i] + adjust_y, heart_drop['駅名 (Station)'].iloc[i], color=color, fontsize=35, ha='center', va='center', fontweight='bold')
# 凡例
ax.text(139.43,34.605,"ー乗り換え駅", color="deeppink", fontsize=35, ha='center', va='center', fontweight='bold')
# 表示と保存
plt.tight_layout()
plt.savefig("result.png",dpi=600)
plt.show()
最後に
身体情報(今回は心拍数)と位置情報を組み合わせて考察するのは結構楽しかったです。でも意外と心拍数は変わらないですね。運動しているわけではないので当たり前と言えば当たり前ですが。もっと心拍数が変化する環境で行うと面白いデータになるかもしれません。
私は鉄道に詳しくないのですが、駅や路線、土地の面白さをもっと知れば、心拍数の変化する楽しい旅になるかも知れません。ワクワクする旅を目指してみたいものです。
参考
- 鉄道路線データを可視化し、最短経路問題を解く (Python+Pandas+NetworkX)
- Python で路線図を描いてみた
- 国土数値情報ダウンロードサイト
- Choosing Colormaps in Matplotlib
- 47都道府県のポリゴンデータ geojson
- 富士山 観測点配置図
-
答える必要があったのかはよくわかっていない。 ↩