Python の GeoPandas パッケージを使用した色分け地図の作成方法を紹介します。
ライブラリの使い方やデータの作成方法などをソースコードも添えて説明をします。
感染症の定点当たりの患者数の色分け地図を題材にしています。
新型コロナとインフルエンザの患者数の違いを地図で比較できます。
▽アプリの結果
目次
- ◆地図データの取得
- ◆色分け用データ取得とGeoDataFrameへの追加
- ◆地図作成
- ◆GeoPandasパッケージ
- ◆pandasパッケージ
- ◆matplotlibパッケージ
- ◆ソースの取得
- ◆さいごに
- ◆参考
◆地図データの取得
GeoPandas では GeoJSON などのベクトルベースの空間データを簡単に読み取ることができます。
ここでは空間データとして、都道府県の境界用の情報を GeoJSON 型式に作成されたデータを使用します。
読み取ると GeoDataFrame オブジェクトが返ります。
GeoDataFrame オブジェクトを使うとそれだけで地図を表示することができます。
◇GeoJSONデータの入手
GeoJSON は、JSON を用いて GIS (地理情報システム)データを記述したファイルフォーマットです。
属性にはポイント(住所や座標)、ライン(各種道路や境界線)、 ポリゴン(国や地域)などが含まれ、それらを使って描画することができます。
データは、いろいろな方が作成して公開してくれています。
GitHub で公開されている場合は、データの raw を表示してその URL を使います。
今回は、公開されているデータの中で次のものを使用します。
日本の行政区画(市区町村)・選挙区の地形ファイル - GitHub
※「N03-21」で始まるファイルは県ごとのファイルです。
※日本全体は「prefectures.json」ファイルです。
実際に使用する URL:https://raw.githubusercontent.com/smartnews-smri/japan-topography/main/data/municipality/geojson/s0010/prefectures.json
◇手順:地図データ取得
- GeoJSON ファイルの読み込み:geopandas モジュールの
read_file()
メソッド[説明⤵] - 地図の作成(必要なら):GeoDateFame クラスの
plot()
メソッド[説明⤵] - 地図の表示(必要なら):matplotlib.pyplot モジュールの
show()
メソッド
◇コード:地図データ取得
import geopandas as gpd import matplotlib.pyplot as plt # 地図データ取得 url1 = "https://raw.githubusercontent.com/smartnews-smri/japan-topography/main/data/municipality/geojson/s0010/prefectures.json" # 全国都道府県 gdf = gpd.read_file(url1) gdf.plot() plt.show()
◆色分け用データ取得とGeoDataFrameへの追加
GeoPandas で地図データに色分け用データを反映させるためには、
地図データの読み取りで作成された GeoDataFrame オブジェクトに色分け用のデータを追加します。
追加したデータ(列)を作図の時に column 引数で指定します。
今回使用する色分け用のデータは、NIID 国立感染症研究所が出している「定点把握疾患の定点あたりの報告数」です。
データの取得方法はこちらの記事を参照してください。
📖 定点把握疾患の定点あたりの報告数をwebから取得 - foliumのChoroplethで色分け地図を作成【Python】 🔗
データは、CSV(Shift-JIS) で提供されています。
pandas を使用して CSV を読み込み、必要なデータを DataFrame オブジェクトとして作成します。
その後、GeoDataFrame オブジェクトに追加します。
◇CSVデータ
NIID国立感染症研究所のサイトから取得するデータは次のような構成です。
※こちらの記事のアプリで表示しています・・・CSV viewerアプリの作り方(ドラッグアンドドロップ)【Python】 🔗【CSVの構成】
ここから次のようなデータに変換します。
実際にはさらに「報告」列と見出しの2行目を削除します。
残すデータに下線を引いています。
1 | 2 | 3 | ・・・ | 4 | 5 |
---|---|---|---|---|---|
N03_001 | インフルエンザ | インフルエンザ | ・・・ | COVID-19 | COVID-19 |
N03_001 | 報告 | 定当 | ・・・ | 報告 | 定当 |
北海道 | 855 | 3.78 | ・・・ | 1850 | 8.19 |
◇pandas DataFrame での操作
GeoPandas は、ベースとなる部分に pandas を使用しています。
したがって、pandas の機能をよく使うことになります。
pandas の機能は、必要なところをつまみ食いしている状態なので、説明が不十分な点はご容赦ください。
CSV から read_csv()
メソッドを使用して DataFrame オブジェクトを作成する場合、列ラベル(列に付けた名前、俗にいう列見出し、データとは別に保持されます)を作成すると後処理が楽になります。
DataFrame は表形式のデータで、データ部分の他にインデックスと列ラベルが存在します。
それぞれ、行見出し、列見出しのイメージでその値で行の指定、列の指定ができます。
前述のような構成の CSV から列ラベルを作成するのですが、見出しとなっている行が5行(5行目は総数)あり、そのうちの一部から列ラベルを作成することになります。
今回、列ラベルを作成するにあたって一度の読み込みでデータと列ラベルを作成する方法も試したのですが、CSV データの見出しで感染症名が必要な全カラムに設定されていないのを補完するには結局見出し用のデータを再構築する必要があると分かったので、データ部分と列ラベル部分は別々に作成して結合することとしました。
データの作成は、ヘッダー部分を除いて読み込めばいいだけなので read_csv()
メソッドで skiprows
引数を指定するだけでできます。
列ラベルの方は、いったん CSV の 3-4 行目を読み DataFrame オブジェクトを作成し、そこから前節で説明したような結果になるように変換します。
感染症名の補完は、ffill()
メソッドを使い、左側のデータで補完します。
感染症名の補完で、都道府県名の列の見出しが欠損値 1 で残るため、fillna()
メソッドで見出し名を補完します。
補完できた DataFrame オブジェクトからマルチインデックスを作成します。
この時、CSV から読み込んだ状態の DataFrame オブジェクトはマルチインデックスを作成するには行と列が反対なので T
属性を使って転置して使用します。
出来上がったマルチインデックスをデータだけで作成した DataFrame オブジェクトの columns
属性に設定します。
さらに不要な列を drop()
メソッドで削除し、マルチインデックスを感染症名だけのシングルインデックスに変換します。
最後に、出来上がった色分けデータを地図データにマージします。
都道府県(列ラベルが N03_001
)をキーして色分けデータの内容を地図データに追加します。
▽マージ前の GeoDataFrame オブジェクト
N03_001 geometry 0 北海道 MULTIPOLYGON (((140.86464 42.00935, 140.86249 ... 1 青森県 MULTIPOLYGON (((140.66079 40.94845, 140.65877 ... 2 岩手県 MULTIPOLYGON (((141.97851 39.83069, 141.97944 ... 3 宮城県 MULTIPOLYGON (((140.56324 38.37720, 140.55655 ... 4 秋田県 POLYGON ((140.05966 39.59779, 140.05613 39.566...
▽マージ後の GeoDataFrame オブジェクト
N03_001 geometry インフルエンザ ... クラミジア肺炎 感染性胃腸炎(ロタウイルス) COVID-19 0 北海道 MULTIPOLYGON (((140.86464 42.00935, 140.86249 ... 24.19 ... - - 12.28 1 青森県 MULTIPOLYGON (((140.66079 40.94845, 140.65877 ... 30.38 ... - - 5.75 2 岩手県 MULTIPOLYGON (((141.97851 39.83069, 141.97944 ... 21.71 ... - - 6.27 3 宮城県 MULTIPOLYGON (((140.56324 38.37720, 140.55655 ... 26.62 ... - - 3.85 4 秋田県 POLYGON ((140.05966 39.59779, 140.05613 39.566... 20.27 ... - - 4.71 [5 rows x 21 columns]
◇手順:色分け用データ取得と追加
- URL の作成:
URL は年と週で構成されます。date オブジェクトのstrftime()
メソッドで書式を使用して年と週を取得します - 色分けデータのデータ部分の作成
- 色分けデータのヘッダー部分の作成
3―4行目を取得してマルチインデックスを作成- 色分けデータの取得:pandas モジュールの
read_csv()
メソッド
3―4行目を取得
Shift-JIS でエンコード - 感染症名が左のカラムにしかないので右にコピー:
ffill
メソッド[説明⤵] - 都道府県名の見出しを付加:
fillna()
メソッド[説明⤵]
ここだけが 欠損値 なのでそこを埋めます
見出し名は、GeoJSON の都道府県名に合わせます - 見出しをマルチインデックス化:
MultiIndex.from_frame()
メソッド[説明⤵]
マルチインデックスを作成するのに取得したデータのままだと行列が反対なので転置して読みます - 報告列を削除:
drop()
メソッド[説明⤵]
「報告」列は使用しないので削除 - マルチインデックスをシングルインデックス化:
droplevel()
メソッド[説明⤵]
感染症名を残します
- 色分けデータの取得:pandas モジュールの
- 色分けデータを地図データにマージ:
merge()
メソッド[説明⤵]
都道府県名をキーにしてマージ
◇コード:色分けデータ取得と追加
# 色分けデータ取得 today = date.today() - timedelta(days=16) year = today.strftime('%Y') week = today.strftime('%V') url2 = f"https://www.niid.go.jp/niid/images/idwr/sokuho/idwr-{year}/{year}{week}/{year}-{week}-teiten.csv" print(f"日付:{today.strftime('%Y/%m/%d')}、週:{week}") # ヘッダーとデータを分けて作る # データ部分の作成 try: df = pd.read_csv(url2, encoding='cp932', skiprows=5, header=None) # ヘッダー部分を読まない except: print('感染症データがない日付を指定されました') exit() df = df.dropna(how='all') # すべての列がNAの行を削除 # ヘッダー部分の作成(ヘッダー部分をデータとして読み込む(2行スキップして2行だけ読む)) cdf = pd.read_csv(url2, encoding='cp932', skiprows=2, nrows=2, header=None) cdf = cdf.ffill(axis=1) # 感染症名が左のカラムにしかないので右にコピー cdf = cdf.fillna('N03_001') # 先頭にNAが残るのでN03_001を設定 df.columns = pd.MultiIndex.from_frame(cdf.T) # カラム名にするためマルチインデックス化(データは行列を入れ替えて読む) s1df = df.drop(columns='報告', level=1) # マルチインデックスのレベル1が「報告」の列を削除 s1df.columns = s1df.columns.droplevel(1) # マルチインデックスのレベル1を削除、シングル化 # データを都道府県名をキーにして地図データgdfにマージ gdf = gdf.merge(s1df, on='N03_001', how='left')
◆地図作成
作成する地図が一つなら plot()
メソッドで描画するだけです。
ここでは、地図を二つ横並びにして表示します。
地図を二つ描画するには、図を作成する領域を用意して、そこに地図を描画します。
図領域の作成と描画の順番はどちらが先でも構いません。
図領域の作成にはいくつか方法がありますが、ここでは subplots()
メソッドを使用します。
更に、色分け値も描画します。
文字を描画するには、annotate()
メソッドを使用します。
値が入っている列名を指定して描画します。
今回、値が大きくなると背景が濃くなるので、ある値を境に文字の色が変わるように三項演算子を使用して指定します。
また、都道府県ごとに for 文で回して実行しても良いのですが、pandas には apply()
メソッドという便利な機能があるので、そちらを利用しました。
apply()
メソッドは、列や行に対して関数を適用してくれます。
◇手順:二つの地図を並べて表示
- 二つ分の図領域を作成:
subplots()
メソッド[説明⤵]
全体のサイズを 12 × 6 インチにし、横に並んだ2つの領域を作成 - 一つ目の地図を描画:
plot()
メソッド[説明⤵] - グラフの軸を非表示:
set_axis_off()
メソッド[説明⤵] - 表示範囲を設定:
set_xlim()
、set_ylim()
メソッドで表示[説明⤵] - 色分け値を表示:
apply()
とannotate()
メソッド[説明⤵] - 二つ目の地図も同様に処理
- 地図を表示:
show()
メソッド
◇コード:二つの地図を並べて表示
# 地図作成 _, axes = plt.subplots(1, 2, figsize=(12, 6)) # 二つ分の図領域を作成 ax1 = gdf.plot(ax=axes[0], column='インフルエンザ', cmap='PuRd', vmin=0, vmax=40 , legend=True, legend_kwds={'label':'インフルエンザ', 'location':'left'}) ax1.set_axis_off() # 目盛軸を非表示 ax1.set_title('インフルエンザ') ax1.set_xlim(130, 145) ax1.set_ylim(30, 45) # 数値の描画 gdf.apply(lambda x: ax1.annotate(x.インフルエンザ , (x.geometry.centroid.x, x.geometry.centroid.y) , fontsize=6 , color='yellow' if x.インフルエンザ > 8 else "black") , axis=1) ax2 = gdf.plot(ax=axes[1], column='COVID-19', cmap='PuRd', vmin=0, vmax=40 , legend=True, legend_kwds={'label':'COVID-19'}) ax2.set_axis_off() # 目盛軸を非表示 ax2.set_title('COVID-19') ax2.set_xlim(130, 145) ax2.set_ylim(30, 45) # 数値の描画 gdf.apply(lambda x: ax2.annotate(x["COVID-19"] , (x.geometry.centroid.x, x.geometry.centroid.y) , fontsize=6 , color='yellow' if x["COVID-19"] > 8 else "black") , axis=1) plt.show()
◆GeoPandasパッケージ
GeoPandas パッケージを使用するためには、 pip でインストールします。
依存するパッケージは自動で一緒にインストールされますが、
一部手動でインストールする必要があるものが存在します。
conda を使用するとその辺の煩わしさがなくなるようですが、
私は使用していないので、ここでは pip でインストールする場合を説明します。
インストール、インポートが必要なパッケージは次の通りです。
- インストール
pip install geopandas
pip install matplotlib
:描画するために必要(plot()が動かない)pip install pyogrio
:ファイルを読むために必要(read_file()が動かない)
- インポート
import geopandas as gpd
import matplotlib.pyplot as plt
import pandas as pd
:コーディングしやすいため
◇geopandas.read_file()メソッド
ファイルまたは URL を読み GeoDataFrame オブジェクトを返します。
ESRI シェープファイル、GeoJSON ファイルなどを含む、ほぼすべてのベクトルベースの空間データ形式を読み取ることができます。
- 【構文】
geopandas.read_file(filename, bbox, mask, rows, engine, **kwargs)
- 主な引数
- 戻り値:GeoDataFrame オブジェクト、または DataFrame オブジェクト
◇GeoDataFrameクラス
GeoDateFrame クラスは、pandas の DataFrame クラスに空間データ形式 geometry を表す属性が追加されたものです。
▽plot()メソッド
matplotlib を使用して GeoDataFrame の geometry を描画します。
column が指定されている場合、その列の値に基づいて色付けします。
- 【構文】
plot(column, cmap, color, ax, cax, categorical, legend, scheme, k, vmin, vmax, markersize, figsize, legend_kwds, categories,classification_kwds, missing_kwds, aspect, **style_kwds)
- 主な引数
column
:色付けに使う列名cmap
:色の指定 ・・・参照:Choosing Colormapsax
:プロットする matplotlib の Axes オブジェクト(複数の図を出力する場合などに指定)cax
:凡例を描画する軸legend
:凡例を描画するかどうかlegend_kwds
:次のキーを辞書で指定・・・参照:matplotlib.figurelabel
:汎用のラベルlocation
:凡例の位置("left"
,"right"
,"top"
,"bottom"
)orientation
:軸:横("horizontal"
)、無指定は縦
vmin
:cmapの最小値vmax
:cmapの最大値figsize
:Figure オブジェクトのサイズ。axis が指定された場合は無視される。
▽日本語表示
plot()
メソッドで描画した内容に日本語を含む場合、デフォルトでは出力されません。
matplotlib パッケージ側で対応する必要があります。
メソッドの fontfamily
引数で対応することもできますが、それだと個々に記述が必要です。
デフォルトのフォントを変更した方が1文で済みます。
- デフォルトのフォントを日本語に対応したものにする
例:※プロットする前に設定します
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'Meiryo'
fontfamily
引数を指定する- 例:
set_title("タイトル", fontfamily='Meiryo')
- 例:
◆pandasパッケージ
◇pandas.read_csv()メソッド
ファイルまたは URL から CSV を読んで DataFrame を返します。
- 【構文】
pandas.read_csv(filepath_or_buffer, *, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)
- 主な引数
filepath_or_buffer
:ファイルまたは URL への有効なパスheader
:列ラベルを含むデータの開始行番号、またはマルチインデックスとする行番号のシーケンス、ヘッダーがない場合は None を指定
- 戻り値:DataFrame オブジェクト
◇DataFrameクラス
▽dropna()メソッド
- 【構文】
dropna(*, axis、 how、 thresh、 subset、 inplace、 ignore_index)
: - 主な引数
how
:"any"
(default),"all"
一つの欠損値か全てが欠損値かの指定axis
:0
(default),"index"
,1
,"columns"
行か列かの指定
- 戻り値:DataFrame オブジェクト
例:dropna(how='all')
:すべての列が欠損値の行を削除
▽ffill()メソッド
最後の有効な観測値で、欠損値を埋めます。
- 【構文】
ffill(*, axis, inplace, limit, downcast)
- 主な引数
axis
:0
(default),"index"
,1
,"columns"
欠損を埋める軸limit
:埋め込む最大回数。デフォルトはすべてinplace
:True
,False
(default) 上書きするかどうか
- 戻り値:DataFrame オブジェクト
▽fillna()メソッド
指定された方法を使用して欠損値を value で埋めます。
- 【構文】
fillna(value, *, method, axis, inplace, limit, downcast)
- 主な引数
value
:埋め込む値method
:方法:"backfill"
,"bfill"
,"ffill"
,None
(default)
現在はffill()
、bfill()
の使用を推奨axis
:0
(default),"index"
,1
,"columns"
欠損を埋める軸limit
:埋め込む最大回数inplace
:True
,False
(default) 上書きするかどうか
- 戻り値:DataFrame オブジェクト
▽apply()メソッド
データフレームの列や行に対して関数を適用するメソッド
- 【構文】
DataFrame.apply(func, axis, raw, result_type, args, by_row, **kwargs)
- 主な引数
func
:列または行に適用する関数axis
:対象が列か行か0
(default),"index"
,1
,"columns"
raw
:行または列が Series または ndarray オブジェクトとして渡されるかどうか default:False
result_type
:列に対する操作の場合だけ ???仕様書だけではよく分からないexpand
:列に変換可能なリストを返すreduce
:Series を返すbroadcast
:None
:デフォルト
args
:関数に渡す引数(タプル)by_row
:False
,"compat
(default)
- 戻り値:関数を適用したSeriesまたはDataFrame
▽drop()メソッド
マルチインデックスを持つDataFrameの列の削除
- 【構文】
df.drop(labels, *, axis, index, columns, level, inplace, errors)
- 主な引数
- columns:列名を指定(labelsとaxis=1を指定したのと同じ)
- level:マルチインデックスのレベルを指定
- labels:削除するインデックスまたは列ラベル
- axis:labels で指定したものをインデックス(0, "index")または列(1, "columns")から削除するか
- 戻り値
- DataFrame か None
例:drop(columns='報告', level=1)
▽merge()メソッド
DataFrame の結合。データベースと同じような結合を行います。
- 【構文】
merge(right, how, on, left_on, right_on, left_index, right_index, sort, suffixes, copy, indicator, validate)
- 主な引数
right
:マージする DataFrame か Series オブジェクトhow
:結合方法"left"
,"right"
,"outer"
,"inner"
(default),"cross"
on
:結合する列名またはインデックスのレベル名。
これらは両方の DataFrame に存在する必要があります。
異なる場合は left_on, right_on を使用
- 戻り値: 二つの DataFrame をマージした DataFrame
◇MultiIndexクラス
▽from_frame()メソッド
DataFrame から MultiIndex を作成します。
- 【構文】
MultiIndex.from_frame(df, sortorder, names)
- 主な引数
df
:DataFrame オブジェクト
▽droplevel()メソッド
マルチインデックスを持つDataFrameの列の削除
- 【構文】
MultiIndex.droplevel(level)
- 引数
- level:レベル名か番号
- 戻り値
- Index か MultiIndex
◆matplotlibパッケージ
matplotlib はグラフ描画ライブラリです。
GeoPandas で作成した地図も set_axis_off()
メソッドを実行しないとグラフのX軸、Y軸が表示されます。
matplotlib では描画するために Figure と Axes オブジェクトが使われます。
Figure はノートのようなもので、Axes は方眼紙のようなイメージです。
ノートに複数の方眼紙を並べて複数のグラフを描画することもできます。
◇matplotlib.pyplot.subplots()メソッド
Figure と Axes のセットを作成します。
- 【構文】
subplots(nrows, ncols, *, sharex, sharey, squeeze, width_ratios, height_ratios, subplot_kw, gridspec_kw, **fig_kw)
- 主な引数
nrows
,ncols
:配置する Axes オブジェクトの行/列の数**fig_kw
figsize
:Figure オブジェクトの寸法のタプル。(幅, 高さ) をインチで指定
- 戻り値
fig
:Figureオブジェクトax
:Axes オブジェクトか Axes オブジェクトの配列
◇Axesクラス
▽set_axis_off()メソッド
軸に関する表示(グラフの周りの線や軸ラベル、目盛マーカー、目盛ラベルなど)を非表示にします。
- 【構文】
set_axis_off()
▽set_xlim(), set_ylim()メソッド
X 軸または Y 軸の表示範囲を設定します。
- 【構文】
set_xlim(left, right, *, emit, auto, xmin, xmax)
- 【構文】
set_ylim(bottom, top, *, emit, auto, ymin, ymax)
- 主な引数
left
:左側の表示位置。(left, right)
のタプルも可right
:右側の表示位置bottom
:下側の表示位置。(bottom, top)
のタプルも可top
:上側の表示位置
▽annotate()メソッド
点 xy にテキスト text で注釈を付けます。(文字の出力)
- 【構文】
annotate(text, xy, xytext, xycoords, textcoords, arrowprops, annotation_clip, **kwargs)
- 主な引数
text
:注釈用テキストxy
:注釈を付ける点 (x, y)。座標系は xycoords によって決定されます。
- text() メソッドと共通の主な引数
fontfamily
:フォントファミリー名、フォント名fontsize
:フォントサイズcolor
,c
:文字の色backgroundcolor
:背景色
◆ソースの取得
全体のソースはこちらから取得できます。
- ソース:
Fixed_point_disease_geo.py
- 取得先:GitHub - juu7g/Python-map-disease
◇ご注意
本記事は次のバージョンの下で動作した内容を基に記述しています。
- Python 3.8.5
- geopandas 0.13.2
- matplotlib 3.7.3
- pyogrio 0.7.2
- pandas 2.0.3
◇免責事項
ご利用に際しては、『免責事項』をご確認ください。
お気づきの点がございましたら『お問い合わせ』からお問い合わせください。
◆さいごに
感染症の定点当たりの患者数の色分け地図を folium を使用したものに続き、GeoPandas を使用して作成しました。
今回、ご紹介しませんでしたが、GeoPandas で作成した地図は、Tkinter で表示することができます。
「FigureCanvasTkAgg」で検索すると方法が分かると思います。
folium では、ブラウザで結果を表示するしかありませんでしたが、GeoPandas であれば、アプリの中に地図を組み込むことができ、応用が広がると思います。