【pandas入門】売上CSVをグラフ化＆ChatGPTで自動分析するBotを作成しよう！

## 今回のゴール!

**下記動画** のように、**CSVファイル** を送るだけで、**グラフと売上分析コメントを返してくれるチャットBot** を一緒に作っていきましょう！

https://x.com/wJPjd9mWRd20878/status/1933069895400632582

<h2 id="自己紹介">自己紹介</h2>

こんにちは！北海道の小売業でお惣菜担当として働いている社会人2年目のヒロです。

前回は機械学習入門として株価予測Botを作成しましたが、今回は実務に取り入れられる**本格的なチャットBotの作成**に挑戦しました！

なぜこのチャットBotを作ろうと思ったかというと、私はサラダと和惣菜の二つの売り場の担当を持っているので、**毎週売り上げの確認・グラフ化を行い分析して来週の販売計画を考えています。**
そして毎回思います。**グラフ作るのがめんどくさい…。**

そこで思いました、そうだ**pamdasで自動前処理＆グラフ作成APIを作ろう**と
最近使い方を学んだDifyなら簡単に作れると直感的にわかりました。
そしてそのAPIにChatGPTを組み合わせることで毎週30分かけて行っている週間分析を楽にできる！と確信し作成に至ります。
使用している技術が一部似ているのでまだ取り組んでない人は前回の記事からチャレンジしてみてください！

https://qiita.com/hiromu2001/items/6203402cfe3c148404d7

## 今回作るチャットBotについて

今回作成するのは、**売上データ（CSV）をアップロードするだけで、自動でグラフを生成し、ChatGPTが分析コメントまで返してくれるチャットボット**です！


### チャットBotの使い方

1. 売上CSVファイルをアップロード  
2. チャットで「分析して！」と送信  
3. 以下の内容が返ってきます

- **6種類のグラフ**（日別・曜日別など）
-  **各グラフに対する分析コメント**（OpenAI APIによる自動生成）

## 使用した技術

このBotを実現するために、以下の技術を組み合わせています

| 技術 | 用途 |
|------|------|
| `FastAPI` | Python製の高速なWebフレームワーク。グラフ生成＆OpenAI連携のAPIを構築 |
| `matplotlib` | グラフ描画の中心ライブラリ。画像として保存可能 |
| `seaborn` | スタイリッシュなグラフ描画。matplotlibの補助として活用 |
| `pandas` | 売上データ（CSV）の集計・加工に使用 |
| `OpenAI API` | GPT-4oモデルを使って、グラフごとの分析コメントを自動生成 |
| `Render` | 作成したAPIを無料で常時稼働させるホスティングサービス |


※**seaborn** は見た目を良くするために使っていますが、今回のグラフ作成の主役は**matplotlib**です！

## 作成手順

このチャットBotは、以下の流れで作成しました。

### 0.（前提条件）
コードはGitHubにプッシュ、使用するフォントファイルの準備、**OpenAPIのAPIkeyの準備も忘れずに！**
今回の記事では[こちら](https://moji.or.jp/ipafont/ipafontdownload/?utm_source=chatgpt.com)のフォントと[こちら](https://github.com/hiromu2001/sales-csv-sample)のダミーデータを使用してください。

<details>
<summary>全体コード（main.py）</summary>

```python
from fastapi import FastAPI, File, UploadFile  # FastAPIでAPI構築、ファイル受け取りに必要なモジュール
from fastapi.responses import JSONResponse  # JSON形式でレスポンスを返す
from fastapi.staticfiles import StaticFiles  # 静的ファイル（画像）を提供するためのモジュール
import pandas as pd  # データフレーム操作用のpandasライブラリ
import matplotlib.pyplot as plt  # グラフ描画用のmatplotlibライブラリ
import seaborn as sns  # 見た目の良いグラフを作るためのseabornライブラリ
import os  # OS関連の操作（ディレクトリ作成など）用ライブラリ
import uuid  # グラフ画像の一意なファイル名を生成するためのライブラリ
import aiofiles  # ファイルの非同期処理に使用するライブラリ（今回は未使用）
from openai import OpenAI  # OpenAIのAPIクライアントライブラリ
from dotenv import load_dotenv  # .envファイルから環境変数を読み込むライブラリ
import matplotlib.font_manager as fm  # matplotlibで日本語フォントを使うためのライブラリ

# 環境変数の読み込み（OpenAI APIキー）
load_dotenv()
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")  # .envに記述されたAPIキーを取得

client = OpenAI(api_key=OPENAI_API_KEY)  # OpenAIのクライアントを初期化

app = FastAPI()  # FastAPIアプリケーションのインスタンスを生成
app.mount("/static", StaticFiles(directory="static"), name="static")  # グラフ画像を保存するディレクトリを公開する

# グラフで日本語を表示するためのフォント設定（Render環境で文字化け防止）
font_path = "fonts/ipaexg.ttf"  # 使用するフォントファイルのパス
fm.fontManager.addfont(font_path)  # フォントマネージャーに追加
font_prop = fm.FontProperties(fname=font_path)  # フォントプロパティを作成
plt.rcParams["font.family"] = font_prop.get_name()  # matplotlibにフォントを適用

# グラフの画像を参照するベースURL（Renderで公開しているアドレス）
BASE_URL = "https://delica-insight-bot.onrender.com"

# 数値列をクリーンに整形する関数（カンマを削除してfloatに変換）
def clean_numeric_column(df, col):
    return df[col].replace(",", "", regex=True).astype(float)

# グラフを保存するディレクトリを準備する関数
def prepare_graph_dir():
    dir_path = "static/graphs"
    os.makedirs(dir_path, exist_ok=True)  # ディレクトリがなければ作成
    return dir_path

# matplotlibの軸やタイトルにフォントを適用する補助関数
def apply_font(ax):
    for label in ax.get_xticklabels() + ax.get_yticklabels():
        label.set_fontproperties(font_prop)
    ax.title.set_fontproperties(font_prop)
    ax.xaxis.label.set_fontproperties(font_prop)
    ax.yaxis.label.set_fontproperties(font_prop)

# データフレームから各種グラフを生成し画像として保存、そのURLを返す関数
def generate_graphs(df: pd.DataFrame, dir_path: str):
    plt.rcParams["font.family"] = font_prop.get_name()
    sns.set(style="whitegrid")  # seabornのスタイル設定
    urls = []  # グラフURLを格納するリスト

    # グラフ1: 日別売上金額の折れ線グラフ
    fig, ax = plt.subplots(figsize=(10, 5))
    daily = df.groupby("日付")["販売金額"].sum()
    daily.plot(marker="o", ax=ax)
    ax.set_title("日別売上金額の推移")
    ax.set_ylabel("売上金額（円）")
    ax.set_xticks(range(len(daily)))
    ax.set_xticklabels(daily.index, rotation=45)
    apply_font(ax)
    path = f"{dir_path}/graph1_{uuid.uuid4()}.png"
    plt.tight_layout()
    plt.savefig(path)
    urls.append({"title": "日別売上金額", "url": f"{BASE_URL}/static/graphs/{os.path.basename(path)}"})
    plt.close()

    # グラフ2: カテゴリ別売上構成比（円グラフ）
    fig, ax = plt.subplots(figsize=(6, 6))
    cat = df.groupby("カテゴリ")["販売金額"].sum()
    ax.pie(cat, labels=cat.index, autopct='%1.1f%%', startangle=140, textprops={'fontproperties': font_prop})
    ax.set_title("カテゴリ別売上構成比", fontproperties=font_prop)
    path = f"{dir_path}/graph2_{uuid.uuid4()}.png"
    plt.tight_layout()
    plt.savefig(path)
    urls.append({"title": "カテゴリ別売上構成", "url": f"{BASE_URL}/static/graphs/{os.path.basename(path)}"})
    plt.close()

    # グラフ3: 商品別販売数量ランキング（横棒グラフ）
    fig, ax = plt.subplots(figsize=(10, 5))
    top = df.groupby("商品名")["販売数量"].sum().sort_values(ascending=False).head(10)
    sns.barplot(x=top.values, y=top.index, ax=ax)
    ax.set_title("商品別販売数量ランキング Top10")
    ax.set_xlabel("販売数量")
    apply_font(ax)
    path = f"{dir_path}/graph3_{uuid.uuid4()}.png"
    plt.tight_layout()
    plt.savefig(path)
    urls.append({"title": "商品別販売数量", "url": f"{BASE_URL}/static/graphs/{os.path.basename(path)}"})
    plt.close()

    # グラフ4: 値引き率の分布（ヒストグラム）
    fig, ax = plt.subplots(figsize=(8, 4))
    discount = df["値引き率"].str.replace("%", "").astype(float)
    sns.histplot(discount, bins=10, kde=True, ax=ax)
    ax.set_title("値引き率の分布")
    apply_font(ax)
    path = f"{dir_path}/graph4_{uuid.uuid4()}.png"
    plt.tight_layout()
    plt.savefig(path)
    urls.append({"title": "値引き率の分布", "url": f"{BASE_URL}/static/graphs/{os.path.basename(path)}"})
    plt.close()

    # グラフ5: 廃棄率 vs 値引き率の散布図
    fig, ax = plt.subplots(figsize=(6, 6))
    discount = df["値引き率"].str.replace("%", "").astype(float)
    waste = df["廃棄率"].str.replace("%", "").astype(float)
    sns.scatterplot(x=discount, y=waste, ax=ax)
    ax.set_title("廃棄率 vs 値引き率")
    ax.set_xlabel("値引き率（%）")
    ax.set_ylabel("廃棄率（%）")
    apply_font(ax)
    path = f"{dir_path}/graph5_{uuid.uuid4()}.png"
    plt.tight_layout()
    plt.savefig(path)
    urls.append({"title": "廃棄率 vs 値引き率", "url": f"{BASE_URL}/static/graphs/{os.path.basename(path)}"})
    plt.close()

    return urls  # グラフの情報（タイトルとURL）をリストで返す

# ChatGPTに送るプロンプトを作成する関数
def create_prompt(df: pd.DataFrame, graphs) -> str:
    # 売上上位商品、カテゴリ、統計量を抽出
    top_product = df.groupby("商品名")["販売数量"].sum().sort_values(ascending=False).head(3)
    top_categories = df.groupby("カテゴリ")["販売金額"].sum().sort_values(ascending=False).head(3)
    discount_stats = df["値引き率"].str.replace("%", "").astype(float).describe().to_dict()
    waste_stats = df["廃棄率"].str.replace("%", "").astype(float).describe().to_dict()
    daily = df.groupby("日付")["販売金額"].sum().to_dict()
    category_share = df.groupby("カテゴリ")["販売金額"].sum().to_dict()
    product_quantity = df.groupby("商品名")["販売数量"].sum().sort_values(ascending=False).head(10).to_dict()

    # プロンプトのテンプレート
    prompt = f"""
あなたは小売部門の売上分析担当アシスタントです。
以下の1週間分の売上データとグラフに基づいて、次のようなアウトプットを生成してください。

- 商品別・カテゴリ別の売上傾向や気づきを分析
- 値引き率や廃棄率が高い商品への改善提案
- 来週に向けた販売戦略（仕入れ強化・POP・販促など）

【出力形式】
・箇条書きで3〜5個にまとめてください
・現場のデリカ担当者がすぐ動けるような視点で書いてください
・300文字以内で

- 売上上位商品: {top_product},
- 売上上位カテゴリ: {top_categories},
- 値引き率の統計情報: {discount_stats},
- 廃棄率の統計情報: {waste_stats},
- 日別売上金額: {daily},
- カテゴリ別売上金額: {category_share},
- 販売数量Top10商品: {product_quantity}

【参考グラフ】
"""
    for graph in graphs:
        prompt += f"\n- {graph['title']}:\n![]({graph['url']})"
    return prompt

# FastAPIのエンドポイント定義（CSVを受け取ってグラフ＆分析コメントを返す）
@app.post("/report")
async def generate_weekly_report(file: UploadFile = File(...)):
    try:
        contents = await file.read()  # アップロードされたファイルを読み込み
        df = pd.read_csv(pd.io.common.BytesIO(contents))  # CSVをDataFrameに変換
        df["販売金額"] = clean_numeric_column(df, "販売金額")  # 販売金額列をfloatに整形
        df["販売数量"] = clean_numeric_column(df, "販売数量")  # 販売数量列も整形

        dir_path = prepare_graph_dir()  # グラフ保存用ディレクトリを準備
        graphs = generate_graphs(df, dir_path)  # グラフを生成し、URLを取得

        prompt = create_prompt(df, graphs)  # ChatGPT用のプロンプトを生成
        response = client.chat.completions.create(
            model="gpt-4o",  # 使用モデル（GPT-4o）
            messages=[
                {"role": "system", "content": "あなたは熟練のデータアナリストです。"},
                {"role": "user", "content": prompt}
            ]
        )
        summary = response.choices[0].message.content  # ChatGPTの出力を取得

        markdown_images = "\n\n".join([f"**{g['title']}**\n\n![]({g['url']})" for g in graphs])
        full_text = f"{summary}\n\n---\n\n{markdown_images}"

        return JSONResponse(content={
            "html": full_text.replace("\n", "<br>")  # 改行をHTML用に変換して返す
        })

    except Exception as e:
        return JSONResponse(status_code=500, content={"error": str(e)})  # エラー時は500で返す
```
</details> 

<details> <summary>requirements.txt</summary>

```python

fastapi
uvicorn
pandas
matplotlib
seaborn
aiofiles
python-dotenv
openai

```
</details>


### 1. FastAPIでAPIの土台を作成

- `FastAPI` を使って、CSVファイルを受け取る `/report` エンドポイントを実装
- 受け取ったCSVを `pandas` で読み込み、必要な集計・加工処理を実施

---

### 2. グラフ画像の生成（matplotlib + seaborn）

- 加工したデータから `matplotlib` や `seaborn` を使って複数のグラフを生成
- 日本語表示のために `ipaexg.ttf` を使ってフォント設定を追加
- グラフを `.png` ファイルとして保存し、**URL経由でアクセス可能に**

---

### 3. OpenAI APIでグラフの分析コメントを生成

- 各グラフのタイトルや種類に応じて、**GPT-4oへプロンプトを送信**
- 返ってきた自然なコメントをそのまま**APIレスポンスに含める**

---

### 4. 生成した画像とコメントをまとめてレスポンスとして返す

- 画像ファイルのURLとコメントを**JSON形式**で返却

```json
{
  "results": [
    {
      "title": "ここにグラフタイトル入る",
      "url": "https://.../graph1.png",
      "comment": "ここにコメント入る"
    },
    ...
  ]
}
```
### 5. Renderにデプロイして公開
- FastAPIアプリを Render にデプロイ
※**Environment Variablesの設定を忘れずに行ってください**
これで、売上データから自動で「グラフ＋AIによる分析コメント」が返ってくるAPIの完成です！

### 6.  Difyでチャットボット化する
[Dify](https://cloud.dify.ai/apps)でチャットフロー作成し、下記画像のように設定

![スクリーンショット 2025-06-12 184801.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4079471/f64049fe-7c78-4dbe-8f47-3d8e33826125.png)

<details>
<summary>report_postはこちらの通りに作成してください
</summary>
1.上部メニューからツール選択
2.「カスタム」を選択
3.「カスタムツールを作成する」を選択後、ツール名を入力し、スキーマを下記通り入力

```json
{
  "openapi": "3.0.0",
  "info": {
    "title": "売上分析API",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://delica-insight-bot.onrender.com"
    }
  ],
  "paths": {
    "/report": {
      "post": {
        "summary": "CSVファイルをアップロードして分析結果を返す",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "成功時にsummaryとgraph URLを返す",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "summary": {
                      "type": "string"
                    },
                    "graphs": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "title": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "title",
                          "url"
                        ]
                      }
                    }
                  },
                  "required": [
                    "summary",
                    "graphs"
                  ]
                }
              }
            }
          }
        }
      }
    }
  }
}
```
</details>

<details>
<summary>各ノードの設定はこちら
</summary>

![スクリーンショット 2025-06-12 190041.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4079471/c6aac705-cf36-4a6c-be39-2ec4a4b18857.png)

![スクリーンショット 2025-06-12 190104.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4079471/af377661-4e18-481f-9a5b-615ae1e2c832.png)


![スクリーンショット 2025-06-12 190125.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4079471/2da922cc-aec4-470d-9f1f-74cbf2a8db7b.png)
</details>

### 7. 完成！
上部メニューのプレビューからCSVファイルを添付して分析・グラフ作成できていれば成功です！


## 苦労したこと
- matplotlibで日本語のラベル（商品名・カテゴリなど）を表示しようとしたところ、Render環境で文字化けして苦労しました。
原因は、**Render側に日本語フォントがインストールされていないため**です。
 
 解決方法は、[IPAexフォント](https://moji.or.jp/ipafont/ipafontdownload/?utm_source=chatgpt.com) を `fonts/ipaexg.ttf` としてプロジェクトに追加し、以下のように読み込んで設定しました。
```python
import matplotlib.font_manager as fm

font_path = "fonts/ipaexg.ttf"
fm.fontManager.addfont(font_path)
font_prop = fm.FontProperties(fname=font_path)
plt.rcParams["font.family"] = font_prop.get_name()
```

- OpenAI APIのプロンプトが長すぎて応答が遅くなる

グラフ情報や統計量をプロンプトに詰め込みすぎると、GPT-4oの**応答時間が遅くなるケース**がありました。

対策として、出力形式を**300字以内で箇条書きに限定**するよう明示しました。

<details>
<summary> ChatGPTに送信するプロンプト</summary>

あなたは小売部門の売上分析担当アシスタントです。
以下の1週間分の売上データとグラフに基づいて、次のようなアウトプットを生成してください。

- 商品別・カテゴリ別の売上傾向や気づきを分析
- 値引き率や廃棄率が高い商品への改善提案
- 来週に向けた販売戦略（仕入れ強化・POP・販促など）

【出力形式】
・箇条書きで3〜5個にまとめてください
・現場のデリカ担当者がすぐ動けるような視点で書いてください
・300文字以内で

- 売上上位商品: {top_product},
- 売上上位カテゴリ: {top_categories},
- 値引き率の統計情報: {discount_stats},
- 廃棄率の統計情報: {waste_stats},
- 日別売上金額: {daily},
- カテゴリ別売上金額: {category_share},
- 販売数量Top10商品: {product_quantity}

</details>

## 今後の発展

### 1. 機械学習による売上予測の自動化

時系列モデル（LightGBM, Prophet, LSTMなど）を導入することで、単なる売上分析にとどまらず、**未来の売上を日別・カテゴリ別に自動予測できるようにする**構想です。  
例えば「来週火曜日は唐揚げ弁当の需要が高い」という具体的な戦略立案に活かせます。

---

### 2. マルチモーダルなデータ連携

将来的には画像や音声、動画データといった非構造データも扱えるようにし、棚割り写真から「品出し状況の最適化」や「欠品の兆候」を抽出するなど、**多面的な売場支援を可能**にします。  
**OpenAIのGPT-4oや画像認識技術との連携が鍵になります。**

---

### 3. 店舗チャットツールとの統合

SlackやLINE、Microsoft Teamsと連携し、現場のスタッフが手軽に「今週の売れ筋を教えて」などの質問を投げかけられるチャットボットに拡張する予定です。  
これにより、**データ分析を日常業務の一部**として自然に取り込むUXが実現されます。

## ChatGPTが便利すぎた
実は今回のチャットBotでは、グラフ作成をメインの機能として考えていて、ChatGPTによる分析コメントはおまけ程度に考えていましたが、このChatGPT、高性能でかなり的を得た回答をしてきます。
おかげで私の毎週の作業時間が30分から10分くらいで終わるようになりました！

しかしChatGPTが**苦手とする部分**もありました…。
それが、

**デバック作業時にコードを一から書き直す**

という事です
私が書いたコードのデバックを頼んでも**一部コードを改変**されて**意図しない関数が作られていたり、省略されていたりしました。**
生成系AIを使いこなす人たちが**プロンプトが重要**というのがよくわかりました。

以上のことを踏まえ、私がChatGPTにデバックのプロンプトを送るときは**2つに分ける**という事を意識していました。

まず一つ目のプロンプトが

**「まず、以下のコードをそのまま読んで問題点を探してください。
この時点では、一切コードを変更しないでください。」**

ここでエラーの原因だけを教えてもらい、**コードは一切変更させません。**

次に2つ目のプロンプトで

**「では、上記の問題を踏まえて「最小限の変更だけ」を加えた修正版を出してください。」**

とするとデバック時のコード書き換えが少なくなりました！

## 実際に職場の人に使ってもらってみた
ここまできたら**実際に使ってもらおうという**事で同じ小売業の会社の先輩社員に使ってもらい、下記の通り感想をいただきました。

:::note info
実際に使用した感想
会社の売上確認ツールをCSV出力するだけでグラフが見られるのは便利だけど**部門によっては見たいグラフが違う。**
**今のこのグラフはヒロさんが担当しているサラダ部門と和惣菜部門の分析には適している**かもしれないけど**私が担当している寿司部門にはあまり気にする必要のないグラフもある**。
作成するグラフをプルダウンから選択できると使いやすいかも
:::

なかなか手厳しい事を言われてしまいましたがその通りだと思いました。
**自分一人で作ろうとするの無意識に自分が使いやすいように考えてしまいがち**です。
今後は作りながらフィードバックを貰い、軌道修正していくことで短時間で実用的なものが作れるのでは！と考えています。

## 最後に

今回作成した売上分析Botは、**CSVをアップロードするだけでグラフ＋ChatGPTの分析コメント**を返してくれる、現場でもすぐ活用できるツールです。  
FastAPIやOpenAI APIの組み合わせで、**業務効率化や販促判断の補助**にも繋がります。

引き続き、「AI×現場」の可能性を探っていきましょう
最後まで読んでいただきありがとうございました！