NTTドコモR&Dの技術ブログです。

StreamlitでコードとUIをスッキリさせるためのノウハウをまとめてみた

1. はじめに

こんにちは、ドコモ・テクノロジの小泉です。「ドコモ・テクノロジ」はNTTドコモの機能分担子会社の一つであり、主にNTTドコモのR&D業務を分担しています。その中で、私は主にドコモにおけるデータ活用促進に関わる内製開発を行っています。

本記事では、プログラミング言語PythonにおけるWebアプリ作成フレームワークの一つであるStreamlitに関して紹介していきたいと思います。Streamlitは、Pythonを用いて手軽にWebアプリを作成できるフレームワークです。ブラウザ上に簡単にインタフェース(UI)を表示できることから、データ活用を中心として最近利用が増加しています。

実際、私もデータ活用の現場でStreamlitを使うことが増えてきていて、使っていくうちにいくつかコツがあることがわかってきました。本記事にて、ぜひみなさんにそれを共有できれば嬉しいです。なお、本記事はPythonに関するプログラミングの知識を持っていて、StreamlitでのWebアプリ作成に興味がある人を対象としています。

2. Streamlitとは

Streamlitは、データ活用でよく利用されているお手軽なPython用Webアプリ作成フレームワークです。Streamlitを使うことで、Pythonでのデータ分析の過程や結果を簡単にブラウザ上にUIとして表示することができます。

streamlit.io

データ分析の業務で多いのは、Pythonを使ったデータの加工や分析です。しかし、分析結果を説明する際に、デザインよりもスピード重視でお手軽にUIを作成したいというニーズがあります。Pythonを使って簡単にWebアプリを作成することができれば、このニーズを満たすことができますが、Streamlitはそのようなニーズにぴったり当てはまるフレームワークです。

一般的に、Webアプリ作成にはプログラミング言語のスキルに加えて、Web系の知識(HTML/CSSやJavaScript)やスキル、フロントエンドの開発経験が無いと難しいのが実態です。しかし、Streamlitでは通常のPythonのコーディングから大きく変わることなく、Webアプリを作成できる強みを持っています。

ただ、ユーザにとってわかりやすいWebアプリを作る場合、Streamlitが持つ特徴を踏まえた上で開発者が少し工夫をしてコーディングする必要があります。本記事では、Streamlitの紹介とともにこの点に関するノウハウをご紹介していきます。

3. Webアプリの起動方法

初めに、Webアプリの起動までを簡単に紹介します。

PythonとStreamlitのインストールについては、巷にたくさんの優良な記事がありますので説明を省略します。

インストールができていれば、早速Webアプリを起動してブラウザに表示させてみましょう。何かしらのテキストエディタを起動し、次のセクションに載せた『1_hello_world.py』のコードをコピー&ペーストして同じファイル名で保存します。このとき、保存先のフォルダを覚えておいてください。特にこだわりが無ければ、自分のユーザフォルダ直下に『Streamlit』というフォルダを新規作成してそこにpyファイルを保存すればOKです。

次に、『ターミナル』上で、pyファイルを保存したフォルダに移動して『streamlit run 1_hello_world.py』とタイピングしてEnterキーを押せば、Webアプリを起動することができます。コマンドの実行例は以下の通りです。『{あなたのユーザ名}』のところは適宜変更してください。

cd /Users/{あなたのユーザ名}/Streamlit
streamlit run 1_hello_world.py

4. ハローワールド

手始めに、プログラミング学習の最初によく実行するHello Worldについて、私なりのサンプルコードを紹介します。下記のコードと出力されるUIを見比べてみてください。Streamlitでは、Webアプリの知識を使わず関数1つでラジオボタンやテキストボックス等のさまざまなUIを表示させるとともに、変数1つでユーザからの入力を受け取れたりすることがわかると思います。

コード❶「ハローワールド」1_hello_world.py

import streamlit as st
import datetime

st.title('❶Hello Worldアプリ')

_name = st.text_input('あなたの名前は?', '小泉')

if st.button('このボタンを押すと挨拶を返してくれます'):
    if len(_name):
        _hour = datetime.datetime.now().hour
        if 4 <= _hour < 12:
            f'**おはようございます、{_name}さん**'
        elif 12 <= _hour < 18:
            f'**こんにちは、{_name}さん**'
        else:
            f'**こんばんは、{_name}さん**'

st.divider()

fruit = st.radio('どちらのほうがお好きですか?', ['みかん', 'りんご'])
if st.button('決めました'):
    f'**そうですか、『{fruit}』って美味しいですものね。**'

アプリ❶「1_hello_world.py」の実行画面

解説❶「ハローワールド」

  • import streamlit as stでStreamlitをstという別名でインポートします。
  • st.title()でタイトルを描画します。
  • まず挨拶を返してくれる部分です。
    • _name = ...以降がWebアプリのUIと動作を記述した部分です。
    • st.text_input(...)でテキスト入力フィールドが描画されます。_nameでその入力を受け取っています。
    • st.button(...)でボタンが描画されます。このボタンが押されると、このst.button(...)の値がTrueとなり、if文配下の処理が実行されます。
    • 以降のif/elif/else文で、時間帯に応じた挨拶を返します。
  • st.divider()で、水平方向に区切り線を描画します。
  • 次に好きなフルーツに関する会話を模擬した部分です。
    • st.radio(...)で選択肢の中から1つを選択してもらうラジオボタンを描画します。
    • ラジオボタンで受け取った選択をfruitに格納し、「決めました」のボタンが押されるとUIに応答を返すようになっています。

なお、サンプルコードでは唐突に文字列の記載がありますが、Streamlitでは、特に何も関数を指定せずに、変数やテキストを記述すると、それがそのままUIに出力される仕組みとなっているため、このように記載しています。 

次のセクションからいよいよ本題に入ります。まず、今後の説明の前提となるStreamlitの特徴を紹介し、その特徴とうまく付き合うためのst.session_stateを紹介します。 その後、Streamlitの挙動を踏まえた上で、ユーザにとって使いやすく、かつ、開発者にとって開発しやすい、複数ステップの処理を実行するWebアプリを作るためのノウハウを紹介します。

5. Streamlitの特徴について

一般にWebアプリでは、ウィジェットと呼ばれるコンポーネント(UIの部品)により、ユーザからの入力を受け取り、その入力に基づいてサーバで処理を行った結果をUIに返すのが基本になります。 Streamlitではウィジェットに何かしらの入力があった場合に、コードの先頭から自動で再実行されるため、この挙動を認識した上でコーディングする必要があります。

ここがStreamlitの特徴であり、通常のデータ処理・分析用のPythonコードにかなり近いかたちでWebアプリ化ができるというメリットに繋がっています。開発者は、通常の分析用のコードにユーザからの入力を求めるウィジェットや、結果を出力するコンポーネントを配置するためのコードを数行追加すれば、シンプルなWebアプリをスピーディに作ることができます。しかし、アプリの機能として、ウィジェットからの入力する情報が増えてきたり、複数のステップを踏んで処理を進める場合においては、逆にこの『毎回コード先頭からの再実行』という挙動が、Webアプリの動作を複雑にしてしまうことがあります。開発者は、この問題にうまく対処しながらコーディングを進めていくことになりますが、私が対処の過程で得たノウハウを以降で紹介していきます。

6. st.session_stateについて

先述の通り、Streamlitでは、Webアプリ上のウィジェットをユーザが操作すると、その度にコードが先頭から再実行されます。そのため、ユーザによるウィジェット操作が多いWebアプリの場合、忙しく先頭からのコードの実行が繰り返されます。 再実行の際に、前回の実行で用意した変数やオブジェクトは基本的に全てリセットされてしまいます。そのため、Streamlitには再実行の際にも変数やオブジェクトがリセットされないように、それらを保存できる辞書型のオブジェクトsession_stateが用意されています。 このオブジェクトを本記事では「状態変数」と呼ぶことにします。 想定外の挙動を防ぐため、コード全体にわたって使う変数やオブジェクトはなるべく状態変数に入れておくのが安全です。 ここで、上記の内容を示したサンプルコードを紹介します。

コード❷「カウントアップ」2_countup.py

import streamlit as st

ss = st.session_state   # 文字数が多いので状態変数の辞書の別名を定義

st.title('❷カウントアップするアプリ')
st.subheader('状態変数の役割')

tab = st.tabs(['誤ったコード', '正しいコード'])

with tab[0]:
    count = 0                               # 初期化
    if st.button('カウントアップ', key='k0'):
        count += 1                          # カウントアップ
    if count > 0:
        st.warning(f'あなたは{count}回クリックしました。')

with tab[1]:
    if 'count' not in ss:           # 初期化
        ss.count = 0                # 初期化
    if st.button('カウントアップ', key='k1'):
        ss.count += 1               # カウントアップ
    if ss.count > 0:
        st.warning(f"あなたは{ss.count}回クリックしました。")

アプリ❷「2_countup.py」の実行画面

解説❷「カウントアップ」

前回までの実行結果を覚えておく、という状態変数の役割を表したサンプルコードです。

  • with tab[0]:配下のコードが、状態変数を使わない誤ったコードです。
  • with tab[1]:配下のコードが、状態変数を適切に使った正しいコードです。
  • 誤ったコードでは通常の変数countを使って、正しいコードでは状態変数ss.countを使って、それぞれカウントアップを実現しようとしています。
  • 実際に試していただくと分かりますが、前者ではcountの値は1のままです。先述の通り、毎回先頭からコードが実行されるため、count = 0が実行されてしまうためです。そこで、後者のように状態変数を使うことで前回の実行結果を保存します。
  • st.subheader()は、st.title()よりも控えめな大きさでサブタイトルに相当する文言を表示したい場合に使うことができます。
    • st.title()st.subheader()の間にst.header()というのもあります。
  • st.tabs()は、同じ位置に複数のUIを重ねて配置する『タブ』を複数配置したい場合に使用する関数で、引数にタブのタイトルにしたい文字列をlistで指定して使用します。
    • そして、左辺に置いた変数をwith tab[0]:with tab[1]:のようにwith構文で書くことで、各タブ内のコンテンツを記述することができます。
    • 上記の例では1つの変数tabで受け取ってインデックスで参照していますが、丸括弧の表記を省略したtupleで受け取ることもできます。
  • なお、状態変数を毎回st.session_stateと書くのは面倒なので、冒頭でss = st.session_stateと別名に置き換える操作(エイリアスの宣言)を行っています。これにより状態変数を以後ssの2文字で呼び出すことができます。

7. ボタンst.button()押下時の挙動を理解する

Streamlitでよく使われるウィジェットですが、意外と注意が必要なのがボタンst.button()です。st.button(...)はボタンのUIを表示しますが、ボタンが押されるまではFalseを返し、ボタンが押されたタイミングで返り値がTrueに変わります。そのため、基本的にはst.button()をif文と組み合わせてif st.button(…):とすることで、ボタンが押されたタイミングで目的の処理を実行することができます。下のコード❸のように、if st.button(...):としてその配下にボタンを押したときに実行する処理を記述するという書き方は非常にシンプルに記述できてわかりやすいというメリットがあります。しかし、これ以降で説明するとおり、コードの実行の流れを把握しておかないと後から困るケースがあります。最大のポイントは、ユーザがボタンを押した直後にif文配下の処理が実行されるわけではないということです。実は、ボタンを押すとコードが先頭から再実行され、if文に到達した時点でif文がTrueと評価され、そこで初めてif文配下の処理が実行されます。つまり、if文配下に記述した処理結果を、if文より前のコードに反映することはできません。ボタン押下によりUIを変更しようとした場合など、注意が必要です。

なお、st.rerun()という関数によりコードの先頭からの再実行を自ら指示することが可能です。そのため、if文の中にst.rerun()を記述することで、if文より前のコードに、if文の中の処理を反映することができます。 ただ、その結果としてif文より前のコードが2回繰り返して実行されることになるため、全体としておかしな挙動にならないようにコードの記載内容に注意が必要です。どのように繰り返し処理が行われるのか、サンプルコードでお見せします。

コード❸「ボタン押下後の処理の流れ」3_process_with_button.py

import streamlit as st

ss = st.session_state

if 'log' not in ss:     # 状態変数の初期化
    ss.log = ''         # 状態変数の初期化
st.title('❸ボタンを押した後の処理の流れ')
ss.log += '<先頭⇒'
if st.button('rerunあり'):
    ss.log += '処理1⇒rerun⇒'
    st.rerun()
ss.log += '中間⇒'
if st.button('rerunなし'):
    ss.log += '処理2⇒'
ss.log += '''末尾>  
'''
st.warning(ss.log)

アプリ❸「3_process_with_button.py」の実行画面

解説❸「ボタン押下後の処理の流れ」

ぜひ実際に実行してみてください。 2つのst.button(...)を押した場合にそれぞれどのようにコードが実行されたか、最後のワーニング出力st.warning(...)のところで確認できます。

  • 添付画像に関して以下解説します。
    • ワーニング出力の1行目は、アプリを起動した際に実行された処理の流れです。
    • ワーニング出力の2行目は、「rerunあり」ボタンを押した結果としての処理の流れです。先頭から処理が開始され、rerunありのif文配下に入った後、st.rerun()に到達して再度先頭に戻り、そこからif文配下のコードが実行されること無く処理を終えていることがわかります。
    • ワーニング出力の3行目は、「rerunなし」ボタンを押した結果としての処理の流れです。キレイに先頭から末尾まで処理が流れていることがわかります。

if st.button(...):st.rerun()を組み合わせたときに先頭からのコードの再実行が二度行われるのは、ボタンを押したことに依るものとst.rerun()の実行に依るもの、の2つのトリガ(契機)があるためであることがわかります。

8. on_clickとコールバック関数でst.button()をうまく使う

前のセクションで説明した挙動を、st.button()のオプション引数on_clickを用いることで、よりスマートな挙動にすることができます。オプション引数on_clickは、ボタンが押された直後に(=コードが先頭から再実行される前に)実行する処理を「コールバック関数」として指定するときに使います。すなわち、コールバック関数をボタンよりも手前(上)に記述し、ボタンの引数on_clickでその関数名を指定することで、ボタンを押すことによるコード先頭からの再実行にコールバック関数内の処理結果を反映できます。

if st.button(...):配下でst.rerun()を用いるケースと比較しながら具体的にサンプルコードで説明します。

コード❹「rerunとコールバックの比較」4_rerun_vs_callback.py

import streamlit as st
from time import sleep

ss = st.session_state

def countup():
    ss.view1 += 1
def reset():
    ss.view1 = 0
if 'view0' not in ss:
    ss.view0 = 0
    reset()

# アプリ本体=================================
st.title('❹rerunとコールバックの比較')
st.radio('', ['rerun', 'コールバック'], key='which', horizontal=True)

if ss.which == 'rerun':
    if ss.view0 == 0:
        with st.spinner('初期化中ですのでお待ちください...'):
            sleep(3)
        st.subheader('はじめに')
        st.warning('冒頭の操作説明文')
        if st.button('ステップ1へ'):
            ss.view0 = 1
            st.rerun()
    if ss.view0 == 1:
        st.subheader('ステップ1')
        st.info('さあ実行しましょう')
        if st.button('実行ボタン'):
            ss.view0 = 2
            st.rerun()
    if ss.view0 == 2:
        st.subheader('ステップ2')
        with st.spinner('処理を実行中です。お待ちください...'):
            sleep(3)
            st.success('完了!')
            if st.button('はじめからやり直す'):
                ss.view0 = 0
                st.rerun()
else:
    if ss.view1 == 0:
        with st.spinner('初期化中ですのでお待ちください...'):
            sleep(3)
        st.subheader('はじめに')
        st.warning('冒頭の操作説明文')
        st.button('ステップ1へ', on_click=countup)
    if ss.view1 == 1:
        st.subheader('ステップ1')
        st.info('さあ実行しましょう')
        st.button('実行ボタン', on_click=countup)
    if ss.view1 == 2:
        st.subheader('ステップ2')
        with st.spinner('処理を実行中です。お待ちください...'):
            sleep(3)
            st.success('完了!')
        st.button('はじめからやり直す', on_click=reset)

アプリ❹「4_rerun_vs_callback.py」の実行画面

解説❹「rerunとコールバックの比較」

このコードもぜひ実行してみてください。

  • 最初のst.radio(...)とif/else文を組み合わせて、st.rerun()を用いるケースとコールバック関数を用いるケースとを切り替えられるようにしています。
  • 各if/else文配下の処理は、はじめに⇒ステップ1⇒ステップ2 と順次処理を進める作りになっており、各ステップの処理内のボタンst.button(...)を押す度に先のステップへ進み、ステップ2でははじめに戻るボタンを置いています。
    • rerunの場合、『ステップ1へ』ボタンを押して『はじめに』から『ステップ1』に移行する際にも初期化の処理が実行され、ムダな待ち時間が発生してしまいます。さらに、『ステップ2』で『はじめからやり直す』ボタンを押した際にも再び「処理を実行中です。お待ちください...」のwith句が実行されてからはじめに戻るというおかしな挙動になってしまいます。
    • 一方、コールバック関数の場合は、想定通りボタン操作に応じてスムーズにUIを切り替えることができます。

この書き方により、ボタン押下をトリガとしてステップbyステップで処理を進めるようなWebアプリではUIの制御をよりシンプルにすることができます。具体例を次のセクションでお見せします。 なお、st.button()以外の他の入力UIのウィジェットの関数にも大抵コールバック用のオプション引数on_changeが用意されていますので、ウィジェットからの入力によるUIの制御では同様のロジックを利用できます。

『if文の条件式に直接st.button(...)を記述する』というStreamlitのWebアプリのコードでよく見かける作法は、ボタンを押したときに実行したい処理をシンプルに記述するという場面で功を奏しますが、本セクションで示したような複数ステップの処理を段階的に進めていくようなケースには不向きであることがわかります。また、用が済んだボタンはもうUIに表示しないという要件も満たすには、ボタンを押したことで実行する処理を状態制御のためのコールバック関数の呼び出しのみとするのがコードのシンプルさを維持しつつUIをシンプルかつ適切に制御する近道となります。

9. UIをst.session_stateで制御する

今までの紹介内容をもとに、複数のステップを踏んで目的を達成するような場面でオススメのWebアプリの作り方を紹介します。 Webアプリのステップを分割し、現在のステップを状態変数に保持することで、少ない状態変数でシンプルなUIを表現するという作り方です。

処理工程があまり単純ではない場合、複数のステップを踏まないと目的のデータを完成できないことはままあるかと思います。 これに対して何の工夫もせずにWebアプリに実装するとUIがビジーになってしまいユーザにとって使いづらくなることが想定されます。 また、開発者としても複数ステップを踏むStreamlitアプリを実装するのはそう簡単ではありません。

一度に表示するUIをシンプルにすることはユーザと開発者の双方にとってメリットがあると思いますので、 StreamlitでのWebアプリ作成の場合にぜひオススメしたいです。サンプルコードにて紹介します。

コード❺「ステップbyステップ」5_step_by_step.py

import streamlit as st

ss = st.session_state

# 状態変数==================================
if 'now' not in ss:     # 初期化
    ss.now = 0          # 初期化
def countup():          # コールバック関数(1/3):次へ
    ss.now += 1
def countdown():        # コールバック関数(2/3):戻る
    ss.now -= 1
def reset():            # コールバック関数(3/3):リセット
    ss.now = 0

# UIパーツ=================================
def buttons(now):
    col = st.columns(3)
    if ss.now < 3:
        col[0].button('次へ進む', on_click=countup)
    if ss.now > 0:
        col[1].button('前へ戻る', on_click=countdown)
    if ss.now > 1:
        col[2].button('はじめから', on_click=reset)

# アプリ本体=================================
col = st.columns([2,1])
col[0].title('❺ステップbyステップ')
if col[1].toggle('ステップbyステップ', True):
    st.write('### A) ステップ毎に表示')
    if   ss.now == 0:
        st.write('#### ステップ1')
        st.error('ここにステップ1の処理を記述します')
        buttons(ss.now)
    elif ss.now == 1:
        st.write('#### ステップ2')
        st.warning('ここにステップ2の処理を記述します')
        buttons(ss.now)
    elif ss.now == 2:
        st.write('#### ステップ3')
        st.info('ここにステップ3の処理を記述します')
        buttons(ss.now)
    else:
        st.write('### 完了!')
        st.success('全てのステップが完了しました')
        buttons(ss.now)
else:
    st.write('### B) 全てを常時表示')
    st.write('#### ステップ1')
    st.error('ここにステップ1の処理を記述します')
    st.write('#### ステップ2')
    st.warning('ここにステップ2の処理を記述します')
    st.write('#### ステップ3')
    st.info('ここにステップ3の処理を記述します')
    st.success('以上で全てのステップが完了です。')

アプリ❺「5_step_by_step.py」の実行画面

解説❺「ステップbyステップ」

このコードもぜひ実行してみてください。

  • if col[1].toggle(...):配下のst.write('### A)...)')から始まるコードにより、ステップ毎にWebアプリのUIが表示されます。
    • UI制御用の状態変数ss.nowを更新するコールバック関数とボタンのUI描画用の関数をはじめに定義しています。Webアプリ本体では、ステップごとにif文と処理内容、UI描画用の関数を記述しています。ステップごとにコードが読みやすくなっていますし、一度に表示されるUIが少ないので、ユーザにとってもわかりやすいと思います。
  • else:配下のst.write('### B)...)')から始まるコードにより、全てのステップのUIが表示されます。
    • 全ステップが一度に表示(画像右側)されているため、ステップ毎の表示(画像左側)と比べるとユーザが今どのステップにいるのか、次にどのUIを操作すればよいのか、が少しわかりづらい部分があります。

10. サンプルアプリ

ここまで紹介してきたノウハウを踏まえて作ってみたサンプルアプリを紹介します。 seabornというライブラリに収録されている20個のデータセットを選択し、さらにその中の列を選択し、そのデータをグラフやテーブルに出力するWebアプリとなっています。

以下の3ステップで構成され、ユーザにとって今何をすべきかが一目瞭然のアプリとなっているかと思いますがいかがでしょうか。

  1. ステップ1:Seabornのデータセットの選択
  2. ステップ2:ステップ1で選択したデータセットにおける列の選択
  3. ステップ3:ステップ1とステップ2での選択内容に応じたデータの可視化

また、各ステップでユーザがウィジェットで選択した状態を適切に状態変数に保持し、ボタンを押してステップ間を移動してもリセットされないようにも工夫しています。

コード❻「seabornの可視化」6_seaborn_visualize.py

import streamlit as st
import seaborn as sns

ss = st.session_state

# 定数====================================
DATA = [
    "anagrams", "anscombe", "attention", "car_crashes", "diamonds",
    "dots", "dowjones", "exercise", "fmri", "geyser",
    "glue", "healthexp", "iris", "mpg", "penguins",
    "planets", "seaice", "taxis", "tips", "titanic"]    # seabornのデータセット

# 状態変数==================================
if 'now' not in ss:     # 初期化
    ss.now = 0
    ss.rst = False
def countup(reset):     # コールバック関数(1/3):次へ
    ss.now += 1
    ss.rst = reset
def countdown():        # コールバック関数(2/3):戻る
    ss.now -= 1
def reset():            # コールバック関数(3/3):リセット
    ss.now = 0

# UIパーツ=================================
def buttons(now, _reset=False):
    col = st.columns([1,1,2])
    if now <= 1:
        col[0].button('次へ進む', on_click=countup, args=(_reset,))
    if now >= 1:
        col[1].button('前へ戻る', on_click=countdown)
    if now >= 2:
        col[2].button('はじめからやり直す', on_click=reset)

# アプリ本体=================================
st.title('❻seabornデータ')
if   ss.now == 0:
    st.write('### ステップ1: データの選択')
    st.info('Seabornに載っているデータセットです。可視化するものを選択してください。')
    if 'idx0' not in ss:                # 初期化
        ss.idx0 = 0
    ss.ds = st.radio('', DATA, ss.idx0, horizontal=True, label_visibility='collapsed')
    ss.idx0 = [i for i, e in enumerate(DATA) if e == ss.ds][0]  # 状態の記憶
    ss.df = sns.load_dataset(ss.ds)     # データセットを読み込む
    st.write('選択中のデータ:(行の数, 列の数) = ({}, {})'.format(*ss.df.shape))
    buttons(ss.now, True)
elif ss.now == 1:
    st.write('### ステップ2: 列の選択')
    st.info(f'『`{ss.ds}`』データにおいて可視化したい列を選択してください。')
    if 'flg1' not in ss or ss.rst:      # 初期化
        ss.flg1 = [False]*ss.df.shape[1]
        ss.rst = False
    for c, f in zip(ss.df.columns, ss.flg1):
        ss[c] = st.checkbox(c, f)
    ss.flg1 = [ss[c] for c in ss.df.columns]    # 状態の記憶
    ss.clmn = ss.df.columns[ss.flg1]
    buttons(ss.now)
elif ss.now == 2:
    st.write('### ステップ3: 可視化')
    msg = st.empty()
    if len(ss.clmn) == 0:
        msg.error('ステップ2で列が選択されていません。')
    else:
        msg.success('指定された条件で可視化しました。')
        col = st.columns([3,2])
        with col[0]:
            st.write('##### グラフ')
            try:
                st.line_chart(ss.df[ss.clmn])
            except:
                msg.error('グラフが表示できないので、列の組み合わせを見直してください。')
        with col[1]:
            st.write('##### テーブル')
            st.dataframe(ss.df[ss.clmn])
    buttons(ss.now)

アプリ❻「seaborn_visualize.py」の実行画面

解説❻「seabornの可視化」

  • seabornライブラリがインストールされていなければ『pip install seaborn』で予めインストールしてください。
  • 『定数』部分で、Seabornに収録されているデータセット一覧を書き出しています。
  • 『状態変数』部分で、アプリ全体を通して使っている状態変数を初期化したり、状態制御用のコールバック関数を定義しています。
    • ss.nowが現在いるステップを管理する状態変数で、UIの表示/隠蔽の制御を担当します。
    • ss.rstがウィジェットの状態をリセットするか否かの制御を担当する状態変数です。
  • 『UIパーツ』部分で、前後のステップへの移行やはじめに戻る機能をもつボタンを共通のUIとして定義しています。
    • 『次へ進む』ボタンには、受け取った引数に応じてウィジェットの状態をリセットする機能も持たせています。
  • 『アプリ本体』部分で、各ステップをif文に分けて表示するUIや処理を記述しています。
    • 各ステップ内部に閉じて用いる状態変数はこの中で初期化処理を記述しています。
      • idx0は選択しているデータセットを記憶するための状態変数名です。
      • flg1は選択している列を記憶するための状態変数名です。

11. さいごに

Streamlitを使ってWebアプリを作成する際のポイントを紹介しました。本記事を読んでくださった皆様がStreamlitによるWebアプリ作成を身近に感じていただき、ご自身でもWebアプリを作成してStreamlitの普及にお力添えいただけると大変嬉しいです。私も引き続きStreamlitを使った便利なWebアプリの作成に取り組んで、また新たなコツを見出せたら紹介していきたいと考えています!