Streamlit を使ってデータ可視化ツールを作る

# 本日は

- [Streamlit](https://www.streamlit.io/) という Python で記述できるアプリケーションフレームワークを紹介します。Webブラウザを用います。Flask, Django, Dash, Plotly のように Web アプリケーションフレームワークがありますが、JavaScript, HTMLやWebアプリケーションを動かす仕組みなどPython以外の知識を暗黙的に要請します。これはプログラマーによりウェブアプリケーションそのものをカスタマイズできる余地を与えます。一方で研究段階のデータサイエンス、機械学習の領域で要請される機能はデータをグラフで可視化、または画像を表示し必要に応じてスライダー、チェックボックスなどの簡易的なUIを持っていれば十分であったりします。これらの機能は素朴ですが（高級な機能なゆえ）その仕組みを作る（実装する）には工数が必要です。例えば、ボタン、セレクトボックスを変更したらその変更を通知し、通知を受け取ったあとのロジックの記述や、スライダーの値を変動しそれを以ってパラメータの変動を表現するという処理は単にデータをプロットするという処理よりも多くのコーディングを要請します。

- Jupyter Notebook/Lab で動作する ipywidgets では interact 関数を提供しておりユーザーが定義した関数と引数の定義範囲を指定するラッパー関数をつることでパラメータを変化させその結果を表示する機能を持たせることができます。これは多くても２、３行でできますのでとても手軽です。ただし実行できる環境が Jupyter Notebook の上でという制約があります。

- マークダウンで文章を書いたり, Python のコードを記述し, 画像を読み込んでそれをPILまたは Matplotllib で表示をするということに慣れていればすぐに入門できるはずです。

> Streamlit.
> The fastest way to
> build data apps

これは Streamlit のトップページ https://www.streamlit.io/ にあるキャッチコピーです。その通り最速でアプリケーションを構築できます。

# どういったものができるか

- 例えば分類問題を解決するタスクを任されたとします。学習用に渡されたデーターはどういうものが入ってるのかそのラベルがアサインされた画像はどういうものがあるのかは確認しておいた方がいいでしょう。下記のgifはそのために作ったものです。
- このデータセットは TensorFlow/Keras のチュートリアル https://www.tensorflow.org/tutorials/load_data/images でも紹介されています。
  - 余談ですがいきなりチュートリアルを読むより guide を読んでから tutorial をよむとTFを用いた学習パイプライン構築の方法が理解しやすかったです。

![goma.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/94670fb7-ac76-627c-b731-60618a7b0d26.gif)

# 準備

streamlit をインストールします。ここでは 0.61.0 が入ります。花のデータセットをロードするために TensorFlow を使います。ここでは 2.2.0 が入ります。

```
$ pip install streamlit　
$ pip install tensorflow
```

# 実装

- コード `app.py` を書きます。上のgifのようなアプリを構成するのに50行以内で実現できました。

```python:app.py

import pathlib
from typing import List

from PIL import Image
import streamlit as st
from tensorflow import keras


def download() -> str:
    data_dir = keras.utils.get_file(
        origin="https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz",
        fname="flower_photos",
        untar=True,
    )
    return data_dir


class FlowerDataset:
    def __init__(self):
        self.data_dir = pathlib.Path(download())
        self.labels = ["sunflowers", "daisy", "roses", "tulips", "dandelion"]

    def select(self, label: str) -> List[pathlib.Path]:
        return list(self.data_dir.glob(f"{label}/*"))


def main():
    st.markdown("# Data visualization tool using Streamlit")
    dataset = FlowerDataset()
    selector = st.sidebar.selectbox("Select your favorite flower", dataset.labels)
    selected_data = dataset.select(selector)
    index = st.sidebar.number_input(
        f"Select index from 0 to {len(selected_data)}",
        min_value=0,
        max_value=len(selected_data),
        value=0,
        step=1,
    )
    sample_path = selected_data[index]
    image = Image.open(sample_path)
    expand = st.sidebar.checkbox("Expand")
    degree = st.sidebar.slider("Degree", min_value=0, max_value=180, step=1)
    st.image(image.rotate(degree, expand=expand))


if __name__ == "__main__":
    main()
```

# 動作方法

`pip install streamlit` で導入すれば `streamlit` コマンドが使えるようになります。

```console
$ streamlit run app.py
```

これを実行するとブラウザが自動的に立ち上がり画面の横側にセレクトボックスやスライダーが見えるようになります。

## gist のコードを実行もできる

上記のコードは gist にて公開しています:
  - https://gist.github.com/terasakisatoshi/a26da05fa8077e9cda816e35ba0a12e1

この場合 app.py のファイルを指す場所を指定すれば手元にファイルを落とす手間を省くこともできます。

```console
$ streamlit run https://gist.githubusercontent.com/terasakisatoshi/a26da05fa8077e9cda816e35ba0a12e1/raw/41541b7a1c842fe484f44ca8be2cd4fea00e7f94/app.py
```

## コードの中身について

- `main()` 関数の中身を見ると `st.markdown` や `st.sidebar.checkbox` のような物が見えますね。これがUIの部品（widget）を作ります。コードが実行された順に画面の上から順に配置されていきます。サイドバー（要するに横にある領域）に配置させたい場合は `st.sidebar.<widgetname>` のように `sidebar` を経由してコードを書きます。

## 開発のワークフロー

- 実際に触るとわかりますが `streamlit run app.py` を実行後, `app.py` のファイルを変更するとStreamlit側でその変更を検知しアプリが再実行されます。この機能はとても便利ですね。わざわざ streamlit run ... をやり直す必要がないので。これを利用すればスクラッチでアプリを書くとき `import streamlit as st` と一行だけ書いておいてから streamlit run をしてファイルに随時記述してライブアップデートをすれば気付いたらそれなりのものができているということができます。

## UIを操作したあとの動作の処理はどう実現するの？

- コードは `value=st.<widgetname>` の形式になっていることに注意します。左辺はセレクトボックスやスライダーが保持している値を格納します。Streamlit は widget の持つ値が変わるとソースコードを再評価します。変更した値は `value` に入っているので変更後の値によってロジックが走ります。これにより「UIの操作により対応するアクションを実行するという」ことを実現しています。もちろん毎回再評価をすると処理が重い関数を複数回実行する悪夢が想定されますが、重たい関数を `@ts.cache` でデコレートすることで解決することができます。上の例ではキャッシュをしなくても重くなかったので省いています。

- ということでユーザーは「UIの配置はざっくり上からこの順に並んで欲しいという」意思とデータを処理実行するロジックのスキルを持っていれば良いことになります。ここでは`st.image` で画像を表示するウィジェットを扱いましたが `st.pyplot(figure)` のように Matplotlib の figure オブジェクトを投げればグラフが表示されるような実装もできます。つまりパラメーターを持っている関数の描画はスライダーと Matplotlib で描画するコードを書けば対話的なグラフ生成をするアプリを作ることができます。

# まとめ

- Streamlit で可視化ツールを作りました。今まで ipywidget で作っていましたが。
Streamlit でDemoを作るというようなことをできそうですね。
- 作っているときは「なんて便利なんだ！」という感動の連続でした。ロジック書くのはできるけれど UI 作成は重荷と感じていれば Streamlit を試す価値はあると思います。