🌍

Streamlit設計方針: 効果的なアプリケーションの構築を考える

2023/11/04に公開

はじめに

初めまして。
都内IT企業で、データアルゴリズムチームのエンジニアをしております、Noraです。

機械学習やデータサイエンスプロジェクトに関わる際、デモプロダクトを作りたい状況は多いと思います。
その際、Streamlitという、PythonのみでWebアプリケーションを作成できるフレームワークが有用です。
https://streamlit.io/

今回の記事では、「運用保守が容易で開発コストを下げる」ディレクトリ設計方針を解説していきます。

Streamlitプロジェクトの、最適なディレクトリ設計方針を考えたい」という、チームで開発しているエンジニアにおすすめの記事となっております!

こちらの記事内容は個人の経験がベースになっておりますので、未熟な点も多いかと思います。
ご意見あればコメントいただけますと幸いです🙆‍♀️ 🙆

では、ぜひご覧ください!

設計の考え方

今回、ディレクトリ設計を考える上で、以下2つのポイントを意識しています。
これらを意識することで、チーム開発の際も開発容易性が高まり、運用保守に優れたサービス開発を実現します。

1. データ処理とUIの分離

データ処理部分とUIを分離し、別ディレクトリに構成しています。
Streamlitにおいては、ページ表示UIの部分をpagesディレクトリへ配置し、データ処理の部分はservicesディレクトリに配置します。
今後、ビジネスロジックや、データモデルが発生した場合には、modelsディレクトリにモデル構造やバリデーションモデルを定義することを検討します。

2. 責務の明確化

責務とは、プログラムで処理するタスクや役割のことです。
1つの関数が複数の処理を担っていると、チームメンバーにとっての理解コストが高まると共に、関数を変更した際の副作用が大きくなり、バグの温床となってしまいます。

プログラムは必ず変更されるものという前提で、責務が明確になっているプログラムを意識しましょう。

ディレクトリ設計の個人的ベストプラクティス

上で述べた2点を意識して、小規模Webアプリケーションの設計を考えます。
今回は、「アップしたCSVデータを元に、簡単な計算をしてからグラフ描画する」というアプリケーションを作成します。

以下のディレクトリ構成をアプリケーションを実装します。

my_streamlit_app/
  ┗ app.py
  ┗ pages/
    ┗ dashboard.py ## ダッシュボードのページを描画する
  ┗ services/
    ┗ calculate.py ## 計算を行う
    ┗ file_service.py ## ファイル操作を実施する
  ┗ models/
    ┗ data_model.py ## 今回は実装しない
  ┗ requirements.txt
  ┗ .gitignore
  ┗ .venv ## Pythonの仮想環境を構築する
    ┗ ...

サンプルコードを実装する

1. Python環境のセットアップ

ファイル実装前に、まずはPythonの仮想環境を構築します。
下のコマンドを実行します。

$ python3 -m venv .venv && source .venv/bin/activate
$ pip install pandas
$ pip install streamlit
$ pip freeze
----------
[出力結果]
numpy==1.26.1
packaging==23.2
pandas==2.1.2
Pillow==10.1.0
protobuf==4.25.0
pyarrow==14.0.0
pydeck==0.8.1b0
....

コマンドを実行できたら、requirements.txtにパッケージを記載します。

requirements.txt
pandas==2.1.2
streamlit==1.28.1

.venv以下のファイルはgit管理しないように、.gitignoreファイルにスクリプトを追加します。

.gitignore
.venv/

2. servicesディレクトリで、処理部分を実装する

まずは、csvの処理を行うfile_service.pyを実装します。
正直、1行コードなので関数にしない方が良いですが、今回は便宜上ファイルに分離しています。

services/file_service.py
import pandas as pd

def read_csv(filename: str) -> pd.DataFrame:
    return pd.read_csv(filename)

次に、データフレーム処理を司るファイルを作成します。

services/calculate.py
import pandas as pd

def calculate(df: pd.DataFrame, sales_column: str, tax_column: str) -> pd.DataFrame:
    calc_df = df.copy()
    calc_df['sales_with_tax'] = calc_df[sales_column] * calc_df[tax_column]
    return calc_df

3. pagesディレクトリで、グラフ描画ページを実装する

pages/dashboard.py
import streamlit as st

from services import calculate, file_service

def display():
    st.header("Upload CSV for Calculation")

    uploaded_file = st.file_uploader("Choose a CSV file", type="csv")
    if uploaded_file:
        data = file_service.read_csv(uploaded_file)

        calc_df = calculate.calculate(data, "sales", "tax")
        st.line_chart(data=calc_df, x="date", y="sales_with_tax")

4. メインファイルを実装する

これまでに実装してきたモジュールを、メインファイル内で呼び込みます。

app.py
import streamlit as st

from pages import dashboard

def main():
    st.title("CSV Data Processing and Graph Display")
    dashboard.display()

if __name__ == "__main__":
    main()

5. 動作確認

では、最後にstreamlitを使って、動作確認を行います。

streamlit run app.pyを実行すると、以下の画面に移ります。

CSVは以下のように「date, sales, tax」カラムの入ったデータを挿入します。

date,sales,tax
2023-01-01,10000,1.08
2023-01-02,12000,1.1
2023-01-03,13000,1.08
2023-01-04,14000,1.1
2023-01-05,12000,1.08
2023-01-06,12000,1.1
2023-01-07,16000,1.08
2023-01-08,24000,1.1
2023-01-09,19000,1.08

以下のように、無事グラフが描画されました!🎉

あと書き

記事をお読みいただき、ありがとうございました。
今回はStreamlitプロジェクトにおける、ディレクトリ設計について解説してきました。

一つのファイルにベタ書きするよりも、適切な設計に沿った開発を行うことで、個人だけでなくチームにとっても、良い開発体制を整えることができると感じました。

今後も皆さんに有益な情報を届けたいと思っております。

もし補足点・修正等ございましたら、ご気軽にコメントいただけますと幸いです。
では、失礼いたします!

参考文献

https://amazon.co.jp/dp/4048930656

https://baigie.me/engineerblog/frontend-dir-structure-design/

https://docs.sakai-sc.co.jp/article/software-engineering/separation-of-concerns.html

Discussion