layout0

Streamlit入門 – 5)レイアウト

本記事はStreamlit入門の5回目です。レイアウトについて説明します。Streamlitは簡単に使えるように設計されているため、見た目を細かく設定できるようにはなっていません。ざっくりとしたレイアウトを素早く構築する、そんなスタンスで進めてみてください。

本記事はFuture Coders独自教材からの抜粋です。

https://docs.streamlit.io/library/api-reference/layout

ここまでウィジェットなどを使って、単一ページ(レイアウト無指定)のアプリを作成してきました。

Streamlit入門 – 3)Widget – Future Coders (future-coders.net)

以下のような要素が用意されています。

  • sidebar = サイドバー
  • columns = カラム(複数列)
  • tabs = タブで表示する内容を切替
  • expander = 複数の要素を含む入れ物で開いたり、畳んだりする入れ物
  • container = 複数の要素を含む入れ物
  • empty = 単一の要素を含む入れ物
レイアウトサンプル
Streamlit入門 – 5)レイアウト 16

サイドバー

画面を分割してサイドバーを作成することができます。サイドバーに要素を配置するには、今までstモジュールに対して行っていた操作をst.sidebarに行うだけです。

import streamlit as st
st.title("main title")
st.sidebar.title("sidebar title")
サイドバー
Streamlit入門 – 5)レイアウト 17

with構文を使って、以下のようにサイドバーに要素を追加してゆくこともできます。

import streamlit as st
st.title("main title")
with st.sidebar:
    st.title("sidebar title")
    st.button("hello")
    st.text("hello world")
    st.divider()
    st.radio("fruits",["apple","orange","melon"])
Withを使ったサイドバー
Streamlit入門 – 5)レイアウト 18

カラム

カラムは列のことです。画面を縦方向に何列かに分割してレイアウトしてゆくことができます。以下は3列に分割した例です。

st.columnsの引数で列数を指定します。st.columnsの戻り値がそれぞれの列のオブジェクトです。with構文を使って、それらのオブジェクトにheaderとimageを書き込んでいます。

import streamlit as st

col1, col2, col3 = st.columns(3)

with col1:
   st.header("A cat")
   st.image("https://static.streamlit.io/examples/cat.jpg")

with col2:
   st.header("A dog")
   st.image("https://static.streamlit.io/examples/dog.jpg")

with col3:
   st.header("An owl")
   st.image("https://static.streamlit.io/examples/owl.jpg")
カラムサンプル
Streamlit入門 – 5)レイアウト 19

カラムの幅を変えたいときには、st.columnsの引数に幅の比率を示すリストを指定します。戻り値で得られたのがそれぞれの列のオブジェクトとなります。そこに書き込んでゆきます。

import streamlit as st
import numpy as np

col1, col2 = st.columns([3, 1])
data = np.random.randn(6, 1)

col1.subheader("A wide column with a chart")
col1.line_chart(data)

col2.subheader("A narrow column with the data")
col2.write(data)
幅指定コラム
Streamlit入門 – 5)レイアウト 20

タブ

見出しでコンテンツを切り替える部品です。

import streamlit as st

tab1, tab2, tab3 = st.tabs(["Cat", "Dog", "Owl"])

with tab1:
   st.header("A cat")
   st.image("https://static.streamlit.io/examples/cat.jpg", width=200)

with tab2:
   st.header("A dog")
   st.image("https://static.streamlit.io/examples/dog.jpg", width=200)

with tab3:
   st.header("An owl")
   st.image("https://static.streamlit.io/examples/owl.jpg", width=200)
タブ
Streamlit入門 – 5)レイアウト 21

エキスパンダー

クリックすることで閉じたり開いたりする部品です。

import streamlit as st

st.bar_chart({"data": [1, 5, 2, 6, 2, 1]})

with st.expander("See explanation"):
    st.write("""
        The chart above shows some numbers I picked for you.
        I rolled actual dice for these, so they're *guaranteed* to
        be random.
    """)
    st.image("https://static.streamlit.io/examples/dice.jpg")
エキスパンダー
Streamlit入門 – 5)レイアウト 22

コンテナー

タブやエキスパンダーを使うときには複数の要素を1つの入れ物にまとめたいときがあるかもしれません。そのようなときはcontainerを使用します。

import streamlit as st
import numpy as np

with st.container():
   st.write("This is inside the container")
   st.bar_chart(np.random.randn(50, 3))
   st.button("update")
   st.divider()

st.write("This is outside the container")
コンテナイメージ
Streamlit入門 – 5)レイアウト 23

ページ設定

デフォールトのページ設定が可能です。
以下のような引数が用意されています。

  • page_title = ページのタイトル
  • page_icon = favicon、絵文字も利用可能
  • layout = “centered”か”wide”
  • menu_items = 画面右上のハンバーガーメニューの項目と内容(リンクやマークダウン)
import streamlit as st

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!"
    }
)
ページ設定
Streamlit入門 – 5)レイアウト 24

演習

ex-layout1.py

サイドバーに3つの項目を配置し、選択状態が変わると右側の写真が切り替わるページを作成してください。

演習1
Streamlit入門 – 5)レイアウト 25

ex-layout2.py

ボタンを横にならべて、当たりのボタンを押したら風船を描画してください。横一列に並べるのにst.columnsを使用します。当たりはボタンを押下する度に変化してもよいものとします。

演習2
Streamlit入門 – 5)レイアウト 26

ex-layout3.py

ボタンを押してはずれたら、その旨を下に表示してください。押したかどうか状態を覚えておくためにst.session_stateを使用します。ページの再読み込みで再ゲームとします。

演習3
Streamlit入門 – 5)レイアウト 27

ex-layout4.py

タブを使って、iris, taxis, titanicと3つのデータフレームを切り替えて表示するページを作成してください。

演習4
Streamlit入門 – 5)レイアウト 28

ex-layout5.py

サイドバーに郵便番号を入力して、検索ボタンを押下するとその住所を表示するページを作成してください。

郵便番号の検索にはzipcloudのWebAPIを使用してください。

r = requests.get(f"https://zipcloud.ibsnet.co.jp/api/search?zipcode={zip}")
data = r.json()
演習5
Streamlit入門 – 5)レイアウト 29

解答例{.answer}

ex-layout1.py

import streamlit as st

pages = ["page1", "page2", "page3"]
page = st.sidebar.radio("image", pages)

if page == "page1":    
    st.header("A cat")
    st.image("https://static.streamlit.io/examples/cat.jpg")
elif page == "page2":
    st.header("A dog")
    st.image("https://static.streamlit.io/examples/dog.jpg")
else:
    st.header("An owl")
    st.image("https://static.streamlit.io/examples/owl.jpg")

ex-layout2.py

import streamlit as st
from random import randint

atari = randint(0,4)
cols = st.columns(5)
for i in range(5):
    with cols[i]:
        if st.button(f"button:{i}"):
            if i == atari:
                st.write(f"当たり!!")
                st.balloons()
            else:
                st.write(f"当たりは{atari}でした")

ex-layout3.py

import streamlit as st
from random import randint

if "atari" not in st.session_state:
    st.session_state.atari = randint(0,4)
    st.session_state.open = [False for _ in range(5)]

cols = st.columns(5)
for i in range(5):
    with cols[i]:
        if st.button(f"button:{i}"):
            if i == st.session_state.atari:
                st.write(f"当たり!!")
                st.balloons()
            else:
                st.session_state.open[i] = True

cols = st.columns(5)
for i in range(5):
    with cols[i]:
        if st.session_state.open[i]:
            st.write("はずれ")

ex-layout4.py

import streamlit as st
import seaborn as sns
iris, taxis, titanic = st.tabs(["iris", "taxis", "titanic"])
with iris:
    df = sns.load_dataset("iris")
    st.dataframe(df)
with taxis:
    df = sns.load_dataset("taxis")
    st.dataframe(df)
with titanic:
    df = sns.load_dataset("titanic")
    st.dataframe(df)

ex-layout5.py

import streamlit as st
import requests

zip = st.sidebar.text_input("郵便番号")
if st.sidebar.button("検索"):
    r = requests.get(f"https://zipcloud.ibsnet.co.jp/api/search?zipcode={zip}")
    data = r.json()
    addr = data["results"][0]["address1"] \
        + data["results"][0]["address2"] \
        + data["results"][0]["address3"] 
    st.write(addr)