99
@SaitoTsutomu

県別データの可視化

この記事は最終更新日から1年以上が経過しています。

これなに

日本地図を県別に色分けする Python3 用のライブラリ(japanmap)を作成したので紹介します。
実行例は、Jupyter Notebook 上で確認しています。

インストール

pipでできます。numpy1、OpenCV、Pillowも一緒にインストールされます。xlrdは、Excelファイルの読み込みで使います。

bash
pip install -U japanmap jupyter matplotlib pandas xlrd

県コードとは

都道府県コード(以下、県コードと略す)は、JIS X 0401 により定められた、各都道府県ごとの01から47のコードです。
プログラムでは整数で扱います(頭の0を無視します)。

県名確認

pref_namesで県コードごとの県名がわかります。

python3
from japanmap import pref_names
pref_names[1]
>>>
'北海道'

pref_codeで県名に対する県コードがわかります。

python3
from japanmap import pref_code
pref_code('京都'), pref_code('京都府')
>>>
(26, 26)

groupsで八地方区分ごとの県コードがわかります。

python3
from japanmap import groups
groups['関東']
>>>
[8, 9, 10, 11, 12, 13, 14]

白地図

pictureで白地図(ラスタデータ)を取得できます。

python3
%config InlineBackend.figure_formats = {'png', 'retina'}
%matplotlib inline
import matplotlib.pyplot as plt
from japanmap import picture
plt.rcParams['figure.figsize'] = 6, 6
plt.imshow(picture());

image.png

県を色で塗ることもできます。

python3
plt.imshow(picture({'北海道': 'blue'}));

image.png

PNGファイルへの保存

savefigでファイルに保存できます。

python3
plt.imshow(picture({'北海道': 'blue'}))
plt.savefig('japan.png')

隣接情報

is_faced2sea で県コードに対して、庁所在地を含むエリアが海に面しているかわかります。

python3
from japanmap import is_faced2sea
for i in [11, 26]:
    print(pref_names[i], is_faced2sea(i))
>>>
埼玉県 False
京都府 True

is_sandwiched2sea で県コードに対して、庁所在地を含むエリアが海に挟まれているかわかります。(連続でない海辺が2つ以上あるか)

python3
from japanmap import is_sandwiched2sea
for i in [2, 28]:
    print(pref_names[i], is_sandwiched2sea(i))
>>>
青森県 False
兵庫県 True

adjacent で県コードに対して、庁所在地を含むエリアが隣接する県コードがわかります。

python3
from japanmap import adjacent
for i in [2, 20]:
    print(pref_names[i], ':', ' '.join([pref_names[j] for j in adjacent(i)]))
>>>
青森県 : 岩手県 秋田県
長野県 : 群馬県 埼玉県 新潟県 富山県 山梨県 岐阜県 静岡県 愛知県

境界線のベクトルデータ

get_data で各県の点リストと点のindexリストが取れます。これを使うと、pref_points で県の境界(index list)のリストが取れます。

python3
from japanmap import get_data, pref_points
qpqo = get_data()
pnts = pref_points(qpqo)
pnts[0]  # 北海道の境界座標(経度緯度)リスト
>>>
[[140.47133887410146, 43.08302992960164],
 [140.43751046098984, 43.13755540826223],
 [140.3625317793531, 43.18162745988813],
...

pref_map で境界線データを可視化できます。

python3
from japanmap import pref_map
svg = pref_map(range(1,48), qpqo=qpqo, width=2.5)
svg

image

SVGファイルへの保存

pref_mapは、SVG形式です。下記のようにしてファイルに保存できます。

python3
with open('japan.svg', 'w') as fp:
    fp.write(svg.data)

関東だけをグレースケールにする例。

python3
pref_map(groups['関東'], cols='gray', qpqo=qpqo, width=2.5)

image

県別データ(人口)を使って、県の面積比を変換

人口比で地図上の面積比を変換してみましょう。総務省統計局の「なるほど統計学園」の人口データの画面下部の「出典の統計表」を押して2017年の県別の人口2のファイル(a00400.xls)をダウンロードしましょう。

python3
import pandas as pd
df = pd.read_excel('a00400.xls', usecols=[9, 12, 13, 14], skiprows=18, skipfooter=3,
                   names='都道府県 男女計 男 女'.split()).set_index('都道府県')
df[:3]
男女計
都道府県
北海道 5320 2506 2814
青森県 1278 600 678
岩手県 1255 604 651

人口比の多い順に表示してみましょう。東京都の比の5.09は、県平均の5.09倍を表しています。

python3
df['比'] = df.男女計 / df.男女計.mean()
df.sort_values('比', ascending=False)[:10]
男女計
都道府県
東京都 13724 6760 6964 5.090665
神奈川県 9159 4569 4590 3.397362
大阪府 8823 4241 4583 3.272729
愛知県 7525 3764 3761 2.791260
埼玉県 7310 3648 3662 2.711510
千葉県 6246 3103 3143 2.316839
兵庫県 5503 2624 2879 2.041237
北海道 5320 2506 2814 1.973356
福岡県 5107 2415 2692 1.894348
静岡県 3675 1810 1866 1.363174

可視化してみます。
trans_areaで県の面積を指定した比率に変換できます。
例えば、県ごとの変換比率を[2, 1, 1, 1, ...]とすると、北海道が元の面積の2倍、他県が元の面積と同じ比率になります。

下記では、人口が平均の2倍であれば面積を2倍にするように表示しています。

python3
from japanmap import trans_area
qpqo = get_data(True, True, True)
pref_map(range(1,48), qpqo=trans_area(df.男女計, qpqo), width=2.5)

image

なるべく、ラフにして歪みを減らしたのですが、なかなか厳しいです。

白地図上で人口を可視化

下記のようにすると、人口の多い県を濃い赤で可視化できます。

python3
cmap = plt.get_cmap('Reds')
norm = plt.Normalize(vmin=df.男女計.min(), vmax=df.男女計.max())
fcol = lambda x: '#' + bytes(cmap(norm(x), bytes=True)[:3]).hex()
plt.colorbar(plt.cm.ScalarMappable(norm, cmap))
plt.imshow(picture(df.男女計.apply(fcol)));

image.png

4色問題

隣接情報を使って4色問題を解いてみましょう。

1つの県を1色とし、隣接する県は異なるように、日本全体を4色で塗りましょう。このように隣り合うもの同士に違う色を割当てる問題を、頂点彩色問題とよびます。
頂点彩色問題とは、グラフ上で隣接する頂点が異なる色になるように、最小の色数で頂点に色を割り当てる問題です。
この応用としては、例えば、携帯電話の基地局ごとの周波数を決める問題があります。(異なる色→異なる周波数→電波が干渉しないので話せる)

area.png

4色問題を解く

どのような平面地図であっても隣接するエリアが異なるようにして、必ず4色以内で塗り分けられることが、数学的に証明されています。しかし、どのように塗り分けたらよいかは、自明ではありません。ここでは、数理最適化を解いて求めることにしましょう。

数理最適化は、コストの最小化などを解くときに使われますが、目的関数がない制約だけの問題でも解くことができます。数理最適化で用いるパッケージ PuLPについては、最適化におけるPythonを見てください。

  • 数理モデル(1)で決めないといけないことは、変数表現、目的関数、制約条件の3つです。
  • 変数表現: 47都道府県×4色で188個の0または1しか取らない変数を用意します。このような変数は、バイナリ変数とよばれます。 変数は、パッケージ pandasの表で管理します。この変数表(2)を使うことにより制約条件をわかりやすく書くことができます。
  • 目的関数: 今回は最大化などを行いませんので、設定しません。PuLPでは、目的関数を設定しなくても実行できます。
  • 制約条件: 1県ごとに1色とします(3)。隣接した県同士は、異なる色にします(4)。
  • 数理モデルができたら、ソルバーを実行するだけで解を求めることができます(5)。 ソルバーは、数理モデルを解くソフトウェアで、pulpをインストールすると一緒にインストールされます。
  • 結果は、変数に対してvalueをよぶことで確認できます。ここでは、変数表に、新しい列"Val"を追加し結果を設定しています(6)。 この新しい列が0でない行を取得することにより、県に対する塗るべき色がわかります。

table.png

実行するためには、追加で PuLPと ortoolpyが必要です(pip install pulp ortoolpy)。

python3
import pandas as pd
from ortoolpy import model_min, addbinvars, addvals
from pulp import lpSum
from japanmap import pref_names, adjacent, pref_map
m = model_min()  # 数理モデル(1)
df = pd.DataFrame([(i, pref_names[i], j) for i in range(1, 48) for j in range(4)], 
                  columns=['Code', '県', '色'])
addbinvars(df)  # 変数表(2)
for i in range(1, 48):
    m += lpSum(df[df.Code == i].Var) == 1  # 1県1色(3)
    for j in adjacent(i):
        for k in range(4):  # 隣接県を違う色に(4)
            m += lpSum(df.query('Code.isin([@i, @j]) and 色 == @k').Var) <= 1
m.solve()  # 求解(5)
addvals(df)  # 結果設定(6)
四色 = ['red', 'blue', 'green', 'yellow']
cols = df[df.Val > 0]..apply(四色.__getitem__).reset_index(drop=True)
pref_map(range(1, 48), cols=cols, width=2.5)

image


  1. numpyは、行列計算などの線形代数のライブラリです。同様のソフトとしては、MATLABなどがよく使われていました。numpyとMATLABは同じベースなので、性能的には大きな差はありません。しかし、MATLABは有料ですが、Python、numpyは無料で使えるというメリットがあります。 

  2. 第2表 都道府県,男女別人口及び人口性比―総人口,日本人人口(平成26年10月1日現在)(エクセル:41KB) 

99
ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
SaitoTsutomu
Programmer
この記事は以下の記事からリンクされています
ty21kyPythonで世界地図-4からリンク
過去の3件を表示する

コメント

Tsutomu Saito さま
https://qiita.com/ty21ky/items/f4af8009be4e0407b6fd
で境界線のベクトルデータを利用して、mpl_toolkits.basemapの地図に都府県境界線を追加しました。
もし、問題があればコメント欄にご連絡ください。すぐに削除しますので。
よろしくお願いします。

0

@ty21ky 問題ないです。

0

こんなのも作りました。
https://pypi.org/project/osm-graph/

0

Tsutomu Saito さま
ありがとう御座います。

0

osm-graph 0.0.3
もインストールが出来たら使ってみます。
私のOSでは、OpenCVもgccのverをサポートしていないエラーでてインストールできないんです。
スキルが不足しているので、どうしようもありません。

0

Tsutomu Saito さま
申し訳ないですが、データの変更をお願い出来ますでしょうか。
例として、山形県の最後のポイントが1つ抜けています。
隣接県の新潟県の方は入っています。

この他に何箇所か見つけました。
もし、変更していただけるならわかる範囲で今回のようなイメージを作成します。

よろしくお願いします。

山形県.png

0

更新しました。

pip install -U japanmap

0

すみません。
pip install -U japanmapで更新して、実行しようとしたらエラーが出たのですが確認していただけますか。
from japanmap import *
でエラーが出てしまいます。
よろしくお願いします。

$ ./県境関数test.py
Traceback (most recent call last):
File "./県境関数test.py", line 7, in
import japan_border #都府県境界線
File "/home/ty/python/map/japan/japanmap/関数化/japan_border.py", line 5, in
from japanmap import *
File "/home/ty/.local/lib/python3.6/site-packages/japanmap/init.py", line 77
def pref_map(ips, cols=None, width=1, qpqo=None, tostr=False, ratio=(0.812,-1)):
^
SyntaxError: invalid syntax

0

おぉ、すいません、更新しました。

0

ありがとう御座います。
動きました。

0

SaitoTsutomuさま
japanmapの境界線のベクトルデータを読み込んで、海岸線等の部分を削除して前回は作成したのですが、japanmapがver upされてデータ数が増減するとデータがずれてしまいました。
そこで、県境部分だけのデータを作成し、新しくライブラリを作成しました。
japanmapの境界線のベクトルデータは、SaitoTsutomuさまの著作権があると思うのですが、ずうずうしいお願いですが、このライブラリを公開しても宜しいでしょうか。
もしよければ、確認のためにコメント欄にコードを貼り付けさせていただきます。
よろしくお願いします。

0

こちらで、直せそうなら、見てみます。

あと、ライセンスは、Python Software Foundation Licenseです。
https://ja.wikipedia.org/wiki/Python_Software_Foundation_License

0

コードの文字数があまりも多いのでコメント欄に添付するのは無理でした。
一時的にQiitaに投稿して、それを見ていただけますか。

0

はい

0

https://qiita.com/ty21ky/items/a5023ed4e07128d81c7b
に投稿しました。
よろしくお願いします。

0

ありがとうございます。見てみます。

0

すいません、「japanmapがver upされてデータ数が増減するとデータがずれてしまいました。」とは、どういう不具合でしょうか?

0

最初のやつは、
from japanmap import *
qpqo = get_data()
pnts = pref_points(qpqo)
これで、各県のjapanmapの境界線のベクトルデータを読み込んで、最初から何個のデータを削除、最後から何個のデータ削除とやっていました。
pip install -U japanmap
を実行した後は、各県のデータの数が数個増えていたので、海岸線・県境の方にはみ出してしまいました。
japanmapとmpl_toolkits.basemapでは海岸線の位置が若干違いますので、線が2重になってしまいました。

0

バグではない、ということでしょうか?

ちなみに、下記のothrに、境界線の反対側の県コードが入っているので、境界線だけなら下記のようにかけます。

qp, qo = get_data()
for k in range(len(qo)):  # 各県ごと
    for othr, ls in qo[k]:  # 各線分ごと
        if othr > k:  # 県コードkが小さい県のとき
            plt.plot([qp[i][0][0] for i in ls], [qp[i][0][1] for i in ls])
0

バグではありません。私のスキルが不足しているため、私の出来る範囲の方法でやっているためです。

上記の方法でも出来ました。ありがとうございます。
先程のものと、上記の方法を両方記載しておいても良いでしょうか。

image.png

0

image.png
先程も書きましたが、十和田湖の所の県境が国交省の地図と違いました。

0

はい。 > 先程のものと、上記の方法を両方記載しておいても良いでしょうか。
何でだろう? > 十和田湖の所の県境が国交省の地図と違いました。

0

image.png
国土数値情報 ダウンロードサービス
http://nlftp.mlit.go.jp/ksj/
の行政区域のN03-17_170101.shpファイルを重ね合わせた物です。

0
0

これは、十和田湖の湖畔か、湖の真ん中かの違いですから、一般の人にはあまり関係ないと思いますが。

0
あなたもコメントしてみませんか :)
ユーザー登録
すでにアカウントを持っている方はログイン
記事投稿イベント開催中
Microsoft Igniteに参加してイベントに関する記事を投稿しよう!
~
ユーザーは見つかりませんでした