Lecture 1環境構築 — Python分析環境を作る

12:00

環境構築 — Python分析環境を作る

このコースでは、Anthropic が提供する AI コーディングツール「Claude Code」を使って、Python によるデータ分析をゼロから実践します。第1回では、Python の仮想環境を作成し、データ分析に必要なライブラリ(pandas、matplotlib、seaborn、Jupyter)をインストールして、プロジェクトのディレクトリ構造を整えます。Claude Code に指示するだけで、面倒な環境構築を数分で完了させましょう。

Python と Claude Code のインストール

まず、分析に必要なツールを準備します。Python は公式サイト(https://www.python.org/)から最新の 3.11 以上をダウンロードしてインストールしてください。インストール時に「Add Python to PATH」にチェックを入れることを忘れないようにしましょう。

python --version
# Python 3.11.x 以上を推奨

pip --version
# pip 23.x 以上

次に Claude Code をインストールします。Claude Code は Anthropic の公式 CLI ツールで、ターミナル上で自然言語の指示を出すだけでコードの生成・編集・実行を行ってくれます。

npm install -g @anthropic-ai/claude-code

インストールが完了したら、プロジェクト用のディレクトリを作成して Claude Code を起動します。

mkdir sales-analysis
cd sales-analysis
claude

Claude Code が起動すると対話型のプロンプトが表示されます。ここからすべての作業を自然言語で指示していきます。

仮想環境の作成とライブラリインストール

Python のプロジェクトでは、仮想環境(venv)を使ってライブラリを管理するのがベストプラクティスです。Claude Code に次のように指示してみましょう。

> Python の仮想環境を作成して、データ分析に必要な
  pandas、matplotlib、seaborn、jupyter、openpyxl を
  インストールして。requirements.txt も作って

Claude Code は以下のようなコマンドを生成して実行します。

python -m venv venv

# Windows の場合
venv\Scripts\activate

# macOS/Linux の場合
source venv/bin/activate

pip install pandas matplotlib seaborn jupyter openpyxl
pip freeze > requirements.txt

各ライブラリの役割を確認しておきましょう。

ライブラリ 役割
pandas データの読み込み・加工・集計を行うデータ分析の中核ライブラリ
matplotlib グラフ描画の基盤ライブラリ。棒グラフ、折れ線、散布図など
seaborn matplotlib をベースにした統計グラフライブラリ。美しいグラフを簡単に作成
jupyter 対話的にコードを実行できるノートブック環境
openpyxl Excel ファイルの読み書きに使用

インストールが完了したら、Python のインタラクティブシェルで動作確認します。

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
print(f"pandas: {pd.__version__}")
print(f"matplotlib: {plt.matplotlib.__version__}")
print(f"seaborn: {sns.__version__}")

すべてのライブラリがエラーなくインポートできれば環境構築は成功です。

プロジェクトのディレクトリ構造

データ分析プロジェクトでは、ファイルを整理して管理することが重要です。Claude Code に構造を作ってもらいましょう。

> データ分析プロジェクトの標準的なディレクトリ構造を作って。
  data/ にはraw(元データ)とprocessed(加工済み)フォルダ、
  notebooks/ にはJupyterノートブック、
  reports/ にはレポート出力、
  src/ にはPythonスクリプトを置く構造にして

Claude Code が生成するディレクトリ構造は以下の通りです。

sales-analysis/
├── data/
│   ├── raw/            # 元データ(CSV、Excel など)
│   └── processed/      # 加工済みデータ
├── notebooks/          # Jupyter ノートブック
├── reports/            # 分析レポート(HTML、PDF)
│   └── figures/        # グラフ画像
├── src/                # Python スクリプト
├── requirements.txt    # ライブラリ一覧
├── .gitignore          # Git 除外設定
└── README.md           # プロジェクト説明

さらに .gitignore を作成して、不要なファイルをバージョン管理から除外します。

> Pythonデータ分析プロジェクト用の .gitignore を作って。
  venv、__pycache__、.ipynb_checkpoints、
  data/raw/ の大きなファイルを除外して

生成される .gitignore の内容です。

venv/
__pycache__/
.ipynb_checkpoints/
*.pyc
data/raw/*.csv
data/raw/*.xlsx
.DS_Store

Jupyter Notebook の起動と動作確認

環境構築の最終確認として Jupyter Notebook を起動します。

jupyter notebook

ブラウザが自動で開き、Jupyter のファイルブラウザが表示されます。notebooks/ フォルダに移動して「New」ボタンから「Python 3」を選択して新しいノートブックを作成しましょう。

最初のセルに以下を入力して実行(Shift + Enter)します。

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# テスト用の簡単なデータフレーム
df = pd.DataFrame({
    '商品': ['りんご', 'バナナ', 'みかん'],
    '売上': [150, 200, 120]
})

print(df)
plt.bar(df['商品'], df['売上'])
plt.title('テスト: 売上グラフ')
plt.show()

棒グラフが表示されれば、pandas と matplotlib が正常に動作しています。このノートブックを notebooks/00_setup_test.ipynb として保存しておきましょう。

なお、Jupyter Notebook を使わずに Claude Code 上で直接 Python スクリプトを実行することもできます。このコースでは両方のアプローチを使い分けていきます。

練習問題

  1. 自分の PC に Python 仮想環境を作成し、pandas・matplotlib・seaborn・jupyter をインストールしてください
  2. Claude Code に「requirements.txt の中身を表示して」と指示して、インストール済みライブラリの一覧を確認してください
  3. Jupyter Notebook を起動し、新しいノートブックで import pandas as pd が動作することを確認してください
  4. Claude Code に「このプロジェクトの README.md を作成して。ECサイトの売上データを分析するプロジェクトだと説明して」と指示して、README を生成してください

参考リンク

Lecture 2pandasの基本 — データフレームを操作する

12:00

pandasの基本 — データフレームを操作する

pandas は Python のデータ分析における最も重要なライブラリです。Excel のスプレッドシートのような表形式のデータを、プログラムで自在に操作できます。この講義では、pandas の2つの基本データ構造「DataFrame」と「Series」を理解し、データの確認・選択・フィルタリングの基本操作を Claude Code と一緒に学びます。

DataFrame と Series の基本

pandas のデータ構造は大きく2つあります。DataFrame は行と列を持つ2次元の表で、Series は1列分のデータ(1次元配列)です。まず Claude Code にサンプルデータを作ってもらいましょう。

> pandasでECサイトの売上サンプルデータを作って。
  DataFrame で、注文ID、商品名、カテゴリ、単価、数量、
  注文日の6列、10行分のデータを作成して

Claude Code が生成するコードは以下のようになります。

import pandas as pd

df = pd.DataFrame({
    '注文ID': [1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010],
    '商品名': ['ワイヤレスイヤホン', 'USBケーブル', 'モバイルバッテリー', 'マウスパッド',
              'キーボード', 'ワイヤレスイヤホン', 'Webカメラ', 'USBハブ',
              'モニターアーム', 'マウスパッド'],
    'カテゴリ': ['音響', 'ケーブル', 'バッテリー', 'アクセサリ',
               '入力機器', '音響', '映像', 'ケーブル', 'モニター', 'アクセサリ'],
    '単価': [3980, 580, 2480, 890, 7980, 3980, 4500, 1980, 5980, 890],
    '数量': [2, 5, 1, 3, 1, 1, 2, 1, 1, 4],
    '注文日': pd.to_datetime([
        '2025-01-05', '2025-01-07', '2025-01-10', '2025-01-12',
        '2025-01-15', '2025-01-18', '2025-01-20', '2025-01-22',
        '2025-01-25', '2025-01-28'
    ])
})

print(df)

このコードを実行すると、10行6列の表形式データが表示されます。DataFrame は行(インデックス)と列(カラム)で構成されており、各列は Series オブジェクトです。

特定の列を取り出すと Series になります。

# 1列だけ取り出す → Series
prices = df['単価']
print(type(prices))   # <class 'pandas.core.series.Series'>
print(prices)

Series は NumPy 配列にインデックス(ラベル)が付いたもので、1次元のデータを効率的に扱えます。

データの概要を確認する

実際の分析では、まずデータの全体像を把握することが最重要です。Claude Code にデータ確認の方法を聞いてみましょう。

> さっき作ったDataFrameの概要を確認したい。
  行数、列名、データ型、基本統計量を表示して

pandas にはデータ確認用のメソッドが豊富に用意されています。

# 先頭5行を表示
print(df.head())

# 末尾3行を表示
print(df.tail(3))

# データの形状(行数, 列数)
print(df.shape)       # (10, 6)

# 列名の一覧
print(df.columns)     # Index(['注文ID', '商品名', 'カテゴリ', '単価', '数量', '注文日'], dtype='object')

# データ型と非null数の一覧
print(df.info())

# 数値列の基本統計量(平均、標準偏差、最小、最大、四分位)
print(df.describe())

info() メソッドは特に重要で、各列のデータ型(dtype)と欠損値の有無を一目で確認できます。出力は以下のようになります。

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10 entries, 0 to 9
Data columns (total 6 columns):
 #   Column  Non-Null Count  Dtype
---  ------  --------------  -----
 0   注文ID     10 non-null     int64
 1   商品名      10 non-null     object
 2   カテゴリ     10 non-null     object
 3   単価       10 non-null     int64
 4   数量       10 non-null     int64
 5   注文日      10 non-null     datetime64[ns]
dtypes: datetime64[ns](1), int64(3), object(2)

describe() は数値列(int64, float64)の統計情報を自動で計算します。平均(mean)、標準偏差(std)、最小値(min)、最大値(max)、四分位数(25%, 50%, 75%)が表示されます。

列の選択とフィルタリング

データ分析では、必要な列だけを取り出したり、条件に合う行だけを抽出したりする操作が頻繁に発生します。

> DataFrameから商品名と単価の2列だけ取り出して。
  さらに、単価が3000円以上の行だけフィルタリングして

Claude Code が生成するコードで、列選択とフィルタリングの基本を学びます。

# 複数列の選択(リストで列名を指定)
subset = df[['商品名', '単価']]
print(subset)

# 条件によるフィルタリング
expensive = df[df['単価'] >= 3000]
print(expensive)

# 複数条件の組み合わせ(& = AND、| = OR)
result = df[(df['単価'] >= 2000) & (df['数量'] >= 2)]
print(result)

# カテゴリが「音響」の行を抽出
audio = df[df['カテゴリ'] == '音響']
print(audio)

# 複数値のいずれかに一致(isin)
selected = df[df['カテゴリ'].isin(['音響', '入力機器'])]
print(selected)

フィルタリングの仕組みを理解しましょう。df['単価'] >= 3000 は各行に対して True/False の Series を返します。この Boolean Series を df[...] に渡すことで、True の行だけが抽出されます。

複数条件を組み合わせる場合は、各条件を () で囲んで &(AND)や |(OR)で接続します。Python の and/or は使えないので注意してください。

データ型の確認と変換

データ型(dtype)は分析の正確性に直結します。文字列として読み込まれた数値データは集計できませんし、日付データが文字列のままだと時系列分析ができません。

> DataFrameの各列のデータ型を確認して、
  必要に応じてデータ型を変換するコードを書いて
# データ型の確認
print(df.dtypes)

# 文字列を数値に変換
df['単価'] = pd.to_numeric(df['単価'], errors='coerce')

# 文字列を日付に変換
df['注文日'] = pd.to_datetime(df['注文日'], format='%Y-%m-%d')

# 数値をカテゴリ型に変換(メモリ効率化)
df['カテゴリ'] = df['カテゴリ'].astype('category')

# 型変換の確認
print(df.dtypes)

errors='coerce' を指定すると、変換できない値は自動的に NaN(欠損値)に置き換えられます。これにより変換エラーでプログラムが止まることを防げます。

新しい列を計算して追加することもよく行います。

# 売上金額 = 単価 × 数量
df['売上金額'] = df['単価'] * df['数量']
print(df[['商品名', '単価', '数量', '売上金額']])

# 注文日から月を抽出
df['注文月'] = df['注文日'].dt.month
print(df[['注文日', '注文月']])

dt アクセサを使うと datetime 型の列から年(year)、月(month)、日(day)、曜日(dayofweek)などを簡単に取り出せます。

練習問題

  1. Claude Code に「20行分のECサイト売上データを作って。商品名、カテゴリ、単価、数量、顧客名、地域の列を含めて」と指示して DataFrame を作成してください
  2. 作成した DataFrame に対して head()info()describe() を実行し、データの概要を確認してください
  3. 単価が 5000 円以上、かつ数量が 2 以上の注文を抽出してください
  4. 「売上金額」列(単価 * 数量)を新しく追加し、売上金額の合計を計算してください
  5. Claude Code に「地域ごとの注文数を数えて」と指示して、value_counts() の使い方を確認してください

参考リンク

Lecture 3データ読み込み — CSVファイルを扱う

12:00

データ読み込み — CSVファイルを扱う

実際のデータ分析では、Excel やデータベースからエクスポートした CSV ファイルを読み込むところから始まります。この講義では、pandas の read_csv を使った CSV ファイルの読み込み方法、文字エンコーディングの扱い、さらに Claude Code を使ってコース全体で使用するサンプル売上データを生成する方法を学びます。

Claude Code でサンプルデータを生成する

まず、このコース全体を通して使用する架空のECサイト売上データを Claude Code に作ってもらいましょう。リアルな分析を体験するために、1年分・1000行程度のデータを生成します。

> ECサイトの売上データCSVを生成するPythonスクリプトを作って。
  以下の条件で:
  - 期間: 2024年1月〜12月の1年分
  - 行数: 1000件
  - 列: 注文ID, 注文日, 商品名, カテゴリ, 単価, 数量, 顧客ID, 顧客名, 地域, 支払方法
  - カテゴリは家電、衣料品、食品、書籍、日用品の5種類
  - 地域は関東、関西、中部、九州、東北の5地域
  - 季節変動を入れて(12月は売上増加)
  - data/raw/sales_2024.csv に保存して

Claude Code が生成するスクリプトは以下のようになります。

import pandas as pd
import numpy as np
from datetime import datetime, timedelta

np.random.seed(42)
n_records = 1000

# 商品マスタ
products = {
    '家電': [('ワイヤレスイヤホン', 3980), ('モバイルバッテリー', 2480),
             ('スマートウォッチ', 12800), ('Bluetoothスピーカー', 5980)],
    '衣料品': [('Tシャツ', 1980), ('デニムパンツ', 4980),
              ('スニーカー', 7980), ('トートバッグ', 3280)],
    '食品': [('コーヒー豆セット', 1580), ('ナッツ詰め合わせ', 980),
            ('オリーブオイル', 1280), ('チョコレートギフト', 2180)],
    '書籍': [('Python入門', 2640), ('データ分析実践', 3080),
            ('ビジネス戦略論', 1760), ('英語学習法', 1540)],
    '日用品': [('洗剤セット', 890), ('タオルセット', 1480),
             ('収納ボックス', 1280), ('LED電球4個', 1980)]
}

categories = list(products.keys())
regions = ['関東', '関西', '中部', '九州', '東北']
payment_methods = ['クレジットカード', '銀行振込', 'コンビニ払い', '電子マネー']

# 季節変動を考慮した注文日生成
dates = []
for _ in range(n_records):
    month = np.random.choice(range(1, 13), p=[
        0.07, 0.06, 0.08, 0.07, 0.08, 0.07,
        0.09, 0.08, 0.08, 0.09, 0.10, 0.13  # 12月が多い
    ])
    day = np.random.randint(1, 29)
    dates.append(datetime(2024, month, day))

records = []
for i in range(n_records):
    cat = np.random.choice(categories)
    product_name, base_price = products[cat][np.random.randint(len(products[cat]))]
    price = int(base_price * np.random.uniform(0.9, 1.1))
    qty = np.random.choice([1, 1, 1, 2, 2, 3], p=[0.4, 0.2, 0.1, 0.15, 0.1, 0.05])

    records.append({
        '注文ID': f'ORD-{i+1:04d}',
        '注文日': dates[i].strftime('%Y-%m-%d'),
        '商品名': product_name,
        'カテゴリ': cat,
        '単価': price,
        '数量': qty,
        '顧客ID': f'C{np.random.randint(1, 201):04d}',
        '顧客名': f'顧客{np.random.randint(1, 201):03d}',
        '地域': np.random.choice(regions, p=[0.35, 0.25, 0.18, 0.12, 0.10]),
        '支払方法': np.random.choice(payment_methods)
    })

df = pd.DataFrame(records)
df.to_csv('data/raw/sales_2024.csv', index=False, encoding='utf-8-sig')
print(f"生成完了: {len(df)}行")
print(df.head())

encoding='utf-8-sig' を指定すると、Excel で開いたときに日本語が文字化けしない BOM 付き UTF-8 で保存されます。

pd.read_csv でファイルを読み込む

保存した CSV ファイルを読み込みましょう。Claude Code に指示します。

> data/raw/sales_2024.csv を pandas で読み込んで、
  先頭5行とデータの概要を表示して
import pandas as pd

# 基本的な読み込み
df = pd.read_csv('data/raw/sales_2024.csv')
print(df.head())
print(df.info())
print(df.describe())

read_csv には多くのオプションがあり、実務で頻繁に使います。

# エンコーディングを明示的に指定
df = pd.read_csv('data/raw/sales_2024.csv', encoding='utf-8-sig')

# 特定の列だけ読み込む
df = pd.read_csv('data/raw/sales_2024.csv', usecols=['注文日', '商品名', '単価', '数量'])

# 日付列を自動的に datetime 型に変換
df = pd.read_csv('data/raw/sales_2024.csv', parse_dates=['注文日'])

# ヘッダがない場合は列名を指定
df = pd.read_csv('data.csv', header=None, names=['col1', 'col2', 'col3'])

# 先頭100行だけ読む(大きなファイルの確認用)
df = pd.read_csv('data/raw/sales_2024.csv', nrows=100)

# 区切り文字がタブの場合
df = pd.read_csv('data.tsv', sep='\t')

文字エンコーディングの問題と対処

日本語の CSV ファイルを扱う際に最も多いトラブルが文字エンコーディングの問題です。Windows で作成された CSV は Shift-JIS(cp932)、Web 系は UTF-8 が多いですが、自動判別できない場合があります。

> この CSV ファイルが文字化けする。
  エンコーディングを自動検出して正しく読み込んで

Claude Code は chardet ライブラリを使った検出コードを生成します。

# エンコーディング自動検出
import chardet

with open('data/raw/sales_2024.csv', 'rb') as f:
    raw = f.read(10000)
    result = chardet.detect(raw)
    print(f"検出されたエンコーディング: {result['encoding']}")
    print(f"信頼度: {result['confidence']:.2%}")

# 検出結果を使って読み込む
df = pd.read_csv('data/raw/sales_2024.csv', encoding=result['encoding'])

よく使うエンコーディングの指定方法をまとめます。

ファイルの出所 エンコーディング指定
Windows Excel encoding='cp932' または encoding='shift_jis'
macOS/Linux encoding='utf-8'
BOM 付き UTF-8 encoding='utf-8-sig'
古い日本語システム encoding='euc-jp'

エンコーディングエラーが出た場合は errors='replace' を付けると、読めない文字を置換して読み込みを継続できます。

df = pd.read_csv('data.csv', encoding='utf-8', errors='replace')

Excel・JSON ファイルの読み込み

pandas は CSV 以外にも様々なファイル形式に対応しています。

> Excel ファイルと JSON ファイルの読み込み方法を
  サンプルコード付きで教えて
# Excel ファイルの読み込み(openpyxl が必要)
df_excel = pd.read_excel('data/raw/report.xlsx', sheet_name='Sheet1')

# 複数シートを一度に読み込む
all_sheets = pd.read_excel('data/raw/report.xlsx', sheet_name=None)
for sheet_name, sheet_df in all_sheets.items():
    print(f"シート名: {sheet_name}, 行数: {len(sheet_df)}")

# JSON ファイルの読み込み
df_json = pd.read_json('data/raw/orders.json')

# ネストした JSON(records 形式)
df_json = pd.read_json('data/raw/orders.json', orient='records')

# Web上のCSVを直接読み込む(URL指定)
df_web = pd.read_csv('https://example.com/data/sales.csv')

読み込んだデータは加工後に CSV として保存する場面も多いです。

# DataFrame を CSV に保存
df.to_csv('data/processed/cleaned_sales.csv', index=False, encoding='utf-8-sig')

# Excel に保存
df.to_excel('data/processed/report.xlsx', index=False, sheet_name='売上データ')

# JSON に保存
df.to_json('data/processed/sales.json', orient='records', force_ascii=False)

force_ascii=False を指定すると、日本語がそのまま保存されます(デフォルトでは Unicode エスケープされます)。

練習問題

  1. Claude Code に「1000行の売上データ CSV を生成して data/raw/sales_2024.csv に保存して」と指示し、サンプルデータを作成してください
  2. 生成した CSV を pd.read_csv で読み込み、head()info()describe() でデータの概要を確認してください
  3. parse_dates=['注文日'] オプションを付けて読み込み、注文日が datetime 型になっていることを dtypes で確認してください
  4. usecols パラメータを使って「注文日」「商品名」「単価」「数量」の4列だけ読み込んでください
  5. 読み込んだ DataFrame に「売上金額」列を追加して、data/processed/sales_with_amount.csv に保存してください

参考リンク

Lecture 4データクリーニング — 欠損値と異常値を処理する

12:00

データクリーニング — 欠損値と異常値を処理する

実際のビジネスデータは完璧ではありません。入力ミス、システム障害、手動集計のズレなど、さまざまな理由でデータには欠損値(NaN)や異常値が含まれます。「ゴミを入れればゴミが出る(Garbage In, Garbage Out)」という格言の通り、分析結果の質はデータの品質に直結します。この講義では、Claude Code を使ってデータクリーニングの実践的な手法を学びます。

欠損値の検出と確認

まず、前回生成した売上データに意図的に欠損値を混ぜたバージョンを Claude Code に作ってもらいましょう。

> sales_2024.csv を読み込んで、ランダムに5%の欠損値を
  各列に入れたデータを作って。欠損値の状況を確認する
  コードも書いて

Claude Code が生成するコードで欠損値を確認します。

import pandas as pd
import numpy as np

df = pd.read_csv('data/raw/sales_2024.csv', parse_dates=['注文日'])

# ランダムに欠損値を挿入(分析練習用)
np.random.seed(42)
for col in ['商品名', '単価', '数量', '地域', '支払方法']:
    mask = np.random.random(len(df)) < 0.05
    df.loc[mask, col] = np.nan

# 欠損値の確認
print("=== 列ごとの欠損値の数 ===")
print(df.isnull().sum())

print("\n=== 列ごとの欠損率 ===")
print((df.isnull().sum() / len(df) * 100).round(2))

print(f"\n=== 全体の欠損セル数 ===")
print(f"欠損セル: {df.isnull().sum().sum()} / {df.size} ({df.isnull().sum().sum()/df.size*100:.2f}%)")

出力例は以下のようになります。

=== 列ごとの欠損値の数 ===
注文ID      0
注文日       0
商品名       48
カテゴリ      0
単価        52
数量        47
顧客ID      0
顧客名       0
地域        51
支払方法      49

isnull() はデータフレーム全体の各セルが欠損かどうかを True/False で返します。.sum() で列ごとの欠損数がわかります。欠損値がどの行にあるかを確認するには次のようにします。

# 欠損値がある行だけを表示
rows_with_na = df[df.isnull().any(axis=1)]
print(f"欠損値がある行数: {len(rows_with_na)}")
print(rows_with_na.head(10))

欠損値の処理 — 削除と補完

欠損値の処理方法は大きく2つあります。「削除」と「補完(穴埋め)」です。どちらを選ぶかはデータの性質と分析目的によって判断します。

> 売上データの欠損値を処理して。
  単価と数量は中央値で埋めて、
  地域と支払方法は最頻値で埋めて、
  商品名が欠損している行は削除して
# 方法1: 欠損行を削除(dropna)
df_dropped = df.dropna()
print(f"削除前: {len(df)}行 → 削除後: {len(df_dropped)}行")

# 方法2: すべて同じ値で埋める
df_filled = df.fillna(0)

# 方法3: 列ごとに異なる方法で補完(実務的なアプローチ)
df_clean = df.copy()

# 数値列は中央値で補完
df_clean['単価'] = df_clean['単価'].fillna(df_clean['単価'].median())
df_clean['数量'] = df_clean['数量'].fillna(df_clean['数量'].median())

# カテゴリ列は最頻値で補完
df_clean['地域'] = df_clean['地域'].fillna(df_clean['地域'].mode()[0])
df_clean['支払方法'] = df_clean['支払方法'].fillna(df_clean['支払方法'].mode()[0])

# 商品名が不明な行は削除(商品が特定できないと分析できない)
df_clean = df_clean.dropna(subset=['商品名'])

print(f"クリーニング後: {len(df_clean)}行")
print(df_clean.isnull().sum())  # すべて0になるはず

中央値(median)は外れ値の影響を受けにくいため、数値データの補完によく使われます。平均値(mean)は外れ値に引っ張られるため、データの分布を確認してから選択しましょう。

補完方法の使い分けをまとめます。

データ型 推奨される補完方法 関数
数値(正規分布) 平均値 fillna(df['col'].mean())
数値(偏った分布) 中央値 fillna(df['col'].median())
カテゴリ 最頻値 fillna(df['col'].mode()[0])
時系列 前方/後方補完 fillna(method='ffill')
重要な列 行を削除 dropna(subset=['col'])

重複データの検出と削除

データの重複は、システム上の二重送信やデータ結合時のミスで発生します。重複を放置すると集計結果が膨れ上がります。

> データの重複行を検出して削除して。
  注文IDで重複を判定して
# 重複行の確認
print(f"重複行数: {df_clean.duplicated().sum()}")

# 特定の列で重複を判定
print(f"注文IDの重複: {df_clean.duplicated(subset=['注文ID']).sum()}")

# 重複している行を表示
duplicates = df_clean[df_clean.duplicated(subset=['注文ID'], keep=False)]
print(duplicates.sort_values('注文ID'))

# 重複を削除(最初の行を残す)
df_clean = df_clean.drop_duplicates(subset=['注文ID'], keep='first')
print(f"重複削除後: {len(df_clean)}行")

keep='first' は最初に出現した行を残し、keep='last' は最後の行を残します。keep=False はすべての重複行を削除します。

異常値の検出と処理

異常値(外れ値)は、入力ミスや特殊な取引によって発生する極端な値です。IQR(四分位範囲)法を使って統計的に検出します。

> 単価列の異常値をIQR法で検出して。
  箱ひげ図で可視化もして
import matplotlib.pyplot as plt

# IQR法による異常値検出
Q1 = df_clean['単価'].quantile(0.25)
Q3 = df_clean['単価'].quantile(0.75)
IQR = Q3 - Q1

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

print(f"Q1: {Q1}, Q3: {Q3}, IQR: {IQR}")
print(f"下限: {lower_bound}, 上限: {upper_bound}")

# 異常値の抽出
outliers = df_clean[(df_clean['単価'] < lower_bound) | (df_clean['単価'] > upper_bound)]
print(f"\n異常値の件数: {len(outliers)}")
print(outliers[['注文ID', '商品名', '単価']].head(10))

# 箱ひげ図で可視化
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].boxplot(df_clean['単価'].dropna())
axes[0].set_title('単価の箱ひげ図')
axes[0].set_ylabel('円')

axes[1].boxplot(df_clean['数量'].dropna())
axes[1].set_title('数量の箱ひげ図')
axes[1].set_ylabel('個')

plt.tight_layout()
plt.savefig('reports/figures/outliers_boxplot.png', dpi=150, bbox_inches='tight')
plt.show()

IQR 法の仕組みを説明します。Q1(第1四分位数、25%点)と Q3(第3四分位数、75%点)の差が IQR です。Q1 - 1.5IQR より小さい値、Q3 + 1.5IQR より大きい値を異常値と判定します。この「1.5」は統計学的な慣例ですが、ビジネスの文脈に応じて調整することもあります。

異常値の処理方法は以下の通りです。

# 方法1: 異常値を除外
df_no_outliers = df_clean[
    (df_clean['単価'] >= lower_bound) & (df_clean['単価'] <= upper_bound)
]

# 方法2: 異常値を上限/下限に置換(クリッピング)
df_clean['単価_clipped'] = df_clean['単価'].clip(lower=lower_bound, upper=upper_bound)

# 方法3: 異常値を中央値で置換
median_price = df_clean['単価'].median()
df_clean.loc[
    (df_clean['単価'] < lower_bound) | (df_clean['単価'] > upper_bound),
    '単価'
] = median_price

最後にクリーニング済みデータを保存します。

df_clean.to_csv('data/processed/sales_cleaned.csv', index=False, encoding='utf-8-sig')
print(f"クリーニング完了: {len(df_clean)}行を保存")

練習問題

  1. Claude Code に「売上データを読み込んで、欠損値の状況をレポートして」と指示してください
  2. 数値列(単価、数量)を中央値で、カテゴリ列(地域、支払方法)を最頻値で補完してください
  3. 注文IDの重複行を検出し、削除してください
  4. 単価と数量に対してIQR法で異常値を検出し、それぞれ何件あるか確認してください
  5. クリーニング済みデータを data/processed/sales_cleaned.csv に保存してください

参考リンク

Lecture 5データ集計 — groupbyで分析する

12:00

データ集計 — groupbyで分析する

データを読み込み、クリーニングが完了したら、いよいよ集計と分析に入ります。pandas の groupby は SQL の GROUP BY に相当する機能で、データをグループに分割し、各グループに対して集計関数(合計・平均・件数など)を適用できます。この講義では、groupby を中心に、pivot_table や crosstab など実務で必須の集計テクニックを Claude Code と一緒に学びます。

groupby の基本

前回クリーニングした売上データを読み込んで、カテゴリ別の集計から始めましょう。

> クリーニング済みの売上データを読み込んで、
  カテゴリ別の売上合計と平均単価を集計して

Claude Code が生成するコードです。

import pandas as pd

df = pd.read_csv('data/processed/sales_cleaned.csv', parse_dates=['注文日'])
df['売上金額'] = df['単価'] * df['数量']

# カテゴリ別の売上合計
sales_by_category = df.groupby('カテゴリ')['売上金額'].sum()
print("=== カテゴリ別 売上合計 ===")
print(sales_by_category.sort_values(ascending=False))

# カテゴリ別の平均単価
avg_price = df.groupby('カテゴリ')['単価'].mean()
print("\n=== カテゴリ別 平均単価 ===")
print(avg_price.round(0))

groupby の動作は3つのステップに分かれます。

  1. Split(分割): 指定した列の値でデータをグループに分ける
  2. Apply(適用): 各グループに集計関数を適用する
  3. Combine(結合): 結果をまとめて返す

複数の集計を同時に行うには agg メソッドを使います。

# 複数の集計関数を同時に適用
summary = df.groupby('カテゴリ').agg(
    注文件数=('注文ID', 'count'),
    売上合計=('売上金額', 'sum'),
    平均売上=('売上金額', 'mean'),
    最大売上=('売上金額', 'max'),
    平均数量=('数量', 'mean')
).round(0)

print(summary)

agg では、列名=('元の列', '集計関数') の形式で、わかりやすい列名を付けて集計できます。使用できる集計関数には summeanmediancountminmaxstd(標準偏差)、nunique(ユニーク数)などがあります。

月別・時系列の集計

ECサイトの分析で最も重要な集計の一つが月別の売上推移です。

> 月別の売上合計と注文件数を集計して。
  前月比の増減率も計算して
# 注文月の列を追加
df['注文月'] = df['注文日'].dt.to_period('M')

# 月別集計
monthly = df.groupby('注文月').agg(
    注文件数=('注文ID', 'count'),
    売上合計=('売上金額', 'sum'),
    平均客単価=('売上金額', 'mean'),
    ユニーク顧客数=('顧客ID', 'nunique')
).round(0)

# 前月比の増減率を計算
monthly['前月比'] = monthly['売上合計'].pct_change() * 100
monthly['前月比'] = monthly['前月比'].round(1)

print(monthly)

dt.to_period('M') は日付を月単位の期間(Period)に変換します。'W' で週単位、'Q' で四半期単位の集計もできます。

pct_change() は前の値からの変化率を計算します。結果は小数で返されるため、100 を掛けてパーセントに変換しています。

さらに曜日別の分析も行ってみましょう。

# 曜日別の集計
df['曜日'] = df['注文日'].dt.day_name()
weekday_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']

weekday_sales = df.groupby('曜日').agg(
    注文件数=('注文ID', 'count'),
    平均売上=('売上金額', 'mean')
).reindex(weekday_order).round(0)

print("\n=== 曜日別の集計 ===")
print(weekday_sales)

pivot_table で多次元集計

pivot_table は Excel のピボットテーブルと同じ機能です。行と列の2軸でクロス集計ができます。

> カテゴリ別・地域別の売上金額をピボットテーブルで
  集計して。合計行と合計列もつけて
# ピボットテーブル: カテゴリ × 地域
pivot = pd.pivot_table(
    df,
    values='売上金額',
    index='カテゴリ',
    columns='地域',
    aggfunc='sum',
    margins=True,       # 合計行・合計列を追加
    margins_name='合計'
).round(0)

print(pivot)

aggfunc には 'sum''mean''count''median' などを指定できます。複数の集計関数を同時に適用することも可能です。

# 複数の集計関数
pivot_multi = pd.pivot_table(
    df,
    values='売上金額',
    index='カテゴリ',
    columns='地域',
    aggfunc=['sum', 'count']
)

print(pivot_multi)

crosstab は度数分布表(クロス集計表)を作成する関数で、カテゴリ同士の関係を調べるのに便利です。

# カテゴリ × 支払方法のクロス集計
cross = pd.crosstab(
    df['カテゴリ'],
    df['支払方法'],
    margins=True
)
print(cross)

# 構成比(行方向のパーセント)
cross_pct = pd.crosstab(
    df['カテゴリ'],
    df['支払方法'],
    normalize='index'  # 行方向で正規化
) * 100

print(cross_pct.round(1))

value_counts と実践的な集計パターン

value_counts() はカテゴリ変数の出現回数を簡単に集計できるメソッドです。

> 商品別の売上ランキングトップ10と、
  地域別の注文構成比を出して
# 商品別の売上ランキング
product_sales = df.groupby('商品名')['売上金額'].sum().sort_values(ascending=False)
print("=== 商品別 売上ランキング TOP 10 ===")
print(product_sales.head(10))

# 地域別の注文構成比
region_counts = df['地域'].value_counts()
region_pct = df['地域'].value_counts(normalize=True) * 100
print("\n=== 地域別 注文件数と構成比 ===")
region_summary = pd.DataFrame({
    '注文件数': region_counts,
    '構成比(%)': region_pct.round(1)
})
print(region_summary)

# 支払方法の構成比
print("\n=== 支払方法の構成比 ===")
print(df['支払方法'].value_counts(normalize=True).mul(100).round(1))

value_counts(normalize=True) を使うと、件数ではなく割合(0〜1)で返されます。100 を掛けるとパーセント表示になります。

最後に、実務でよく使う集計パターンをまとめます。

# 顧客別の購買分析(RFM分析の基礎)
customer_summary = df.groupby('顧客ID').agg(
    購入回数=('注文ID', 'count'),
    合計売上=('売上金額', 'sum'),
    平均客単価=('売上金額', 'mean'),
    最終購入日=('注文日', 'max'),
    購入カテゴリ数=('カテゴリ', 'nunique')
).sort_values('合計売上', ascending=False)

print("=== 顧客別購買サマリー TOP 10 ===")
print(customer_summary.head(10).round(0))

# 集計結果を保存
customer_summary.to_csv('data/processed/customer_summary.csv', encoding='utf-8-sig')
monthly.to_csv('data/processed/monthly_sales.csv', encoding='utf-8-sig')

練習問題

  1. Claude Code に「カテゴリ別の売上合計、平均単価、注文件数を一覧表で出して」と指示してください
  2. 月別の売上推移を集計し、前月比の増減率を計算してください
  3. カテゴリ(行) x 地域(列) のピボットテーブルを作成し、売上金額の合計を集計してください
  4. 支払方法別の注文件数と構成比(%)を value_counts で計算してください
  5. 顧客別の「購入回数」「合計売上」「平均客単価」を集計し、合計売上の上位10顧客を表示してください

参考リンク

Lecture 6matplotlib入門 — グラフを描画する

12:00

matplotlib入門 — グラフを描画する

データ分析の結果を他者に伝えるうえで、グラフによる可視化は不可欠です。matplotlib は Python で最も広く使われているグラフ描画ライブラリで、棒グラフ、折れ線グラフ、散布図、ヒストグラムなど、あらゆるグラフを自在に作成できます。この講義では、matplotlib の基本操作から日本語フォントの設定まで、Claude Code を使いながら一通り習得します。

日本語フォントの設定

matplotlib はデフォルトで日本語フォントに対応していないため、最初に設定が必要です。日本語を含むグラフでは文字化け(豆腐: □□□)が発生します。Claude Code に設定を任せましょう。

> matplotlibで日本語が表示されるように設定して。
  Windows でも macOS でも動くようにして
import matplotlib.pyplot as plt
import matplotlib

# Windows の場合
plt.rcParams['font.family'] = 'MS Gothic'

# macOS の場合
# plt.rcParams['font.family'] = 'Hiragino Sans'

# Linux の場合
# plt.rcParams['font.family'] = 'IPAGothic'

# マイナス記号の文字化け防止
plt.rcParams['axes.unicode_minus'] = False

# 自動判別する汎用的な方法
import platform
if platform.system() == 'Windows':
    plt.rcParams['font.family'] = 'MS Gothic'
elif platform.system() == 'Darwin':
    plt.rcParams['font.family'] = 'Hiragino Sans'
else:
    plt.rcParams['font.family'] = 'IPAGothic'

この設定はスクリプトの先頭やノートブックの最初のセルに書いておくと、以降のすべてのグラフに適用されます。フォントが正しく設定されたかテストしましょう。

plt.figure(figsize=(6, 3))
plt.text(0.5, 0.5, '日本語テスト: データ分析', fontsize=20, ha='center')
plt.title('フォント確認')
plt.show()

基本的なグラフの描画

売上データを使って、基本的なグラフを描画していきます。

> 月別の売上合計を棒グラフで描画して。
  タイトル、軸ラベル、数値ラベルもつけて
import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('data/processed/sales_cleaned.csv', parse_dates=['注文日'])
df['売上金額'] = df['単価'] * df['数量']
df['月'] = df['注文日'].dt.month

monthly_sales = df.groupby('月')['売上金額'].sum()

# 棒グラフ
fig, ax = plt.subplots(figsize=(10, 6))
bars = ax.bar(monthly_sales.index, monthly_sales.values, color='#2196F3', edgecolor='white')

# 数値ラベルを棒の上に表示
for bar in bars:
    height = bar.get_height()
    ax.text(bar.get_x() + bar.get_width()/2., height,
            f'¥{height:,.0f}', ha='center', va='bottom', fontsize=9)

ax.set_title('2024年 月別売上推移', fontsize=16, fontweight='bold')
ax.set_xlabel('月', fontsize=12)
ax.set_ylabel('売上金額(円)', fontsize=12)
ax.set_xticks(range(1, 13))
ax.set_xticklabels([f'{m}月' for m in range(1, 13)])

plt.tight_layout()
plt.savefig('reports/figures/monthly_sales_bar.png', dpi=150, bbox_inches='tight')
plt.show()

fig, ax = plt.subplots() はオブジェクト指向スタイルの書き方です。plt.bar() のような関数スタイルもありますが、細かい調整がしやすいオブジェクト指向スタイルをお勧めします。

続いて折れ線グラフです。

# 折れ線グラフ(月別売上推移)
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(monthly_sales.index, monthly_sales.values,
        marker='o', linewidth=2, color='#E91E63', markersize=8)

ax.set_title('2024年 月別売上推移(折れ線)', fontsize=16)
ax.set_xlabel('月')
ax.set_ylabel('売上金額(円)')
ax.set_xticks(range(1, 13))
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('reports/figures/monthly_sales_line.png', dpi=150, bbox_inches='tight')
plt.show()

散布図とヒストグラム

データの分布や変数間の関係を調べるには散布図とヒストグラムが有効です。

> 単価と数量の散布図を作って。
  さらに売上金額のヒストグラムも作って
# 散布図
fig, ax = plt.subplots(figsize=(8, 6))
scatter = ax.scatter(df['単価'], df['数量'],
                     c=df['売上金額'], cmap='viridis',
                     alpha=0.6, edgecolors='white', s=50)

ax.set_title('単価 vs 数量(色: 売上金額)', fontsize=14)
ax.set_xlabel('単価(円)')
ax.set_ylabel('数量')
plt.colorbar(scatter, label='売上金額(円)')

plt.tight_layout()
plt.savefig('reports/figures/price_qty_scatter.png', dpi=150, bbox_inches='tight')
plt.show()

散布図の c パラメータに数値列を渡すと、値に応じて色が変わります。cmap でカラーマップを指定します。

# ヒストグラム
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 売上金額の分布
axes[0].hist(df['売上金額'], bins=30, color='#4CAF50', edgecolor='white', alpha=0.8)
axes[0].set_title('売上金額の分布', fontsize=14)
axes[0].set_xlabel('売上金額(円)')
axes[0].set_ylabel('度数')
axes[0].axvline(df['売上金額'].mean(), color='red', linestyle='--', label=f"平均: ¥{df['売上金額'].mean():,.0f}")
axes[0].legend()

# 単価の分布
axes[1].hist(df['単価'], bins=20, color='#FF9800', edgecolor='white', alpha=0.8)
axes[1].set_title('単価の分布', fontsize=14)
axes[1].set_xlabel('単価(円)')
axes[1].set_ylabel('度数')
axes[1].axvline(df['単価'].median(), color='blue', linestyle='--', label=f"中央値: ¥{df['単価'].median():,.0f}")
axes[1].legend()

plt.tight_layout()
plt.savefig('reports/figures/distribution_hist.png', dpi=150, bbox_inches='tight')
plt.show()

axvline は縦の参照線を描画します。平均値や中央値を示すのに便利です。

subplots で複数グラフを配置する

分析レポートでは複数のグラフを並べて表示することが多いです。subplots を使えば1つの図に複数のグラフを配置できます。

> カテゴリ別の売上を棒グラフ、
  地域別の注文構成比を円グラフ、
  月別の推移を折れ線グラフで、
  3つ並べて1枚の図にして
fig, axes = plt.subplots(1, 3, figsize=(18, 5))

# 1. カテゴリ別売上(横棒グラフ)
cat_sales = df.groupby('カテゴリ')['売上金額'].sum().sort_values()
axes[0].barh(cat_sales.index, cat_sales.values, color='#2196F3')
axes[0].set_title('カテゴリ別売上', fontsize=14)
axes[0].set_xlabel('売上金額(円)')

# 2. 地域別注文構成比(円グラフ)
region_counts = df['地域'].value_counts()
colors = ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF']
axes[1].pie(region_counts.values, labels=region_counts.index,
            autopct='%1.1f%%', colors=colors, startangle=90)
axes[1].set_title('地域別注文構成比', fontsize=14)

# 3. 月別売上推移(折れ線グラフ)
monthly = df.groupby(df['注文日'].dt.month)['売上金額'].sum()
axes[2].plot(monthly.index, monthly.values, marker='o', color='#4CAF50', linewidth=2)
axes[2].set_title('月別売上推移', fontsize=14)
axes[2].set_xlabel('月')
axes[2].set_ylabel('売上金額(円)')
axes[2].set_xticks(range(1, 13))
axes[2].grid(True, alpha=0.3)

plt.suptitle('ECサイト 売上ダッシュボード 2024年', fontsize=18, fontweight='bold', y=1.02)
plt.tight_layout()
plt.savefig('reports/figures/dashboard_basic.png', dpi=150, bbox_inches='tight')
plt.show()

subplots(2, 2) のように指定すれば2行2列の配置も可能です。suptitle は図全体のタイトルです。

savefig で画像として保存するときのポイントは以下の通りです。

パラメータ 説明
dpi=150 解像度。印刷用は300、Web用は150が目安
bbox_inches='tight' 余白を自動でトリミング
facecolor='white' 背景色を白に(Jupyter の背景が透明になる場合)
format='png' 形式を指定(png, pdf, svg など)

練習問題

  1. Claude Code に「月別の売上を棒グラフで描画して。タイトルと軸ラベルも付けて」と指示してください
  2. カテゴリ別の売上を横棒グラフ(barh)で描画し、売上の大きい順に並べてください
  3. 売上金額のヒストグラムを作成し、平均値を赤い縦線で示してください
  4. 2行2列の subplots で「棒グラフ、折れ線、散布図、円グラフ」を1枚の図にまとめてください
  5. 作成したグラフを reports/figures/ に PNG 形式(dpi=150)で保存してください

参考リンク

Lecture 7seabornで美しいグラフ — 統計グラフを作る

12:00

seabornで美しいグラフ — 統計グラフを作る

seaborn は matplotlib をベースにした統計データ可視化ライブラリです。matplotlib では数十行かかるグラフも、seaborn なら数行で美しく描画できます。特に統計的な情報(信頼区間、分布、相関)を含むグラフに強みがあります。この講義では、seaborn の主要なグラフタイプとスタイリングを Claude Code と一緒にマスターします。

seaborn のテーマ設定

seaborn はデフォルトで美しいスタイルが適用されますが、テーマをカスタマイズすることでさらに見栄えが良くなります。

> seabornのグラフスタイルを設定して。
  ビジネスレポート向けの見やすいテーマにして
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# テーマ設定
sns.set_theme(
    style='whitegrid',     # 背景スタイル
    palette='Set2',        # カラーパレット
    font='MS Gothic',      # 日本語フォント(Windows)
    font_scale=1.1         # フォントサイズの倍率
)

# マイナス記号の文字化け防止
plt.rcParams['axes.unicode_minus'] = False

seaborn のスタイルは5種類あります。

スタイル 特徴 用途
whitegrid 白背景 + グリッド線 ビジネスレポート
darkgrid グレー背景 + グリッド線 分析ノートブック
white 白背景のみ 論文・学術発表
dark グレー背景のみ ダッシュボード
ticks 白背景 + 軸目盛り線 シンプルなプレゼン

カラーパレットも豊富です。Set2(パステル系)、husl(均等な彩度)、muted(落ち着いた色)、deep(デフォルト)などから選べます。

# カラーパレットの確認
fig, axes = plt.subplots(2, 3, figsize=(15, 6))
palettes = ['deep', 'Set2', 'husl', 'muted', 'pastel', 'colorblind']
for ax, pal in zip(axes.flat, palettes):
    sns.palplot(sns.color_palette(pal, 8))
    ax.set_title(pal)
plt.tight_layout()
plt.show()

barplot と countplot

売上データを使って、seaborn の棒グラフを描画しましょう。

> seabornでカテゴリ別の平均売上金額を棒グラフで描いて。
  信頼区間のエラーバーも表示して
df = pd.read_csv('data/processed/sales_cleaned.csv', parse_dates=['注文日'])
df['売上金額'] = df['単価'] * df['数量']

# barplot: 平均値 + 信頼区間を表示
fig, axes = plt.subplots(1, 2, figsize=(14, 6))

# カテゴリ別の平均売上金額(信頼区間付き)
sns.barplot(data=df, x='カテゴリ', y='売上金額', ax=axes[0],
            estimator='mean', errorbar='ci')
axes[0].set_title('カテゴリ別 平均売上金額', fontsize=14)
axes[0].set_ylabel('売上金額(円)')

# 地域別の注文件数
sns.countplot(data=df, x='地域', ax=axes[1],
              order=df['地域'].value_counts().index)
axes[1].set_title('地域別 注文件数', fontsize=14)
axes[1].set_ylabel('件数')

# 各棒の上に数値を表示
for container in axes[1].containers:
    axes[1].bar_label(container)

plt.tight_layout()
plt.savefig('reports/figures/seaborn_barplot.png', dpi=150, bbox_inches='tight')
plt.show()

sns.barplot は自動的に平均値を計算し、95%信頼区間のエラーバーを表示します。これにより、平均値の信頼性が一目でわかります。sns.countplot はカテゴリの出現回数を数えて棒グラフにします。order パラメータで並び順を指定できます。

lineplot で時系列を描く

月別の売上推移を seaborn の折れ線グラフで表現します。

> seabornで月別・カテゴリ別の売上推移を
  折れ線グラフで描いて。カテゴリごとに色分けして
df['月'] = df['注文日'].dt.month

# カテゴリ別の月次売上
monthly_cat = df.groupby(['月', 'カテゴリ'])['売上金額'].sum().reset_index()

fig, ax = plt.subplots(figsize=(12, 6))
sns.lineplot(data=monthly_cat, x='月', y='売上金額', hue='カテゴリ',
             marker='o', linewidth=2, ax=ax)

ax.set_title('カテゴリ別 月次売上推移', fontsize=16)
ax.set_xlabel('月')
ax.set_ylabel('売上金額(円)')
ax.set_xticks(range(1, 13))
ax.legend(title='カテゴリ', bbox_to_anchor=(1.05, 1), loc='upper left')

plt.tight_layout()
plt.savefig('reports/figures/seaborn_lineplot.png', dpi=150, bbox_inches='tight')
plt.show()

hue パラメータでカテゴリごとに色分けされます。凡例は bbox_to_anchor でグラフの外側に配置すると見やすくなります。

heatmap で相関を可視化する

ヒートマップは、数値データの大小関係を色で表現します。相関行列やピボットテーブルの可視化に最適です。

> カテゴリ × 地域の売上ピボットテーブルを
  seabornのヒートマップで可視化して
# ピボットテーブルの作成
pivot = pd.pivot_table(df, values='売上金額', index='カテゴリ',
                       columns='地域', aggfunc='sum')

fig, axes = plt.subplots(1, 2, figsize=(16, 5))

# 売上金額のヒートマップ
sns.heatmap(pivot, annot=True, fmt=',.0f', cmap='YlOrRd',
            linewidths=0.5, ax=axes[0])
axes[0].set_title('カテゴリ × 地域 売上金額', fontsize=14)

# 数値列の相関行列
numeric_cols = df[['単価', '数量', '売上金額']].copy()
corr = numeric_cols.corr()

sns.heatmap(corr, annot=True, fmt='.2f', cmap='coolwarm',
            center=0, vmin=-1, vmax=1, linewidths=1, ax=axes[1])
axes[1].set_title('数値列の相関行列', fontsize=14)

plt.tight_layout()
plt.savefig('reports/figures/seaborn_heatmap.png', dpi=150, bbox_inches='tight')
plt.show()

annot=True でセルに数値を表示、fmt で表示形式を指定します。cmap でカラーマップを選択し、center=0 は相関行列で0を中心に色分けするための設定です。

boxplot で分布を比較する

箱ひげ図はカテゴリごとのデータ分布を比較するのに最適です。中央値、四分位範囲、外れ値が一目でわかります。

> カテゴリ別の売上金額の分布を箱ひげ図とバイオリンプロットで
  並べて表示して
fig, axes = plt.subplots(1, 2, figsize=(14, 6))

# 箱ひげ図
sns.boxplot(data=df, x='カテゴリ', y='売上金額', ax=axes[0],
            palette='Set2', showfliers=True)
axes[0].set_title('カテゴリ別 売上金額分布(箱ひげ図)', fontsize=13)
axes[0].set_ylabel('売上金額(円)')

# バイオリンプロット(箱ひげ図 + カーネル密度推定)
sns.violinplot(data=df, x='カテゴリ', y='売上金額', ax=axes[1],
               palette='muted', inner='box')
axes[1].set_title('カテゴリ別 売上金額分布(バイオリン)', fontsize=13)
axes[1].set_ylabel('売上金額(円)')

plt.tight_layout()
plt.savefig('reports/figures/seaborn_boxviolin.png', dpi=150, bbox_inches='tight')
plt.show()

バイオリンプロットは箱ひげ図にデータの密度情報(どの価格帯にデータが集中しているか)を加えたグラフです。inner='box' で内部に箱ひげ図を表示できます。

seaborn には他にも swarmplot(蜂群図)、stripplot(ストリッププロット)、pairplot(散布図行列)など、データ探索に便利なグラフが多数あります。

# pairplot: 数値列すべての組み合わせを散布図で表示
pair_data = df[['単価', '数量', '売上金額', 'カテゴリ']].copy()
g = sns.pairplot(pair_data, hue='カテゴリ', palette='Set2', height=3)
g.fig.suptitle('数値列のペアプロット', y=1.02)
plt.savefig('reports/figures/seaborn_pairplot.png', dpi=150, bbox_inches='tight')
plt.show()

練習問題

  1. Claude Code に「seabornのテーマを whitegrid に設定して」と指示し、スタイルを適用してください
  2. sns.barplot でカテゴリ別の平均売上金額を信頼区間付きで描画してください
  3. sns.lineplot で月別・カテゴリ別の売上推移を色分けして描画してください
  4. カテゴリ x 地域のピボットテーブルを sns.heatmap で可視化してください
  5. sns.boxplot でカテゴリ別の売上金額の分布を表示し、外れ値を確認してください

参考リンク

Lecture 8実践分析 — 売上データを分析する

12:00

実践分析 — 売上データを分析する

ここまでの講義で、データの読み込み・クリーニング・集計・可視化の個別スキルを習得しました。この講義では、それらを統合してECサイトの売上データに対する本格的な分析ワークフローを実践します。月次トレンド分析、商品ランキング、顧客セグメンテーション、季節パターンの発見まで、ビジネスの意思決定に直結する分析を Claude Code と一緒に行います。

分析の全体設計

実務のデータ分析は、闇雲にグラフを作るのではなく、ビジネスの問いから出発します。今回は架空のECサイト「CLAN SHOP」の2024年売上データに対して、以下の4つの分析課題に取り組みます。

  1. 月次トレンド: 売上はどのように推移しているか。成長しているか。
  2. 商品分析: どの商品・カテゴリが売上を牽引しているか。
  3. 顧客分析: 顧客の購買パターンはどうなっているか。優良顧客は誰か。
  4. 季節性分析: 季節や曜日による売上の変動はあるか。

Claude Code にまず分析の全体設計を依頼しましょう。

> ECサイトの売上データを本格的に分析したい。
  月次トレンド、商品ランキング、顧客分析、季節パターンの
  4つの視点で分析するコードを書いて。
  sales_cleaned.csv を使って
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import platform

# 日本語フォント設定
if platform.system() == 'Windows':
    plt.rcParams['font.family'] = 'MS Gothic'
elif platform.system() == 'Darwin':
    plt.rcParams['font.family'] = 'Hiragino Sans'
plt.rcParams['axes.unicode_minus'] = False
sns.set_theme(style='whitegrid', font_scale=1.1)

# データ読み込み
df = pd.read_csv('data/processed/sales_cleaned.csv', parse_dates=['注文日'])
df['売上金額'] = df['単価'] * df['数量']
df['月'] = df['注文日'].dt.month
df['曜日'] = df['注文日'].dt.dayofweek  # 0=月曜, 6=日曜
df['四半期'] = df['注文日'].dt.quarter

print(f"データ件数: {len(df)}行")
print(f"期間: {df['注文日'].min()} 〜 {df['注文日'].max()}")
print(f"売上合計: ¥{df['売上金額'].sum():,.0f}")

月次トレンド分析

まず売上の時系列推移を可視化して、成長トレンドを確認します。

> 月別の売上合計と注文件数を折れ線グラフで描いて。
  前月比の増減も計算して、成長しているか判断して
# 月別集計
monthly = df.groupby('月').agg(
    売上合計=('売上金額', 'sum'),
    注文件数=('注文ID', 'count'),
    平均客単価=('売上金額', 'mean'),
    ユニーク顧客数=('顧客ID', 'nunique')
).round(0)

monthly['前月比(%)'] = monthly['売上合計'].pct_change().mul(100).round(1)

print("=== 月別サマリー ===")
print(monthly)

# 可視化: 売上と注文件数の二軸グラフ
fig, ax1 = plt.subplots(figsize=(12, 6))
ax2 = ax1.twinx()

bars = ax1.bar(monthly.index, monthly['売上合計'], color='#2196F3', alpha=0.7, label='売上合計')
line = ax2.plot(monthly.index, monthly['注文件数'], color='#E91E63',
                marker='o', linewidth=2, markersize=8, label='注文件数')

ax1.set_xlabel('月', fontsize=12)
ax1.set_ylabel('売上金額(円)', fontsize=12, color='#2196F3')
ax2.set_ylabel('注文件数', fontsize=12, color='#E91E63')
ax1.set_title('月別 売上合計 & 注文件数', fontsize=16, fontweight='bold')
ax1.set_xticks(range(1, 13))
ax1.set_xticklabels([f'{m}月' for m in range(1, 13)])

# 凡例を統合
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper left')

plt.tight_layout()
plt.savefig('reports/figures/analysis_monthly_trend.png', dpi=150, bbox_inches='tight')
plt.show()

# KPIサマリー
print(f"\n=== KPIサマリー ===")
print(f"年間売上: ¥{df['売上金額'].sum():,.0f}")
print(f"月平均売上: ¥{monthly['売上合計'].mean():,.0f}")
print(f"最高月: {monthly['売上合計'].idxmax()}月 (¥{monthly['売上合計'].max():,.0f})")
print(f"最低月: {monthly['売上合計'].idxmin()}月 (¥{monthly['売上合計'].min():,.0f})")

商品・カテゴリ分析

どの商品がビジネスを牽引しているかを明らかにします。パレートの法則(80/20の法則)を検証してみましょう。

> 商品別の売上ランキングTOP10を棒グラフで描いて。
  カテゴリ別の売上構成比も円グラフで出して。
  パレート分析(累積構成比)もやって
# 商品別売上ランキング
product_sales = df.groupby('商品名')['売上金額'].sum().sort_values(ascending=False)

fig, axes = plt.subplots(1, 2, figsize=(16, 6))

# TOP10 商品
top10 = product_sales.head(10)
axes[0].barh(range(len(top10)), top10.values, color=sns.color_palette('viridis', len(top10)))
axes[0].set_yticks(range(len(top10)))
axes[0].set_yticklabels(top10.index)
axes[0].invert_yaxis()
axes[0].set_title('商品別売上 TOP10', fontsize=14)
axes[0].set_xlabel('売上金額(円)')

for i, v in enumerate(top10.values):
    axes[0].text(v + 1000, i, f'¥{v:,.0f}', va='center', fontsize=9)

# カテゴリ別構成比
cat_sales = df.groupby('カテゴリ')['売上金額'].sum().sort_values(ascending=False)
colors = ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF']
axes[1].pie(cat_sales.values, labels=cat_sales.index, autopct='%1.1f%%',
            colors=colors, startangle=90, pctdistance=0.85)
axes[1].set_title('カテゴリ別売上構成比', fontsize=14)

plt.tight_layout()
plt.savefig('reports/figures/analysis_product_ranking.png', dpi=150, bbox_inches='tight')
plt.show()

# パレート分析
cumulative_pct = product_sales.cumsum() / product_sales.sum() * 100
top_80 = cumulative_pct[cumulative_pct <= 80]
print(f"\n=== パレート分析 ===")
print(f"全商品数: {len(product_sales)}")
print(f"売上80%を占める商品数: {len(top_80)} ({len(top_80)/len(product_sales)*100:.0f}%)")

顧客セグメント分析

顧客の購買行動を分析し、優良顧客を特定します。RFM(Recency, Frequency, Monetary)分析の簡易版を実施します。

> 顧客別の購入回数、合計売上、平均客単価を集計して。
  顧客を購買金額で4つのセグメントに分けて可視化して
# 顧客別集計
customer = df.groupby('顧客ID').agg(
    購入回数=('注文ID', 'count'),
    合計売上=('売上金額', 'sum'),
    平均客単価=('売上金額', 'mean'),
    最終購入日=('注文日', 'max'),
    購入カテゴリ数=('カテゴリ', 'nunique')
).round(0)

# 顧客セグメント(合計売上の四分位で分類)
customer['セグメント'] = pd.qcut(customer['合計売上'], 4,
                               labels=['ライト', 'ミドル', 'ヘビー', 'VIP'])

print("=== 顧客セグメント別 統計 ===")
segment_summary = customer.groupby('セグメント').agg(
    顧客数=('購入回数', 'count'),
    平均購入回数=('購入回数', 'mean'),
    平均売上=('合計売上', 'mean'),
    売上合計=('合計売上', 'sum')
).round(0)
print(segment_summary)

# 可視化
fig, axes = plt.subplots(1, 2, figsize=(14, 6))

# セグメント別顧客数
seg_counts = customer['セグメント'].value_counts().reindex(['ライト', 'ミドル', 'ヘビー', 'VIP'])
colors = ['#90CAF9', '#42A5F5', '#1E88E5', '#0D47A1']
axes[0].bar(seg_counts.index, seg_counts.values, color=colors)
axes[0].set_title('顧客セグメント別 人数', fontsize=14)
axes[0].set_ylabel('顧客数')

for i, v in enumerate(seg_counts.values):
    axes[0].text(i, v + 1, str(v), ha='center', fontweight='bold')

# 購入回数 vs 合計売上の散布図
scatter = axes[1].scatter(customer['購入回数'], customer['合計売上'],
                          c=customer['セグメント'].cat.codes, cmap='Blues',
                          alpha=0.6, edgecolors='white', s=50)
axes[1].set_title('購入回数 vs 合計売上', fontsize=14)
axes[1].set_xlabel('購入回数')
axes[1].set_ylabel('合計売上(円)')

plt.tight_layout()
plt.savefig('reports/figures/analysis_customer_segment.png', dpi=150, bbox_inches='tight')
plt.show()

# TOP10顧客
print("\n=== 売上 TOP10 顧客 ===")
print(customer.sort_values('合計売上', ascending=False).head(10))

季節性パターン分析

売上の季節変動を発見し、マーケティング施策に活かせるインサイトを抽出します。

> 曜日別・四半期別の売上パターンを分析して。
  ヒートマップで曜日 × カテゴリの売上を可視化して
# 曜日別売上
weekday_names = ['月', '火', '水', '木', '金', '土', '日']
df['曜日名'] = df['曜日'].map(dict(zip(range(7), weekday_names)))

fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# 曜日別平均売上
weekday_avg = df.groupby('曜日')['売上金額'].mean()
axes[0, 0].bar(weekday_names, weekday_avg.values, color='#4CAF50')
axes[0, 0].set_title('曜日別 平均売上金額', fontsize=13)
axes[0, 0].set_ylabel('平均売上金額(円)')

# 四半期別売上
quarterly = df.groupby('四半期')['売上金額'].sum()
axes[0, 1].bar([f'Q{q}' for q in quarterly.index], quarterly.values,
               color=['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0'])
axes[0, 1].set_title('四半期別 売上合計', fontsize=13)
axes[0, 1].set_ylabel('売上金額(円)')

for i, v in enumerate(quarterly.values):
    axes[0, 1].text(i, v + 1000, f'¥{v:,.0f}', ha='center', fontsize=9)

# 曜日 × カテゴリのヒートマップ
pivot_weekday = pd.pivot_table(df, values='売上金額', index='曜日名',
                                columns='カテゴリ', aggfunc='sum')
pivot_weekday = pivot_weekday.reindex(weekday_names)
sns.heatmap(pivot_weekday, annot=True, fmt=',.0f', cmap='YlOrRd',
            linewidths=0.5, ax=axes[1, 0])
axes[1, 0].set_title('曜日 × カテゴリ 売上ヒートマップ', fontsize=13)

# 月別 × カテゴリの売上推移
monthly_cat = df.groupby(['月', 'カテゴリ'])['売上金額'].sum().reset_index()
sns.lineplot(data=monthly_cat, x='月', y='売上金額', hue='カテゴリ',
             marker='o', ax=axes[1, 1])
axes[1, 1].set_title('月別 × カテゴリ 売上推移', fontsize=13)
axes[1, 1].set_xticks(range(1, 13))
axes[1, 1].legend(title='カテゴリ', fontsize=9)

plt.suptitle('季節性パターン分析', fontsize=18, fontweight='bold', y=1.02)
plt.tight_layout()
plt.savefig('reports/figures/analysis_seasonal.png', dpi=150, bbox_inches='tight')
plt.show()

# 分析インサイトの出力
print("\n" + "=" * 50)
print("分析インサイト")
print("=" * 50)
best_month = monthly['売上合計'].idxmax() if 'monthly' in dir() else df.groupby('月')['売上金額'].sum().idxmax()
best_weekday = weekday_names[df.groupby('曜日')['売上金額'].sum().idxmax()]
best_category = df.groupby('カテゴリ')['売上金額'].sum().idxmax()
print(f"1. 最も売上が高い月: {best_month}月")
print(f"2. 最も売上が高い曜日: {best_weekday}曜日")
print(f"3. 売上トップカテゴリ: {best_category}")
print(f"4. VIP顧客数: {len(customer[customer['セグメント']=='VIP'])}人")
print(f"5. 年間売上: ¥{df['売上金額'].sum():,.0f}")

練習問題

  1. Claude Code に「売上データの月次トレンドを分析して、KPI(年間売上、月平均売上、最高月・最低月)をまとめて」と指示してください
  2. 商品別売上ランキング TOP10 を横棒グラフで作成し、パレート分析を実施してください
  3. 顧客を合計売上額で4セグメントに分割し、各セグメントの顧客数と平均売上を集計してください
  4. 曜日別・四半期別の売上パターンを可視化して、マーケティング施策に活かせるインサイトを3つ挙げてください
  5. すべての分析結果のグラフを reports/figures/ に保存してください

参考リンク

Lecture 9分析レポート — 結果をまとめる

12:00

分析レポート — 結果をまとめる

データ分析の最終成果物は、数字の羅列ではなくビジネスの意思決定に使えるレポートです。いくら優れた分析をしても、伝え方が悪ければ価値は半減します。この講義では、Jupyter Notebook を使ってマークダウンテキストとグラフを組み合わせた分析レポートを作成し、HTML として共有可能な形にエクスポートする方法を学びます。

Jupyter Notebook でレポートを書く

Jupyter Notebook はコードセル(Python)とマークダウンセル(テキスト)を交互に配置でき、データ分析レポートに最適な環境です。Claude Code にレポートの骨格を作ってもらいましょう。

> ECサイトの売上分析レポートをJupyter Notebookで作りたい。
  エグゼクティブサマリー、月次トレンド、商品分析、顧客分析、
  提言の5セクションで構成して。
  マークダウンセルとコードセルの構成を教えて

Jupyter Notebook のレポート構成は以下のようになります。

セル1 [Markdown]: タイトルと概要
セル2 [Code]:     ライブラリインポート + データ読み込み
セル3 [Markdown]: エグゼクティブサマリー
セル4 [Code]:     KPI計算
セル5 [Markdown]: 月次トレンド分析
セル6 [Code]:     月別売上グラフ
セル7 [Markdown]: 商品分析
セル8 [Code]:     商品ランキンググラフ
セル9 [Markdown]: 顧客分析
セル10 [Code]:    顧客セグメントグラフ
セル11 [Markdown]: 提言まとめ

まず、最初のマークダウンセルにレポートのタイトルと概要を書きます。

# CLAN SHOP 売上分析レポート 2024年

**分析期間**: 2024年1月 〜 2024年12月
**作成日**: 2025年1月
**作成者**: データ分析チーム

---

## 分析の目的

本レポートでは、CLAN SHOP の2024年1年間の売上データ(1,000件)を分析し、
以下の問いに答える。

1. 売上はどのように推移しているか
2. どの商品カテゴリが成長しているか
3. 優良顧客の特徴は何か
4. 今後のアクションとして何が有効か

エグゼクティブサマリーの作成

経営層向けのレポートでは、冒頭に結論を置く「エグゼクティブサマリー」が不可欠です。Claude Code にデータから自動でサマリーを生成してもらいます。

> 売上データの主要KPIを計算して、
  エグゼクティブサマリー用の文章を自動生成して
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import platform

# 日本語フォント設定
if platform.system() == 'Windows':
    plt.rcParams['font.family'] = 'MS Gothic'
elif platform.system() == 'Darwin':
    plt.rcParams['font.family'] = 'Hiragino Sans'
plt.rcParams['axes.unicode_minus'] = False
sns.set_theme(style='whitegrid', font_scale=1.1)

# データ読み込み
df = pd.read_csv('data/processed/sales_cleaned.csv', parse_dates=['注文日'])
df['売上金額'] = df['単価'] * df['数量']
df['月'] = df['注文日'].dt.month

# KPI 計算
total_sales = df['売上金額'].sum()
total_orders = len(df)
unique_customers = df['顧客ID'].nunique()
avg_order_value = df['売上金額'].mean()
monthly_sales = df.groupby('月')['売上金額'].sum()
best_month = monthly_sales.idxmax()
worst_month = monthly_sales.idxmin()
top_category = df.groupby('カテゴリ')['売上金額'].sum().idxmax()
growth_rate = (monthly_sales.iloc[-1] - monthly_sales.iloc[0]) / monthly_sales.iloc[0] * 100

# サマリー出力
print("=" * 60)
print("エグゼクティブサマリー")
print("=" * 60)
print(f"""
■ 年間売上: ¥{total_sales:,.0f}(月平均: ¥{total_sales/12:,.0f})
■ 総注文数: {total_orders:,}件(ユニーク顧客: {unique_customers}人)
■ 平均客単価: ¥{avg_order_value:,.0f}
■ 最高売上月: {best_month}月(¥{monthly_sales.max():,.0f})
■ 売上トップカテゴリ: {top_category}
■ 12月 vs 1月 成長率: {growth_rate:+.1f}%

【主要な発見】
1. 年間を通じて売上は増加傾向にある
2. {top_category}カテゴリが売上全体の大きな割合を占める
3. 12月に季節的な売上ピークが見られる
""")

グラフの埋め込みとスタイリング

レポートのグラフは、見やすさと情報の密度のバランスが重要です。Claude Code にレポート品質のグラフを作ってもらいます。

> レポート用の見やすいグラフを4つ作って。
  1. 月別売上推移(棒+折れ線の二軸)
  2. カテゴリ別売上構成比(円グラフ)
  3. 商品ランキングTOP5(横棒)
  4. 顧客セグメント分布
  すべて統一感のある配色にして
# レポート用カラーパレット
COLORS = {
    'primary': '#1565C0',
    'secondary': '#E91E63',
    'accent': ['#2196F3', '#4CAF50', '#FF9800', '#9C27B0', '#00BCD4'],
    'bg': '#FAFAFA'
}

fig, axes = plt.subplots(2, 2, figsize=(16, 12))
fig.patch.set_facecolor(COLORS['bg'])

# 1. 月別売上推移
ax1 = axes[0, 0]
ax1_twin = ax1.twinx()
monthly_orders = df.groupby('月')['注文ID'].count()
ax1.bar(monthly_sales.index, monthly_sales.values,
        color=COLORS['primary'], alpha=0.7, label='売上合計')
ax1_twin.plot(monthly_orders.index, monthly_orders.values,
              color=COLORS['secondary'], marker='o', linewidth=2, label='注文件数')
ax1.set_title('月別売上推移', fontsize=14, fontweight='bold')
ax1.set_xlabel('月')
ax1.set_ylabel('売上金額(円)', color=COLORS['primary'])
ax1_twin.set_ylabel('注文件数', color=COLORS['secondary'])
ax1.set_xticks(range(1, 13))

# 2. カテゴリ別構成比
ax2 = axes[0, 1]
cat_sales = df.groupby('カテゴリ')['売上金額'].sum().sort_values(ascending=False)
wedges, texts, autotexts = ax2.pie(
    cat_sales.values, labels=cat_sales.index,
    autopct='%1.1f%%', colors=COLORS['accent'],
    startangle=90, pctdistance=0.85
)
for autotext in autotexts:
    autotext.set_fontsize(10)
    autotext.set_fontweight('bold')
ax2.set_title('カテゴリ別売上構成比', fontsize=14, fontweight='bold')

# 3. 商品ランキング TOP5
ax3 = axes[1, 0]
product_sales = df.groupby('商品名')['売上金額'].sum().sort_values(ascending=True).tail(5)
ax3.barh(product_sales.index, product_sales.values, color=COLORS['accent'])
ax3.set_title('商品別売上 TOP5', fontsize=14, fontweight='bold')
ax3.set_xlabel('売上金額(円)')
for i, v in enumerate(product_sales.values):
    ax3.text(v + 500, i, f'¥{v:,.0f}', va='center', fontsize=9)

# 4. 地域別売上
ax4 = axes[1, 1]
region_sales = df.groupby('地域')['売上金額'].sum().sort_values(ascending=False)
ax4.bar(region_sales.index, region_sales.values, color=COLORS['accent'])
ax4.set_title('地域別売上', fontsize=14, fontweight='bold')
ax4.set_ylabel('売上金額(円)')
for i, v in enumerate(region_sales.values):
    ax4.text(i, v + 500, f'¥{v:,.0f}', ha='center', fontsize=9)

plt.suptitle('CLAN SHOP 売上分析ダッシュボード 2024年',
             fontsize=20, fontweight='bold', y=1.02)
plt.tight_layout()
plt.savefig('reports/figures/report_dashboard.png', dpi=200, bbox_inches='tight',
            facecolor=COLORS['bg'])
plt.show()

HTML エクスポートと共有

完成したレポートを HTML に変換して、ブラウザで閲覧できるようにします。

> Jupyter NotebookをHTMLにエクスポートして。
  コードセルは非表示にして、グラフとテキストだけの
  きれいなレポートにして

Jupyter Notebook の HTML エクスポートは、コマンドラインから実行できます。

# 基本的なHTML変換
jupyter nbconvert --to html notebooks/sales_report.ipynb

# コードを非表示にしたバージョン
jupyter nbconvert --to html --no-input notebooks/sales_report.ipynb

# 出力先を指定
jupyter nbconvert --to html --no-input \
    --output-dir=reports/ \
    notebooks/sales_report.ipynb

--no-input オプションを使うと、Pythonコードセルが非表示になり、マークダウンテキストとグラフ出力だけの見栄えの良いレポートが生成されます。

さらに、Python スクリプトからプログラム的にエクスポートすることもできます。

import subprocess

# nbconvert で HTML 変換
subprocess.run([
    'jupyter', 'nbconvert',
    '--to', 'html',
    '--no-input',
    '--output-dir=reports/',
    'notebooks/sales_report.ipynb'
], check=True)

print("レポートを reports/sales_report.html に出力しました")

提言セクションの書き方

レポートの最後には、分析結果に基づいた具体的な提言をまとめます。Claude Code にデータに基づいた提言を生成してもらいましょう。

> 分析結果をもとに、ECサイトの売上向上のための
  提言を3つ、データの根拠とセットで書いて

レポートの提言セクションの例です。

## 提言

### 1. 12月の売上集中を活かしたキャンペーン強化
12月は年間最高売上(前月比+XX%)。この時期に合わせて
早期割引やギフトセットの展開を推奨。

### 2. VIP顧客向けロイヤリティプログラムの導入
上位25%の顧客が全売上のXX%を占める。リピート率向上のための
ポイント制度やVIP限定セールの実施を提案。

### 3. 低パフォーマンスカテゴリの改善
XX カテゴリの売上構成比が最も低い。商品ラインナップの見直し
またはマーケティング強化が必要。

データの数値を根拠として示すことで、提言の説得力が大幅に高まります。抽象的な感想ではなく、具体的な数字に裏打ちされたアクションアイテムを書くことがポイントです。

練習問題

  1. Jupyter Notebook で新しいノートブックを作成し、タイトルと概要のマークダウンセルを書いてください
  2. Claude Code に「エグゼクティブサマリー用のKPIを計算して」と指示し、主要指標を算出してください
  3. 4つのグラフ(月次トレンド、カテゴリ構成比、商品ランキング、地域別売上)を1枚のダッシュボードにまとめてください
  4. jupyter nbconvert --to html --no-input でコードを非表示にした HTML レポートを出力してください
  5. 分析結果に基づいた提言を3つ、データの根拠と合わせて書いてください

参考リンク

Lecture 10自動化 — 分析パイプラインを構築する

12:00

自動化 — 分析パイプラインを構築する

ここまでの講義で、データの読み込みからクリーニング、集計、可視化、レポート作成までを一通り学びました。しかし実務では、同じ分析を毎週・毎月繰り返す必要があります。毎回手動で Jupyter Notebook を開いて実行するのは非効率です。最終回のこの講義では、分析全体を1つの Python スクリプトにまとめ、コマンドライン引数で柔軟に動作する分析パイプラインを Claude Code と一緒に構築します。

分析パイプラインの設計

分析パイプラインとは、データの取得から最終レポートの生成まで、一連の処理を自動で実行するプログラムです。今回のパイプラインは以下の5ステップで構成します。

[1. データ読み込み] → [2. クリーニング] → [3. 集計] → [4. 可視化] → [5. レポート出力]

Claude Code にパイプラインの骨格を作ってもらいましょう。

> ECサイト売上データの分析パイプラインを
  Pythonスクリプトで作って。
  1. CSVデータ読み込み
  2. クリーニング(欠損値処理、異常値除去)
  3. 月別・カテゴリ別の集計
  4. グラフ生成(4種類)
  5. HTMLレポート出力
  コマンドライン引数で入力ファイルと出力先を指定できるようにして

Claude Code が生成するスクリプトの全体構造です。

#!/usr/bin/env python
"""ECサイト売上データ 分析パイプライン"""

import argparse
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import platform
from pathlib import Path
from datetime import datetime


def setup_plot_style():
    """グラフの日本語フォントとスタイルを設定する"""
    if platform.system() == 'Windows':
        plt.rcParams['font.family'] = 'MS Gothic'
    elif platform.system() == 'Darwin':
        plt.rcParams['font.family'] = 'Hiragino Sans'
    else:
        plt.rcParams['font.family'] = 'IPAGothic'
    plt.rcParams['axes.unicode_minus'] = False
    sns.set_theme(style='whitegrid', font_scale=1.1)


def load_data(filepath):
    """CSVファイルを読み込んでDataFrameを返す"""
    print(f"[1/5] データ読み込み: {filepath}")
    df = pd.read_csv(filepath, parse_dates=['注文日'])
    print(f"  読み込み完了: {len(df)}行 x {len(df.columns)}列")
    return df


def clean_data(df):
    """欠損値処理と異常値除去を行う"""
    print("[2/5] データクリーニング")
    original_count = len(df)

    # 欠損値処理
    df['単価'] = df['単価'].fillna(df['単価'].median())
    df['数量'] = df['数量'].fillna(df['数量'].median())
    for col in ['地域', '支払方法', 'カテゴリ']:
        if col in df.columns:
            df[col] = df[col].fillna(df[col].mode()[0])
    df = df.dropna(subset=['商品名'])

    # 異常値除去(IQR法)
    for col in ['単価', '数量']:
        Q1 = df[col].quantile(0.25)
        Q3 = df[col].quantile(0.75)
        IQR = Q3 - Q1
        df = df[(df[col] >= Q1 - 1.5 * IQR) & (df[col] <= Q3 + 1.5 * IQR)]

    # 重複削除
    df = df.drop_duplicates(subset=['注文ID'], keep='first')

    # 派生列の追加
    df['売上金額'] = df['単価'] * df['数量']
    df['月'] = df['注文日'].dt.month
    df['四半期'] = df['注文日'].dt.quarter

    print(f"  クリーニング完了: {original_count}行 → {len(df)}行")
    return df


def aggregate_data(df):
    """月別・カテゴリ別の集計を実行する"""
    print("[3/5] データ集計")

    results = {}

    # 月別集計
    results['monthly'] = df.groupby('月').agg(
        売上合計=('売上金額', 'sum'),
        注文件数=('注文ID', 'count'),
        平均客単価=('売上金額', 'mean'),
        ユニーク顧客数=('顧客ID', 'nunique')
    ).round(0)

    # カテゴリ別集計
    results['category'] = df.groupby('カテゴリ').agg(
        売上合計=('売上金額', 'sum'),
        注文件数=('注文ID', 'count'),
        平均単価=('単価', 'mean')
    ).round(0).sort_values('売上合計', ascending=False)

    # 商品別売上
    results['products'] = df.groupby('商品名')['売上金額'].sum()\
        .sort_values(ascending=False)

    # 地域別集計
    results['region'] = df.groupby('地域')['売上金額'].sum()\
        .sort_values(ascending=False)

    # 顧客セグメント
    customer = df.groupby('顧客ID').agg(
        購入回数=('注文ID', 'count'),
        合計売上=('売上金額', 'sum')
    ).round(0)
    customer['セグメント'] = pd.qcut(
        customer['合計売上'], 4,
        labels=['ライト', 'ミドル', 'ヘビー', 'VIP']
    )
    results['customers'] = customer

    print(f"  集計完了: {len(results)}種類の集計テーブル生成")
    return results


def generate_charts(df, results, output_dir):
    """分析グラフを生成して画像として保存する"""
    print("[4/5] グラフ生成")
    figures_dir = output_dir / 'figures'
    figures_dir.mkdir(parents=True, exist_ok=True)

    COLORS = ['#2196F3', '#4CAF50', '#FF9800', '#9C27B0', '#00BCD4']

    # ダッシュボード(4グラフ構成)
    fig, axes = plt.subplots(2, 2, figsize=(16, 12))

    # 1. 月別売上推移
    monthly = results['monthly']
    axes[0, 0].bar(monthly.index, monthly['売上合計'],
                   color='#2196F3', alpha=0.8)
    axes[0, 0].set_title('月別売上推移', fontsize=14, fontweight='bold')
    axes[0, 0].set_xlabel('月')
    axes[0, 0].set_ylabel('売上金額(円)')
    axes[0, 0].set_xticks(range(1, 13))

    # 2. カテゴリ別構成比
    cat = results['category']
    axes[0, 1].pie(cat['売上合計'].values, labels=cat.index,
                   autopct='%1.1f%%', colors=COLORS, startangle=90)
    axes[0, 1].set_title('カテゴリ別売上構成比', fontsize=14, fontweight='bold')

    # 3. 商品ランキング TOP5
    top5 = results['products'].head(5).sort_values()
    axes[1, 0].barh(top5.index, top5.values,
                    color=COLORS[:len(top5)])
    axes[1, 0].set_title('商品別売上 TOP5', fontsize=14, fontweight='bold')
    axes[1, 0].set_xlabel('売上金額(円)')

    # 4. 地域別売上
    region = results['region']
    axes[1, 1].bar(region.index, region.values, color=COLORS)
    axes[1, 1].set_title('地域別売上', fontsize=14, fontweight='bold')
    axes[1, 1].set_ylabel('売上金額(円)')

    plt.suptitle('売上分析ダッシュボード',
                 fontsize=20, fontweight='bold', y=1.02)
    plt.tight_layout()

    chart_path = figures_dir / 'dashboard.png'
    plt.savefig(chart_path, dpi=200, bbox_inches='tight',
                facecolor='white')
    plt.close()

    print(f"  グラフ保存: {chart_path}")
    return chart_path


def generate_report(df, results, chart_path, output_dir):
    """HTML形式の分析レポートを生成する"""
    print("[5/5] レポート生成")

    monthly = results['monthly']
    total_sales = df['売上金額'].sum()
    total_orders = len(df)
    unique_customers = df['顧客ID'].nunique()
    avg_order = df['売上金額'].mean()
    best_month = monthly['売上合計'].idxmax()
    top_category = results['category'].index[0]
    vip_count = len(results['customers'][
        results['customers']['セグメント'] == 'VIP'
    ])

    # 月別テーブルHTML
    monthly_rows = ""
    for month, row in monthly.iterrows():
        monthly_rows += f"""
        <tr>
            <td>{month}月</td>
            <td>&yen;{row['売上合計']:,.0f}</td>
            <td>{row['注文件数']:.0f}件</td>
            <td>&yen;{row['平均客単価']:,.0f}</td>
        </tr>"""

    report_date = datetime.now().strftime('%Y年%m月%d日')
    chart_relative = chart_path.name if chart_path else 'figures/dashboard.png'

    html = f"""<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>売上分析レポート 2024年</title>
    <style>
        body {{ font-family: 'Helvetica Neue', Arial, 'Hiragino Sans', sans-serif;
               max-width: 900px; margin: 0 auto; padding: 20px;
               color: #333; line-height: 1.8; }}
        h1 {{ color: #1565C0; border-bottom: 3px solid #1565C0; padding-bottom: 10px; }}
        h2 {{ color: #1565C0; margin-top: 40px; }}
        .kpi-grid {{ display: grid; grid-template-columns: repeat(3, 1fr);
                     gap: 16px; margin: 20px 0; }}
        .kpi-card {{ background: #E3F2FD; border-radius: 8px; padding: 20px;
                     text-align: center; }}
        .kpi-value {{ font-size: 28px; font-weight: bold; color: #1565C0; }}
        .kpi-label {{ font-size: 14px; color: #666; margin-top: 4px; }}
        table {{ width: 100%; border-collapse: collapse; margin: 20px 0; }}
        th, td {{ border: 1px solid #ddd; padding: 10px; text-align: right; }}
        th {{ background: #1565C0; color: white; }}
        tr:nth-child(even) {{ background: #f9f9f9; }}
        img {{ max-width: 100%; height: auto; margin: 20px 0;
               border: 1px solid #ddd; border-radius: 4px; }}
        .insight {{ background: #FFF3E0; border-left: 4px solid #FF9800;
                    padding: 16px; margin: 16px 0; border-radius: 4px; }}
        footer {{ margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd;
                  color: #999; font-size: 12px; }}
    </style>
</head>
<body>
    <h1>CLAN SHOP 売上分析レポート 2024年</h1>
    <p><strong>作成日:</strong> {report_date} | <strong>分析期間:</strong> 2024年1月〜12月</p>

    <h2>エグゼクティブサマリー</h2>
    <div class="kpi-grid">
        <div class="kpi-card">
            <div class="kpi-value">&yen;{total_sales:,.0f}</div>
            <div class="kpi-label">年間売上</div>
        </div>
        <div class="kpi-card">
            <div class="kpi-value">{total_orders:,}</div>
            <div class="kpi-label">総注文数</div>
        </div>
        <div class="kpi-card">
            <div class="kpi-value">{unique_customers}</div>
            <div class="kpi-label">ユニーク顧客数</div>
        </div>
        <div class="kpi-card">
            <div class="kpi-value">&yen;{avg_order:,.0f}</div>
            <div class="kpi-label">平均客単価</div>
        </div>
        <div class="kpi-card">
            <div class="kpi-value">{best_month}月</div>
            <div class="kpi-label">最高売上月</div>
        </div>
        <div class="kpi-card">
            <div class="kpi-value">{vip_count}人</div>
            <div class="kpi-label">VIP顧客数</div>
        </div>
    </div>

    <h2>分析ダッシュボード</h2>
    <img src="figures/{chart_relative}" alt="売上分析ダッシュボード">

    <h2>月別売上詳細</h2>
    <table>
        <tr><th>月</th><th>売上合計</th><th>注文件数</th><th>平均客単価</th></tr>
        {monthly_rows}
    </table>

    <h2>主要インサイト</h2>
    <div class="insight">
        <strong>1. 年末に売上が集中</strong><br>
        {best_month}月が年間最高売上。ギフト需要とセール施策の効果が見られる。
    </div>
    <div class="insight">
        <strong>2. {top_category}カテゴリが牽引</strong><br>
        {top_category}が売上構成比トップ。高単価商品のラインナップ強化が有効。
    </div>
    <div class="insight">
        <strong>3. VIP顧客の維持が重要</strong><br>
        上位{vip_count}人のVIP顧客が売上の大きな部分を占める。ロイヤリティプログラムの導入を推奨。
    </div>

    <footer>
        <p>本レポートは Python + pandas + matplotlib で自動生成されました。</p>
        <p>Generated by CLAN SHOP Analysis Pipeline</p>
    </footer>
</body>
</html>"""

    report_path = output_dir / 'sales_report.html'
    report_path.write_text(html, encoding='utf-8')
    print(f"  レポート保存: {report_path}")
    return report_path


def main():
    parser = argparse.ArgumentParser(
        description='ECサイト売上データ 分析パイプライン'
    )
    parser.add_argument(
        '--input', '-i',
        default='data/raw/sales_2024.csv',
        help='入力CSVファイルのパス(デフォルト: data/raw/sales_2024.csv)'
    )
    parser.add_argument(
        '--output', '-o',
        default='reports',
        help='出力ディレクトリ(デフォルト: reports/)'
    )
    args = parser.parse_args()

    output_dir = Path(args.output)
    output_dir.mkdir(parents=True, exist_ok=True)

    print("=" * 50)
    print("ECサイト売上分析パイプライン 開始")
    print("=" * 50)

    setup_plot_style()
    df = load_data(args.input)
    df = clean_data(df)
    results = aggregate_data(df)
    chart_path = generate_charts(df, results, output_dir)
    report_path = generate_report(df, results, chart_path, output_dir)

    print("=" * 50)
    print("パイプライン完了")
    print(f"レポート: {report_path}")
    print("=" * 50)


if __name__ == '__main__':
    main()

このスクリプトを src/pipeline.py として保存します。

argparse によるコマンドライン引数

argparse モジュールを使うと、スクリプトにコマンドライン引数を柔軟に追加できます。

> pipeline.py にオプションを追加して。
  --month で特定の月だけ分析、
  --category で特定カテゴリだけ分析、
  --no-charts でグラフ生成をスキップできるようにして
parser.add_argument(
    '--month', '-m', type=int, choices=range(1, 13),
    help='特定の月だけ分析する(1〜12)'
)
parser.add_argument(
    '--category', '-c', type=str,
    help='特定のカテゴリだけ分析する'
)
parser.add_argument(
    '--no-charts', action='store_true',
    help='グラフ生成をスキップする'
)

実行例です。

# 基本実行
python src/pipeline.py

# 入力ファイルと出力先を指定
python src/pipeline.py -i data/raw/sales_2024.csv -o reports/2024

# 12月だけ分析
python src/pipeline.py --month 12

# 家電カテゴリだけ分析
python src/pipeline.py --category 家電

# グラフなしで集計だけ
python src/pipeline.py --no-charts

# ヘルプを表示
python src/pipeline.py --help

スケジュール実行の設定

定期的に分析を実行するために、スケジューラーを設定します。

> 毎週月曜の朝9時にパイプラインを自動実行する
  設定方法を教えて。Windows と macOS の両方

Windows の場合はタスクスケジューラ、macOS/Linux の場合は cron を使います。

# macOS/Linux: crontab に追加
crontab -e

# 毎週月曜 9:00 に実行
0 9 * * 1 cd /path/to/sales-analysis && /path/to/venv/bin/python src/pipeline.py >> logs/pipeline.log 2>&1

Windows のタスクスケジューラの場合は、バッチファイルを作成します。

@echo off
cd C:\Users\user\sales-analysis
call venv\Scripts\activate
python src/pipeline.py -o reports\weekly

Python の schedule ライブラリを使ったシンプルなスケジューラーも作れます。

import schedule
import time
import subprocess

def run_pipeline():
    print(f"パイプライン開始: {datetime.now()}")
    subprocess.run(['python', 'src/pipeline.py'], check=True)
    print(f"パイプライン完了: {datetime.now()}")

# 毎週月曜9時に実行
schedule.every().monday.at("09:00").do(run_pipeline)

# 毎日18時に実行
# schedule.every().day.at("18:00").do(run_pipeline)

print("スケジューラー起動中... (Ctrl+C で終了)")
while True:
    schedule.run_pending()
    time.sleep(60)

Claude Code でパイプラインを拡張する

Claude Code を使えば、パイプラインの機能追加も自然言語で指示できます。

> パイプラインに以下の機能を追加して:
  1. 実行ログをファイルに記録する(loggingモジュール)
  2. 処理時間を計測して表示する
  3. エラー発生時にメール通知する(smtplib)
import logging
import time

# ログ設定
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    handlers=[
        logging.FileHandler('logs/pipeline.log', encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

def main():
    start_time = time.time()
    logger.info("パイプライン開始")

    try:
        setup_plot_style()
        df = load_data(args.input)
        df = clean_data(df)
        results = aggregate_data(df)
        chart_path = generate_charts(df, results, output_dir)
        report_path = generate_report(df, results, chart_path, output_dir)

        elapsed = time.time() - start_time
        logger.info(f"パイプライン完了(処理時間: {elapsed:.1f}秒)")

    except Exception as e:
        logger.error(f"パイプラインエラー: {e}", exc_info=True)
        raise

このようにして、一連の分析ワークフローを完全に自動化できます。Claude Code を使えば、新しいデータソースの追加、グラフの種類変更、レポートのフォーマット調整なども自然言語で指示するだけで実現できます。

練習問題

  1. Claude Code に「src/pipeline.py を作成して。データ読み込み、クリーニング、集計、可視化、レポート出力の5ステップで」と指示してパイプラインスクリプトを作成してください
  2. python src/pipeline.py --help を実行して、コマンドライン引数のヘルプを確認してください
  3. --month 12 オプションを追加して、12月のデータだけを分析するモードを実装してください
  4. パイプラインに logging を追加して、実行ログを logs/pipeline.log に記録するようにしてください
  5. Claude Code に「このパイプラインの処理時間を計測してレポートに含めて」と指示して、パフォーマンス計測機能を追加してください

参考リンク