Streamlitでデータを可視化する

# 概要

- データをインタラクティブに可視化できるWebアプリを作りたい
- D3.jsなどを生で使うのはちょっと重たい（技術力的に）
- そもそもWeb系の知識が謙遜抜きでほぼ0(Vue?React?なにそれ美味しいの？)
- じゃあ[Streamlit](https://www.streamlit.io/)使ってみよう

# 環境構築

こんな記事読まずに[公式ドキュメント](https://docs.streamlit.io/en/stable/)を読もうぜ。

## インストール

pipでインストールします。

```bash
$ pip install streamlit
```

## 動作確認

```bash
$ streamlit hello
```

これでローカルに8063番ポート(デフォルト設定)でwebサーバが立ちます。
ということで、ブラウザで`http://localhost:8063/`にアクセスするとサンプルページを確認できます。

ちなみに、streamlitをimportしたpythonファイルでWebアプリを立ち上げたい場合には、以下のようにrunの後にpythonファイルを指定します。

```
$ streamlit run [python-file]
```

# 基本的な書式

公式ドキュメントが充実しているのでそちらを参照されるのが速いです。

- [API reference](https://docs.streamlit.io/en/stable/api.html)
- [Streamlit cheat sheet](https://share.streamlit.io/daniellewisdl/streamlit-cheat-sheet/app.py)

ここでは雰囲気だけ。

## テキスト

見出しは、[streamlit.header](https://docs.streamlit.io/en/stable/api.html?highlight=markdown#streamlit.header)や`subheader`で指定できます。
文章は、[streamlit.text](https://docs.streamlit.io/en/stable/api.html?highlight=markdown#streamlit.text)や、[streamlit.write](https://docs.streamlit.io/en/stable/api.html?highlight=markdown#streamlit.write)で書くことができます。
また、Markdownも使えます（[streamlit.markdown](https://docs.streamlit.io/en/stable/api.html?highlight=markdown#streamlit.markdown)）。

例えば

```python
st.title('Sample page')
st.markdown('''
チョコボールの内容量

- ピーナッツ
  - 28g
''')
```

## データフレーム(表)

pandas.DataFrameを使って表を描けます。
[streamlit.table](https://docs.streamlit.io/en/stable/api.html?highlight=markdown#streamlit.table)や[streamlit.dataframe](https://docs.streamlit.io/en/stable/api.html?highlight=markdown#streamlit.dataframe)を使います。`st.dataframe`は特定の条件でセルをハイライトできたり、ソートできたりと高機能です。

例えば

```python
def rand_df(r=10, c=5):
    df = pd.DataFrame(
        np.random.randn(r, c),
        columns=('col %d' % i for i in range(c)))
    return df
dataframe = rand_df(c=3, r=2)
st.dataframe(dataframe)
```

## グラフ

チャートなどの描画はstreamlitでチャートを描く方法とmatplotlibのfigureオブジェクトなどを描画する方法があります。

例えば

```python
dataframe = ...
st.line_chart(dataframe)
```

デフォルトでカラム毎の折れ線グラフが描画されます。また、マウスホイールの操作で拡大/縮小ができたり、ドラッグすることで位置を動かしたりできます。（インタラクティブ！）

また、matplotlibに慣れている場合は[streamlit.pyplot](https://docs.streamlit.io/en/stable/api.html?highlight=markdown#streamlit.pyplot)でfigureオブジェクトを描画することもできます。当然seabornなども使えます。

```python
fig = plt.Figure(figsize=(6,2))
ax = fig.subplots(1,1)
dataframe.plot(ax=ax)
st.pyplot(fig)
```

ただしこれだと拡大/縮小などはできないです（当然ですが）。
他にも[bokeh](https://docs.bokeh.org/en/latest/)のチャートを描画できたりもしますし、いろいろサポートされているので公式ドキュメントを(以下略)

## Widget
データの選択などが行える各種のWidgetが用意されています。

例えば

```python
lst_flavors = ['ピーナッツ', 'いちご']
select_taste = st.selectbox('Flavor', lst_flavors)
```

プルダウンで選択項目が出てきます。
様々なものが用意されているので、[ドキュメント](https://docs.streamlit.io/en/stable/api.html#display-interactive-widgets)を確認してください。

## サイドバー

`st.sidebar.xxx`と指定するとサイドバーが左端に現れます。`xxx`には上記のコマンドがそのまま使えます。

例えば

```python
st.sidebar.header('サイドバー')
p = st.sidebar.slider('確率の設定', min_value=0.0, max_value=1.0, value=0.8)
st.sidebar.write(f'設定値は {p} です')
```

こんな感じ。

## キャッシュ

streamlitはWidgetなどで要素に変更があると、pythonファイルを上から順に全て実行しなおします。そのため、変更した要素に関係なくても、ちょっと重たい計算などをしていると毎回その処理が走ります。

そこで、関係する要素に変更が無ければキャッシュを利用するようにしたいです。
streamlitでは、`@st.cache`デコレータを使います。

例えば

```python
@st.cache
def rand_df(r=10, c=5):
    df = pd.DataFrame(
        np.random.randn(r, c),
        columns=('col %d' % i for i in range(c)))
    return df
dataframe = rand_df(c=7)
```

こうすれば、Widgetなどに変更があっても `dataframe`は更新されません。


# サンプルソースコード

上で記載した例をそのまま使って、ただのサンプルなWebアプリを作ってみます。
e.g. `sample.py`

```python
import numpy as np
import pandas as pd
import streamlit as st
import matplotlib.pyplot as plt

# テキストの描画
st.title('Sample page')
st.header('Header')
st.markdown('''
チョコボールの内容量

- ピーナッツ
  - 28g
- いちご
  - 25g
''')

@st.cache
def rand_df(r=10, c=5):
    df = pd.DataFrame(
        np.random.randn(r, c),
        columns=('col %d' % i for i in range(c)))
    return df
dataframe = rand_df(c=3, r=10)

# 表の描画
st.dataframe(dataframe.head(n=3))
# チャートの描画
st.line_chart(dataframe)
# widget
lst_flavors = ['ピーナッツ', 'いちご']
select_taste = st.selectbox('Flavor', lst_flavors)
# Sidebar
st.sidebar.header('サイドバー')
p = st.sidebar.slider('確率の設定', min_value=0.0, max_value=1.0, value=0.8)
st.sidebar.write(f'設定値は {p} です')
```

ローカルにテストサーバを立ててブラウザで確認してみます。

```bash
streamlit run src/sample2.py
```

これでこんな感じのアプリができます

![スクリーンショット 2021-01-19 1.05.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66005/46701634-5e88-3c66-f671-e052e857f64e.png)


おしまい


# 参考

- https://www.streamlit.io/
- https://docs.streamlit.io/en/stable/
- https://share.streamlit.io/daniellewisdl/streamlit-cheat-sheet/app.py