18
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[備忘録] Streamlitで複数画面構成を実現するページ分割機能の実装方法

Last updated at Posted at 2025-04-19

こんにちは!今回は、Streamlitで複数画面構成のアプリケーションを作成する方法について解説します。単一ファイルでシンプルなWebアプリが作れるStreamlitですが、機能が増えてくるとページを分けたくなりますよね。この記事では、Streamlit公式のマルチページ機能を使って、効率的に複数画面のアプリを構築する方法を学びましょう。

image.png

目次

環境準備

まずは必要なライブラリをインストールしましょう。

pip install streamlit
pip install pandas numpy matplotlib  # データ処理・可視化用

Streamlitのマルチページ機能とは

Streamlit 1.10.0以降では、公式に複数ページをサポートする機能が追加されました。この機能を使うことで、アプリケーションを複数のPythonファイルに分割し、構造化された複数画面のアプリを開発できます。

マルチページアプリケーションの主な特徴:

  1. ファイル構造に基づく画面分割: メインファイルとpagesフォルダ内のファイルが自動的に複数ページとして認識されます
  2. 自動生成されるサイドバーナビゲーション: ファイル名に基づいたナビゲーションが自動的に生成されます
  3. セッション状態を利用したページ間のデータ共有: st.session_stateでページ間でデータを共有できます

シナリオ:シンプルなデータ分析ダッシュボード

この記事では、以下の3ページから構成される「シンプルなデータ分析ダッシュボード」を例として実装します:

  1. ホームページ: アプリケーションの概要とデータのアップロード
  2. データ分析ページ: 基本的な統計情報と分析結果の表示
  3. データ可視化ページ: グラフやチャートによるデータの可視化

image.png

image.png

データ可視化ページにはタブがあり、タブを切り替えて表示内容を変更できます。

image.png

image.png

image.png

マルチページアプリケーションの構築

ディレクトリ構成

マルチページアプリケーションを作成するには、以下のようなディレクトリ構成を作成します:

データ分析ダッシュボード/
├── Home.py           # メインページ(ホーム)
├── utils.py          # 共通ユーティリティを格納
└── pages/            # 追加ページを格納するフォルダ(必須名称)
    ├── 1_📊_データ分析.py
    └── 2_📈_データ可視化.py

重要なポイント

  • pagesフォルダ名は変更できません(Streamlitがこの名前を特別に認識します)
  • ファイル名の先頭に数字やアイコン(例:1_📊_)を付けることで、メニューの表示順やアイコンを制御できます

共通ユーティリティの作成

まず、共通のユーティリティ関数をutils.pyに記述します:

# utils.py
import pandas as pd
import numpy as np

def create_sample_data():
    """サンプルデータ(売上データ)を生成する関数"""
    dates = pd.date_range(start="2023-01-01", end="2023-12-31", freq="D")
    sales = np.random.randint(100, 5000, size=len(dates))
    categories = np.random.choice(["食品", "衣料", "電化製品", "雑貨"], size=len(dates))
    
    data = pd.DataFrame({
        "日付": dates,
        "売上": sales,
        "カテゴリ": categories
    })
    return data

メインアプリケーション(Home.py)の作成

# Home.py
import streamlit as st
import pandas as pd
from utils import create_sample_data

# ページ設定
st.set_page_config(
    page_title="データ分析ダッシュボード",
    page_icon="📊",
    layout="wide"
)

st.title("データ分析ダッシュボード")
st.sidebar.success("上のメニューからページを選択してください")

# セッションステートの初期化(初回のみサンプルデータを作成)
if "data" not in st.session_state:
    st.session_state.data = create_sample_data()

# ホームページのコンテンツ
st.header("ホームページ")
st.write("このアプリケーションでは、売上データの分析と可視化を行います。")

# データアップロード機能
st.subheader("データのアップロード")
st.write("CSVファイルをアップロードするか、サンプルデータを使用してください。")

uploaded_file = st.file_uploader("CSVファイルを選択", type=["csv"])

if uploaded_file is not None:
    try:
        data = pd.read_csv(uploaded_file)
        st.success("ファイルのアップロードに成功しました!")
        st.session_state.data = data  # セッションステートにデータを保存
        st.dataframe(data.head())
    except Exception as e:
        st.error(f"エラーが発生しました: {e}")
else:
    st.info("ファイルがアップロードされていないため、サンプルデータを使用します。")
    st.dataframe(st.session_state.data.head())

# アプリケーションの使用方法
st.markdown("""
## 使用方法

1. CSVファイルをアップロードするか、サンプルデータを使用します。
2. サイドバーのメニューから各ページにアクセスできます:
   - **データ分析**: 基本統計情報とカテゴリ別分析
   - **データ可視化**: グラフによるデータの可視化
   
## データ形式

アップロードするCSVファイルは以下の列を含むことを推奨します:
- `日付`: 日付データ (YYYY-MM-DD形式)
- `売上`: 数値データ
- `カテゴリ`: カテゴリ/区分データ

上記の列がない場合も、アプリは動作しますが一部の機能が利用できない場合があります。
""")

データ分析ページの作成

次に、pagesフォルダ内に分析ページを作成します:

# pages/1_📊_データ分析.py
import streamlit as st
import pandas as pd
import numpy as np

# ページ設定
st.set_page_config(
    page_title="データ分析",
    page_icon="📊",
)

st.title("データ分析ダッシュボード")
st.header("データ分析")

# セッションステートからデータの取得
if "data" not in st.session_state:
    st.error("データがありません。ホームページでデータをアップロードしてください。")
    st.stop()  # これ以降の処理を中止

data = st.session_state.data

# 日付データの処理(存在する場合)
if "日付" in data.columns:
    # 日付型に変換
    try:
        data["日付"] = pd.to_datetime(data["日付"])
    except:
        st.warning("日付の変換に失敗しました。日付形式を確認してください。")

# 基本統計情報
st.subheader("基本統計情報")
# 数値データのみを対象に統計情報を表示
numeric_cols = data.select_dtypes(include=[np.number]).columns.tolist()
if numeric_cols:
    st.write(data[numeric_cols].describe())
else:
    st.warning("数値データが見つかりません。")

# カテゴリ別分析
st.subheader("カテゴリ別分析")
if "カテゴリ" in data.columns and "売上" in data.columns:
    # カテゴリごとの集計
    category_summary = data.groupby("カテゴリ")["売上"].agg(["sum", "mean", "count"])
    category_summary.columns = ["合計売上", "平均売上", "データ数"]
    category_summary = category_summary.sort_values("合計売上", ascending=False)
    
    st.write(category_summary)
    
    # カテゴリごとの売上合計をバーチャートで表示
    st.subheader("カテゴリ別売上合計")
    st.bar_chart(category_summary["合計売上"])
else:
    st.warning("「カテゴリ」または「売上」列がデータに存在しません。")

# 時系列分析
st.subheader("時系列分析")
if "日付" in data.columns and "売上" in data.columns:
    # 日付でグループ化するレベルを選択
    time_level = st.selectbox(
        "時間単位を選択",
        options=["日別", "週別", "月別", "四半期別", "年別"],
        index=2  # デフォルトは月別
    )
    
    # 選択した時間単位でグループ化
    if time_level == "日別":
        data["時間単位"] = data["日付"].dt.date
    elif time_level == "週別":
        data["時間単位"] = data["日付"].dt.to_period("W").dt.start_time.dt.date
    elif time_level == "月別":
        data["時間単位"] = data["日付"].dt.to_period("M").dt.start_time.dt.date
    elif time_level == "四半期別":
        data["時間単位"] = data["日付"].dt.to_period("Q").dt.start_time.dt.date
    else:  # 年別
        data["時間単位"] = data["日付"].dt.year
    
    # グループ化して集計
    time_summary = data.groupby("時間単位")["売上"].sum().reset_index()
    time_summary = time_summary.sort_values("時間単位")
    
    # 時系列チャートの表示
    st.line_chart(time_summary.set_index("時間単位"))
    
    # 詳細データの表示
    with st.expander("詳細データを表示"):
        st.dataframe(time_summary)
else:
    st.warning("「日付」または「売上」列がデータに存在しません。")

データ可視化ページの作成

続いて、データ可視化ページを作成します:

# pages/2_📈_データ可視化.py
import streamlit as st
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

# 日本語フォント設定
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['MS Gothic', 'Yu Gothic', 'Meiryo', 'sans-serif']
plt.rcParams['axes.unicode_minus'] = False

# ページ設定
st.set_page_config(
    page_title="データ可視化",
    page_icon="📈",
    layout="wide"
)

st.title("データ分析ダッシュボード")
st.header("データ可視化")

# セッションステートからデータの取得
if "data" not in st.session_state:
    st.error("データがありません。ホームページでデータをアップロードしてください。")
    st.stop()

data = st.session_state.data

# 日付データの処理(存在する場合)
if "日付" in data.columns:
    try:
        data["日付"] = pd.to_datetime(data["日付"])
        data[""] = data["日付"].dt.strftime("%Y-%m")
    except:
        st.warning("日付の変換に失敗しました。")

# タブで可視化を分類
tab1, tab2, tab3 = st.tabs(["カテゴリ分析", "時系列分析", "データ分布"])

# タブ1: カテゴリ分析
with tab1:
    st.subheader("カテゴリ別売上割合")
    
    if "カテゴリ" in data.columns and "売上" in data.columns:
        # カテゴリ別集計
        category_sales = data.groupby("カテゴリ")["売上"].sum()
        
        # 円グラフの表示
        fig, ax = plt.subplots(figsize=(10, 6))
        ax.pie(
            category_sales, 
            labels=category_sales.index, 
            autopct='%1.1f%%',
            startangle=90,
            shadow=True,
        )
        ax.axis('equal')  # 円を真円に
        plt.title("カテゴリ別売上割合")
        st.pyplot(fig)
        
        # データ表示
        st.dataframe(category_sales.reset_index().rename(
            columns={"index": "カテゴリ", "売上": "合計売上"}
        ))
    else:
        st.warning("「カテゴリ」または「売上」列がデータに存在しません。")

# タブ2: 時系列分析
with tab2:
    st.subheader("月別売上トレンド")
    
    if "日付" in data.columns and "売上" in data.columns:
        # 月別集計
        monthly_data = data.groupby("")["売上"].sum().reset_index()
        
        # グラフ表示
        fig, ax = plt.subplots(figsize=(12, 6))
        ax.bar(monthly_data[""], monthly_data["売上"])
        plt.xticks(rotation=45)
        plt.title("月別売上推移")
        plt.tight_layout()
        st.pyplot(fig)
        
        # カテゴリ別月次推移(存在する場合)
        if "カテゴリ" in data.columns:
            st.subheader("カテゴリ別月次売上推移")
            
            # カテゴリと月でグループ化
            category_monthly = data.groupby(["", "カテゴリ"])["売上"].sum().reset_index()
            
            # グラフ表示
            fig, ax = plt.subplots(figsize=(12, 8))
            
            # カテゴリごとに異なる色で線グラフを描画
            for category in category_monthly["カテゴリ"].unique():
                category_data = category_monthly[category_monthly["カテゴリ"] == category]
                ax.plot(category_data[""], category_data["売上"], marker='o', label=category)
            
            ax.legend()
            plt.xticks(rotation=45)
            plt.title("カテゴリ別月次売上推移")
            plt.tight_layout()
            st.pyplot(fig)
    else:
        st.warning("「日付」または「売上」列がデータに存在しません。")

# タブ3: データ分布
with tab3:
    st.subheader("売上データの分布")
    
    if "売上" in data.columns:
        # ヒストグラム
        fig, ax = plt.subplots(figsize=(10, 6))
        ax.hist(data["売上"], bins=20, alpha=0.7, density=True)

        # 平均線を追加
        mean_value = data["売上"].mean()
        ax.axvline(mean_value, color='red', linestyle='--', linewidth=1, label=f'平均: {mean_value:.1f}')
        plt.title("売上データのヒストグラム")
        ax.legend()
        st.pyplot(fig)
        
        # 箱ひげ図(カテゴリ別)
        if "カテゴリ" in data.columns:
            st.subheader("カテゴリ別売上分布")
            fig, ax = plt.subplots(figsize=(10, 6))
            
            # カテゴリごとに箱ひげ図を作成
            category_list = data["カテゴリ"].unique()
            category_data = [data[data["カテゴリ"] == category]["売上"] for category in category_list]
            ax.boxplot(category_data, tick_labels=category_list)
            
            plt.title("カテゴリ別売上の箱ひげ図")
            plt.xticks(rotation=45)
            plt.tight_layout()
            st.pyplot(fig)
    else:
        st.warning("「売上」列がデータに存在しません。")

アプリケーションの実行

アプリケーションを実行するには、メインディレクトリ(Home.pyがあるディレクトリ)で以下のコマンドを実行します:

streamlit run Home.py

すると、ブラウザが自動的に開き、アプリケーションが表示されます。サイドバーには自動的にページ一覧が表示され、ユーザーは各ページを切り替えることができます。

ページ間のデータ共有

Streamlitでは、st.session_stateを使用してページ間でデータを共有します。上記の実装でも、アップロードされたデータや分析結果をページ間で共有するために使用しています。

セッションステートの基本的な使い方

# データの保存
st.session_state.データ名 = データ値

# データの取得
if "データ名" in st.session_state:
    data = st.session_state.データ名
else:
    # データが存在しない場合の処理
    st.error("データが見つかりません")

セッションステートの活用例

  1. ユーザー入力の保持

    if "user_settings" not in st.session_state:
        st.session_state.user_settings = {"theme": "light", "language": "ja"}
    
    # 設定を変更
    selected_theme = st.selectbox("テーマを選択", ["light", "dark"], 
                                  index=0 if st.session_state.user_settings["theme"] == "light" else 1)
    
    # 変更を保存
    if selected_theme != st.session_state.user_settings["theme"]:
        st.session_state.user_settings["theme"] = selected_theme
    
  2. 計算結果のキャッシュ

    # 重い計算の結果をキャッシュ
    if "analysis_results" not in st.session_state:
        # 初回のみ計算を実行
        st.session_state.analysis_results = perform_heavy_analysis(data)
        
    # キャッシュした結果を使用
    results = st.session_state.analysis_results
    

実装のポイントとベストプラクティス

1. わかりやすいファイル名とページ構成

マルチページアプリケーションでは、ファイル名がそのままメニュー項目になります。以下のような命名規則がおすすめです:

1_🏠_ホーム.py
2_📊_データ分析.py
3_📈_可視化.py
  • 数字プレフィックス: 順序を制御
  • 絵文字: 視覚的にわかりやすく
  • 簡潔な名前: 機能を表す

2. 共通のユーティリティ関数とスタイルの一貫性

共通のユーティリティ関数は別ファイルに切り出し、各ページで再利用します。また、ページタイトルやレイアウトなども一貫性を持たせましょう。

# 全ページで同じ設定を使用
st.set_page_config(
    page_title="アプリ名 - ページ名",
    page_icon="アイコン",
    layout="wide"  # 一貫したレイアウト
)

# 一貫したタイトル構造
st.title("アプリケーション名")
st.header("ページ名")

3. エラー処理とユーザーガイダンス

データが存在しない場合や形式が正しくない場合のエラー処理を適切に行いましょう。

# データの存在確認
if "data" not in st.session_state:
    st.error("データがありません。ホームページでデータをアップロードしてください。")
    st.stop()  # これ以降の処理を中止

# データの形式確認
if "必要な列" not in st.session_state.data.columns:
    st.warning("「必要な列」がデータに存在しません。一部の機能が利用できない場合があります。")

4. レスポンシブな設計

さまざまな画面サイズに対応するため、レスポンシブな設計を心がけましょう。

# 画面幅に応じたレイアウト
if st.session_state.get("screen_width", "wide") == "wide":
    col1, col2 = st.columns([2, 1])  # 大画面では2:1の比率
else:
    col1, col2 = st.columns([1, 1])  # 小画面では1:1の比率

まとめ

マインドマップ (39).png

この記事では、Streamlitの公式マルチページ機能を使って複数画面構成のアプリケーションを実装する方法を学びました。実際のデータ分析ダッシュボードの例を通じて、以下のポイントを解説しました:

  1. 適切なフォルダ構成とファイル名の付け方
  2. ページ間でのデータ共有方法
  3. 一貫性のあるユーザーインターフェース設計
  4. エラー処理とユーザーガイダンス

Streamlitの公式マルチページ機能を使うことで、メンテナンスしやすく拡張性の高いWebアプリケーションを開発できます。ぜひ、この記事で紹介した方法を活用して、あなた自身のアイデアを形にしてみてください!

参考リンク

最新の実装方法や注意事項は公式情報を確認してください。

免責事項

本記事の作成にあたり、文章や図解の生成にClaude Sonnet, Feloを、ファクトチェックにChatGPTを活用しました。最終的な編集と確認は筆者が行っています。

18
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?