前置き
こんにちはデータエンジニアの山口歩夢です!
こちら基本的な内容から実践的な内容、自分が開発する上で躓いたポイントやTipsなどを書かせていただいているStreamlitの入門書です。
電子版が技術書典にて購入可能なため、是非チェックいただけると幸いです。
本題
Streamlitには、st.pydeck_chart
と言うPydeckライブラリで作成した図を、地図上に表示させることができる関数が用意されています。
公式ドキュメントに紹介されているような、地図上に3Dグラフが表示されるアプリケーションを作成したいと思い、ドキュメントを真似しながらアプリケーションを構築してみたのですが、なかなかうまくいきませんでした。
その時の解決策を共有させていただきます!
以下が公式ドキュメントで紹介されている図で、こういった3D棒グラフを日本地図にプロットしてみようと思います。
具体的にやりたいこと
以下のlatitude
、longitude
、sales
と言う3つのカラムがあるデータフレームのsales
カラムの値を基準に3D棒グラフを地図上にプロットします。
latitude
とlongitude
の値で3D棒グラフがプロットされる緯度と経度が決まり、sales
の値が大きければ3D棒グラフの高さが高くなり、低ければ低くなるといったイメージです。
import pandas as pd
# 元のデータフレームに追加します
data = pd.DataFrame({
'latitude': [35.6895, 34.6937, 35.0116, 35.1802, 43.2203,
33.5904, 35.4478, 35.6051, 35.8617, 34.9769,
34.3853, 34.6617, 38.2688, 36.6513, 35.2153,
36.5947, 34.1856, 33.6248, 31.9111, 26.2124],
'longitude': [139.6917, 135.5023, 135.7681, 136.9066, 142.8635,
130.4017, 139.6425, 140.1233, 139.6486, 138.3831,
132.4553, 133.9350, 140.8710, 138.1812, 136.0722,
136.5851, 131.4714, 132.8560, 130.6769, 127.6790],
'sales': [100000, 75000, 50000, 80000, 60000,
40000, 60000, 30000, 45000, 25000,
35000, 20000, 30000, 40000, 50000,
60000, 70000, 80000, 90000, 100000]
})
結論
今回やりたかったことを実現するためには、HexagonLayerではなくColumnLayerを使用しなければならなかったが結論になります。
以下が最終的に出来上がったスクリプトです。
import streamlit as st
import pandas as pd
import pydeck as pdk
import numpy as np
# 元のデータフレームに追加します
data = pd.DataFrame({
'latitude': [35.6895, 34.6937, 35.0116, 35.1802, 43.2203,
33.5904, 35.4478, 35.6051, 35.8617, 34.9769,
34.3853, 34.6617, 38.2688, 36.6513, 35.2153,
36.5947, 34.1856, 33.6248, 31.9111, 26.2124],
'longitude': [139.6917, 135.5023, 135.7681, 136.9066, 142.8635,
130.4017, 139.6425, 140.1233, 139.6486, 138.3831,
132.4553, 133.9350, 140.8710, 138.1812, 136.0722,
136.5851, 131.4714, 132.8560, 130.6769, 127.6790],
'sales': [100000, 75000, 50000, 80000, 60000,
40000, 60000, 30000, 45000, 25000,
35000, 20000, 30000, 40000, 50000,
60000, 70000, 80000, 90000, 100000] # 売上データ(例)
})
# PyDeckを使用して地図を描画します
st.pydeck_chart(pdk.Deck(
map_style=None,
initial_view_state=pdk.ViewState(
latitude=38.5,
longitude=137.0,
zoom=4,
pitch=50,
),
layers=[
pdk.Layer(
'ColumnLayer',
data=data,
get_position=['longitude', 'latitude'],
get_elevation='sales',
elevation_scale=5,
radius=10000,
get_fill_color=[180, 0, 200, 140],
pickable=True,
extruded=True,
),
],
))
そして、以下が出来上がった地図です。
ColumnLayerのget_position
に経度と緯度を設定して、get_elevation
に棒グラフで可視化したいカラムを指定してあげることで日本地図上に3D棒グラフを可視化することができました。
以上です!
HexagonLayerで3D棒グラフを可視化できなかった理由
冒頭で紹介した通り、公式ドキュメントではHexagonLayerを使用して地図上に棒グラフをプロットしています。公式ドキュメントでHexagonLayerを使って地図上に3D棒グラフを可視化しているから、HexagonLayerを使用するに違いないという思い込みがつまづいてしまった原因でした。
そもそも、HexagonLayerは指定したカラムの値に応じて3D棒グラフをプロットすると言う機能がありませんでした。
どうして公式ドキュメントでは3D棒グラフを地図上にプロットできていたのでしょうか。
公式ドキュメントでは、以下のようにDataFrameを用意しています。lat
カラムとlon
カラムの2つのカラムしか用意していません。
import streamlit as st
import pandas as pd
import numpy as np
chart_data = pd.DataFrame(
np.random.randn(1000, 2) / [50, 50] + [37.76, -122.4],
columns=['lat', 'lon'])
HexagonLayerは、緯度 (lat) と経度 (lon) のデータポイントを基に地図上に六角形をプロットします。そして、その六角形の密度に応じて3D棒グラフの高さを表現しています(六角形を何個も重ねて高さを出しているイメージ)。
そのため、公式ドキュメントではlat
カラムとlon
カラムだけを使用することで、その位置に六角形をプロットして、六角形の密度を基に3D棒グラフをプロットすることができていたのでした。
都道府県ごとにsales
カラムの値を元に3D棒グラフを作成するスクリプトに関しても、以下のように改良すれば、HexagonLayerでも3D棒グラフを可視化することは可能です。
以下のスクリプトでは、各都道府県の緯度・経度と売上データを使用して、その都道府県の売上に応じた数の行を持つ新しいデータフレームを作成しています。例えば、東京(latitude:35.6895
, longitude:139.6917
, sales:100000
)は、新しいデータフレームに100,000行として追加されます。
import streamlit as st
import pandas as pd
import pydeck as pdk
import numpy as np
# 元のデータフレームに追加します
data = pd.DataFrame({
'latitude': [35.6895, 34.6937, 35.0116, 35.1802, 43.2203,
33.5904, 35.4478, 35.6051, 35.8617, 34.9769,
34.3853, 34.6617, 38.2688, 36.6513, 35.2153,
36.5947, 34.1856, 33.6248, 31.9111, 26.2124],
'longitude': [139.6917, 135.5023, 135.7681, 136.9066, 142.8635,
130.4017, 139.6425, 140.1233, 139.6486, 138.3831,
132.4553, 133.9350, 140.8710, 138.1812, 136.0722,
136.5851, 131.4714, 132.8560, 130.6769, 127.6790],
'sales': [100000, 75000, 50000, 80000, 60000,
40000, 60000, 30000, 45000, 25000,
35000, 20000, 30000, 40000, 50000,
60000, 70000, 80000, 90000, 100000]
})
# 新しいデータフレームを作成します
new_data = pd.DataFrame(columns=['latitude', 'longitude'])
for idx, row in data.iterrows():
num_sales = row['sales'] if 'sales' in row else 1 # salesがない場合は1とします
temp_df = pd.DataFrame({
'latitude': np.repeat(row['latitude'], num_sales),
'longitude': np.repeat(row['longitude'], num_sales)
})
new_data = pd.concat([new_data, temp_df], ignore_index=True)
# PyDeckを使用して地図を描画します
st.pydeck_chart(pdk.Deck(
map_style=None,
initial_view_state=pdk.ViewState(
latitude=38.5,
longitude=137.0,
zoom=4,
pitch=50,
),
layers=[
pdk.Layer(
'HexagonLayer',
data=new_data,
get_position=['longitude', 'latitude'],
radius=20000,
elevation_scale=50,
elevation_range=[0, 1000],
extruded=True,
coverage=1,
),
],
))
上記のスクリプトからは、以下のようなマップが出来上がりました。
試してみたところ、HexagonLayerで特定のカラムを基準にレコード数を増やして3D棒グラフを可視化しようとすると、アプリケーションの動きが重くなってしまいました。
やはり今回のように特定のカラムの値を元に3D棒グラフを作りたい場合は、ColumnLayerを使うのが良さそうです。
まとめ
st.pydeck_chartを使用して、データフレームの特定のカラムを基準にマップ上に3D棒グラフをプロットしたい場合は、HexagonLayerではなくColumnLayerを使いましょう!