どこにでもいるSEの備忘録

たぶん動くと思うからリリースしようぜ

Streamlitを使ってみる

f:id:nogawanogawa:20210127222023p:plain

最近こちらの記事を拝見しました。

tech.jxpress.net

昨年くらいから、Streamlitはちょっとした話題になっており、良い機会だったので使ってみたので、今回はそのメモです。

Streamlit is なに?

めっちゃ簡単にデモアプリを作れるライブラリです。

www.streamlit.io

柔軟性をある程度捨て去っているので作れるものに制限はありますが、その分とにかく簡単にデモアプリを作れる点が最大の利点でしょう。 アプリを作るのに、Python以外の記述をすることは基本的になく、Pythonだけで全てが完結します。 これならPythonしか書けない人でも、気軽にWebアプリを作ることができそうです。

使い方

ドキュメントはこちらです。

docs.streamlit.io

大まかな動作のイメージは、Pythonで評価したものがすべてブラウザ上にレンダリングされる感じです。 なので、明示的にコンポーネントを記述するというよりは、jupyter notebookで変数の中身を確認するみたいなコマンドが、そのままいい感じにwebページになっていきます。

試しに作ってみる

ただただ適当に、tutorialのコードをペタペタ貼り付けていくと、書いたコードはこれだけ。

コード

import streamlit as st
# To make things easier later, we're also importing numpy and pandas for
# working with sample data.
import numpy as np
import pandas as pd
import time

st.title('MyApp')

st.write("Here's our first attempt at using data to create a table:")

df = pd.DataFrame({
  'first column': [1, 2, 3, 4],
  'second column': [10, 20, 30, 40]
})

df

map_data = pd.DataFrame(
    np.random.randn(1000, 2) / [50, 50] + [37.76, -122.4],
    columns=['lat', 'lon'])

st.map(map_data)

if st.checkbox('Show dataframe'):
    chart_data = pd.DataFrame(
       np.random.randn(20, 3),
       columns=['a', 'b', 'c'])

    st.line_chart(chart_data)


option = st.sidebar.selectbox(
    'Which number do you like best?',
     df['first column'])

'You selected:', option

left_column, right_column = st.beta_columns(2)
pressed = left_column.button('Press me?')
if pressed:
    right_column.write("Woohoo!")

expander = st.beta_expander("FAQ")
expander.write("Here you could put in some really, really long explanations...")

'Starting a long computation...'

# Add a placeholder
latest_iteration = st.empty()
bar = st.progress(0)

'1'
for i in range(100):
  # Update the progress bar with each iteration.
  latest_iteration.text(f'Iteration {i+1}')
  bar.progress(i + 1)
  time.sleep(0.1)

'...and now we\'re done!'

あとは、下記のコマンドでサーバーを起動してあげます。

$ streamlit run main.py

すると、こんな感じの画面を作ることができます。

f:id:nogawanogawa:20210202090436p:plain

ここまで、Pythonしか書いてませんがこんなにきれいな画面を作ることができました。

考慮すべきこと

基本的に1ページのアプリしか作れません。 見かけ上複数ページがあるように見せる(例:SPAのroutingのように、見かけ上画面遷移しているように見える)ことは、タブによる切り替えを使用することで可能なんですが、本当の意味での別ページ(通常のrequest/responseに応じて画面を構成し直すやり方)は作ることができないようです。

medium.com

一応、なんかがんばるとできるみたいな記述もありました。

discuss.streamlit.io

なんかSessionStateってやつを使うとできるような、できないような、、、って感じですね。 これを使っても、Sessionの状態を見て、画面をrerunすること表示を切り替えることで複数ページに見せるようなんで、複雑なフローは書けないような気もしますが。

適当に作ってみる

お題

文書の分析をちょっぴりインタラクティブに行うようなアプリを作成しようと思います。

構成

概要はこんな感じです。

f:id:nogawanogawa:20210202221058j:plain

謎の作りになってしまっているのはスルーの方向で。

環境構築

docker-composeで作ります。 理由は単純にローカルで作るとき楽なので。

Elasticsearchにsudachi入れたり、その他ゴニョゴニョしてるんですが、最後にリポジトリ付けといたんで詳しくはそちらをご参照ください。そっち見た方が早いと思うので。

下準備

今回はデータをElasticsearchに入れる関係で、ETLらしきものを作成してデータを整形してElasticsearchに投入してます。 まあ、検索できるとなにかと便利なのと、生データ扱うよりはこっちのほうが健全かなーというレベルです。

Streamlitでアプリを作る

本題です。上で定義したようなものを作っていきたいと思います。

入力

分析対象の絞り込みとして、カテゴリと検索窓を付けました。 凝ったことするならもっと複雑でもいいですが、今回は簡単めでこんな感じで。

コード

#### Input
search = st.sidebar.text_input("Search", "")

df = pd.DataFrame(document.search_all())
labels = df['category'].unique()
labels = np.insert(labels, 0, "")
category = st.sidebar.selectbox(
    'Category', labels)

入力 -> 出力処理

サイドバーで入力された内容から、該当するドキュメントを検索してきます。

コード

#### Input -> Output

result = pd.DataFrame(document.search(search, category))

出力

上でとってきたデータフレームを思考停止でnlplotに突っ込んで、画面に表示してます。

コード

#### Output

'You selected:', category
if len(result) == 0 :
  pass
else:
  if len(result) > 100:
    result = result.iloc[:70,:]
  npt = nlplot.NLPlot(result, target_col='wakati')
  # Stopword calculations can be performed.
  stopwords = npt.get_stopword(top_n=30, min_freq=0)
  # 1. N-gram bar chart
  unigram = npt.bar_ngram(title='uni-gram', ngram=1, top_n=50, stopwords=stopwords)
  st.write(unigram)

  bigram = npt.bar_ngram(title='bi-gram', ngram=2, top_n=50, stopwords=stopwords)
  st.write(bigram)

  # 2. N-gram tree Map
  tree_map = npt.treemap(title='Tree of Most Common Words', ngram=1, top_n=30, stopwords=stopwords)
  st.write(tree_map)

  # 3. Histogram of the word count
  dist = npt.word_distribution(title='words distribution')
  st.write(dist)

作ったもの

画面としては、こんな感じで入力に応じてグラフがインタラクティブに変化するものになってます。

f:id:nogawanogawa:20210202162900p:plain

コードはこちら。

github.com

参考文献

下記の記事を参考にさせていただきました。

tech.jxpress.net

感想

簡易版のアプリを作るだけなら、これで十分ですね。 個人的には、Pythonだけできれいなアプリが作れるのは非常に便利だと感じる一方で、ちょっとでも複雑なことをやろうとするとReactやVueでフロント書きたくなっちゃいますね。 そういったことを思うので、作ったものを捨てることを前提にするPoCなどの場面で、とにかく速くアプリをつくってフィードバックを得たい、といった用途では非常に強力なのかなと感じました。 実際、pythonで100行いかないくらいで全然作れてしまってますし。

ちなみに、
「今回の題材ならKibanaで良くね?」ってツッコミはなしでお願いします。(作ってる途中で自分で気付いた)

オチもついてお後がよろしいようなので、このへんで。