駅ごとにその駅を最寄り駅としている人の数を求めてみた(練馬区編)

## はじめに
本記事では練馬区の鉄道駅ごとの最寄り駅人口を求めます。
筆者が練馬区民のため対象を練馬区としていますが、コードの一部を変えるだけであらゆる地域の最寄り駅人口を求めることができます。

今回のコードは[ヘルシンキ大学が公開しているGISに関する教材](https://autogis-site.readthedocs.io/en/2021/)を元にしています。
筆者はそちらの教材を勉強した後のアウトプットでこの記事を書いています。
GISについて勉強してみたい方はぜひそちらを使ってみてください。そちらの教材についての質問があればQiitaのコメントで議論させていただけると、筆者も勉強になり大変嬉しいです。

※本記事はGoogle Colab上で使用することを想定しています。ローカル環境でのコード実行時には事前に必要なライブラリのインストールが必要になります。

## 扱うデータについて
本記事では表1のデータを扱います。
練馬区であっても隣接する市区町村の鉄道駅が最寄り駅のケースがあると考えて、練馬区と隣接する埼玉県(新座市、和光市、朝霞市)のデータも取得します。
ちなみに練馬区は、板橋区、中野区、杉並区、豊島区、西東京市、武蔵野市、東京以外では北側が埼玉県の新座市、和光市、朝霞市と隣接しているようです。(※2)

扱うデータとデータ取得先
東京都、埼玉県の町丁目ごとの境界データ:[e-Stat](https://www.e-stat.go.jp/gis)
東京都、埼玉県の町丁目ごとの人口データ:[e-Stat](https://www.e-stat.go.jp/gis)
全国の鉄道データ:[国土交通省](https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-N02-v3_0.html)

e-Statのデータは下記コードでもダウンロードが可能です。
```python
import download

data_fp = "データを格納するフォルダパスをここに入力してください"

# 東京都の町丁目ごとの境界データをダウンロードする
download.download(
  "https://www.e-stat.go.jp/gis/statmap-search/data?dlserveyId=A002005212020&code=13&coordSys=1&format=shape&downloadType=5&datum=2000",
   data_fp,
   kind="zip",
   replace=True,
)

# 東京都の町丁目ごとの人口データをダウンロードする
download.download(
   "https://www.e-stat.go.jp/gis/statmap-search/data?statsId=T001081&code=13&downloadType=2",
   data_fp,
   kind="zip",
   replace=True,
)

# 埼玉県の町丁目ごとの境界データをダウンロードする
download.download(
  "https://www.e-stat.go.jp/gis/statmap-search/data?dlserveyId=A002005212020&code=11&coordSys=1&format=shape&downloadType=5&datum=2000",
   data_fp,
   kind="zip",
   replace=True,
)

# 埼玉県の町丁目ごとの人口データをダウンロードする
download.download(
   "https://www.e-stat.go.jp/gis/statmap-search/data?statsId=T001081&code=11&downloadType=2",
   data_fp,
   kind="zip",
   replace=True,
)
```

## 練馬区と隣接する市区町村の鉄道駅を地図に表示する
まずは必要なライブラリをインポートします。
```python
!pip install geopandas
!pip install japanize_matplotlib
!pip install mapclassify
!pip install rtree
import rtree
import mapclassify
import japanize_matplotlib
import geopandas as gpd
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import folium
import os
from shapely.geometry import Point, LineString, Polygon
from shapely.ops import nearest_points
```

まずはGeoPandasで鉄道データを取得します。
鉄道駅のデータは[国土交通省](https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-N02-v3_0.html)から取得します。令和3年の鉄道駅のデータをダウンロードして、datasetフォルダに格納しておいて、Geopandasで読み込みます。
```python
fp =  os.path.join(data_fp, 'N02-21_Station.geojson')
StationData = gpd.read_file(fp)r
StationData.head()
```

鉄道名と鉄道駅名、それらのgeometryが取得できています。
![image15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152045/72fb44a8-4f68-1676-b887-4bb5c14ea539.png)

取得したデータのgeometryはLINESTRING型で線データになっています。対象駅から下り方面への次の駅までの軌跡の座標データが入っています。今回は駅のPOINTデータが必要なため、それぞれの線の開始点のデータを取得します。
```python
get_point = lambda row:Point(row.coords[0])
StationData["geometry"] = StationData["geometry"].apply(get_point)
StationData.head()
```

POINTデータを取得することができました。
![image10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152045/095a2bb5-0e4a-9c2c-4b2d-3a3e0a801a71.png)

必要なカラムを取り出して、カラム名を変更します。
```python
StationData = StationData.loc[:, ["N02_005", "geometry"]]
StationData = StationData.rename(columns={'N02_005': 'StationName'})
StationData.head()
```

![image2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152045/004512e9-7afe-2a6d-11db-fa1e8a25a3f1.png)

東京都と埼玉県の境界データをGeoPandasで取得します。
```python
fp_tokyo =  os.path.join(data_fp, 'r2ka13.shp')
fp_saitama =  os.path.join(data_fp, 'r2ka11.shp')

tokyo_geo = gpd.read_file(fp_tokyo)
saitama_geo = gpd.read_file(fp_saitama)
```

東京都と埼玉県のデータから、上で述べた練馬区と隣接する市区町村のデータを抽出して、マージします。
```python
def gdf_arrange(gdf, city_list):
 temp = gdf.loc[:, ['geometry', 'CITY_NAME', 'KEY_CODE']]
 temp['KEY_CODE'] = temp['KEY_CODE'].astype(str)
 temp = temp[temp['CITY_NAME'].isin(city_list)]
 return temp

tokyo_list = ['練馬区', '板橋区', '中野区', '杉並区', '豊島区', '西東京市', '武蔵野市']
saitama_list = ['新座市', '和光市', '朝霞市']

tokyo_geo = gdf_arrange(tokyo_geo, tokyo_list)
saitama_geo = gdf_arrange(saitama_geo, saitama_list)

geo = tokyo_geo.append(saitama_geo)
geo.head()
```

練馬区と隣接する市区町村のポリゴンデータを取得することができました。
![image12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152045/212c650d-ba55-da6d-f4a5-93880647f556.png)

練馬区と隣接する市区町村のポリゴンデータ(geo)と交差する鉄道駅のデータを抽出します。GeoPandasにはsjoin(Spatial join)というmethodが実装されており、ポイントデータやポリゴンデータを空間結合することができます。"intersects"、"within"、"contains"、”Covers”というオプションがありますが、今回はポリゴンデータ内にあるポイントデータを探索するので、”within”を用います。オプションの説明は以下のサイトが参考になります。
https://mentin.medium.com/which-predicate-cb608b470471
```python
Station_point = gpd.sjoin(StationData, geo, how="inner", op="within")
Station_point = Station_point.loc[:, ["StationName", "geometry"]]
Station_point.head()
```

対象の鉄道駅を抽出することができました。
![image8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152045/75f8e8f0-deb2-5926-4819-bdaf6a1d9db8.png)

望んだデータが得られているか、地図に可視化して確かめてみます。
```python
fig, ax = plt.subplots(figsize=(15,8))
geo.plot(ax=ax)
Station_point.plot(ax=ax, color='red', markersize=20)
```

対象地域のポリゴンデータとそれらと交差する鉄道駅のポイントデータを得られていることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152045/25fb5412-3ff5-8ae7-ecdb-ca740647d3b3.png)


## 町丁目ごとの最寄り駅を求める
まず町丁目ごとの重心座標を求めます。
```python
geo['centroid'] = geo.centroid
geo.head()
```

各地域の重心座標が求まり、”centroid”カラムに入りました。
![image4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152045/09c6551d-81ae-187f-fa43-6a93834b60be.png)


複数のポイントから、あるポイントと最も近いポイントを探索してそのポイントの情報を返す関数を定義します。あるポイントと最も近いポイントを探索するメソッドはnearest_pointsで実装されています。今回は最も近いポイントの駅名を得たいため、求まったポイントの行を取得し、その行の指定したカラムの値を返しています。
```python
def get_nearest_values(row, other_gdf, point_column='geometry', value_column="geometry"):
   # 駅のポイントをマルチポイントにする
   other_points = other_gdf["geometry"].unary_union

   # 地域のポリゴンの重心と最も近い駅のポイントを取得
   nearest_geoms = nearest_points(row[point_column], other_points)

   # 上で求めたポイントの行を取得
   nearest_data = other_gdf.loc[other_gdf["geometry"] == nearest_geoms[1]]

   # 上で求めたポイントの指定したカラムの値を取得
   nearest_value = nearest_data[value_column].values[0]
   return nearest_value
```

上で定義した関数(get_nearest_values)を使って、各町丁目の重心から最も近い鉄道駅名を取得します。
```python
geo["nearest_loc"] = geo.apply(get_nearest_values, other_gdf=Station_point, point_column="centroid", value_column="StationName", axis=1)
geo.head()
```

“nearest_loc”カラムに駅名が入りました。
![image16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152045/b7b27864-111d-7ab6-2efd-6d20e867ad2e.png)

正しく最寄り駅を取得できているか、地図に表示して確認してみます。
“nearest_loc”カラムの値ごとに色を塗ったような地図を表示し、鉄道駅のポイントデータをプロットします。
```python
fig, ax = plt.subplots(figsize=(15,8))
geo.plot(ax=ax, column="nearest_loc" ,cmap="tab20")
Station_point.plot(ax=ax, color='black', markersize=40)
```

各町丁目ごとに最寄り駅を取得できている様子がわかります。
![image9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152045/c5c0ad11-22bf-201d-fcd6-465d503ecee7.png)

## 駅ごとにその駅を最寄り駅としている人の数を求める
まずは上で求めた練馬区と隣接する市区町村のポリゴンデータから、練馬区のデータのみを抽出します。
```python
geo_nerima = geo[geo["CITY_NAME"] == "練馬区"]
geo_nerima.head()
```

練馬区のデータを抽出することができました。
![image3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152045/b6e4a5d5-f475-29ee-33a2-9d101a679f8f.png)


鉄道駅のポイントデータから、練馬区のポリゴンデータ内に存在するものを抽出して、地図に表示します。
```python
Station_point_nerima = gpd.sjoin(StationData, geo_nerima, how="inner", op="within")
Station_point_nerima = Station_point_nerima.loc[:, ["StationName", "geometry"]]

fig, ax = plt.subplots(figsize=(15,8))

geo_nerima.plot(ax=ax, column="nearest_loc",cmap="tab20")

Station_point_nerima.plot(ax=ax, color='black', markersize=40)
```

練馬区のみを抽出して表示することができました。
![image1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152045/78fb8385-5ebf-5692-2b6d-1925a4b27d34.png)

Pandasで人口データをダウンロードします。必要なカラムのみを抽出やNaNの行を削除する等のデータの前処理を行います。この人口データの前処理については[こちらの記事](https://qiita.com/oz_oz/items/e6c48341275cfaa3927a)で記載しておりますので、ぜひご覧ください。
```python
fp_pop =  os.path.join(data_fp, 'tblT001081C13.txt')
data = pd.read_csv(fp_pop, encoding="CP932", dtype={'KEY_CODE': str, 'HYOSYO': str})
data = data.loc[:, ["KEY_CODE", "HYOSYO","CITYNAME","NAME", "T001081001"]]
data = data[data['HYOSYO'] == "4"]
data = data.rename(columns={'T001081001': 'pop'})
data['KEY_CODE'] = data['KEY_CODE'].astype(str)
data = data.replace('-', '0')
data['pop'] = data['pop'].astype(float)
data.dropna(inplace=True)
data.head()
```

町丁目ごとの人口データを取得することができました。
![image14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152045/b6e15a1d-5b5d-74a0-8cfc-bce74353ee99.png)

練馬区の町丁目ごとの最寄り駅のデータ(geo)と人口データ(data)をマージします。
```python
geodata_nerima =  geo_nerima.merge(data, on='KEY_CODE')
geodata_nerima.head()
```

マージすることができました。
![image5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152045/a8118a3a-6aad-467d-b523-03fd42837294.png)

最寄り駅が同じ町丁目の人口の合計値を取得します。このような場合はPandasのgroupby()かGeoPandasのdissolve()を適用することでデータを結合できます。今回は最寄り駅名(nearest_loc)をキーとしてdissolved()でデータを結合します。
```python
dissolved = geodata_nerima.dissolve(by="nearest_loc", aggfunc={"pop": "sum"})
dissolved
```

最寄り駅ごとの人口を集計することができました。
![image11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152045/4431affe-fac0-4f34-4fd8-6760f11dda79.png)


鉄道駅ごとの最寄り駅人口を地図に表示します。
```python
fig, ax = plt.subplots(1, figsize=(10, 8))
dissolved.plot(ax=ax, column="pop", legend=True)
Station_point_nerima.plot(ax=ax, color='black', markersize=40)
```

傾向として、西側の方が最寄り駅人口が多そうです。
![image6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152045/852d3c35-eebe-4fea-71cb-5b6d5181f588.png)

最寄り駅人口をグラフに表示します。
```python
data_nerima = pd.DataFrame(dissolved.drop(columns='geometry'))
fig, ax = plt.subplots(1, figsize=(10, 8))
data_nerima.plot.barh(ax=ax)
```

練馬区の鉄道駅ごとの最寄り駅人口を求めることができました。
練馬区では「大泉学園駅」が最寄り駅の人口が最も多いようです。
![image7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152045/c3f20443-655f-b751-e545-696a8bf49154.png)

[西武池袋線の駅別乗降人員](https://www.seiburailway.jp/file.jsp?file/2021joukou.pdf)と[都営大江戸線の駅別乗降人員](https://www.kotsu.metro.tokyo.jp/subway/kanren/passengers.html#d)を見てみると、大泉学園駅の乗降人員は練馬区の中では練馬駅に次いで２番目に多いようです。練馬駅は西武池袋線と都営大江戸線の乗換駅であるため必然的に利用者が多くなると思われます。乗換駅を除いた中では大泉学園駅が最も利用されているということになります。今回の結果とも合致していそうです。また、[都営大江戸線の延伸計画](https://www.city.nerima.tokyo.jp/kusei/machi/kakuchiiki/oedo/index.html)では、大泉学園周辺の鉄道空白地域の改善を目的の一つとして謳っており、大泉学園駅を最寄駅とする人が多いことが伺えます。

## まとめ
今回は町丁目ごとのポリゴンデータ、人口データ、鉄道駅データを使って鉄道駅ごとの練馬区の最寄り駅人口を求めてました。
町丁目の重心から最も近い駅を求めてその町丁目の人口の最寄り駅としているため、少し精度は気になりますが、概ね近い値を求められていると思われます。

これからも勉強しながら成果を記事にしていこうと思うので、よろしくお願いします。

## 参考文献
※1 ヘルシンキ大学　GIS教材
https://autogis-site.readthedocs.io/en/2021/

※2 さとふる　東京都練馬区の地域情報
https://www.satofull.jp/static/html/municipality/131202/#:~:text=%E9%9A%A3%E6%8E%A5%E3%81%99%E3%82%8B%E5%8C%BA%E3%81%AF%E6%9D%BF%E6%A9%8B,%E3%81%AA%E3%81%84%E3%81%AA%E3%81%A0%E3%82%89%E3%81%8B%E3%81%AA%E5%9C%B0%E5%BD%A2%E3%81%A7%E3%81%99%E3%80%82
 
※3　Geopandasリファレンス
https://geopandas.org/en/stable/gallery/choropleths.html