PythonのWebフレームワーク Streamlit でアプリを作ってみた

本記事は[DeNA 23 新卒 Advent Calendar 2022](https://qiita.com/advent-calendar/2022/dena-23-shinsotsu) の19日目の記事です。

https://qiita.com/advent-calendar/2022/dena-23-shinsotsu

こんにちは。DeNA23卒内定者の juni です。
現在大学院修士2年で、情報系の研究室でアルゴリズムと最適化に関する研究を行っています。

今回は、Pythonのデータサイエンス用Webフレームワークの [Streamlit](https://streamlit.io/) について、簡単なアプリケーションを作成しつつ紹介します！

# Streamlit とは
https://streamlit.io/

Streamlit は、機械学習やデータサイエンス向けのオープンソースWebフレームワークです。特徴として、
- Pythonで全てのコードを記述することができ、HTMLやCSS、JavaScript といったフロントエンドの知識が不要
- Pythonのライブラリとの相性が良く、PandasやMatplotlibなどを用いて表・グラフをそのまま表示することができる
- 公式でデプロイ用のクラウドサーバー（Streamlit Cloud）が用意されており、無料で簡単にデプロイできる

があります。同じPythonのフレームワークであるDjangoやFlaskに比べて、機能は限られている分、手軽に開発出来るところが強みです。

# 作ったもの
まずは簡単なアプリケーションを実装しようと思い、学食のメニューの組合せを自動で選ぶアプリを作成しました。（メニューは実データではなくサンプルです。）

### Webアプリ
[学食メニュー組合せ機](https://yumekumo-streamlit-restaurant-menu-app-5egqsm.streamlit.app/)

![FireShot Capture 025 - app · Streamlit - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1009617/60ec2190-2e2d-556d-1fc0-fde536b5da93.png)

### GitHubリポジトリ

https://github.com/yumekumo/streamlit-restaurant-menu

# 導入手順
Webアプリ開発ではバージョンの干渉を防ぐため、 pipenv などの仮想環境を準備することをお勧めします。今回は Python3.11 で導入しましたが、バージョン3.7以降であれば問題なく動くと思います。

参考：[Pipenvを使ったPython開発まとめ - Qiita](https://qiita.com/y-tsutsu/items/54c10e0b2c6b565c887a)
### インストール
```shell
$ pip install streamlit
```
ちなみに、Streamlit上で描画できるライブラリ（NumpyやMatplotlib,Pandasなど）が導入されていない場合、同時にインストールしてくれます。
### 動作確認
```shell
$ streamlit hello
```

# 実装
学食のメニュー情報を記した[csvファイル](https://github.com/yumekumo/streamlit-restaurant-menu/blob/main/menu.csv)を準備し、`app.py`として以下のように実装しました。

## Pythonコード

このプログラムでは、画面描画を`view`関数、商品の選択処理を`foods_select`関数に分けています。`foods_select`関数では、Pythonのrandomモジュールを用いて価格、カロリーの制限を満たす商品を選んで出力しています。

Streamlitはコーディング規約が明確に定められていないため、書き方が人によって自由な気がします。適宜分かりやすいように関数やクラス、モジュールに分割するのが良いです。


```python:app.py
import streamlit as st
import pandas as pd
import random

MENU_PATH = './menu.csv'

menu = pd.read_csv(MENU_PATH, index_col=0)

def foods_select(max_price, max_calorie):
    lottery_ids = list(menu.index)
    result_ids = []
    total = {'price':0, 'calorie':0, 'salt':0}

    while(total['price'] < max_price and total['calorie'] < max_calorie \
          and len(lottery_ids) != 0):
        selected_id = random.choice(lottery_ids)
        selected_food = menu.loc[selected_id]
        if total['price'] + selected_food.price <= max_price \
           and total['calorie'] + selected_food.calorie <= max_calorie:
            result_ids.append(selected_id)
            total['price'] += selected_food.price
            total['calorie'] += selected_food.calorie
            total['salt'] += selected_food.salt

        lottery_ids.remove(selected_id)

    return result_ids, total


def view():
    st.title('学食メニュー組合せ機🍚')

    # 結果表示用
    c_top = st.container()
    # 実行ボタン
    c_button = st.empty()

    st.subheader('設定')
    price_input = st.number_input(
        '予算上限を決めてください（円）',
        300, 3000, 1000, 100,
    )

    # チェックボックスが空の場合はinfになる
    calorie_input = float('inf')
    if st.checkbox('カロリーを気にする方へ'):
        calorie_input = st.number_input(
            'カロリー上限を決めてください（kcal）',
            400, 3000, 700, 100,
        )

    if c_button.button('メニューを決める'):
        result_ids, total = foods_select(price_input, calorie_input)
        c_top.write(menu.loc[result_ids].style.format({'salt':'{:.1f}'}))
        c_top.info("合計 {}円 {}kcal 塩分 {}g".format(int(total['price']), int(total['calorie']), round(total['salt'],1)))

    # 折りたたみ要素
    with st.expander('メニューを表示'):
        st.subheader('メニュー表')
        st.write(menu.style.format({'salt':'{:.1f}'}))


view()
```
### ローカルでの起動
```shell
$ streamlit run app.py
```

# Streamlit Cloud へのデプロイ方法

デプロイ方法は非常に簡単です。
あらかじめ作成したアプリケーションをGitHubに上げておきます。

## 1. Streamlit Cloudにアカウントを登録する

下記のリンクから、GmailやGitHubアカウントを使って登録ができます。

https://streamlit.io/cloud


## 2. ホーム画面からNew appボタンを選択する
選択後にGitHubのサインインを求められる場合があります。
![スクリーンショット 2022-12-13 21.23.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1009617/b8fdb47d-426c-240f-8b6f-dbb303c43bf4.png)

## 3. デプロイのための設定を行う
リポジトリ、ブランチ、メインファイルの設定を行えば完了です。
![スクリーンショット 2022-12-16 8.21.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1009617/934c869e-3368-66a8-3e53-33278a8f0ca9.png)
Advansed settings では、Pythonのバージョン、環境変数を設定することができます。
![スクリーンショット 2022-12-13 21.29.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1009617/ec1237a8-6f20-ab24-b7fd-da56164c972e.png)

### (補足)Streamlit Cloud 無料版の制約について
無料版では、
- 1アプリケーションあたりメモリ1GB
- プライベートアプリは一つまで
- パブリックアプリは無制限

という仕様になっています。

※ プライベートアプリ：Streamlit Cloudにログインしており、管理者が指定したメールアドレスのアカウントのみ閲覧できる。

# まとめ
Streamlitは、フロント実装とデプロイの負担をできる限り軽減したフレームワークで、手軽にアプリケーションを作成・デプロイすることができました。
Pythonのみで書けるため、Webアプリケーション開発の敷居が下がったように感じます。（もちろん、Streamlitでは実装できない要件も多いですが...）
コーディングがとても楽しかったので、個人開発でもっと使ってみようと思います。

## 参考
[Streamlit documentation](https://docs.streamlit.io/)