streamlitによる可視化

streamlitを使うと解析のコードを簡単にweb appにすることができます

jupyterコードそのままでweb appに

例えばpandasによる可視化のソースコードを次のようにするとweb appにします

import streamlit as st
import pandas as pd

df_eth_eur = pd.read_pickle("../../data/binance_btc-eur.pkl")
df_btc_eur = pd.read_pickle("../../data/binance_eth-eur.pkl")

df_btc_eur.head()

df_btc_eur.info()

rule = "15min"
df_ohlc_btc_eur = df_btc_eur["price"].resample(rule, label="right").ohlc()
df_ohlc_btc_eur["volume"] = df_btc_eur["size"].resample(rule, label="right").sum()

df_ohlc_eth_eur = df_eth_eur["price"].resample(rule, label="right").ohlc()
df_ohlc_eth_eur["volume"] = df_eth_eur["size"].resample(rule, label="right").sum()

df_ohlc_btc_eur.head()

fig = df_ohlc_btc_eur["close"].plot()
st.pyplot(fig=fig.figure)


fig = df_ohlc_btc_eur["close"].plot(
    grid=True, # 罫線
    figsize=(30,5),  # 描画サイズ(横、縦)
    title="Close",  # グラフタイトル
    legend=True,  # 凡例
    rot=45,  # xtick の ローテーション
    fontsize=15, # 文字サイズ
    style={"close": "g--"}, # 色と線の種類
)
st.pyplot(fig=fig.figure)

次のように実行します。

streamlit run streamlit_01.py
_images/streamlit_01.png

Fig. 1 web browserで見える画面

streamlitでweb appにする際にjupyter notebookのコードに加える変更ポイント下記の通りです

  • streamlitの読み込み

import streamlit as st
  • pandasグラフをstreamlitで表示するようにしていする

df_ohlc_btc_eur["close"].plot()

を次のように書き換えます

fig = df_ohlc_btc_eur["close"].plot()
st.pyplot(fig=fig.figure)

インターラクティブにしてみる

jupyterの結果をwebに表示しただけでは嬉しくないですよね。すこしインターラクティブにしてみましょう。

上のコードではデータのサンプルを設定してました

rule = "15min"

これを次のようにスライドバーで動かして操作できるようにしましょう

interval = st.slider('resample interval(minutes):', 1, 60*24, 15, 1)
rule = '{}min'.format(int(interval))

ソースコードは次のようになります

import streamlit as st
import pandas as pd

df_eth_eur = pd.read_pickle("../../data/binance_btc-eur.pkl")
df_btc_eur = pd.read_pickle("../../data/binance_eth-eur.pkl")

df_btc_eur.head()

df_btc_eur.info()

interval = st.slider('interval(minutes):', 1, 60*24, 15, 1)

rule = '{}min'.format(int(interval))
df_ohlc_btc_eur = df_btc_eur["price"].resample(rule, label="right").ohlc()
df_ohlc_btc_eur["volume"] = df_btc_eur["size"].resample(rule, label="right").sum()

df_ohlc_eth_eur = df_eth_eur["price"].resample(rule, label="right").ohlc()
df_ohlc_eth_eur["volume"] = df_eth_eur["size"].resample(rule, label="right").sum()

df_ohlc_btc_eur.head()

fig = df_ohlc_btc_eur["close"].plot()
st.pyplot(fig=fig.figure)


fig = df_ohlc_btc_eur["close"].plot(
    grid=True, # 罫線
    figsize=(30,5),  # 描画サイズ(横、縦)
    title="Close",  # グラフタイトル
    legend=True,  # 凡例
    rot=45,  # xtick の ローテーション
    fontsize=15, # 文字サイズ
    style={"close": "g--"}, # 色と線の種類
)
st.pyplot(fig=fig.figure)

_images/streamlit_02.png

Fig. 2 スライドバーの画面

スライドバーを動かして、異なる時間スケールで価格をみることができます。

streamlitの基本

  • streamlitの関数を呼び出すことで画面にコンポーネントを配置する。ユーザ入力の値は戻り値として受け取れるので、その値で次の処理を変えることができるので気軽にインターラクティブなものを作れる。

  • 書いた順番に実行される

とても手軽である反面、高度なカスタマイズができません。簡単なログインを付けられるが、ごく簡易的なものになります。

概念の検証やツール作成に最適です。

次の課題に対する工夫をすれば強力なものを短期間で作成できます。

  • コード実行の順番と画面に表示するものの順番が違う場合にst.empty()を使って制御できる。

    • 例:グラフの下にスライドバーを配置し、それを動かすとグラフを変更したい場合

  • 画面で何かを動かす度に全部実行されるため、パフォーマンスの問題がある

    • @st.cacheを使って処理を一度しか実行しないようにできる

    • ラジオボタンを設置し、その選択に応じて実行する処理を切り分ける(ページ分けをするイメージ)

詳細については過去の勉強会資料を参照していただきたいです。

streamlit勉強会

他の機能など

タイトル、文章、HTMLはそれぞれ次のように配置できます

st.title('streamlitによる可視化')
st.markdown('# streamlitによる可視化')
st.components.v1.html('<h1>streamlitによる可視化</h1>')

他にボタン、ラジオボタン、セレクト枠、テキスト入力枠、カレンダー入力などのコンポーネントが提供されています。 詳細の使い方は公式ドキュメントがわかりやすいです。

streamlit document

実践編:ツール化してみる

最後にstreamlitを使って解析ツールを作ってみます

_images/streamlit_03.png

Fig. 3 解析ツール

ページ分け

様々な可視化を使いたいが、それを一度に全部を表示すると時間がかかってしまうので、 これを複数の画面に分けて、サイドバーで切り替えるようにします。

具体的にはまず各々の処理を関数にまとめ、ユーザ選択に応じて呼び出します。

pages = {
    '価格': viz_price,
    '価格+指標': viz_2nd_axe,
    '出来高': viz_volume
}

page = st.sidebar.radio('画面:', list(pages.keys()))
pages[page]()

ここでは価格データを可視化するコードはviz_priceという関数にまとめています。

前処理は一度だけ実行する

関数のまえに次のように書くとその関数は一度だけ呼び出されます。

@st.cache

注意点:関数内で使用したオブジェクトを書換えないように注意しましょう。データ処理して新しいカラムなどを作成して使う場合にはコピーされたDataframeを使うといいでしょう。

まとめ

コード

import streamlit as st
import datetime
import pandas as pd
import logging
import ta
import matplotlib.pyplot as plt

logging.basicConfig(level=logging.INFO,
                    format="%(asctime)s %(levelname)s %(message)s",
                    datefmt="%m/%d/%Y %X")
st.set_page_config(layout='wide')


# 前処理を一度だけ実行するようにする
@st.cache(allow_output_mutation=True)
def pre_process():
    logging.info('load data')
    df_eth_eur = pd.read_pickle("../../data/binance_btc-eur.pkl")
    df_btc_eur = pd.read_pickle("../../data/binance_eth-eur.pkl")

    rule = '15min'
    df_ohlc_btc_eur = df_btc_eur["price"].resample(rule, label="right").ohlc()
    df_ohlc_btc_eur["volume"] = df_btc_eur["size"].resample(rule, label="right").sum()
    df_ohlc_btc_eur["RSI14"] = ta.momentum.rsi(df_ohlc_btc_eur["close"], window=14)

    df_ohlc_eth_eur = df_eth_eur["price"].resample(rule, label="right").ohlc()
    df_ohlc_eth_eur["volume"] = df_eth_eur["size"].resample(rule, label="right").sum()
    df_ohlc_eth_eur["RSI14"] = ta.momentum.rsi(df_ohlc_btc_eur["close"], window=14)

    return df_eth_eur, df_btc_eur, df_ohlc_eth_eur, df_ohlc_btc_eur


# 価格の可視化
def viz_price():
    interval = st.slider('interval(minutes):', 1, 60*24, 15, 1)
    # どの銘柄をみるか選べるようにする
    token = st.radio('coin:', ['eth', 'btc'])
    rule = '{}min'.format(int(interval))

    if token == 'btc':
        # 注意点: st.cacheで指定した関数内のオブジェクトは書換えないようにしましましょう。
        # 値を代入したりする場合は元のdataframeをコピーして、それを使う
        df_view = df_btc_eur["price"].resample(rule, label="right").ohlc()
        df_view["volume"] = df_btc_eur["size"].resample(rule, label="right").sum()
    elif token == 'eth':
        df_view = df_eth_eur["price"].resample(rule, label="right").ohlc()
        df_view["volume"] = df_eth_eur["size"].resample(rule, label="right").sum()

    fig = df_view["close"].plot()
    st.pyplot(fig=fig.figure)

    fig = df_view["close"].plot(
        grid=True, # 罫線
        figsize=(30,5),  # 描画サイズ(横、縦)
        title="Close",  # グラフタイトル
        legend=True,  # 凡例
        rot=45,  # xtick の ローテーション
        fontsize=15, # 文字サイズ
        style={"close": "g--"}, # 色と線の種類
    )
    st.pyplot(fig=fig.figure)


def viz_2nd_axe():
    index = st.radio('2nd axe:', ['RSI14', 'volume'])
    if_change_window = st.checkbox('指標算出の窓を変える')

    if not if_change_window:
        figs = df_ohlc_btc_eur[["close", index]].plot(
            grid=True,
            figsize=(30,20),
            title="Close & {}".format(index),
            legend=True,
            subplots=True,
            layout=(2,1), # レイアウト(行,欄)
        )
        st.pyplot(fig=figs[0, 0].figure)
    else:
        window = st.slider('window:', 1, 100, 14, 1)
        window = int(window)
        rule = '{}min'.format(window)
        df_view = df_btc_eur["price"].resample(rule, label="right").ohlc()
        if index == 'volume':
            df_view["volume_flex"] = df_btc_eur["size"].resample(rule, label="right").sum()
        elif index == 'RSI14':
            df_view["RSI14_flex"] = ta.momentum.rsi(df_view["close"], window=window)
        figs = df_view[["close", index + '_flex']].plot(
            grid=True,
            figsize=(30,20),
            title="Close & {}_flex".format(index),
            legend=True,
            subplots=True,
            layout=(2,1), # レイアウト(行,欄)
        )
        st.pyplot(fig=figs[0, 0].figure)
        st.dataframe(df_view)


def viz_volume():
    date_start = st.date_input('start:', datetime.datetime(2022, 3, 1))
    date_end = st.date_input('end:', datetime.datetime(2022, 3, 31))
    df_15min = df_btc_eur["price"].resample("15min", label="right").ohlc()
    df_15min["volume"] = df_btc_eur["size"].resample("15min", label="right").sum()
    df_15min["pricecut"] = pd.cut(df_15min["close"], 30, ).apply(lambda x: x.left)
    df_15min = df_15min.loc[date_start: date_end]
    s_vol_by_price = df_15min.groupby("pricecut")["volume"].sum()
    fig, axes = plt.subplots(1, 2, figsize=(20, 5), constrained_layout = True)
    df_15min["close"].plot(ax=axes[0], yticks=s_vol_by_price.index, grid=True)
    fig = s_vol_by_price.plot(kind="barh",ax=axes[1],sharey=axes[0], grid=True)
    st.pyplot(fig=fig.figure)


df_eth_eur, df_btc_eur, df_ohlc_eth_eur, df_ohlc_btc_eur = pre_process()
st.title('Botterのためのデータ可視化入門、streamlitの巻')

pages = {
    '価格': viz_price,
    '価格+指標': viz_2nd_axe,
    '出来高': viz_volume
}

page = st.sidebar.radio('画面:', list(pages.keys()))
pages[page]()