小学校の在籍人数を地図上にマッピングする（Folium、GeoPandas利用）

## はじめに

子どもの数が減ってきて、学校の統廃合が現実に議論されるようになってきました。
また、公共施設の在り方（取り壊す、残す）についても議論が及んできています。
そこで、各校区の在籍人数を地図上にマッピングをしたいと思い、試行錯誤して何とか表示できた過程の記録として残します。

使用したJupyterファイルなどはこちらに置いてあります。
https://github.com/SGyutan/folium_kuki

なお、小学校の校区の境界データは
[国土数値情報　小学校区データ](http://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-A27-v2_1.html)
からダウンロードしてください。


## 行いたいこと

（1）学校の位置を地図上に示す
→経度緯度と学校名のリストがあればよい
（2）学校の校区を示す
→ポリゴン操作が必要
（3）各学校の在籍人数を示す
→階層プロットが必要
なお対象地域は、埼玉県久喜市とします。

### 環境
Win10
Anaconda
python3.7
モジュール
Folium、geopandas

## GISデータを取り扱う上での最低限のイメージ

![スライド1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/d4c201e5-0a4b-a180-cd84-2f4e5f511c9e.png)

### GEOデータマッピングで必要な知識
[ジオメトリタイプ一覧をながめる](https://qiita.com/boiledorange73/items/6d1200b69b5d5c88bcd0)
マッピングの基本種類
POINT：単一の点。
LINESTRING：単一の折れ線。
POLYGON：単一のポリゴン。リングは複数あっても良い。
POLYHEDRALSURFACE：多面体サーフェス。
MULTIPOINT：複数の点からなるジオメトリ
MULTILINESTRING：複数の折れ線からなるジオメトリ
MULTIPOLYGON：複数のポリゴンからなるジオメトリ

### データをマッピングする方法（ソフト）
・QGIS（GISソフト）
・Folium（Python、OpenStreetMap）
（Folium は javascript ライブラリの leafletを、Python で使えるようにしたモジュール。）
など
[GISエンジンおよびサービスのまとめ](https://www.gis-py.com/entry/2018/10/16/170338)
に詳しく書かれている
今回は、Foliumを利用します。
### データの入手
#### ベースになる地図（日本）
Open Street Map というオープンライセンスのソフト
（QGIS3,Foliumではデフォルトで使えます。）
[基盤地図情報サイト国土地理院](https://www.gsi.go.jp/kiban/)
必要な地図をダウンロードする。

#### 境界データ
国土交通省が提供している[国土数値情報 ダウンロードサービス](http://nlftp.mlit.go.jp/ksj/)からダウンロード

#### ファイル形式
GeoJSON形式
Shape形式
CSV形式（データが入っているもの）
その他にもありますが今回私が利用したファイル形式。

### データの前処理
#### データの整形ソフト・モジュール
Excel（最終的にはCSVで保存する）
Pandas(python)
GeoPandas(python)

GeoPandasの使い方
[geopandasの使い方をマスターしよう　～Shapeファイルの読込・作成、GeoDataFrameの扱い方まで～](https://www.gis-py.com/entry/geopandas-master)
[geopandasを使ってShapeファイルを作成しよう！ ～Airbnbのデータを可視化してみよう～](https://www.gis-py.com/entry/geopandas_shape)
geopandasやそれ以外のツールやファイル変換など
[Pythonを用いたshapefileやgeojsonの読込および描画](https://qiita.com/HidKamiya/items/5e7240f8f66c9af8b10e)

### Foliumのドキュメント
[Folium 0.10.1 documentation ](https://python-visualization.github.io/folium/index.html)

#### 必要なモジュールのインストールはこちらを参照してください。
anacondaに入っているモジュールの他に、
folium、geopandasなど
[folium（位置情報の可視化）の使い方メモ](https://qiita.com/hotoku/items/0d9ddbd24568c1a07f9f)
[Folium: Python で地図可視化](https://takaishikawa42.hatenablog.com/entry/2019/01/11/234716)
[【folium】Pythonで位置情報の可視化](https://www.st-hakky-blog.com/entry/2019/04/22/180619)

### すごく参考になった記事
[python folium で、都内の公園にまつわる情報を地図上に描画する](https://www.monotalk.xyz/blog/python-folium-%E3%81%A7%E9%83%BD%E5%86%85%E3%81%AE%E5%85%AC%E5%9C%92%E3%81%AB%E3%81%BE%E3%81%A4%E3%82%8F%E3%82%8B%E6%83%85%E5%A0%B1%E3%82%92%E5%9C%B0%E5%9B%B3%E4%B8%8A%E3%81%AB%E6%8F%8F%E7%94%BB%E3%81%99%E3%82%8B/)
[python folium を使い、都道府県の夫婦年齢差をプロットする](https://www.monotalk.xyz/blog/python-folinum-%E3%82%92%E4%BD%BF%E3%81%84%E9%83%BD%E9%81%93%E5%BA%9C%E7%9C%8C%E3%81%AE%E5%A4%AB%E5%A9%A6%E5%B9%B4%E9%BD%A2%E5%B7%AE%E3%82%92%E3%83%97%E3%83%AD%E3%83%83%E3%83%88%E3%81%99%E3%82%8B/)


##（0）Foliumで地図を表示してみます。
埼玉県久喜市を例にします。 
久喜市の市役所：大字下早見８５－３
緯度・経度 36.06205900,139.66683800　

```python
#必要なモジュールのインポート
import json
from pathlib import Path

import folium
from folium import plugins
print( "folium version is {}".format(folium.__version__) )

import geopandas as gpd
import pandas as pd

import matplotlib.pyplot as plt
```
```python
# ベースマップの作製
map_center = [36.06205900,139.66683800] #久喜市に設定
m_kuki = folium.Map(location=map_center, tiles='openstreetmap', zoom_start=13)
m_kuki.save('kuki.html')
```
![kuki.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/1c5fa61c-ae4a-8ebe-6e63-9d2b34f07213.jpeg)
市役所を中心とした地図が描かれます。
参考：
[【珈琲たいむ】Pythonでコロプレス図を描く](https://akatak.hatenadiary.jp/entry/2018/08/26/093839)
※コロプレス図 (choropleth map)日本語では「階級区分図」
に記載されている内容
locationで地図の中心を指定、
zoom_startで縮尺のレベルを指定（デフォルト値は10）、
tilesでベースマップの種類を指定します(デフォルトはopenstreetmap）。なお、コロプレス図が見やすいのはcartodbpositron。
foliumにビルトインされているベースマップのうち、API_keyがなくても利用できるのは以下の8個
openstreetmap,
mapboxbright,
cartodbdark_matter,
cartodbpositron,
mapboxcontrolroom,
stamenterrain,
stamentoner,
stamenwatercolor

##（1）小学校の位置をプロット
[Open Data Saitama](https://opendata.pref.saitama.lg.jp)
にて検索して
組織 久喜市 【久喜市】公共施設情報 【H30】公共施設情報 のCSVファイルを取得します。
https://opendata.pref.saitama.lg.jp/data/dataset/0703b822-dee1-4cf5-943b-7cbc837108ba/resource/f200c959-b425-4d1f-81cb-75c1780dd2fa/download/inet-file01.inet-kuki.localpublic01010500.votiro02011001h30sisetu-kuki.csv
このデータには、久喜市の公共施設の住所、名称、経度緯度などの情報が記載されています。
※ダウンロードしたファイル名が長いので、「kukikokyoshisetsu.csv」に変更します。

```python
# 日本語CSVファイルの読み込みのために engine = "python"としています。
df_kuki = pd.read_csv("kukikokyoshisetsu.csv", engine = "python")
```
```python
#元のデータから学校関係を抽出します。
df_school=df_kuki[df_kuki["種別"]=="小中学校・給食センター"]
# 小学校だけ抽出
df_esh=df_school.loc[117:139]
df_esh.head()
```
![kukisyougakkou.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/9bc3a17b-e423-5539-eabd-5e8d21a11181.jpeg)

```python
#地図に位置をマッピングします。
map_center = [36.06205900,139.66683800] #久喜市に設定
m2_kuki = folium.Map(location=map_center, tiles='openstreetmap', zoom_start=13)
#Forで回します。
for i, dt in df_esh.iterrows():
    folium.Marker(location=[dt['施設_緯度'],dt['施設_経度']], popup='{},{}'.format(dt['名称'],dt['施設_電話番号'])).add_to(m2_kuki)
#     print(dt['施設_緯度'], dt['施設_経度'], dt['名称'])
m2_kuki.save('kuki_e_school.html')
```
![kukieschoolpop.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/a957ea26-32c2-e4cc-f5f3-e3aa9d13695c.jpeg)

##（2）久喜市の小学校の校区をプロット
#### データの入手先
[国土数値情報　小学校区データ](http://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-A27-v2_1.html)
埼玉県を指定
データ形式：
①「GML形式＝高度な内容を表現できる世界標準規格。普及はこれから
②「シェープファイル形式」＝現在の殆どのＧＩＳソフトで読み書きでき普及している規格
このデータは上記の形式で提供されています。
データの属性
範囲、通学区域の範囲面型（GM_Surface）
市区町村コード(A27_005)：小学校が属する行政コード、コードリスト「行政コード」　
設置主体(A27_006)：小学校の設置主体、文字列型(CharacterString)　
名称(A27_007)：小学校の名称、文字列型(CharacterString)　
所在地(A27_008)：小学校の設置所在地、文字列型(CharacterString)

```python
#GeopandasでShapeファイルを読み込む
#埼玉県の校区
#ファイル読込（encoding='SHIFT-JIS'とすることで日本語データにも対応）
shdf = gpd.read_file('A27-16_11.shp',encoding='SHIFT-JIS')
```
```python
#久喜市だけ取り出します。
#A27_005には市町村コード番号が入っているので、この値でセレクトしてもよい。
kuki_esh=shdf[shdf['A27_006']=='久喜市立']

#この後の処理のために学校を識別するためにidをつけます。ここではid2としています。
#このままだと一番左のインデックス番号がid番号になります。
kuki_esh['id2']=[x for x in range(0,23)]
# id2は数値ではなく文字列にしておく必要があるため型変換をします。
kuki_esh['id2'] =  kuki_esh['id2'].astype('str')
```
```python
map_center = [36.06205900,139.66683800] #久喜市に設定
m_kuki_sh = folium.Map(location=map_center, tiles='cartodbpositron', zoom_start=13)
folium.Choropleth(geo_data=kuki_esh.to_json()).add_to(m_kuki_sh)
for i, dt in df_esh.iterrows():
    folium.Marker(location=[dt['施設_緯度'],dt['施設_経度']], popup='{}'.format(dt['名称'])).add_to(m_kuki_sh)
folium.LayerControl().add_to(m_kuki_sh)
m_kuki_sh.save('kuki_e_sh_koku.html')

#geo_data=kuki_esh.to_json() dfのデータをJSON形式にして渡します。
```
![kokukupop.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/45095c41-52bb-0da9-1b48-c09ae302944e.jpeg)

##（3）各小学校の在校者数をプロット
各小学校の在籍者のデータを取ってきます。
[教育要覧（久喜市の教育）](https://www.city.kuki.lg.jp/kosodate/kyoikugyosei/kyoikuiinkai/gaiyo/kyoikuyoran.html)
このHPにある「令和元年度久喜市の教育」
分割版2（学校教育、13ページから38ページまで）（PDF：30,337KB）
をダウンロードします。
この19ページ目に年度による生徒数がまとめられています。
これを数値化します。私がやったのは、PDFをWebサービスでWordファイルにして、そのあといろいろとExcelでやって、CSVファイルにしました。
前処理したファイルを「児童生徒数推移.csv」とします。
なお先ほど、境界データで付加したIDと同じ番号を同じ小学校に付加しています。

```python
df_num=pd.read_csv('児童生徒数推移.csv', engine = "python")

#　この後にgeojsonのデータと結びつけます。
#　geojsonの値は、基本文字列なので、その文字列と結びつけるためにIDをIntからStringに変換しています。
df_num['id'] = df_num['id'].astype('str')
```
児童数の推移のデータを見てみます。
![スライド10.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/a7d95fae-ce94-f89a-dd07-23f2d16ea2cd.jpeg)
100人を切っている小学校がある一方、急激に増加している小学校もあります。
全体としては右肩下がりです。

```python
#　最新の令和元年度のマップを作ってみます。
map_center = [36.06205900,139.66683800] #久喜市に設定
m_kuki_sh = folium.Map(location=map_center, tiles='cartodbpositron', zoom_start=13)
folium.Choropleth(geo_data=kuki_esh.to_json()).add_to(m_kuki_sh)
m_kuki_sh.choropleth(geo_data=kuki_esh.to_json(),data=df_num,
                     columns=['id', 'R01'],
                     key_on='feature.properties.id2',
                     fill_color='OrRd',
                     threshold_scale=[0, 100, 200, 300, 400, 500, 600, 700, 800, 900],
                     fill_opacity=0.7,
                     line_opacity=0.2,
                     legend_name='生徒数'
                    )
# folium.LayerControl().add_to(m_kuki_sh)
for i, dt in df_esh.iterrows():
    folium.Marker(location=[dt['施設_緯度'],dt['施設_経度']], popup='{}'.format(dt['名称'])).add_to(m_kuki_sh)
# m_saitama
m_kuki_sh.save('kuki_esh_r1.html')
```
![スライド12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/5b3c6378-75fe-d36b-0fd5-3fd05c4f1406.png)

```python
#　一番古いH22年度のマップを作ってみます。
map_center = [36.06205900,139.66683800] #久喜市に設定
m_kuki_sh = folium.Map(location=map_center, tiles='cartodbpositron', zoom_start=13)
folium.Choropleth(geo_data=kuki_esh.to_json()).add_to(m_kuki_sh)
m_kuki_sh.choropleth(geo_data=kuki_esh.to_json(),data=df_num,
                     columns=['id', 'H22'],
                     key_on='feature.properties.id2',
                     fill_color='OrRd',
                     threshold_scale=[0, 100, 200, 300, 400, 500, 600, 700, 800, 900],
                     fill_opacity=0.7,
                     line_opacity=0.2,
                     legend_name='生徒数'
                    )
# folium.LayerControl().add_to(m_kuki_sh)
for i, dt in df_esh.iterrows():
    folium.Marker(location=[dt['施設_緯度'],dt['施設_経度']], popup='{}'.format(dt['名称'])).add_to(m_kuki_sh)
# m_saitama
m_kuki_sh.save('kuki_esh_H22.html')
```
![スライド11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/776a1905-132f-5835-de07-b4e65973d71d.png)
fill_color　を変更することで色を選べます。
Colerのサンプル
[color mapの一覧をheatmapで(160個くらい画像があるので注意)](https://pod.hatenablog.com/entry/2018/09/20/212527)
key_on の設定について
[python folium を使い、都道府県の夫婦年齢差をプロットする](https://www.monotalk.xyz/blog/python-folinum-%E3%82%92%E4%BD%BF%E3%81%84%E9%83%BD%E9%81%93%E5%BA%9C%E7%9C%8C%E3%81%AE%E5%A4%AB%E5%A9%A6%E5%B9%B4%E9%BD%A2%E5%B7%AE%E3%82%92%E3%83%97%E3%83%AD%E3%83%83%E3%83%88%E3%81%99%E3%82%8B/)
Geojson 上に feature はキー値として存在しませんが、features は存在しており、且つ、配列です。key_onの指定がfeature.properties.id なので、features > 1要素 feature としての解釈は folium 側でうまいことやってくれています。

geojsonファイルは以下のようになっていて、今回 id としたいのは地域コードで、properties の下にぶらさがっており、on_key に feature.properties.id2 を指定しています。
![スライド13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/fde8b743-c7c2-20a5-2f4a-c3d9be77011f.png)

##感想
いろいろと突っ込みどころ満載だと思いますが、何とか当初の目的の校区ごとの在籍者の可視化ができました。
少子化でどんどん子どもが減っていく一方、在籍者の偏りが地域で激しくなってきているのがわかりました。現在増加しているところも10年もすると途端に人数が減ってくるのがわかります。
それにしてもPDFのデータは何とかならないのでしょうか。