Streamlit入門＋応用 ～ データ分析Webアプリを爆速で開発する

# はじめに

この記事では、Streamlitの概要を説明し、Streamlitを使ってデータ分析Webアプリを実際に開発する中でStreamlitの機能をいろいろと紹介していきます。最終的に以下のような[ApacheやTomcatなどのアクセスログを解析するWebアプリ](https://github.com/k-tamura/mossala)をつくります。

![strmlt.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/6bcaf870-47a2-2f58-cca7-dd5e53c02c3e.gif)

# Streamlitとは

Streamlitは、Pythonで実装されたオープンソースのWebアプリケーションのフレームワークであり、機械学習やデータサイエンス向けのグラフィカルなWebアプリを簡単に作成して全世界に公開（クラウドサービスにデプロイ）できます。

## 主な特徴

- Pythonのみで実装可能（HTMLやCSS、JavaScriptなどフロントエンドのコードを書かなくていい）
- 豊富なウィジェットが利用可能
- Google ColabやJupyter Notebookで作成したPythonのコードがほぼそのまま利用できる
- 開発したアプリをStreamlit Cloudで全世界に簡単に公開可能
- コードの修正が即座に反映され、確認できる

## どのようなものがつくれるのか

[Streamlitの公式サイトのギャラリー](https://streamlit.io/gallery)を見ると具体的なイメージができるのではないかと思います。

## インストールとデモの実行

以下のコマンドでインストールします。

```bash
$ pip install streamlit
```

まずは、簡単なデモを見てみましょう。以下のコマンドでアプリケーションが起動します。

```bash
$ streamlit hello
```

シンプルなコードで、以下のようにグラフィカルなWebアプリケーションが実装できます。
![AnyConv.com__Screen recording 2022-06-03 16.43.45.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/4ec5fd1e-2a76-d4be-7e7b-5d303499bd7c.gif)

## 主な機能

以下に特徴的な機能をいくつか紹介します。

### マジックコマンド

マジックコマンドとは、表示したいもの（マークダウン、データ、グラフ）の変数名を記述するだけで、アプリに表示させる機能です。例えば、変数名だけをソースコードに書くと、

```python
x = 10
x
```

画面上に変数の値が表示されます。
![Screenshot 2022-06-20 10.58.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/21d1ecd9-90ec-703d-f1ea-b70c401a523d.png)
Pandasの`DataFrame`を格納した変数だけを記述すると、

```python
import pandas as pd
df = pd.DataFrame({'col1': [1,2,3]})
df
```

画面上にテーブルを表示できます。
![Screenshot 2022-06-20 10.57.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/5ebbd8bb-c561-72ab-51b6-d1ef451d63db.png)
matplotlibを使えば、
```python
import matplotlib.pyplot as plt
import numpy as np

arr = np.random.normal(1, 1, size=100)
fig, ax = plt.subplots()
ax.hist(arr, bins=20)

fig
```

グラフも表示できます。
![Screenshot 2022-06-20 10.58.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/5d4ffe3e-d8bf-a17b-c704-92dcf642cdcc.png)

### 簡単なコードでのウィジェットの追加

ウィジェットを追加することは、変数を宣言することと同じです。 バックエンドを作成したり、ルートを定義したり、HTTPリクエストを処理したり、フロントエンドに接続したり、HTML、CSS、JavaScriptなどを作成したりする必要はありません。

例えば、ラジオボックスを表示したければ、`streamlit`をインポートして、

```python
import streamlit as st
```

次の1行を記述すればOKです。

```python
st.radio("好きなマイケルは？", ('ジャクソン', 'ジョーダン', 'ホフマン'))
```

![Screenshot 2022-06-20 11.09.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/f78e96ef-3bcf-a9d3-0079-735c29578e1d.png)

そして、戻り値に選択した値がセットされます。

### 簡単なデプロイ

Streamlitで開発したアプリを自身のGitHubにコミットし、Streamlit社が提供するStreamlit Cloudというクラウドサービスと連携すると、自動的にアプリをデプロイし、インターネット上に公開することができます。デプロイしたアプリは、以下のような画面で管理できます。

![Screenshot from 2022-07-14 10-35-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/b47ca007-c657-bc77-0ca8-fbc96c967561.png)

### オートリロード

Streamlitで開発したアプリへのソースコードの修正は即座に反映されます。GitHubに修正をコミットすれば、Streamlit Cloud上のアプリにすぐに修正が反映されます。ローカル環境で開発している場合は、ソースコードの修正後に画面右上の「Rerun」をクリックすることで、その修正が反映されたアプリを動かすことができます。

![strmlt_auto_reload.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/be91f77d-d243-5abd-2add-4d1db62ac201.gif)

# 応用編 〜 データ分析Webアプリの開発

では、実際にデータ分析Webアプリを開発してみましょう。ここでは、Apacheなどのアクセスログを解析するアプリをつくり、Streamlit Cloudにデプロイしてインターネットにアプリを公開します。

## 簡単なWebアプリの作成

まずは画面に簡単な文字列を表示するアプリケーションを作成して、GitHubにコミットしましょう。GitHubにアクセスし、適当な名前でアプリケーション用のリポジトリーを作成して下さい。

![Screenshot 2022-06-06 12.30.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/36278552-caf4-e956-2073-3e82944afb38.png)

<!--
![Screenshot 2022-06-06 12.30.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/d5bbf7d3-7bd7-dd69-b71f-a73094ab9b40.png)
-->
リポジトリーを作成したら、適当なファイル名でpythonファイルを作成します。公式ドキュメントにあるとおり、`streamlit_app.py`というファイル名にすると、URLにファイル名を含めずにアクセスできます（少しURLを短縮できます）。「Add new file」ボタンをクリックして、つくってしまいましょう。

![Screenshot 2022-06-06 12.30.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/6cbee6b5-b6b0-0bb4-ea9d-b31f461e1ab6.png)

マジックコマンドで以下のコードを記述すれば、画面上に`x: 10`と表示することができます。

```python
x = 10
'x: ', x 
```

![Screenshot 2022-06-06 12.38.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/17275c84-8cde-179e-152b-37ce5770f198.png)

「Commit new file」ボタンをクリックして、保存します。Webアプリの作成は、これで完了です。

![Screenshot 2022-06-06 12.32.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/93057b4d-c13f-ab26-b802-c8514d998d33.png)

## Streamlit Cloudアカウントの登録

<!--
:::note info
※このステップは、Streamlit Cloudでアプリを公開する場合のみ必要です。
:::
-->
次に、 https://streamlit.io/cloud にアクセスします。以下のような画面が表示されるので、「Get Started」ボタンをクリックします。
![Screenshot 2022-06-06 12.21.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/e2a332b6-ec03-abe7-4832-fe4329c2146b.png)
GoogleまたはGitHubの既存アカウントを使用するか、メールアドレスを入力してStreamlit Cloudアカウントを作成します。
![Screenshot 2022-06-06 12.21.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/58a6f5e0-fca3-ac24-bb59-e94edb1e13e9.png)
以降はGitHubの既存アカウントを使用します。よくあるOAuthのフローで、Streamlit CloudがGitHubアカウントの情報にアクセスすることを許可し、
![Screenshot 2022-06-06 12.21.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/927fab91-c8b8-2f53-930d-9f685780bcb4.png)
確認のためのパスワードを入力します。
![Screenshot 2022-06-06 12.22.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/fa72d97c-ecf3-0e3c-e575-e5656806caeb.png)
アカウントの情報などを入力したら、
![Screenshot 2022-06-06 12.23.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/14dd6b23-939f-c284-d13f-879d6dfac98c.png)
「New app」ボタンをクリックして、アプリケーションのプロファイルを作成します。
![Screenshot 2022-06-06 12.23.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/bad89214-b9c9-4bd4-a510-cf25c0dcf4df.png)
ここで再度OAuthの同意画面が表示されるので、同意しましょう。
![Screenshot 2022-06-06 12.23.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/dac674bf-358f-340f-0af0-ed0e99396108.png)
Streamlit Cloudで公開するアプリの情報を入力します。ここでのアプリの情報とは、先ほど作成したアプリのGitHubリポジトリ名やパスなどです。
![Screenshot 2022-06-06 12.33.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/1c58fd4a-34ba-4230-5ac0-cb86ed63f26d.png)
「Deploy!」ボタンをクリックすると、Streamlit Cloud上でアプリが起動します。
![Screenshot 2022-06-06 12.33.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/44504df2-6b6a-2512-b872-e83b738cbe18.png)
正常に起動できたら、以下のように画面に`x: 10`と表示されます。
![Screenshot 2022-06-06 12.38.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/6792fef9-809e-de6f-6123-b41e0575f287.png)

## データ分析Webアプリの開発

簡単なWebアプリケーションができあがったので、これに機能を追加して、アクセスログを解析するWebアプリケーションにしていきましょう。

### アップローダーの追加

まずは、アクセスログをアップロードする機能（アップローダー）がなければ始まりません。アップローダーは、以下のように追加できます。

```python
import streamlit as st
uploaded_file = st.file_uploader("アクセスログをアップロードしてください。")
```

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/c0c5d42e-a8e6-c4f6-96d6-6e5233ba46a4.png)

### アップロードしたアクセスログファイルの読み込み

ファイルがアップロードできたら、`st.file_uploader()`の戻り値にファイルオブジェクトが格納されるので、これを使って処理を開始します。Apacheのアクセスログであれば、以下のように`pd.read_csv()`で読み込めばOKです。

```python
import pandas as pd
if uploaded_file is not None:
    df = pd.read_csv(
        uploaded_file,
        sep=r'\s(?=(?:[^"]*"[^"]*")*[^"]*$)(?![^\[]*\])',
        engine='python',
        na_values='-',
        header=None)
```

アクセスログファイルを読み込んだら、先頭5行を表示してみましょう。

```python
    st.markdown('### アクセスログ（先頭5件）')
    st.write(df.head(5))
```

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/725149ee-dac7-57ab-561b-dc26ebedbc46.png)

ここまでできたら、あとは変数`df`に格納されたPandasの`DataFrame`型のアクセスログデータをいろいろな観点で解析できるでしょう。Apacheのアクセスログの解析においては、以前書いた[この記事](https://qiita.com/tamura__246/items/79cd4fad1d7ea1fed12b)とともにつくったノートブックがほぼそのまま使えそうだったので、これを利用することにしました。前述しましたが、Jupyter NotebookやGoogle Colabでつくったノートブックのコードがほぼそのまま活用できるのが、Streamlitのメリットと言えます。

このようにしてつくったアクセスログ解析用のWebアプリケーションがこれです。

https://github.com/k-tamura/mossala

このページの中にある「Open in Streamlit」ボタンをクリックすると、Streamlit Cloud上で動作するこのアプリケーションを試してみることができます。上記、GitHubリポジトリー内にサンプルのアクセスログがありますので、それをアップロードしてください。

:::note warn
顧客環境のアクセスログをStreamlit Cloudにアップロードしないように注意して下さい（Streamlit社に利用されるとは思いませんが...）。機密情報を含むアクセスログを解析したい場合は、以下の手順でローカルでこのアプリケーションを起動して下さい。
```bash
$ git clone https://github.com/k-tamura/mossala.git
$ cd mossala/
$ pip install -r requirements.txt
$ streamlit run streamlit_app.py
```
:::

アクセスログ解析用のノートブックをアクセスログ解析用のWebアプリケーションに変えるために使ったStreamlitの機能を、以下にいくつか解説します。

### ページタイトルとページアイコン

ページタイトルとページアイコンは、以下のように設定できます。この場合、`icon.png`というファイル名でアイコンの画像ファイルをルートディレクトリーにおいておく必要があります。

```python
st.set_page_config(page_title="メインページ", page_icon='icon.png')
st.title("Multiple OSS Access Log Analyzer")
```

これにより、ブラウザーのタブの部分が以下のように表示されます。
![Screenshot 2022-06-21 17.31.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/59054e3c-03a9-8770-2430-4a257e324e27.png)

### 列選択のためのウィジェット

何番目の列を解析に使用するかを選択させるための、複数選択ができる入力ボックスは、以下のように実装します。第2引数は選択可能な値のリストで、第3引数はデフォルトで選択される値のリストです。

```python
    usecols = st.multiselect(
        '何番目の列を解析の対象にしますか？',
        [0, 3, 4, 5, 6],
        [0, 3, 4, 5, 6])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/77541bf9-70d6-8d74-4b8e-4a69eba271a5.png)

### エラーメッセージの表示

`st.error()`でエラーメッセージを表示することができます。入力チェックはこれで実装します。

```python
if len(usecols) == 0 or len(names) == 0:
    st.error('解析対象の列が指定されていません。')
```

![Screenshot 2022-06-21 17.38.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/d5f4de63-25a8-9992-71bd-78e277b3533f.png)

### ヘルプテキスト

画面の項目にヘルプテキストを付けることもできます。

```python
    help_txt = '''
        以下のフォーマット文字列を解析可能です。詳細については、[公式ページ](https://httpd.apache.org/docs/2.4/ja/mod/mod_log_config.html)を参照して下さい。

        | 列名 | フォーマット文字列 | 説明 | 
        |:-----|:-----:|:-----|
        | Remote Host | `%h` | リモートホスト |
        | Time | `%t` | リクエストを受付けた時刻 | 
        | Request | `\"%r\"` | リクエストの最初の行 | 
        | Status | `%>s` | ステータス | 
        | Size | `%b` | レスポンスのバイト数 | 
        | User Agent | `\"%{User-agent}i\"` | リクエストのUser-agentヘッダの内容 | 
        | Response Time | `%D` または `%T` | リクエストを処理するのにかかった時間 |         
        '''

    names = st.multiselect(
        'これらの列を何を意味しますか？',
        ['Remote Host', 'Time', 'Request', 'Status', 'Size', 'User Agent', 'Response Time'],
        default_names, help=help_txt)
```

ウィジェット用の関数の引数`help`にマークダウン書式で書いた値を渡せば、次のようなヘルプテキストが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/03504562-f13e-34c2-3a74-25455045b379.png)

### 世界地図の表示

`st.map()`を使用すると、地図上の指定した場所にプロットすることもできます。引数には、緯度と経度を意味する`lat`列と`lon`列を含む`DataFrame`を渡します。この機能とIPアドレスから場所と緯度・経度を取得するライブラリーを利用して、アクセスログのリモートホスト（アクセス元のクライアント）の地域を地図上にプロットします。

```python
st.map(df)
```
![Screenshot from 2022-06-21 21-52-55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/34a33418-933b-b98c-6a60-9710f7a4ef3e.png)

### プログレスバー

時間がかかる処理に対してはプログレスバーを表示さた方がよいでしょう。アクセスログも件数が増えると解析には時間がかかるので、以下のように大雑把な進捗状況がわかるプログレスバーを導入しています。

```python
my_bar = st.progress(0)
#　・・・何らかの処理
my_bar.progress(10)
#　・・・何らかの処理
my_bar.progress(20)
#　・・・何らかの処理
#　・・・
my_bar.progress(100)
```
![Screen recording 2022-06-22 15.05.15.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/78222f4f-6d43-d022-24e0-1e389c6e8e95.gif)

### ログ出力

一般的なWebアプリケーションにおいてログ出力は重要です。Streamlitはログ出力機能を提供していないので、loggingモジュールを使って実現します。

```python
import logging
logging.info('This message should go to the log file')
```

以下のコマンドで起動すれば、`app.log`ファイルに出力できます。

```bash
$ streamlit run app.py  --logger.level=info 2>>app.log
```

出力された内容は、以下のようになります。

```
2022-06-22 16:06:01.608 This message should go to the log file
```

### 複数ページの作成

複数のページを作成したい場合は、以下のように`pages`ディレクトリ配下にpythonファイルを格納しておくことで実現できます。
![Screenshot from 2022-06-21 22-57-15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/2457b388-811e-bbe0-3f6d-8631491c98ff.png)

これにより、左メニューに次のようなラベルが追加され、ここをクリックするとそのページへ遷移します。ファイル名がページ名のラベルになるので注意して下さい。

![Screenshot from 2022-06-21 22-59-39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/4aa2cb9c-0a48-2afd-dc52-c7dd2aa2fae9.png)

### デザインの変更

Streamlitは、`.streamlit/config.toml`でいくつかの設定を行うことができます。デザインの設定もその一つで、`[theme]`セクションに`primaryColor`などのプロパティーを追加することで、変更ができます。以下の設定はすべてデフォルト値なので、これを設定しても何も変化はありませんが、例えば、`primaryColor="blue"`にすれば、チェックボックスやスライドバーなどの色が変わります。

```python
[theme]
primaryColor="#F63366"
backgroundColor="#FFFFFF"
secondaryBackgroundColor="#F0F2F6"
textColor="#262730"
font="sans serif"
```

![Screenshot 2022-06-22 15.35.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/b4bfe84d-d241-8201-fbeb-ab3bcdb69b51.png)

### 画像の挿入

画像は、`st.image()`に画像ファイルのパスを渡すことで、挿入できます。

```python
st.image('logo.png')
```

せっかくなので、このアプリにもロゴをつくって、それをトップページに挿入してみることにしました。
![366c5aed4f9aeb472284919754bb19524fbdebd5da2e0241c52c17cc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/4a631719-ef62-807b-6736-7c76914bb19b.png)

### アニメーション

Streamlitには以下のような遊び心のあるアニメーションも少しだけ用意されています。

```python
st.balloons()
```

データ分析中の待ち時間を退屈させないために入れるとか、まあ、何らかの用途に使えるでしょうか。
![animate_balloons.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/f18f538b-dfd0-4e39-2bb1-653dc350a70a.gif)

### JavaScriptで動きをつける

`stc.html()`を使用すれば、HTMLを書くこともできます。これを使ってAnimate.cssを読み込み、ただのメッセージに動きを付けてみます。

```python
import streamlit.components.v1 as stc
stc.html('''
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css">
<p class="animated bounceInRight">👈 左のサイドバーからアクセスログを解析したいOSSを選んで下さい。</p>
''')
```

![animate_css.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/ddd12787-f9b0-f9fe-718d-ed9b1775486d.gif)

### グラフとテーブルを横に並べる

グラフとテーブルを横に並べるなど、画面レイアウトの調整には`st.columns()`と`with`を使いました。以下のように実装すれば、

```python
st.markdown('### レスポンスのステータスコード')
status_df = DataFrame(df.groupby(['Status']).size().index)
status_df['count'] = df.groupby(['Status']).size().values
status_df['percentage'] = (df.groupby(['Status']).size() / len(df) * 100).values
col1, col2 = st.columns(2)
with col1:
    fig = plt.figure()
    labels = [str(n)+'xx' for n in list(df.groupby([df['Status'] // 100]).groups.keys())]
    plt.pie(df.groupby([df['Status'] // 100]).size(), autopct = '%1.1f%%', labels = labels, startangle = 90)
    plt.axis('equal')
    fig
with col2:
    status_df
```

このように表示されます。

![Screenshot from 2022-07-14 11-42-24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43869/86301d4c-b026-73bf-ce6c-8650fb785a33.png)


# 感想

Streamlitは、高レベルのフレームワークで汎用性は低そうですが、開発したいアプリの要件を満たせれば、アプリ開発の期間を短縮する非常に良い選択肢になると思いました。逆に画面レイアウトや機能などを詳細に設計してある状況から、Streamlitでそれを実現しようとすると、かなり苦戦するのではないかと思います。まずは、Streamlitができることを大まかに把握して、アプリの要件を満たせるかどうかを検討するのがいいでしょう。

# さいごに

Pythonで実装しているので、今回開発したアプリでこんなことも簡単にできそうですね。

- 機械学習によるアクセスログのカラムの自動判別
- 多言語対応
- PyPI登録

# 参考

- [公式サイト](https://streamlit.io/)
- [公式ドキュメント](https://docs.streamlit.io/)
- [GitHub - Streamlit](https://github.com/streamlit/streamlit)