Streamlitの標準機能によるグラフ描写

Streamlitの記事2本目です。今回はデータの可視化として、Streamlit標準のグラフ描写機能と少し他のデータ表示方法を紹介します。

前回の記事でも描きましたが、Streamlitにはmatplotlibのグラフを表示する機能があります。また、これ以外にもgraphvizとかplotlyとかのグラフを表示する機能もあって、それらを使えば良いからというのもあってか標準のグラフ作成機能は作成できるグラフの種類がかなり限られます。

具体的には、エリアグラフ/棒グラフ/折れ線グラフ/地図/散布図の5種類です。
ドキュメントはこちら

今回の記事ではこの5種類のグラフと、あと指標の数値をそのまま表示する機能、そして画像データ(行列データ)を表示するサンプルコードをそのまま紹介します。

サクッと動かせるのでコピペして試してみてください。

import streamlit as st
import pandas as pd
import numpy as np

# データの準備
data = pd.DataFrame(
    np.random.randn(20, 3),
    columns=['a', 'b', 'c']
)

# エリアグラフ
st.area_chart(data)
# 棒グラフ
st.bar_chart(data)
# 折れ線グラフ
st.line_chart(data)
# 散布図(x軸、y軸に利用したい列を指定して使う)
st.scatter_chart(data, x="a", y="b")

# 地図表記用の緯度経度データの作成
data_map = pd.DataFrame(
    {
        'lat': [37.76, 37.76],
        'lon': [-122.4, -122.41]
    }
)
# 地図
st.map(data_map)

# メトリックを表示。valueが値で、deltaで変化幅を表示可能
st.metric(label="為替", value="165円", delta="-2円")

# imageで画像データを表示可能。identityはサンプルとして用意した斜め線の図(単位行列)
st.image(1-np.identity(100), caption="斜線")

念のためですが、実行方法は $streamlit run {ファイル名} です。

Streamlit入門

以前から気になっていたのですが、Streamlitというライブラリを最近本格的に使い始めました。これは、簡単にWebアプリケーションを作成できるPythonライブラリなのですが、データの可視化や分析を行うアプリケーションの作成に使うことを念頭において開発されており、僕らの業務と大変相性の良いライブラリです。

インストールとサンプルの実行

インストールはPyPIからpipで行えます。
参考: streamlit · PyPI

インストールしたあと、PyPIのサイトに掲載されているコマンドでサンプルを起動できます。

$ pip install streamlit
$ streamlit hello

結構面白いサンプルなので、これからStreamlitを使っていこうというモチベーションを上げる意味でも一度試すことをお勧めします。

ここから超基礎的な使い方の説明に入ります。(上記のサンプルよりしょぼくてすいません。今後の記事でもう少し色々解説します。)

最もシンプルなアプリの実装

最初に、起動の確認としてただテキストを表示するだけのアプリを作ってみましょう。

app.py というファイルに以下のコードを書いて保存します。

import streamlit as st

st.title('初めてのStreamlitアプリケーション')
st.write('こんにちは、Streamlit!')

そして、次のコマンドで起動します。

$ streamlit run app.py

これで、タイトルとテキストを表示するだけのアプリが起動します。(localhostの8501番ポートです)

インタラクティブなウィジェットの利用

少しだけ動きを出してみます。ドキュメントのウィジェットのページ に色々紹介されているのですが、ここではスライダーを試します。選択した値を表示してみましょう。

import streamlit as st

st.title('スライダーの例')

value = st.slider('数値を選んでください', 0, 100, 50)
st.write('選択した数値:', value)

これを同じように実行すると、スライダーが表示され、0から100の整数(初期値が50)を一つ選べ、スライダーを動かすとその下のテキストボックスに選んだ数値が表示されます。

データフレームの表示

次は、データフレームの表示方法を紹介します。StreamlitはPandas (と他にもPyArrow, Snowpark,PySpark) のDataFrameを表示する専用のメソッドを持っているのです。

参考: st.dataframe – Streamlit Docs

import streamlit as st
import pandas as pd

st.title('データフレームの表示')

data = {
    '名前': ['Alice', 'Bob', 'Charlie'],
    '年齢': [24, 27, 22],
    '得点': [88, 92, 85]
}
df = pd.DataFrame(data)

st.write('データフレーム:')
st.dataframe(df)

これでデータフレームが表示されます。

matplotlibのグラフの表示

最後にmatplotlibのグラフを表示する方法を紹介します。一応、データフレームとグラフが表示できたら超最低限のダッシュボードは作れます。

matplotlibのグラフは、st.pyplotメソッドで表示します。とりあえずsinのグラフでも表示しておきましょう。

import streamlit as st
import matplotlib.pyplot as plt
import numpy as np

# タイトルを設定
st.title('matplotlibのグラフを表示する例')

# データを作成
x = np.linspace(0, 10, 100)
y = np.sin(x)

# matplotlibでグラフを作成
fig, ax = plt.subplots()
ax.plot(x, y, label='sin(x)')
ax.set_xlabel('X軸')
ax.set_ylabel('Y軸')
ax.set_title('Sine Wave')
ax.legend()

# Streamlitでグラフを表示
st.pyplot(fig)

matplotlibの使い方に慣れている人(ぼくもそうです)はとりあえずいつものノリでグラフを書いて渡すだけで表示できるので便利です。

これ以外にも、Streamlit自体の機能でのグラフ描写等も行えるので今後の記事で紹介していきたいと思います。

http.serverでCGIを動かす

今回までhttp.serverの話です。

前回の記事でカスタムハンドラーを作成する方法を紹介しましたのでPythonを使って動的にサイトを作ることもできるようになりましたが、これ以外にもCGIを使って動的なサイトを作ることもできます。
参考: Pythonのhttp.serverモジュールでカスタムハンドラーを実装する方法

要するに普通にPythonファイルをドキュメントルートに設置しておいて、それを動かせるわけですね。

一番シンプルな方法は、 http.serverをコマンドで起動する際に –cgi オプションをつけることです。デフォルトでは、ドキュメントルート直下の、 /cgi-bin と /htbin の 二つのディレクトリに配置されたファイルはCGIとして処理されるようになります。

この二つのディレクトリに固定されるのは、class http.server.CGIHTTPRequestHandler の、cgi_directories ってプロパティにそう指定されているからです。逆にこれ以外のディレクトリに.pyファイルを配置していてもhtmlファイルと同じようにただそのファイルの中身が返されます。

やってみましょう。

cgi-binというディレクトリを作成して、その直下に sample-cgi.py というファイル名で以下のスクリプトを書いておきます。

#!/usr/bin/env python

print("Content-Type: text/html; charset=utf-8\n")
print("<html><body>")
print("<h1>CGIスクリプト実行!</h1>")
print("</body></html>")

そして、このファイルにchmod 744 で実行権限をつけておきます。これ重要です。

そして、http.serverを起動します。

 % python -m http.server --cgi

こうすると localhost:8000/cgi-bin/sample-cgi.py にブラウザでアクセスすると、
CGIスクリプト実行! の文字が表示されます。

CGIで作成するとファイル名とURLがそのままシンプルに対応していくのでいくつも作る場合はシンプルで良いですね。

実は、class http.server.CGIHTTPRequestHandler というのを使うと、CGIディレクトリの指定とかがもっと柔軟に行えるのですが、http.server使ってそこまで凝ったことをすることもないんじゃないかなぁと思うので簡潔ですが今回の記事はここまでとします。

Pythonのhttp.serverモジュールでカスタムハンドラーを実装する方法

前回の記事に続いて、http.serverの話です。せっかくPythonを使ってWebサーバーを立てるわけですからファイルの内容を表示する静的サイトだけでなく、クエリパラメーターやフォームからPOSTされたデータを処理して表示する動的サイトの作り方を軽く紹介しておきます。

ただ、前回の記事でも書きました通り、http.server自体がプロダクション環境に適さない簡易的なものなのであくまでもちょっとした手元のツール等での利用に止めることを推奨します。

カスタムハンドラーの作成

http.serverのBaseHTTPRequestHandlerを継承してカスタムハンドラーを作成することで、特定の処理に対して独自の処理を行えます。

例えば、以下の内容で、sample1.py というファイルを作ってみましょう。

from http.server import BaseHTTPRequestHandler, HTTPServer
import urllib.parse

class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/hello":
            self.send_response(200)
            self.send_header("Content-type", "text/html; charset=utf-8")
            self.end_headers()
            self.wfile.write("こんにちは!".encode("utf-8"))
        else:
            super().do_GET()

PORT = 8000
with HTTPServer(("", PORT), MyHandler) as httpd:
    print(f"Serving on port {PORT}")
    httpd.serve_forever()

そして、このファイルを実行します。

% python sample1.py

そうすると、`http://localhost:8000/hello` にアクセすると、こんにちは! のメッセージが表示されます。あとはプログラムで出力したい文字列を作成すれば任意のhtmlを返せますし、テンプレートファイルを読み込んでそれを表示すると言ったこともできます。

クエリパラメータの処理

次は、クエリパラメーターを受け取ってそれに応じた表示をするようにしてみましょう。

ファイル名は sample2.py等で作ります。実行方法は同じようにpythonコマンドにファイル名を渡すだけです。

from http.server import BaseHTTPRequestHandler, HTTPServer
import urllib.parse

class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        parsed_path = urllib.parse.urlparse(self.path)
        query_params = urllib.parse.parse_qs(parsed_path.query)
        self.send_response(200)
        self.send_header("Content-type", "text/html; charset=utf-8")
        self.end_headers()
        response = f"Path: {parsed_path.path}, Query parameters: {query_params}"
        self.wfile.write(response.encode("utf-8"))


PORT = 8000
with HTTPServer(("", PORT), MyHandler) as httpd:
    print(f"Serving on port {PORT}")
    httpd.serve_forever()

pathとパラメーターを受け取ってそれを表示するようにしてみました。先ほどの例と同様に、
% python sample2.py で起動して、 http://localhost:8000/hello?name=%E3%82%86%E3%81%86%E3%81%9F%E3%82%8D%E3%81%86 にアクセスすると、
Path: /hello, Query parameters: {‘name’: [‘ゆうたろう’]}
という表示が得られます。

上記のスクリプトでパスとパラメーターが取得できているのであとはそれを自由に活用するコードを書くだけです。

POSTリクエストの処理(フォームデータの処理)

最後にポストされたデータの処理方法を書いておきます。

これはポストするフォームも必要なのでそちらから用意します。
index.html という名前で次のファイルを作っておいてください。

<!DOCTYPE html>
<html>
<head>
    <title>Form Submission</title>
</head>
<body>
    <form action="/submit" method="post">
        <label for="name">Name:</label>
        <input type="text" id="name" name="name"><br>
        <label for="age">Age:</label>
        <input type="text" id="age" name="age"><br>
        <input type="submit" value="Submit">
    </form>
</body>
</html>

そして作成するpythonファイル、 sample3.pyを用意します。

from http.server import BaseHTTPRequestHandler, HTTPServer
import urllib.parse

class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/":
            self.send_response(200)
            self.send_header("Content-type", "text/html; charset=utf-8")
            self.end_headers()
            with open("index.html", "rb") as file:
                self.wfile.write(file.read())
        else:
            self.send_response(404)
            self.end_headers()

    def do_POST(self):
        if self.path == "/submit":
            content_length = int(self.headers["Content-Length"])
            post_data = self.rfile.read(content_length)
            data = urllib.parse.parse_qs(post_data.decode("utf-8"))
            self.send_response(200)
            self.send_header("Content-type", "text/html; charset=utf-8")
            self.end_headers()
            response = f"Received: {data}"
            self.wfile.write(response.encode("utf-8"))
        else:
            self.send_response(404)
            self.end_headers()

PORT = 8000
with HTTPServer(("", PORT), MyHandler) as httpd:
    print(f"Serving on port {PORT}")
    httpd.serve_forever()

これで、 localhost:8000 にアクセスすると、 do_GETメソッドが実行されてindex.htmlのファイルの中身(フォーム)が表示され、フォームにデータをPOSTすると、do_POSTメソッドが実行されてフォームで送信した内容が表示されます。

簡単ではありますが、以上がhttp.serverモジュールを利用したカスタムハンドラーの作成やクエリパラメーターの処理、POSTリクエストの処理方法でした。