2024年10月: Streamlitの基本機能の紹介

寺田 学@terapyonです。2024 年 10 月の「Python Monthly Topics」は、Python Web UIフレームワークの一つである Streamlit の基本的な使い方を紹介します。

2024年4月には、 Python Web UIフレームワークで作るデスクトップアプリ と題し、Steamlitを使ってデスクトップアプリ化をする紹介を行いました。

今回は、Streamlitにフォーカスを当てて、よく使う機能を紹介します。 Streamlitには、たくさんの機能があり、 公式ドキュメント APIリファレンス を見てもどの機能から使って良いのかわからないという声がありました。 今回は筆者目線でよく使うであろう機能に絞って紹介します。

Streamlitとは

StreamlitはPythonで構築できるWeb用のフレームワークです。 Pythonのモジュールを定義することでインタラクティブなWebアプリを、簡単に構築できるという特徴があります。

PythonのWebフレームワークには、DjangoやFlask、FastAPIなどがあります。これらはサーバサイドフレームワークやMVCフレームワークと呼ばれています。 StreamlitはDjangoやFlaskとは違い、Web UIに特化したフレームワークです。筆者は、「Web UIフレームワーク」と呼んでいます。 フレームワークの違いについては、先に紹介した、 2024年4月公開の Python Web UIフレームワークで作るデスクトップアプリ を参照してください。 また、Streamlit以外のWeb UIフレームワークの名前も紹介していますので参照してください。

Streamlitでは、細かなデザインやフロントエンドの処理を自由に表現することよりも、簡単にWebアプリを作ることにフォーカスしたフレームワークです。 特にデータ系の表現や、簡単に動的な動きを実現できることに魅力があります。

環境設定から起動方法

最初に、Streamlitの開発及び起動方法を紹介します。

開発環境

今回は、Python 3.12を用いて開発を行います。

執筆時点(2024年10月14日)において、Pythonは3.13がリリースされていますが、Streamlitが公式にサポートしているのは、Python 3.8から3.12までとなっています。

Pythonの仮想環境である、 venv を準備して、Streamlitをインストールします。 ここでは、macOSのターミナルで実行をしています。 venvは、WindowsやLinuxでも利用可能です。

venvの作成と有効化
% python3.12 -m venv venv
% source venv/bin/activate
(venv) % 

venvの準備と有効化が終わりましたので、Streamlitをインストールします。

Streamlitのインストール
(venv) % pip install streamlit

ここまでで、Streamlitの準備が終わりました。

Streamlitは、pandasなど多くの依存パッケージが一緒にインストールされます。

起動方法

Streamlitを起動するには、以下のように実行します。 その際にPythonモジュールを指定します。

Streamlitの起動
(venv) % streamlit run モジュールファイル

タイトルだけを表示するアプリ app.py を作ります。

app.py
import streamlit as st

st.title("サンプルアプリ")

app.pyモジュールを起動するには以下のように行います。

app.pyを起動
(venv) % streamlit run app.py

アプリを起動するとデフォルトブラウザが立ち上がり、以下のように表示されます。

app.pyを表示

サンプルアプリ - ランダム選択アプリ

ここから、「スペース区切りの文字列からランダムに一つの単語を選択する」というアプリを作っていきます。

アプリケーションの内容は以下のとおりです。

  • タイトル: 文字列から単語を選択するアプリ

  • 説明: 入力された文字列をスペース区切りで分割し、ボタンを押したら一つが選ばれる

  • 入力: 1行文字入力

  • 結果: 一つの単語

  • モジュール: choice_app.py

ランダム選択アプリのコード

Streamlitのコードは以下のとおりです。

choice_app.py
import random
import streamlit as st

st.title("文字列から単語を選択するアプリ")
# 入力ボックス
text = st.text_input("スペース区切りで単語を入力してください。")
# スペース区切りでリスト化
words = text.replace(" ", " ").split(" ")

# リストを表示
st.write("入力した単語: ", words)

# ボタンを設置し、ボタンが押されたら実行
if st.button("一つを選ぶ"):
    choice = random.choice(words)  # ランダムに1つだけを選択
    st.write("選択された単語: ", choice)  # 選択されたものを表示

以下、choice_app.pyのコードを説明します。

  • st.text_input()

    • 1行入力(textの入力)を定義し、入力されたものを変数 text に格納しています。

  • text.replace(" ", " ").split(" ")

    • Pythonの文字列メソッド replace() で全角スペースを半角に置き換えています。

    • その後、同じく文字列メソッド split() で半角スペースで文字列を分割してリストを作っています。

    • 上記の結果を変数 words に格納しています。

  • st.write()

    • アプリ上(ブラウザ上)に表示するための機能を使って、分割された文字列を表示しています。

    • st.write()は、与えられるデータに沿って、データの内容を表示してくれます。

    • st.write()の詳細は次項で説明します。

  • st.button()

    • クリック可能なボタンを設置しています。

    • if 文のあとに、ボタンを設置することで、ボタンが押されたときにifブロックが実行できるようにします。

  • random.choice()

    • Pythonのrandomモジュールの choice() を使って、リストの中から1つを選択しています。

ランダム選択アプリを実行した結果は以下のとおりです。

ランダム選択アプリの実行結果

今回は、「中華、和食、イタリアン、フレンチ」の中からランダムに選び、「和食」が選択されました。

st.write()

st.write() 機能は非常に便利な機能です。

文字列を渡せば文字列をそのまま出力してくれます。今回のサンプルコードでは、リストも st.write() に渡しています。リストの場合はリスト用の表示になっていることがわかったかと思います。

この st.write() には、さまざまなデータ型を渡すと、内部で表示形式が自動で選択され、適切な表示にしてくれます。 たとえば、pandasのDataFrameは表形式になりますし、Markdownを渡すとフォーマットが解釈されて自動で変換されて表示してくれます。 他にも、画像(PIL.image)やgraphvizなどもきれいに表示してくれる機能があります。

一方でStreamlitには、DataFrameを表示する st.dataframe() や 画像を表示する st.image() といった専用の機能が存在します。専用の機能には、表示する大きさなどの引数があります。専用の機能を用いることで、表示をより細かく制御することができます。

サンプルアプリ - サイコロを2つ振り結果表示アプリ

ここからはもう少し複雑なアプリを作っていきます。

アプリケーションの内容は以下のとおりです。

  • タイトル: サイコロを2つ振った結果を表示

  • 説明: サイコロ2個を何度か振って結果を表形式で表示し、棒グラフで結果を表示する

  • モジュール: dice_app.py

  • ステップ

    1. サイコロを模して2つの1-6の数値を生成

    2. 合計値を求める

    3. サイコロの値と合計値を表で表示

    4. 合計値の棒グラフを書く

    5. 「まとめてサイコロをたくさん振る」機能をスライダーウィジェットを使用して拡張してみます。

サイコロを2つ振り結果表示アプリの最初のコード

ここでは、最初のコードとして、上記のステップの3まで(合計値を表で表示)するコードを示します。

dice_app.py
import random
import streamlit as st
import pandas as pd

dices = []  # 結果を格納する空リスト

st.title("2つのサイコロを振るアプリ")

if st.button("サイコロを振る"):
    dice1 = random.randint(1, 6)  # 1-6までをランダムに生成
    dice2 = random.randint(1, 6)
    dice_sum = dice1 + dice2  # 2つのサイコロの値を合計
    dices.append((dice1, dice2, dice_sum))  # リストに結果を格納
    st.write(f"1つ目のサイコロ: {dice1} / 2つ目のサイコロ: {dice2}")

df = pd.DataFrame(dices, columns=["1つ目のサイコロ", "2つ目のサイコロ", "合計"])
st.dataframe(df)  # DataFrameを出力
st.write("試行回数: ", len(dices))

以下、dice_app.pyを説明します。

  • dices = []

    • サイコロを振った結果を格納する空リストを定義

  • random.randint(1, 6)

    • 1から6までの整数をランダムに生成

  • dices.append()

    • 各出目と合計値をタプルで保存

  • pd.DataFrame()

    • DataFrame化する。これは次の表示で表形式で表示するため

  • st.dataframe(df)

    • DataFrameを表形式で出力

    • データをCSV形式でダウンロードしたり、検索する機能がUI上に表示される

実行結果を以下に示します。

サイコロを2つ振り結果表示アプリの初期状態

このままででは狙い通りのアプリができていません。 実行して、何度か「サイコロを振る」ボタンをクリックするとわかりますが、試行回数が「1」のまま増えません。 表で表現しているDataFrame上も1件しか表示されません。

これは、Streamlitの特徴である、ボタンが押されたり入力値が変更され状態が変わったときに、モジュールのすべてが再実行されるという特徴のためです。 次の項で、解決策を示します。

サイコロを2つ振り結果表示アプリに状態保持機能を入れる

先程までのコードでは、ボタンを押すたびに、モジュールが再実行されます。 結果を保持しようとしていた dices = [] が毎回初期化されてしまい、アプリケーションが狙い通りの動きができませんでした。

毎回初期化されないようにする方法はいくつか存在します。

  • セッションにデータを保持する

  • 関数化してキャッシュ化する

  • コードの一部だけを再実行する

今回は、セッションにデータを保持する方法で解決します。

なお、関数化してキャッシュ化するには、 @st.cache_data@st.cache_resource を用います。 詳しくは、 公式ドキュメント キャッシュ を参照してください。

また、コードの一部を再実行する方法は、Streamlit 1.37.0 に入ったフラグメント機能を使います。 詳しくは、 公式ドキュメント フラグメント を参照してください。

セッションにデータを保持 するには、 st.session_state を使います。

セッションを使った、変更後のコードをみてみましょう。

import random
import streamlit as st
import pandas as pd


if "dices" not in st.session_state:  # セッションデータの初期化
    st.session_state.dices = []

st.title("2つのサイコロを振るアプリ")

if st.button("サイコロを振る"):
    dice1 = random.randint(1, 6)
    dice2 = random.randint(1, 6)
    dice_sum = dice1 + dice2
    st.session_state.dices.append((dice1, dice2, dice_sum))  # セッションにappend
    st.write(f"1つ目のサイコロ: {dice1} / 2つ目のサイコロ: {dice2}")

df = pd.DataFrame(st.session_state.dices, columns=["1つ目のサイコロ", "2つ目のサイコロ", "合計"])
st.dataframe(df)
st.write("試行回数: ", len(st.session_state.dices))

変更点は以下のとおりです。

  • "dices" not in st.session_state

    • st.session_state に、dicesが存在するかを確認

  • st.session_state.dices = []

    • st.session_state に、dicesが存在しないときは、空リストで初期化

  • st.session_state.dices.append()

    • st.session_state の dicesにデータを追記

  • DataFrame化、試行回数表示の変更

動作を確認してみます。

サイコロを2つ振り結果表示アプリにセッションを導入

ボタンが押されるたびにDataFrameの表が増え、試行回数も増えていることがわかりました。

サイコロを2つ振り結果表示アプリにグラフを表示

Streamlitには、グラフを表現する機能があります。 ここでは、Streamlitの標準機能を用いてグラフ化します。 グラフは、2つのサイコロの和が、ボタンが押されるごとにどのようになるかの回数を棒グラフで表現してみます。

グラフを表示するには、先程までのコードに以下の2行を追加するだけです。

if st.button("結果を表示"):
    st.bar_chart(df["合計"].value_counts())

ここでは、「結果を表示」ボタンをクリックするごとに、棒グラフを表示します。

  • st.bar_chart()

    • 棒グラフを表示

  • df["合計"].value_counts()

    • 定義済みDataFrameから、"合計" カラムを取得し、合計値が同じ回数をカウントしています。

以下は、30回サイコロを振った結果を表示しました。

30回サイコロを振った結果を棒グラフで表示

サイコロを2つ振り結果表示アプリに複数回のサイコロをまとめて振る

このアプリの完成形として、「まとめてサイコロをたくさん振る」機能をスライダーウィジェットを使用して拡張してみます。

import random
import streamlit as st
import pandas as pd


if "dices" not in st.session_state:
    st.session_state.dices = []

st.title("2つのサイコロを振るアプリ")

multiple = st.toggle("複数回振る", False)  # 複数回振るかのトグルスイッチ
if not multiple:
    if st.button("サイコロを振る"):
        dice1 = random.randint(1, 6)
        dice2 = random.randint(1, 6)
        dice_sum = dice1 + dice2
        st.session_state.dices.append((dice1, dice2, dice_sum))
        st.write(f"1つ目のサイコロ: {dice1} / 2つ目のサイコロ: {dice2}")
else:
    n = st.slider("回数", 1, 1000, 500)  # 回数をスライダーで入力
    if st.button("サイコロを振る"):
        for _ in range(n):
            dice1 = random.randint(1, 6)
            dice2 = random.randint(1, 6)
            dice_sum = dice1 + dice2
            st.session_state.dices.append((dice1, dice2, dice_sum))
        st.write(f"{n}回振りました。")
if st.session_state.dices and st.button("リセット"):  # 過去のデータを削除する機能
    st.session_state.dices = []


df = pd.DataFrame(st.session_state.dices, columns=["1つ目のサイコロ", "2つ目のサイコロ", "合計"])
st.dataframe(df)
st.write("試行回数: ", len(st.session_state.dices))

if st.button("結果を表示"):
    st.bar_chart(df["合計"].value_counts())

追加した機能を説明します。

  • st.toggle("複数回振る", False)

    • トグルスイッチを設置し、複数回振るモードかを設定

    • 第2引数はデフォルト値。 False とし1回ずつ振るモードがデフォルト。

  • st.slider("回数", 1, 1000, 500)

    • 数値を入力するスライダーを準備

    • 第2引数と第3引数で範囲を指定。1から1000回までが選択可能

    • 第4引数でデフォルト値を設定。500回がデフォルト。

  • st.session_state.dices and st.button("リセット")

    • 過去の振ったサイコロの記録を削除する機能を設置

    • セッションにデータがあった場合のみ「リセット」ボタンを表示

    • リセットをクリックすると、ifブロックが実行され、セッションを初期化

完成したアプリは以下のようになります。

サイコロを2つ振り結果表示アプリ完成-1 サイコロを2つ振り結果表示アプリ完成-1

その他のStreamlitの機能

ここでは、今回のアプリで使用しなかった機能の中から、Streamlitのその他の機能を紹介します。

入力

さまざまな入力ウィジェットが準備されています。 詳細は、 公式ドキュメント Widget を参照してください。

この中からいくつかを紹介します。

  • テキストエリア

    • st.text_area

    • 複数行の文字列を入力

  • セレクトボックス

    • st.selectbox

    • リストから一つを選択

  • マルチセレクト

    • st.multiselect

    • リストから複数を選択

  • ファイルアップロード

    • st.file_uploader

    • ファイルをアップロード

    • ドラッグ&ドロップに対応

  • 日付入力

    • st.date_input

    • カレンダーから日付を選択

  • ダウンロードボタン

    • st.download_button

    • ボタンをクリックすると、ファイルがダウンロード

出力

出力方法もさまざまなものが準備されています。

レイアウト

サイドバーやカラムの設定、タブ切り替えにも対応しています。

詳細は、 公式ドキュメント Layouts and Containers を参照してください。

筆者がよく使うのは2つです。

  • タブ化

    • st.tabs

    • 複数の機能をタブで切り替える

  • サイドバー

    • st.sidebar

    • 情報や補足的な設定をサイドバーに設置

デプロイ

Streamlitはサーバ系のアプリケーションです。 サーバにデプロイすることでURLを持ち、Webブラウザからインターネット経由で利用することができます。

今回は、Streamlitをホストするサービスを利用してデプロイします。

Streamlit Cloud

Streamlit公式のホスティング環境です。

Streamlit Cloudは以下の特徴があります。

  • 無料

  • 簡単にデプロイできる

  • 認証などの機能がない

Streamlit Cloudを利用するには、ログインアカウントを作り、 ダッシュボード にアクセスします。

ここでは、GitHubでアプリケーションを管理している前提で、アプリをデプロイする手順を紹介します。

  • ダッシュボードの右上の Create app をクリック

  • 「Do you already have an app?」と聞かれるので、「Yup, I have an app」を選択

  • GitHubのオーガニゼーションを選択し、認証を行います。

  • その後、以下の画面の通り入力します。

    • Repository: GitHubのレポジトリのURLをペーストします。

    • Branch: ここではmainを選びました。

    • Main file path: アプリのモジュールを選びます。

    • App URL (optional): 今回は「st-sample-gihyo-202410」としまいた。

  • Deplyボタンをクリック

Streamlit Cloudにデプロイ

デプロイされ、App URLに入力したURLが表示されます。

今回は、以下にサイコロを振るアプリをデプロイしました。 https://st-sample-gihyo-202410.streamlit.app/

サイコロを振るアプリがデプロイ

その他の選択肢

Streamlit Cloud以外にも、HuggingFace Spacesを活用する方法もあります。 HuggingFace SpacesはGPUの利用が可能な有料プランがあります。

筆者は何度かHuggingFace Spacesを利用してアプリをホストしています。 普段は無料プランでホスティングしておいて、利用が多くなるときやGPUが必要なタイミングだけ有料プランに変更するといった柔軟な運用ができることに魅力を感じています。

また、独自の環境へのデプロイには、サーバにPythonをセットアップし、Streamlitを起動するスクリプトを書いてホスティングする方法があります。 他にはDockerを使ってホスティングすることも可能です。

まとめ

今回は、PythonのWeb UIフレームワークの一つである、Streamlitを使い始めるための基本機能の紹介を行いました。

Webアプリ化すると、Pythonスクリプトで作っていたものをGUIから実行できるものにできます。 このような用途にもStreamlitは活用できると思います。

みなさんも、挑戦してみてください。