Streamlit 覚書
Streamlit は、高速で UI を備えた Web アプリケーションのプロトタイプを作成できる Python ライブラリ。
記述する言語が Python のみであるため、Javascript や HTML を知らなくても、それなりの見た目で動くアプリケーションを作成することができる。
セキュリティ機能などは弱い(というか、ほぼ無い)ため、本番で使用するには別のフレームワークで認証機能などを実装する必要がある。
公式ドキュメント:https://docs.streamlit.io/
1. インストール及びアプリケーションの起動
1.1. インストール
pip install streamlit
1.2. 基本的なファイルの作成と起動
touch home.py
import streamlit as st
# ページ表示用関数
def main():
# Streamlit API の title を使用して文字表示
st.title('Hello World!')
if __ name__ == '__main__':
main()
streamlit run home.py
1.3. 起動したページの表示
デフォルトの場合は8501
ポートでサーバが起動する。
任意のブラウザでhttp://localhost:8501
に接続し、起動を確認する。
2. テキストに関する記述式
2.1. 基本的なテキストの表示
# タイトル。最もサイズが大きい。ページタイトル向け
st.title('Title')
# ヘッダ。2番目に大きい。項目名向け
st.header('Header')
# サブレベルヘッダ。3番目に大きい。小項目向け
st.subheader('Sub Header')
# 普通のテキスト。Html や Markdown のパースはしない。
st.text('Text')
# 普通のテキストその2。Markdown のパースをする他、複数の値を渡せる。
st.write('### Current date: ', date.today())
2.2. リッチなテキストの表示
# マークダウン。markdown 又は write
st.markdown('*Markdown*')
st.write('**Text parsing**')
# HTMl 記述
import streamlit.components.v1 as components
components.html('''
<a href="http://sample.html">Link</a>
''')
# コードブロック
st.codde('print("Hello World!")')
# エラーメッセージ。赤文字に薄赤背景
st.error('Error message')
# 警告メッセージ。黄色文字に薄黄色背景
st.warning('Warning message')
# 情報メッセージ。青文字に薄青背景
st.info('Information message')
# 成功メッセージ。緑文字に薄緑背景
st.success('Success message')
# 例外メッセージ。Exception部分が太字の赤文字・薄赤背景
st.exception(Exception('Ooops!'))
# 辞書型データ(JSON)の表示。各階層に折り畳み機能付
dictData = {
'foo': 'bar',
'users': [
'alice',
'bob',
],
}
st.json(d)
# 折り畳み表示
with st.expander('Expander title'):
st.json(d)
2.3. プレースホルダー
画面の一部を後から更新する場合などに使用する。
Streamlit は複数回st.text()
を重ねると、重ねた分だけ文字が出力されるため、
ループ処理による表示の更新などが、主な使い方となる。
表示の更新にはplaceHolder.text()
又はplaceHolder.write
等、
テキスト系メソッドを使用できる。
注意:下記のように永続的なループをさせた場合、
それ以降のコードにリーチできなくなる。
処理を継続させたい場合はスレッディングを利用すること。
# 現在の時刻を取得、1秒ごとに更新するサンプルコード
import streamlit as st
from time import sleep, time
from datetime import datetime, timezone
def home():
# プレースホルダーを作成。実際の表示位置はこの位置になる。
timeHolder = st.empty()
while(True):
curTime = convert_unix_time(time())
# プレースホルダーが持つメソッドを使用して表示を更新
timeHolder.write(f'Current Time: {curTime}')
sleep(1)
def convert_unix_time(unixTime):
res = datetime.fromtimestamp(unixTime).strftime('%Y/%m/%d %H:%M:%S') # 形式指定
return res
if __name__ == '__main__':
home()
3. データの視覚化に関する記述式
3.1. グラフ、エリアチャート、バーチャート
pandas
データフレームを利用したチャートやグラフを表示する。
チャート表示関数の戻り値であるオブジェクトにadd_rows()
を使用することで、
表示後も動的にデータを追加することができる。
Streamlit 標準の描画機能であり、追加でライブラリをインポートする事無く使用可能
def create_random_graphs():
# ランダムなデータの作成
data = {
'x': np.random.random(20),
'y': np.random.random(20) - 0.5,
'z': np.random.random(20) - 1.0,
}
df = pd.DataFrame(data)
# ラインチャート
objLchart = st.line_chart(df)
# エリアチャート
objAchart = st.area_chart(df)
# バーチャート
objBchart = st.bar_chart(df)
# チャートにデータを動的に追加する場合
additional_data = np.random.random(size=(5,2))
objLchart.add_rows(additional_data)
3.2. データフレームの表示
データフレームをテーブル形式で表示する。
表示方法は次の2通り。
-
st.dataframe()
: スクロールバーを付けて表示 -
st.table()
: 全てのデータを一括で表示
import streamlit as st
import numpy as np
import pandas as pd
data = {
'x': np.random.random(20),
'y': np.random.random(20) - 0.5,
'z': np.random.random(20) - 1.0,
}
df = pd.DataFrame(data)
st.dataframe(df)
st.table(df)
3.3. 画像の表示
st.image()
関数で画像を表示できる。
画像データは、画像ファイル名を直接指定するか、pillow
で取得する。
import streamlit as st
from PIL import Image
# 画像ファイルを直接指定する場合
st.image('image_file_name.jpg')
# 画像データを一旦取得してから表示する場合
img = Image.open('image_file_name.jpg')
st.image(img)
# 画像株に表示するキャプション、表示サイズを指定(ピクセル)
# 画像サイズを指定した場合、実際に縮小処理されて表示される。
st.image(img, caption='image file', width=100)
3.4. 地図へのプロット
mapbox
が提供する OSS のマップを使用してデータを可視化する。
表示に使用するデータはデータフレーム形式である必要がある他、
其々のカラム名はlat
及びlon
でなければならない。
import streamlit as st
import numpy as np
def create_map_data():
# マップ表示するランダムデータを作成
data = {
'lat': np.random.randn(100) / 100 + 35.68,
'lon': np.random.randn(100) / 100 + 139.75,
}
# データをデータフレーム形式に変換
map_data = pd.DataFrame(data)
# マップの表示
st.map(map_data)
4. ウィジェット配置に関する記述式
ウィジェット配置時の注意点
ウィジェットにはラベル(明示しない場合、第一引数)が付与される。
このラベルが以下の様に重複すると、Streamlit はキーの重複エラーを返す。
button_value_A = st.button('Push me!')
button_value_B = st.button('Push me!')
このエラーを回避したい場合は、ウィジェットにkey
を設定する。
button_value_A = st.button('Push me!', key=1)
button_value_B = st.button('Push me!', key='second')
4.1. ボタン
クリックすることでTrue
を返す。
また、ボタンクリックはページの変更イベントとして認識されるため、
何の処理も記述しなくとも、ボタンクリック自体がページの再読み込みトリガーとして扱われる。
import streamlit as st
# ボタンクリックはページの再読み込みトリガーとなる。
st.button('Reload')
# ボタンの返り値を評価する場合は、ボタンより後に条件式を記述する。
# ボタンクリックにより、flgButton に True が格納された状態でページが再読み込みされる。
flgButton = st.button('Switch')
if flgButton:
st.balloons()
4.2. チェックボックス
チェックすることでTrue
、チェックを外す事でFalse
を返す。
ボタンと異なり、チェックボックスはチェックの状態を保持する特性がある。
正し、保持する状態はページのリロードで失われる。
import streamlit as st
flgCheck_A = st.checkbox('Checkbox A')
flgCheck_B = st.checkbox('Checkbox B')
# チェックボックスのイベントは並列して発生する可能性があるため、
# elif を使用すろとイベントのキャッチ漏れに繋がる。
if flgCheck_A:
st.text('Checkbox A has checked')
if flgCheck_B:
st.text('Checkbox B has checked')
4.3. ラジオボタン
複数の選択から1つの結果を得る事ができる。
戻り値は選択した文字列であるため注意
import streamlit as st
selected_item = st.radio(
'which Animal do you like?',
['Dog', 'Cat']
)
st.text(selected_item)
4.4. セレクトボックス
できる事は基本的にラジオボタンと同じ。
選択部分が入力可能なフィールドとなっており、選択肢から検索をかける事ができる。
大量の選択肢を動的に作成、選択する場合などに。
import streamlit as st
selected_item = st.selectbox('Select item', ['A', 'B', 'C'])
st.text(selected_item)
# デフォルト値を設定する場合は index オプションを設定する。
selected_item = st.selectbox('Select item', ['A', 'B', 'C'], index=1)
# 初期値が 1 なので B が表示される。
st.text(selected_item)
4.5. マルチセレクト
複数の選択を同時に選択することができる。
タグのフィルタリング選択などに。これも検索が可能
選択したアイテムはリストとして戻り値にセットされる。
import streamlit as st
selected_item = st.multiselect('Select item', ['A', 'B', 'C'])
st.text(selected_item)
# デフォルト値の設定は default オプションで設定
selected_item = st.multiselect('Select item', ['A', 'B', 'C'], default=['B', 'C'])
# 初期値に設定された 'B' 'C' が表示される。
st.text(selected_item)
4.6. スライダー
特定の範囲(最小値・最大値)の間から数値をスライダーで選択する。
最小値・最大値とスライダーのサイズは比例しないため、
あまりにも大きな数を扱うには不向き。
また、デフォルト値に2つの要素を指定すると、レンジ入力となる。
import streamlit as st
# 1つの値を指定する
age = st.slider(label='Your Age', min_value=0, max_value=130, value=20)
# 2つの値を指定し、レンジを取得する。戻り値は2つの値を持つタプル型
age = st.slider(label='Your Age', min_value=0, max_value=130, value=(20, 30))
4.7. 日付・時間入力
-
st.date_input
- カレンダー形式の入力ウィジェットから日付を入力する。
- 戻り値の形式は
YYYY-MM-DD
-
st.time_input
- ドロップダウンリスト又は直接入力から時間を入力する。
- 戻り値の形式は
HH:MM:SS
import streamlit as st
# 日付入力
date = st.date_input('Input date')
# 入力の最大値・最小値指定も可能
date = st.date_input('Input date',
min_value=date(1900, 1, 1),
max_value=date.today(),
value=date(2000, 1, 1)
)
st.write('Birthday: ', birthday)
# 時間入力
time = st.time_input('Input time')
4.8. 文字列入力
-
st.text_input
- 一行の文字列入力に使用。戻り値は入力値
- ページが更新されても入力は保持される。
-
st.text_area
- 複数行の文字列入力に使用。
- 改行を行っても
\n
が入力されているだけらしく、戻り値がリストになったりはしない。
import streamlit as st
# 文字列の入力。第一引数は入力ボックス上部のラベル名
inputText_A = st.text_input('Input any words')
# 初期値を指定することも可能
inputText_B = st.text_input(label='Please input text', value='aaa')
# テキストエリアも使い方は同様
inputArea = st.text_area('Please input any strings', 'Place holder')
4.9. 数値入力
ナンバーインプットを使う事で、入力形式を指定・制限することが可能
初期値を整数にすればint
型、少数にすればfloat
型となる。
未指定の場合はfloat
型になるので注意
また、入力ボックスに表示されるのは、小数点以下2桁まで。
それ以上(以下)の桁を入力した場合、四捨五入されて表示されるが、
内部の戻り値は完全な数値で入力されている。
import streamlit as st
# 整数を入力させる場合
number = st.number_input('Input any number', 0)
# 小数値を入力させる場合
number = st.number_input('Input any number', 0.0)
4.10. ファイルアップローダ
クライアント側のファイルをアプリケーションに渡すことが出来る。
渡されたファイルはUploadedFile
クラスオブジェクトとなっているが、
一般的なファイルオブジェクトに似た使い方ができる。
-
read
メソッド: ファイルの内容を文字データとして取得 -
getvalue
メソッド: ファイルの内容をバイトデータとして取得
import streamlit as st
# ファイルのアップロード
f = st.file_uploader('Upload image file')
# 先頭4バイト(マジックナンバー)を取得
b = f.read(4)
# マジックナンバーが jpg だった場合
if f and b == b'\xff\xd8\xff\xe0':
# ファイルを画像として表示(f.name でファイル名を取得)
st.image(f, caption=f.name, width=100)
# ファイルの内容を書き込み(元がバイトデータなので read で読み込み)
with open(f'new_{f.name}', 'wb') as file:
file.write(f.read())
5. 地図操作(Streamlit-Folium)
5.1. インストールと基本的な使用方法
インストール
pip install streamlit_folium
地図表示
import streamlit as st
from streamlit_folium import st_folium
import folium
# 地図の表示箇所とズームレベルを指定してマップデータを作成
# attr(アトリビュート)は地図右下に表示する文字列。
# デフォルトマップの場合は省略可能
m = folium.Map(
location=[39.94610, -75.150282],
zoom_start=16,
attr='Folium map'
)
# マーカーを作成してデータを追記
folium.Marker(
[39.949610, -75.150282],
popup="Liberty Bell",
tooltip="Liberty Bell"
).add_to(m)
# 地図を表示
st_data = st_folium(m, width=725)
5.2. 地図操作レスポンスの取得
地図描画時の戻り値を取得することで、操作のレスポンスを取得できる。
レスポンスは JSON 形式で、以下の要素を持つ。
-
last_clicked
- 最後のクリック地点の緯度・経度(
lat
,lng
)
- 最後のクリック地点の緯度・経度(
-
last_object_cliecked
- 最後にクリックしたマーカーなどのオブジェクトの緯度・経度(
lat
,lng
)
- 最後にクリックしたマーカーなどのオブジェクトの緯度・経度(
all_drawings
last_active_drawing
-
bounds
- 表示している地図の左下(
_southWest
)・右上(_notrhEast
)の緯度・経度(lat
,lng
)
- 表示している地図の左下(
-
zoom
- 表示している地図のズームレベル。
0
~18
- 表示している地図のズームレベル。
last_circle_radius
last_circle_polygon
-
center
- 表示している地図の中心の緯度・経度(
lat
,lng
)
- 表示している地図の中心の緯度・経度(
# 地図を表示
st_data = st_folium(m, width=725)
# レスポンスの表示
with st.expander('st_data'):
st_data
レスポンス例
{
"last_clicked":{
"lat":35.76329879184181,
"lng":139.62910652160647
},
"last_object_clicked":{
"lat":35.7619914613808,
"lng":139.62983024004825
},
"all_drawings":[],
"last_active_drawing":NULL,
"bounds":{
"_southWest":{
"lat":-89.60474447014906,
"lng":-361.40625000000006
},
"_northEast":{
"lat":89.9988518664863,
"lng":658.1250000000001
},
},
"zoom":0,
"last_circle_radius":NULL,
"last_circle_polygon":NULL,
"center":{
"lat":83.86761625146907,
"lng":148.71093750000003
},
}
5.3. 表示する地図の種類を変更
デフォルトではOpenStreetMap
が使用される。
国土地理院地図を使用する場合は
国土地理院 HPを参考にする。
# 単色地図を使用する場合の例
# attr(アトリビュート)は自分で適当な物を決定する。
# 指定したアトリビュートは右下に表示される。
m = folium.Map(
tiles='https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
attr='都道府県庁所在地、人口、面積(2016年)',
location=[35.7619914613808, 139.62983024004825],
zoom_start=16
)
5.4. マーカーのカスタマイズ
デフォルトのマーカーは青色だが、パラメータを設定することでカスタマイズが可能
folium.Maeker(
location=[35.7619914613808, 139.62983024004825],
popup='Location name (popup)',
tooltip='Location name (hover)',
icon=folium.Icon(color='red', icon='info-sign')
).add_to(m)
使用できる色の一覧
['red', 'blue', 'green', 'purple', 'orange', 'darkred', 'lightred', 'beige',
'darkblue', 'darkgreen', 'cadetblue', 'darkpurple', 'white', 'pink',
'lightblue', 'lightgreen', 'gray', 'black', 'lightgray']
使用できるアイコンの参考
https://glyphsearch.com/?library=glyphicons
5.5. 円形のマーカーを描画
指定した座標を中心に円形のマーカーを設置できる。
使用できる色はマーカーで使える物と同じ。(デフォルトは薄い青色)
半径(radius
)はメートル単位(デフォルトは 50)
fill
を有効にすることで、円の内側を薄い指定色で塗ることができる。
folium.Circle(
location=[35.7619914613808, 139.62983024004825],
popup='Location name (popup)',
tooltip='Location name (hover)',
color='green',
radius=100,
fill=True
).add_to(m)
5.6. ユーザ操作によるオブジェクトの描画
プラグイン(例:Draw
)を使用することで、ユーザによるオブジェクト描画ができる。
描画されたオブジェクトは地図データのall_drawings
に反映される。
import folium
import streamlit as st
from folium.plugins import Draw
from streamlit_folium import st_folium
m = folium.Map(location=[39.949610, -75.150282], zoom_start=5)
# 描画プラグインを有効化
Draw(export=True).add_to(m)
# 地図データの描画
st_data = st_folium(m, width=700, height=500)
# 現在の状態を出力
st.write(st_data)
5.7. 描画したデータの取得と再描画
描画したオブジェクトの情報は、以下の様な形式でマップデータのall_drawings
に記述される。
[{'type': 'Feature', 'properties': {}, 'geometry': {'type': 'LineString', 'coordinates': [[-81.615731, 39.800079], [-83.059331, 38.49326]]}}]
このデータを再描画する場合は、次の通り行う。
# 複数のデータが存在する場合は for 分で取得、1件ずつ追加
gjsons = [{'type': 'Feature', 'properties': {}, 'geometry': {'type': 'LineString', 'coordinates': [[-80.288169, 40.479801], [-81.734443, 39.076687]]}},{'type': 'Feature', 'properties': {}, 'geometry': {'type': 'Point', 'coordinates': [-82.479982, 40.084226]}}, {'type': 'Feature', 'properties': {}, 'geometry': {'type': 'Point', 'coordinates': [-83.100586, 39.639538]}}]
for gjson in gjsons:
folium.GeoJson(gjson).add_to(m)
# データの追加は地図の描画前に行う必要がある。
st_data = st_folium(m, width=700, height=500)
# 現在のレスポンスデータ。事前に追加したオブジェクトは、`all_drawings`に含まれない。
6. レイアウトに関する記述式
6.1. ページコンフィグ
st.set_page_config
を使用することで、ページの設定を行える。
-
page_title
- ブラウザのタブに表示されるページのタイトル
- 未設定の場合はファイル名が使用される。
-
layout
-
centered
: デフォルト値。中央に固定幅の列を適用 -
wide
: 画面全体を使用する。
-
-
initial_sidebar_state
-
auto
: デフォルト値。モバイルサイズではサイドバー非表示、それ以外は表示 -
expanded
: 常にサイドバーを展開して開始 -
collapsed
: 常にサイドバーを表示で開始
-
-
menu_items
- 右上に表示するメニューアイテムの一部を編集する。
Get Help
Report a bug
About
- 右上に表示するメニューアイテムの一部を編集する。
st.set_page_config(
page_title="Ex-stream-ly Cool App",
page_icon="🧊",
layout="wide",
initial_sidebar_state="expanded",
menu_items={
'Get Help': 'https://www.extremelycoolapp.com/help',
'Report a bug': "https://www.extremelycoolapp.com/bug",
'About': "# This is a header. This is an *extremely* cool app!"
'Test': "Test"
}
)
6.2. カラム
画面を縦に分割する。
引数での指定が無い場合、分割は基本的に等分となる。
import streamlit as st:
# 等分で分割
col1, col2 = st.columns(2)
# 左右を 2:1 で分割、分割の幅を大きく
col1, col2 = st.columns([2, 1], gap='large')
with col1:
st.title('column 1')
with col2:
st.title('column 2')
6.3. サイドバー
左側にサイドバーを表示させる。
サイドバーは自動生成されるボタンで表示・非表示を操作できる。
また、マルチページ実装時は、自動的にサイドバーが生成・表示される。
import streamlit as st
st.sidebar.title('Control panel')
st.sidebar.button('Reload button')
6.4. マルチページ
複数の.py
ファイルそれぞれを独立したページとみなす。
ページごとに関数を作製する必要が無いため便利だが、
柔軟性は若干低下する。
マルチページの実装には、ファイル構造を次の通りとする。
app_dir/
├ home.py
└ pages/
├ 01_users.py
├ 02_groups.py
├ 03_repositories.py
└ 04_:smile:settings.py
それぞれのページへのリンクは、自動生成されたサイドバーに表示される。
各ページは独立しているため、共通して使用する関数などはimport
が必要
ファイルの先頭に番号を付与することでページの並び順を制御できる。
この際、番号はサイドバーに表示されない。
また、先頭に絵文字コードを記述することで、絵文字を項目名に付与できる。
7. 処理に関する記述式
7.1. 処理の停止
何らかのエラーを検知したり、特定の条件を満たしていない場合等、
それ以上先の処理進めたくない場合は、st.stop()
を使用する。
この関数が実行されると、その先の処理は実行されなくなる。
import streamlit as st
text = st.text_input('Input any text.')
# テキスト入力が無い場合、この先の処理を実行させない。
if not text:
st.warning('Please input any text.')
st.stop()
# テキスト入力が無い場合に実行されない処理
st.text(f'Inputed text: {text}')
7.2. セッションデータの保持
現在のタブ単位でセッションデータを保持し、
ページ遷移や再読み込みがあった際も、データを参照することができる。
import streamlit as st
# セッションデータ自体が存在しない場合、新たに作成して値を初期化
# st.session_state['flag'] = False でも同様の処理が可能
if 'flag' not in st.session_state:
st.session_state.flag = False
flgButton = st.button('Switch')
# ボタンが押された場合、セッションデータを更新
if flgButton == True and st.session_state.flag == False:
st.session_state.flag = True
elif flgButton == True and st.session_state.flag == True:
st.session_state.flag = True
# セッションデータによる条件分岐
if st.sessioin_state.flag == True:
st.text('Flag: True')
else:
st.text('Flag: False')
7.3. キャッシュ機構
関数の処理結果が同じである場合、関数を再実行する事無く、
キャッシュしてあるデータを利用するようにする。
処理が複雑かつ時間がかかる場合のパフォーマンス低下を防止できる。
キャッシュはメモリ上に保管されるため、頻繁にキャッシュが走る場合、
メモリの枯渇に繋がる可能性がある。
なお、グラフ・チャート表示用関数を含む関数をキャッシュすると、
警告が表示される他、予期せぬ動作に繋がるため、注意が必要
import streamlit as st
import pandas as pd
import numpy as np
# 関数の出力をキャッシュする
@st.cache
def cached_data():
data = {
'x': np.random.random(20),
'y': np.random.random(20),
}
df = pd.DataFrame(data)
return df
def main():
# リロードしても同じ結果が得られる
df = cached_data()
st.dataframe(df)
8. その他
8.1. プログレスバー
左から右に伸びるバーで処理の進捗を可視化する。
バーの最小値は0
、最大値は100
となる。
from time import sleep
import streamlit as st
def main():
# 進捗表示用のプレースホルダー
status_text = st.empty()
# プログレスバー
progress_bar = st.progress(0)
for i in range(100):
# 進捗表示の文字列を更新
status_text.text(f'Progress: {i}%')
# for ループ内でプログレスバーの状態を更新する
progress_bar.progress(i + 1)
sleep(0.1)
status_text.text('Done!')
st.balloons()
if __name__ == '__main__':
main()
8.2. 画面に風船を飛ばす
画面の下から上に、カラフルな風船が複数飛ぶ。
何かの処理に成功した場合などに。
st.balloons()
8.3. 画面に雪の結晶を降らせる
画面の上から下に、複数の雪の結晶を降らせる。
結晶のサイズは、割と大きい。
st.snow()
8.4. 適当なデータセットが欲しい場合の参考
- LinkData.org
- http://linkdata.org/
- 国内のオープンソースデータセット
- 個人が作成・公開しているので種類が多種多様
- 市区町村の公的組織が作成・アップロードしているデータも含まれる。
- 総務省統計局
- https://stat.go.jp/data/
- 人口推計や消費者物価指数など、公的に公開されるデータセット
- Our World in Data
- https://ourworldindata.org/
- Co2 排出量の国別比較や人口推移予測など、世界の統計情報
- 各国の公開する情報をベースにしているため、元のデータの信憑性に結果が左右される。