疾病別患者分布をジオプロットする方法

4月になり新採用職員の皆さんが多数入職されました。そんな彼らを前に電子カルテ研修と題してjupyter notebookのスライド機能(Reveal.js)を使ったプレゼンを行ってきました。それについてはまた別途記事にしたいと思いますが、ここではそのプレゼン資料にさらっと挟んでおいた疾病別患者分布の作り方を覚書きしておきます。

ステップ1:ジオコーディング

私の使用言語はPythonです。DWHから抽出してきた疾病別郵便番号一覧をあらかじめ五大疾病だけに絞り込んでおきます。絞り込みが完了したファイルを"五大疾病データ.xlsx"とします。それをPythonのライブラリpandasで読み取り、dataframeにした上で、ジオコーディング(郵便番号を緯度経度に変換)用公開API(http://geoapi.heartrails.com/api/json)に郵便番号を一つずつ投げていく関数を作り実行させます。

import requests
import json
import pandas as pd

df = pd.read_excel("五大疾病データ.xlsx")
df["latitude"] = ""
df["longitude"] = ""

def getlocation(dx):
    url = 'http://geoapi.heartrails.com/api/json'
    payload = {'method':'searchByPostal'}
    for i in dx.index:
        payload['postal'] = dx.loc[i,"郵便番号"]
        res = requests.get(url, params=payload).json()["response"]["location"][0]
        dx.loc[i,"latitude"] = res['y']
        dx.loc[i,"longitude"] = res['x']

getlocation(df)

さらっと書きましたが、実際には一気に処理させるとエラーになってしまいました。全部で9000レコード超だったのですが、500レコードずつ分割してgetlocation()を複数回実行させるという地道な作業が必要でした。(daskというpandasのdataframeを分割並列処理するライブラリが功を奏すか考えもしたのですが実際には試しませんでした。)

ステップ2:ジオプロット

次に、取得した経緯緯度を元に地図上に点描(ジオプロット)していく処理を行います。 前処理として、ステップ1で取得したdataframeを疾病ごとに分割し余分なカラムをそぎ落とします。

mf = df
cancer = mf[mf["疾病分類"] == "がん"]
ci = mf[mf["疾病分類"] == "脳卒中"]
mi = mf[mf["疾病分類"] == "急性心筋梗塞"]
di = mf[mf["疾病分類"] == "糖尿病"]
si = mf[mf["疾病分類"] == "精神疾患"]

ll_cancer = cancer[["latitude","longitude"]]
ll_ci = ci[["latitude","longitude"]]
ll_mi = mi[["latitude","longitude"]]
ll_di = di[["latitude","longitude"]]
ll_si = si[["latitude","longitude"]]

疾病ごとに緯度経度のみのdataframeが取得できました。これで準備万端です。

gmapsでやる場合

googlemapにジオプロットできるgmapsというライブラリを使う場合は以下の通りコードを書いていきます。

import gmaps
gmaps.configure(api_key="YOUR_GOOGLE_MAPS_API")

add_layer_cancer = gmaps.symbol_layer(ll_cancer, fill_color="red", stroke_color="red", scale=2)
add_layer_ci = gmaps.symbol_layer(ll_ci, fill_color="blue", stroke_color="blue", scale=2)
add_layer_mi = gmaps.symbol_layer(ll_mi, fill_color="green", stroke_color="green", scale=2)
add_layer_di = gmaps.symbol_layer(ll_di, fill_color="yellow", stroke_color="yellow", scale=2)
add_layer_si = gmaps.symbol_layer(ll_si, fill_color="purple", stroke_color="purple", scale=2)

m = gmaps.Map()

m.add_layer(add_layer_cancer) 
m.add_layer(add_layer_ci) 
m.add_layer(add_layer_mi) 
m.add_layer(add_layer_di) 
m.add_layer(add_layer_si) 

m

これで疾病ごとに色分けされたドットがプロットされたグーグルマップを表示することができます。(筆者はjupyter notebook上で実行し、inlineにマップが表示されることを確認しました。)

Foliumでやる場合

gmapsと異なりライブラリ内蔵の専用地図にジオプロットできるのが、Foliumというライブラリです。こちらはアウトプットをHTMLファイル保存できます。以下の通りコードを書いていきます。

import folium

map_ = folium.Map(location=[YOUR_LAT, YOUR_LON],
           tiles='Stamen Terrain',
           zoom_start=9)

for i in ll_cancer.index:
    x = ll_cancer.loc[i,"latitude"]
    y = ll_cancer.loc[i,"longitude"]
    folium.CircleMarker([x,y],
                 radius=4,
                 popup="がん",
                 color="#011efe",
                 fill_color="#011efe"
                 ).add_to(map_)

for i in ll_ci.index:
    x = ll_ci.loc[i,"latitude"]
    y = ll_ci.loc[i,"longitude"]
    folium.CircleMarker([x,y],
                 radius=4,
                 popup="脳卒中",
                 color="#fe0000",
                 fill_color="#fe0000"
                 ).add_to(map_)

for i in ll_mi.index:
    x = ll_mi.loc[i,"latitude"]
    y = ll_mi.loc[i,"longitude"]
    folium.CircleMarker([x,y],
                 radius=4,
                 popup="急性心筋梗塞",
                 color="#0bff01",
                 fill_color="#0bff01"
                 ).add_to(map_)

for i in ll_di.index:
    x = ll_di.loc[i,"latitude"]
    y = ll_di.loc[i,"longitude"]
    folium.CircleMarker([x,y],
                 radius=4,
                 popup="糖尿病",
                 color="#fdfe02",
                 fill_color="#fdfe02"
                 ).add_to(map_)

for i in ll_si.index:
    x = ll_si.loc[i,"latitude"]
    y = ll_si.loc[i,"longitude"]
    folium.CircleMarker([x,y],
                 radius=4,
                 popup="精神疾患",
                 color="#fe00f6",
                 fill_color="#fe00f6"
                 ).add_to(map_)

map_.save(outfile="fivemap_circle.html")

ちなみに保存したHTMLファイルをjupyter notebook上に表示する場合は

from IPython.display import HTML
HTML('<iframe src=./fivemap_circle.html width=800 height=500></iframe>')

とすればOKです。

考察:患者分布図で個人が特定されることはないか?

経度緯度を取得すると言っても、郵便番号が素ですから個人宅がピンポイントに指し示されることはありません。また病名についても五大疾病という大きな分類で表していますので、マイナーかつ詳細病名が地図上に表示されて推定されてしまうこともまずありません。(ただしアウトプットをネット公開することは控えたほうがいいかも知れません。あくまで院内資料ということで。)

患者分布図を作成する有料サービスも存在しているようですが今やオープンソースのライブラリで手軽に実行できる時代になっています。病院のデータ分析担当者は自らのスキルを院内にアピールし、病院長が無駄なコンサルタント料を支払うことの無いよう監視して行かなければなりません。