📉 STEP 35: 移動平均と季節調整
データから季節性を除去し、真のトレンドを見極めよう
📋 このステップで学ぶこと
- 移動平均を使う目的と効果
- 単純移動平均と加重移動平均の違い
- 中心化移動平均の考え方
- 季節分解(加法モデル・乗法モデル)
- 季節指数の計算と季節調整
- ExcelとPythonでの実装方法
学習時間の目安: 3.5時間
🔍 1. 移動平均とは何か
なぜ移動平均を使うのか
📌 データの「ノイズ」を除去し、本質的なパターンを見つける
生データには様々な変動が混在しています
生データの問題点:
・日々の売上は大きく変動する
・一時的なイベント(セール、天候)の影響
・曜日による違い
・測定誤差やデータ入力ミス
→ 「木を見て森を見ず」になりがち
移動平均の効果:
・短期的な変動(ノイズ)を平滑化
・長期的なトレンドが明確に
・異常値の影響を軽減
・意思決定がしやすくなる
ビジネスでの活用シーン:
1. 経営報告
「先月の売上は前月比-5%でした」
→ 一時的な変動?本当の減少?
→ 移動平均で判断
2. 在庫管理
日々の出荷数は変動が大きい
→ 移動平均で安定した需要予測
3. KPIモニタリング
・コンバージョン率の推移
・顧客満足度スコア
→ 移動平均でトレンドを把握
・日々の売上は大きく変動する
・一時的なイベント(セール、天候)の影響
・曜日による違い
・測定誤差やデータ入力ミス
→ 「木を見て森を見ず」になりがち
移動平均の効果:
・短期的な変動(ノイズ)を平滑化
・長期的なトレンドが明確に
・異常値の影響を軽減
・意思決定がしやすくなる
ビジネスでの活用シーン:
1. 経営報告
「先月の売上は前月比-5%でした」
→ 一時的な変動?本当の減少?
→ 移動平均で判断
2. 在庫管理
日々の出荷数は変動が大きい
→ 移動平均で安定した需要予測
3. KPIモニタリング
・コンバージョン率の推移
・顧客満足度スコア
→ 移動平均でトレンドを把握
移動平均のイメージ
💡 「窓」をスライドさせながら平均を計算
3期間移動平均の動き:
データ: [100, 110, 105, 115, 120, 125]
ステップ1: 窓を1〜3期間に設定
[100, 110, 105, 115, 120, 125]
→ 平均 = (100+110+105)/3 = 105
ステップ2: 窓を1つ右にスライド
[100, 110, 105, 115, 120, 125]
→ 平均 = (110+105+115)/3 = 110
ステップ3: さらに右にスライド
[100, 110, 105, 115, 120, 125]
→ 平均 = (105+115+120)/3 = 113.3
ステップ4: 最後まで
[100, 110, 105, 115, 120, 125]
→ 平均 = (115+120+125)/3 = 120
結果:
元データ: 100, 110, 105, 115, 120, 125
移動平均: -, -, 105, 110, 113, 120
→ 3月の落ち込み(105)が目立たなくなり、
上昇トレンドが明確に!
データ: [100, 110, 105, 115, 120, 125]
ステップ1: 窓を1〜3期間に設定
[100, 110, 105, 115, 120, 125]
→ 平均 = (100+110+105)/3 = 105
ステップ2: 窓を1つ右にスライド
[100, 110, 105, 115, 120, 125]
→ 平均 = (110+105+115)/3 = 110
ステップ3: さらに右にスライド
[100, 110, 105, 115, 120, 125]
→ 平均 = (105+115+120)/3 = 113.3
ステップ4: 最後まで
[100, 110, 105, 115, 120, 125]
→ 平均 = (115+120+125)/3 = 120
結果:
元データ: 100, 110, 105, 115, 120, 125
移動平均: -, -, 105, 110, 113, 120
→ 3月の落ち込み(105)が目立たなくなり、
上昇トレンドが明確に!
📊 2. 移動平均の種類
単純移動平均(SMA: Simple Moving Average)
📌 すべての期間に等しい重みをつける
計算式:
SMA = (x₁ + x₂ + … + xₙ) / n
具体例(3期間移動平均):
売上: 100, 110, 120万円
SMA = (100 + 110 + 120) / 3
= 330 / 3
= 110万円
特徴:
✅ シンプルで計算が容易
✅ Excelで簡単に実装
✅ 説明しやすい(経営層向け)
✅ ノイズを効果的に除去
欠点:
❌ すべての期間を同じ重みで扱う
❌ 最近の変化に鈍感
❌ 急激な変化への反応が遅い
❌ トレンド転換の検出が遅れる
SMA = (x₁ + x₂ + … + xₙ) / n
具体例(3期間移動平均):
売上: 100, 110, 120万円
SMA = (100 + 110 + 120) / 3
= 330 / 3
= 110万円
特徴:
✅ シンプルで計算が容易
✅ Excelで簡単に実装
✅ 説明しやすい(経営層向け)
✅ ノイズを効果的に除去
欠点:
❌ すべての期間を同じ重みで扱う
❌ 最近の変化に鈍感
❌ 急激な変化への反応が遅い
❌ トレンド転換の検出が遅れる
加重移動平均(WMA: Weighted Moving Average)
💡 最近のデータに大きな重みをつける
計算式:
WMA = Σ(wᵢ × xᵢ) / Σwᵢ
具体例(3期間、重み: 1, 2, 3):
売上: 100, 110, 120万円
重み: 1(古い), 2(中間), 3(最新)
WMA = (1×100 + 2×110 + 3×120) / (1+2+3)
= (100 + 220 + 360) / 6
= 680 / 6
= 113.3万円
SMAとの比較:
SMA = 110.0万円
WMA = 113.3万円
差 = +3.3万円
→ WMAは上昇トレンドをより反映!
なぜ差が出るか:
・SMA: 100, 110, 120を等しく扱う
・WMA: 120を最も重視(重み3)
→ 最新の上昇傾向を強く反映
用途:
・トレンド転換を早く検出したい
・株価のテクニカル分析
・短期予測
WMA = Σ(wᵢ × xᵢ) / Σwᵢ
具体例(3期間、重み: 1, 2, 3):
売上: 100, 110, 120万円
重み: 1(古い), 2(中間), 3(最新)
WMA = (1×100 + 2×110 + 3×120) / (1+2+3)
= (100 + 220 + 360) / 6
= 680 / 6
= 113.3万円
SMAとの比較:
SMA = 110.0万円
WMA = 113.3万円
差 = +3.3万円
→ WMAは上昇トレンドをより反映!
なぜ差が出るか:
・SMA: 100, 110, 120を等しく扱う
・WMA: 120を最も重視(重み3)
→ 最新の上昇傾向を強く反映
用途:
・トレンド転換を早く検出したい
・株価のテクニカル分析
・短期予測
中心化移動平均
📌 偶数期間の移動平均を正確に計算する方法
問題: 12ヶ月移動平均はどこに配置する?
例: 1月〜12月のデータで12ヶ月MA計算
→ この平均値は「何月」の値?
→ 6月と7月の間(6.5月)に相当
→ グラフ上でずれてしまう!
解決策: 2×12移動平均
ステップ1: 12ヶ月移動平均を計算
MA₁ = (1月〜12月の平均) → 6.5月の位置
MA₂ = (2月〜翌1月の平均) → 7.5月の位置
ステップ2: 隣り合う2つの平均を再平均
中心化MA = (MA₁ + MA₂) / 2
→ 7月の位置に配置!
Pythonでの実装:
なぜ重要か:
・季節分解で正確な結果を得るため
・トレンドと元データの比較を正確に
・実務では自動計算されることが多い
例: 1月〜12月のデータで12ヶ月MA計算
→ この平均値は「何月」の値?
→ 6月と7月の間(6.5月)に相当
→ グラフ上でずれてしまう!
解決策: 2×12移動平均
ステップ1: 12ヶ月移動平均を計算
MA₁ = (1月〜12月の平均) → 6.5月の位置
MA₂ = (2月〜翌1月の平均) → 7.5月の位置
ステップ2: 隣り合う2つの平均を再平均
中心化MA = (MA₁ + MA₂) / 2
→ 7月の位置に配置!
Pythonでの実装:
df['MA_12'] = df['sales'].rolling(12, center=True).mean()なぜ重要か:
・季節分解で正確な結果を得るため
・トレンドと元データの比較を正確に
・実務では自動計算されることが多い
期間の選び方
💡 目的に応じて最適な期間を選択
実務のコツ:
・迷ったら3ヶ月と12ヶ月を併記
・短期と長期の両方の視点を提供
・経営層は「両方見たい」ことが多い
| 期間 | 特徴 | 用途 | 注意点 |
|---|---|---|---|
| 3ヶ月 | 変化に敏感 ノイズ残る |
短期トレンド 月次レポート |
季節性は除去できない |
| 6ヶ月 | バランス良い 実務で人気 |
中期トレンド 四半期レポート |
半年周期は除去可能 |
| 12ヶ月 | 年次季節性除去 滑らか |
長期トレンド 年次計画 |
変化への反応遅い |
実務のコツ:
・迷ったら3ヶ月と12ヶ月を併記
・短期と長期の両方の視点を提供
・経営層は「両方見たい」ことが多い
💻 3. Pythonでの移動平均実装
基本的な実装
# ============================================
# 移動平均の実装
# ============================================
# このセクションで学ぶこと:
# 1. pandas の rolling() 関数の使い方
# 2. 単純移動平均(SMA)の計算
# 3. 加重移動平均(WMA)の計算
# 4. 期間による平滑化効果の違い
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# ============================================
# サンプルデータ作成
# ============================================
# 実際のビジネスデータに近い形で作成
# – 上昇トレンド(成長企業を想定)
# – 年次季節性(夏に売上増)
# – ランダムなノイズ(日々の変動)
np.random.seed(42) # 再現性のため固定
# 36ヶ月分の日付を生成
# freq=’MS’: Month Start(月初)
months = pd.date_range(‘2022-01-01’, ‘2024-12-01′, freq=’MS’)
# 時系列の3要素を作成
# 1. トレンド: 100万円 → 150万円へ線形に成長
trend = np.linspace(100, 150, len(months))
# 2. 季節性: 振幅±20万円の年次サイクル
# 2*pi/12 = 12ヶ月で1周期
seasonality = 20 * np.sin(np.arange(len(months)) * 2 * np.pi / 12)
# 3. ノイズ: 標準偏差5万円のランダム変動
noise = np.random.normal(0, 5, len(months))
# 3要素を合成
sales = trend + seasonality + noise
# データフレーム作成
df = pd.DataFrame({
‘date’: months,
‘sales’: sales
})
print(“【サンプルデータ確認】”)
print(f”期間: {df[‘date’].min().strftime(‘%Y年%m月’)} 〜 {df[‘date’].max().strftime(‘%Y年%m月’)}”)
print(f”データ数: {len(df)}ヶ月”)
print(f”売上範囲: {df[‘sales’].min():.1f} 〜 {df[‘sales’].max():.1f}万円”)
print()
rolling()関数の詳細
# ============================================
# pandas の rolling() 関数
# ============================================
# rolling() は「窓関数」を適用するための関数
#
# 主なパラメータ:
# window: 窓のサイズ(何期間分を使うか)
# min_periods: 計算に必要な最小データ数(デフォルト=window)
# center: 窓の中心を現在位置にするか(デフォルト=False)
# ============================================
# 1. 基本的な使い方
# ============================================
# 3ヶ月移動平均
df[‘SMA_3’] = df[‘sales’].rolling(window=3).mean()
# 上記は以下と同じ意味:
# df[‘SMA_3’] = df[‘sales’].rolling(
# window=3, # 3期間の窓
# min_periods=3, # 最低3期間必要(最初の2行はNaN)
# center=False # 窓の右端が現在位置
# ).mean()
# ============================================
# 2. 複数期間の移動平均
# ============================================
df[‘SMA_6’] = df[‘sales’].rolling(window=6).mean() # 6ヶ月
df[‘SMA_12’] = df[‘sales’].rolling(window=12).mean() # 12ヶ月
# ============================================
# 3. 中心化移動平均(center=True)
# ============================================
# center=True: 窓の中心が現在位置になる
# → 季節分解でよく使用
df[‘SMA_12_center’] = df[‘sales’].rolling(window=12, center=True).mean()
# 違いの確認
print(“【center=True と center=False の違い】”)
print()
print(“通常の12ヶ月MA(center=False):”)
print(” → 1月〜12月のデータで12月に値が入る”)
print(” → 「過去12ヶ月の平均」を表す”)
print()
print(“中心化12ヶ月MA(center=True):”)
print(” → 1月〜12月のデータで6月付近に値が入る”)
print(” → 「その時点のトレンド」を表す”)
print()
# ============================================
# 4. min_periods の使い方
# ============================================
# min_periods: 計算に必要な最小データ数
# → NaNを減らしたい時に使用
df[‘SMA_3_min1’] = df[‘sales’].rolling(window=3, min_periods=1).mean()
print(“【min_periods の効果】”)
print()
print(“min_periods=3(デフォルト):”)
print(df[[‘date’, ‘sales’, ‘SMA_3’]].head(5).to_string(index=False))
print()
print(“min_periods=1:”)
print(df[[‘date’, ‘sales’, ‘SMA_3_min1’]].head(5).to_string(index=False))
print()
print(“→ min_periods=1 だと1期目から計算される(データ不足でも)”)
加重移動平均の実装
# ============================================
# 加重移動平均(WMA)の実装
# ============================================
# pandasには標準でWMA関数がないため、自作する
def weighted_moving_average(series, weights):
“””
加重移動平均を計算する関数
Parameters:
———–
series : pd.Series or np.array
時系列データ
weights : list or np.array
重み(古いデータから順に指定)
例: [1, 2, 3] → 最も古いデータに1、最新に3
Returns:
——–
list : 加重移動平均値のリスト
計算式:
WMA = Σ(weight_i × value_i) / Σ(weight_i)
“””
weights = np.array(weights)
result = []
for i in range(len(series)):
# 窓サイズ分のデータがない場合はNaN
if i < len(weights) - 1:
result.append(np.nan)
else:
# 直近n期間のデータを取得
window = series[i - len(weights) + 1 : i + 1]
# 加重平均を計算
wma = np.sum(window * weights) / np.sum(weights)
result.append(wma)
return result
# ============================================
# 線形重みでWMA計算
# ============================================
# 重み: [1, 2, 3] → 最新データを最も重視
weights_linear = np.array([1, 2, 3])
df['WMA_3'] = weighted_moving_average(df['sales'].values, weights_linear)
# ============================================
# SMAとWMAの比較
# ============================================
print("【SMA vs WMA の比較】")
print()
print("直近6ヶ月のデータ:")
print(df[['date', 'sales', 'SMA_3', 'WMA_3']].tail(6).to_string(index=False))
print()
# 差を計算
df['SMA_WMA_diff'] = df['WMA_3'] - df['SMA_3']
recent_diff = df['SMA_WMA_diff'].tail(6).mean()
print(f"WMA - SMA の平均差: {recent_diff:+.2f}万円")
print()
if recent_diff > 0:
print(“→ WMA > SMA: 上昇トレンドをWMAがより反映”)
else:
print(“→ WMA < SMA: 下降トレンドをWMAがより反映")
可視化
# ============================================
# 可視化
# ============================================
fig, axes = plt.subplots(2, 2, figsize=(16, 10))
# ============================================
# グラフ1: 期間別の移動平均比較
# ============================================
ax1 = axes[0, 0]
ax1.plot(df[‘date’], df[‘sales’], label=’実績’,
alpha=0.4, marker=’o’, markersize=3, color=’gray’)
ax1.plot(df[‘date’], df[‘SMA_3′], label=’3ヶ月MA’,
linewidth=2, color=’red’)
ax1.plot(df[‘date’], df[‘SMA_6′], label=’6ヶ月MA’,
linewidth=2, color=’blue’)
ax1.plot(df[‘date’], df[‘SMA_12′], label=’12ヶ月MA’,
linewidth=2, color=’green’)
ax1.set_title(‘期間別 移動平均の比較’, fontsize=13, fontweight=’bold’)
ax1.set_ylabel(‘売上(万円)’, fontsize=11)
ax1.legend(loc=’upper left’)
ax1.grid(alpha=0.3)
# ============================================
# グラフ2: SMA vs WMA
# ============================================
ax2 = axes[0, 1]
ax2.plot(df[‘date’], df[‘sales’], label=’実績’,
alpha=0.4, marker=’o’, markersize=3, color=’gray’)
ax2.plot(df[‘date’], df[‘SMA_3′], label=’単純MA(3ヶ月)’,
linewidth=2, color=’blue’)
ax2.plot(df[‘date’], df[‘WMA_3′], label=’加重MA(3ヶ月)’,
linewidth=2, linestyle=’–‘, color=’red’)
ax2.set_title(‘単純移動平均 vs 加重移動平均’, fontsize=13, fontweight=’bold’)
ax2.set_ylabel(‘売上(万円)’, fontsize=11)
ax2.legend(loc=’upper left’)
ax2.grid(alpha=0.3)
# ============================================
# グラフ3: 平滑化効果の比較
# ============================================
ax3 = axes[1, 0]
ax3.plot(df[‘date’], df[‘sales’], label=’元データ’,
alpha=0.6, color=’gray’, linewidth=1)
ax3.plot(df[‘date’], df[‘SMA_3′], label=’3ヶ月MA(弱い平滑化)’,
linewidth=2, color=’orange’)
ax3.plot(df[‘date’], df[‘SMA_12′], label=’12ヶ月MA(強い平滑化)’,
linewidth=2, color=’purple’)
ax3.set_title(‘期間による平滑化効果の違い’, fontsize=13, fontweight=’bold’)
ax3.set_xlabel(‘日付’, fontsize=11)
ax3.set_ylabel(‘売上(万円)’, fontsize=11)
ax3.legend(loc=’upper left’)
ax3.grid(alpha=0.3)
# ============================================
# グラフ4: 変動の大きさ比較(標準偏差)
# ============================================
ax4 = axes[1, 1]
labels = [‘元データ’, ‘3ヶ月MA’, ‘6ヶ月MA’, ’12ヶ月MA’]
stds = [
df[‘sales’].std(),
df[‘SMA_3’].std(),
df[‘SMA_6’].std(),
df[‘SMA_12’].std()
]
colors = [‘gray’, ‘red’, ‘blue’, ‘green’]
bars = ax4.bar(labels, stds, color=colors, alpha=0.7, edgecolor=’black’)
ax4.set_ylabel(‘標準偏差(変動の大きさ)’, fontsize=11)
ax4.set_title(‘移動平均による平滑化効果’, fontsize=13, fontweight=’bold’)
ax4.grid(axis=’y’, alpha=0.3)
# 値を表示
for bar, std in zip(bars, stds):
ax4.text(bar.get_x() + bar.get_width()/2, bar.get_height(),
f'{std:.1f}’, ha=’center’, va=’bottom’, fontsize=11)
plt.tight_layout()
plt.show()
print(“【平滑化効果の数値比較】”)
print(f”元データの標準偏差: {df[‘sales’].std():.2f}”)
print(f”3ヶ月MAの標準偏差: {df[‘SMA_3’].std():.2f} (元の{df[‘SMA_3’].std()/df[‘sales’].std()*100:.0f}%)”)
print(f”6ヶ月MAの標準偏差: {df[‘SMA_6’].std():.2f} (元の{df[‘SMA_6’].std()/df[‘sales’].std()*100:.0f}%)”)
print(f”12ヶ月MAの標準偏差: {df[‘SMA_12’].std():.2f} (元の{df[‘SMA_12’].std()/df[‘sales’].std()*100:.0f}%)”)
print()
print(“→ 期間が長いほど変動が小さくなる(平滑化効果が大きい)”)
移動平均のラグ(遅延)問題
⚠️ 移動平均は「過去を見ている」ため反応が遅れる
ラグとは:
・移動平均は過去n期間のデータを使う
・そのため、変化が起きてもすぐには反映されない
・期間が長いほどラグが大きい
実務での問題:
・トレンド転換の検出が遅れる
・急激な売上変化に気づかない
・意思決定のタイミングを逃す
対策:
・短期MAと長期MAを併用
・加重移動平均(WMA)を使う
・指数平滑法を検討(STEP 34参照)
・移動平均は過去n期間のデータを使う
・そのため、変化が起きてもすぐには反映されない
・期間が長いほどラグが大きい
実務での問題:
・トレンド転換の検出が遅れる
・急激な売上変化に気づかない
・意思決定のタイミングを逃す
対策:
・短期MAと長期MAを併用
・加重移動平均(WMA)を使う
・指数平滑法を検討(STEP 34参照)
# ============================================
# 移動平均のラグ(遅延)問題の可視化
# ============================================
# 問題: 急激な変化が起きた時、移動平均は
# どれくらい遅れて反応するか?
# 急激な変化を含むデータを作成
df_lag = df.copy()
change_point = 20 # 20ヶ月目に急上昇
# 20ヶ月目以降、売上が30万円増加
df_lag.loc[change_point:, ‘sales’] = df_lag.loc[change_point:, ‘sales’] + 30
# 移動平均を再計算
df_lag[‘SMA_3’] = df_lag[‘sales’].rolling(window=3).mean()
df_lag[‘SMA_6’] = df_lag[‘sales’].rolling(window=6).mean()
df_lag[‘SMA_12’] = df_lag[‘sales’].rolling(window=12).mean()
# 可視化
fig, axes = plt.subplots(1, 2, figsize=(16, 5))
# グラフ1: ラグの比較
ax1 = axes[0]
ax1.plot(df_lag[‘date’], df_lag[‘sales’], label=’実績(急上昇あり)’,
alpha=0.6, marker=’o’, markersize=3, color=’gray’)
ax1.plot(df_lag[‘date’], df_lag[‘SMA_3′], label=’3ヶ月MA’,
linewidth=2, color=’red’)
ax1.plot(df_lag[‘date’], df_lag[‘SMA_12′], label=’12ヶ月MA’,
linewidth=2, color=’blue’)
ax1.axvline(x=df_lag[‘date’].iloc[change_point], color=’green’,
linestyle=’–‘, linewidth=2, label=’変化点’)
ax1.set_title(‘移動平均のラグ(遅延)’, fontsize=13, fontweight=’bold’)
ax1.set_xlabel(‘日付’, fontsize=11)
ax1.set_ylabel(‘売上(万円)’, fontsize=11)
ax1.legend()
ax1.grid(alpha=0.3)
# グラフ2: ラグの大きさを数値で比較
ax2 = axes[1]
# 変化点から何ヶ月後に追いつくか計算
target_level = df_lag[‘sales’].iloc[change_point:].mean()
# 各MAが目標レベルの90%に達するまでの期間
def months_to_catch_up(ma_series, target, start_idx):
threshold = target * 0.9
for i, val in enumerate(ma_series.iloc[start_idx:]):
if not np.isnan(val) and val >= threshold:
return i
return len(ma_series) – start_idx
lag_3 = months_to_catch_up(df_lag[‘SMA_3’], target_level, change_point)
lag_6 = months_to_catch_up(df_lag[‘SMA_6’], target_level, change_point)
lag_12 = months_to_catch_up(df_lag[‘SMA_12’], target_level, change_point)
labels = [‘3ヶ月MA’, ‘6ヶ月MA’, ’12ヶ月MA’]
lags = [lag_3, lag_6, lag_12]
colors = [‘red’, ‘orange’, ‘blue’]
bars = ax2.bar(labels, lags, color=colors, alpha=0.7, edgecolor=’black’)
ax2.set_ylabel(‘追いつくまでの月数’, fontsize=11)
ax2.set_title(‘ラグの大きさ比較’, fontsize=13, fontweight=’bold’)
ax2.grid(axis=’y’, alpha=0.3)
for bar, lag in zip(bars, lags):
ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height(),
f'{lag}ヶ月’, ha=’center’, va=’bottom’, fontsize=12)
plt.tight_layout()
plt.show()
print(“【ラグ問題のまとめ】”)
print()
print(f”変化点: {df_lag[‘date’].iloc[change_point].strftime(‘%Y年%m月’)}”)
print(f”3ヶ月MA: 約{lag_3}ヶ月で追いつく(反応が速い)”)
print(f”6ヶ月MA: 約{lag_6}ヶ月で追いつく”)
print(f”12ヶ月MA: 約{lag_12}ヶ月で追いつく(反応が遅い)”)
print()
print(“→ トレンド転換を早く検出したい場合は短期MAを使う”)
print(“→ ノイズを除去したい場合は長期MAを使う”)
print(“→ 両方を併用するのがベストプラクティス”)
🌊 4. 季節性の理解と分解
季節性とは
📌 周期的に繰り返されるパターン
季節性の種類:
1. 年次季節性(12ヶ月周期)
・アイスクリーム: 夏に売上増
・暖房器具: 冬に売上増
・お歳暮: 12月に需要集中
・入学グッズ: 3〜4月に需要集中
2. 週次季節性(7日周期)
・外食: 金土日に客数増
・ECサイト: 週末にアクセス増
・法人向けサービス: 平日に需要集中
3. 日次季節性(24時間周期)
・カフェ: 朝と昼にピーク
・居酒屋: 夜にピーク
・電力使用量: 日中にピーク
なぜ季節性を分析するのか:
✅ 真のトレンドを見極める
✅ 正確な前年同月比を計算
✅ 需要予測の精度向上
✅ 在庫・人員計画の最適化
✅ 異常値の検出が容易に
1. 年次季節性(12ヶ月周期)
・アイスクリーム: 夏に売上増
・暖房器具: 冬に売上増
・お歳暮: 12月に需要集中
・入学グッズ: 3〜4月に需要集中
2. 週次季節性(7日周期)
・外食: 金土日に客数増
・ECサイト: 週末にアクセス増
・法人向けサービス: 平日に需要集中
3. 日次季節性(24時間周期)
・カフェ: 朝と昼にピーク
・居酒屋: 夜にピーク
・電力使用量: 日中にピーク
なぜ季節性を分析するのか:
✅ 真のトレンドを見極める
✅ 正確な前年同月比を計算
✅ 需要予測の精度向上
✅ 在庫・人員計画の最適化
✅ 異常値の検出が容易に
加法モデルと乗法モデル
💡 どちらのモデルを選ぶか
加法モデル(Additive):
売上 = トレンド + 季節性 + 残差
特徴:
・季節変動が「金額」で一定
・例: 毎年冬に+20万円
・売上100万でも1000万でも+20万
向いているケース:
・売上規模が安定している
・季節変動が金額ベースで一定
・成熟した市場
乗法モデル(Multiplicative):
売上 = トレンド × 季節性 × 残差
特徴:
・季節変動が「割合」で一定
・例: 毎年冬は売上の×1.2倍
・売上100万なら+20万、1000万なら+200万
向いているケース:
・売上が成長/縮小している
・季節変動が比例的
・成長企業やスタートアップ
選び方のコツ:
1. グラフを見て季節変動の幅を確認
2. 変動幅が一定 → 加法モデル
3. 変動幅が拡大/縮小 → 乗法モデル
4. 迷ったら両方試して比較
売上 = トレンド + 季節性 + 残差
特徴:
・季節変動が「金額」で一定
・例: 毎年冬に+20万円
・売上100万でも1000万でも+20万
向いているケース:
・売上規模が安定している
・季節変動が金額ベースで一定
・成熟した市場
乗法モデル(Multiplicative):
売上 = トレンド × 季節性 × 残差
特徴:
・季節変動が「割合」で一定
・例: 毎年冬は売上の×1.2倍
・売上100万なら+20万、1000万なら+200万
向いているケース:
・売上が成長/縮小している
・季節変動が比例的
・成長企業やスタートアップ
選び方のコツ:
1. グラフを見て季節変動の幅を確認
2. 変動幅が一定 → 加法モデル
3. 変動幅が拡大/縮小 → 乗法モデル
4. 迷ったら両方試して比較
季節分解の実装
# ============================================
# 季節分解の実装
# ============================================
# 季節分解とは:
# 時系列データを3つの成分に分解する手法
# 1. トレンド: 長期的な傾向
# 2. 季節性: 周期的なパターン
# 3. 残差: 説明できない変動(ノイズ)
from statsmodels.tsa.seasonal import seasonal_decompose
# ============================================
# 加法モデルによる季節分解
# ============================================
# seasonal_decompose() のパラメータ:
# model: ‘additive’(加法)or ‘multiplicative’(乗法)
# period: 季節周期(月次データで年次季節性なら12)
# extrapolate_trend: トレンドの端の欠損を補完
result_add = seasonal_decompose(
df[‘sales’], # 分解するデータ
model=’additive’, # 加法モデル
period=12 # 12ヶ月周期
)
# 分解結果の確認
print(“【加法モデルの季節分解結果】”)
print()
print(“元データ = トレンド + 季節性 + 残差”)
print()
# 各成分を確認(中央の月を例に)
mid_idx = len(df) // 2
print(f”例: {df[‘date’].iloc[mid_idx].strftime(‘%Y年%m月’)}”)
print(f” 元データ: {df[‘sales’].iloc[mid_idx]:.1f}万円”)
print(f” トレンド: {result_add.trend.iloc[mid_idx]:.1f}万円”)
print(f” 季節性: {result_add.seasonal.iloc[mid_idx]:+.1f}万円”)
print(f” 残差: {result_add.resid.iloc[mid_idx]:+.1f}万円”)
print(f” 合計: {result_add.trend.iloc[mid_idx] + result_add.seasonal.iloc[mid_idx] + result_add.resid.iloc[mid_idx]:.1f}万円”)
print()
# ============================================
# 乗法モデルによる季節分解
# ============================================
result_mult = seasonal_decompose(
df[‘sales’],
model=’multiplicative’,
period=12
)
print(“【乗法モデルの季節分解結果】”)
print()
print(“元データ = トレンド × 季節性 × 残差”)
print()
print(f”例: {df[‘date’].iloc[mid_idx].strftime(‘%Y年%m月’)}”)
print(f” 元データ: {df[‘sales’].iloc[mid_idx]:.1f}万円”)
print(f” トレンド: {result_mult.trend.iloc[mid_idx]:.1f}万円”)
print(f” 季節性: {result_mult.seasonal.iloc[mid_idx]:.3f}(×倍率)”)
print(f” 残差: {result_mult.resid.iloc[mid_idx]:.3f}(×倍率)”)
季節分解の可視化
# ============================================
# 季節分解の可視化
# ============================================
fig, axes = plt.subplots(4, 1, figsize=(14, 12))
# 1. 元データ
axes[0].plot(df[‘date’], df[‘sales’], color=’black’, linewidth=1)
axes[0].set_title(‘① 元データ(売上)’, fontsize=12, fontweight=’bold’)
axes[0].set_ylabel(‘売上(万円)’, fontsize=10)
axes[0].grid(alpha=0.3)
# 2. トレンド成分
axes[1].plot(df[‘date’], result_add.trend, color=’blue’, linewidth=2)
axes[1].set_title(‘② トレンド成分(長期的な傾向)’, fontsize=12, fontweight=’bold’)
axes[1].set_ylabel(‘売上(万円)’, fontsize=10)
axes[1].grid(alpha=0.3)
# 解説テキスト
axes[1].text(0.02, 0.95, ‘→ 12ヶ月移動平均で算出\n→ 季節性を除いた本質的な成長’,
transform=axes[1].transAxes, fontsize=10, verticalalignment=’top’,
bbox=dict(boxstyle=’round’, facecolor=’wheat’, alpha=0.5))
# 3. 季節成分
axes[2].plot(df[‘date’], result_add.seasonal, color=’green’, linewidth=1)
axes[2].axhline(y=0, color=’black’, linestyle=’–‘, alpha=0.5)
axes[2].set_title(‘③ 季節成分(周期的なパターン)’, fontsize=12, fontweight=’bold’)
axes[2].set_ylabel(‘季節効果(万円)’, fontsize=10)
axes[2].grid(alpha=0.3)
# 解説テキスト
axes[2].text(0.02, 0.95, ‘→ 毎年同じパターンが繰り返される\n→ プラス=平均より高い月、マイナス=低い月’,
transform=axes[2].transAxes, fontsize=10, verticalalignment=’top’,
bbox=dict(boxstyle=’round’, facecolor=’lightgreen’, alpha=0.5))
# 4. 残差成分
axes[3].plot(df[‘date’], result_add.resid, color=’red’, linewidth=1, alpha=0.7)
axes[3].axhline(y=0, color=’black’, linestyle=’–‘, alpha=0.5)
axes[3].set_title(‘④ 残差成分(説明できない変動)’, fontsize=12, fontweight=’bold’)
axes[3].set_xlabel(‘日付’, fontsize=10)
axes[3].set_ylabel(‘残差(万円)’, fontsize=10)
axes[3].grid(alpha=0.3)
# 解説テキスト
axes[3].text(0.02, 0.95, ‘→ トレンドと季節性で説明できない部分\n→ ランダムなノイズや一時的なイベント’,
transform=axes[3].transAxes, fontsize=10, verticalalignment=’top’,
bbox=dict(boxstyle=’round’, facecolor=’lightyellow’, alpha=0.5))
plt.tight_layout()
plt.show()
print(“【季節分解の解釈】”)
print()
print(“トレンド: 100万円 → 150万円へ成長(+50%)”)
print(“季節性: 夏にプラス、冬にマイナスのパターン”)
print(“残差: ±5万円程度のランダムな変動”)
📈 5. 季節指数と季節調整
季節指数とは
📌 各月の「平均からの乖離」を数値化
季節指数の意味:
・年間平均を100とした場合の相対値
・季節指数120 = 平均の1.2倍(20%高い)
・季節指数80 = 平均の0.8倍(20%低い)
計算式:
季節指数 = (その月の平均) / (全体の平均) × 100
例:
・年間平均売上: 100万円
・7月の平均売上: 130万円
・7月の季節指数 = 130 / 100 × 100 = 130
→ 「7月は平均より30%高い」
12ヶ月の季節指数の合計 = 1200
(12ヶ月 × 平均100 = 1200)
・年間平均を100とした場合の相対値
・季節指数120 = 平均の1.2倍(20%高い)
・季節指数80 = 平均の0.8倍(20%低い)
計算式:
季節指数 = (その月の平均) / (全体の平均) × 100
例:
・年間平均売上: 100万円
・7月の平均売上: 130万円
・7月の季節指数 = 130 / 100 × 100 = 130
→ 「7月は平均より30%高い」
12ヶ月の季節指数の合計 = 1200
(12ヶ月 × 平均100 = 1200)
季節指数の計算
# ============================================
# 季節指数の計算
# ============================================
# ステップ:
# 1. 月を抽出
# 2. 月別の平均売上を計算
# 3. 全体平均で割って指数化
# ステップ1: 月を抽出
df[‘month’] = df[‘date’].dt.month
# ステップ2: 月別平均売上
monthly_avg = df.groupby(‘month’)[‘sales’].mean()
# ステップ3: 全体平均
overall_avg = df[‘sales’].mean()
# ステップ4: 季節指数(全体平均を100として)
seasonal_index = (monthly_avg / overall_avg * 100).round(1)
# 結果表示
print(“【季節指数の計算結果】”)
print()
print(f”全体平均売上: {overall_avg:.1f}万円”)
print()
month_names = [‘1月’, ‘2月’, ‘3月’, ‘4月’, ‘5月’, ‘6月’,
‘7月’, ‘8月’, ‘9月’, ’10月’, ’11月’, ’12月’]
print(“月別売上と季節指数:”)
print(“-” * 50)
for month in range(1, 13):
avg = monthly_avg[month]
idx = seasonal_index[month]
# 棒グラフ風の表示
bar_length = int((idx – 70) / 2) # 70〜130を0〜30にスケール
bar = ‘█’ * max(0, bar_length)
if idx > 100:
comment = f”+{idx-100:.0f}%”
color_hint = “↑高”
elif idx < 100:
comment = f"{idx-100:.0f}%"
color_hint = "↓低"
else:
comment = "±0%"
color_hint = "→平均"
print(f"{month_names[month-1]}: 平均{avg:6.1f}万円 → 指数{idx:5.1f} ({comment:>5}) {bar}”)
print(“-” * 50)
print(f”季節指数の合計: {seasonal_index.sum():.0f}(理論値: 1200)”)
print()
# 季節性の強さを評価
max_idx = seasonal_index.max()
min_idx = seasonal_index.min()
range_idx = max_idx – min_idx
print(“【季節性の強さ】”)
print(f”最高: {month_names[seasonal_index.idxmax()-1]} = {max_idx:.1f}”)
print(f”最低: {month_names[seasonal_index.idxmin()-1]} = {min_idx:.1f}”)
print(f”変動幅: {range_idx:.1f}ポイント”)
print()
if range_idx > 30:
print(“→ 季節性が強い(季節調整が必須)”)
elif range_idx > 15:
print(“→ 季節性がやや強い(季節調整を推奨)”)
else:
print(“→ 季節性は弱い(季節調整は任意)”)
季節調整の実施
💡 季節要因を除去して「真の実力」を見る
季節調整の計算式:
季節調整済み売上 = 実績 ÷ (季節指数 / 100)
具体例:
・12月の実績: 240万円
・12月の季節指数: 120
季節調整済み = 240 ÷ (120/100)
= 240 ÷ 1.2
= 200万円
解釈:
「12月は季節的に売れやすい月なので、
季節要因を除くと実質200万円相当の実力」
なぜ割るのか:
季節指数120 = 平均の1.2倍売れやすい
→ 1.2で割ると「平均的な月だったら」の値に
季節調整済み売上 = 実績 ÷ (季節指数 / 100)
具体例:
・12月の実績: 240万円
・12月の季節指数: 120
季節調整済み = 240 ÷ (120/100)
= 240 ÷ 1.2
= 200万円
解釈:
「12月は季節的に売れやすい月なので、
季節要因を除くと実質200万円相当の実力」
なぜ割るのか:
季節指数120 = 平均の1.2倍売れやすい
→ 1.2で割ると「平均的な月だったら」の値に
# ============================================
# 季節調整の実施
# ============================================
# 季節指数をデータフレームにマッピング
# .map(): 月(1〜12)を対応する季節指数に変換
df[‘seasonal_index’] = df[‘month’].map(seasonal_index / 100)
# 季節調整済み売上 = 実績 / 季節指数
df[‘seasonally_adjusted’] = df[‘sales’] / df[‘seasonal_index’]
print(“【季節調整の結果】”)
print()
print(df[[‘date’, ‘sales’, ‘seasonal_index’, ‘seasonally_adjusted’]].tail(12).to_string(index=False))
print()
# ============================================
# 季節調整の効果を確認
# ============================================
print(“【季節調整の効果】”)
print()
print(f”元データの標準偏差: {df[‘sales’].std():.2f}万円”)
print(f”季節調整後の標準偏差: {df[‘seasonally_adjusted’].std():.2f}万円”)
print(f”変動の減少率: {(1 – df[‘seasonally_adjusted’].std()/df[‘sales’].std())*100:.1f}%”)
print()
print(“→ 季節変動が除去され、データが平滑化されました”)
🎯 6. 実務での活用
前年同月比の正しい計算
# ============================================
# 前年同月比の計算
# ============================================
# 問題: 単純な前年同月比は季節要因を含んでしまう
# 解決: 季節調整後のデータで比較する
# 1. 単純な前年同月比(季節調整なし)
df[‘yoy_raw’] = df.groupby(‘month’)[‘sales’].pct_change(periods=12) * 100
# 2. 季節調整後の前月比
df[‘mom_adjusted’] = df[‘seasonally_adjusted’].pct_change(periods=1) * 100
print(“【前年同月比 vs 季節調整後前月比】”)
print()
print(“直近12ヶ月:”)
print(df[[‘date’, ‘sales’, ‘yoy_raw’, ‘seasonally_adjusted’, ‘mom_adjusted’]].tail(12).to_string(index=False))
print()
# 解釈の例
last_row = df.iloc[-1]
print(“【直近月の解釈】”)
print(f”実績: {last_row[‘sales’]:.1f}万円”)
print(f”前年同月比: {last_row[‘yoy_raw’]:+.1f}%”)
print(f”季節調整済み: {last_row[‘seasonally_adjusted’]:.1f}万円”)
print(f”季節調整後前月比: {last_row[‘mom_adjusted’]:+.1f}%”)
print()
print(“→ 季節調整後の前月比で「真の成長」を判断”)
Excelでの実装手順
💡 Excelで季節調整を行う具体的手順
データ準備(A〜C列):
A列: 日付(2022/1/1, 2022/2/1, …)
B列: 月(=MONTH(A2))
C列: 売上(実績値)
ステップ1: 12ヶ月移動平均(D列)
D7に入力(7行目=7月から計算開始):
→ D7以降にコピー
ステップ2: 季節比率(E列)
E7に入力:
→ E列全体にコピー
ステップ3: 月別平均季節比率(別シート)
G2に入力(1月の平均):
→ G2:G13にコピー(2〜12月)
ステップ4: 季節指数の正規化(H列)
H2に入力:
→ H列全体にコピー
ステップ5: 季節調整済み(元シートF列)
F2に入力:
→ F列全体にコピー
A列: 日付(2022/1/1, 2022/2/1, …)
B列: 月(=MONTH(A2))
C列: 売上(実績値)
ステップ1: 12ヶ月移動平均(D列)
D7に入力(7行目=7月から計算開始):
=AVERAGE(C2:C13)→ D7以降にコピー
ステップ2: 季節比率(E列)
E7に入力:
=C7/D7→ E列全体にコピー
ステップ3: 月別平均季節比率(別シート)
G2に入力(1月の平均):
=AVERAGEIF($B$2:$B$37, 1, $E$2:$E$37)→ G2:G13にコピー(2〜12月)
ステップ4: 季節指数の正規化(H列)
H2に入力:
=G2/AVERAGE($G$2:$G$13)*100→ H列全体にコピー
ステップ5: 季節調整済み(元シートF列)
F2に入力:
=C2/VLOOKUP(B2, 季節指数!$A$2:$B$13, 2, FALSE)*100→ F列全体にコピー
⚠️ 7. 実務でよくある間違い
移動平均・季節調整の落とし穴
❌ 間違い1: 季節調整前後のデータを混同する
問題:
「12月の売上は240万円、11月は200万円。
20%も成長した!」
→ でも12月は季節的に売れやすい月…
正しい分析:
・12月の実績: 240万円
・12月の季節指数: 120
・季節調整済み: 240÷1.2 = 200万円
・11月の季節調整済み: 190万円
・真の成長率: +5.3%
対策:
・常に「季節調整済み」を明記
・レポートには両方の数値を記載
・「季節要因を除くと…」と説明
「12月の売上は240万円、11月は200万円。
20%も成長した!」
→ でも12月は季節的に売れやすい月…
正しい分析:
・12月の実績: 240万円
・12月の季節指数: 120
・季節調整済み: 240÷1.2 = 200万円
・11月の季節調整済み: 190万円
・真の成長率: +5.3%
対策:
・常に「季節調整済み」を明記
・レポートには両方の数値を記載
・「季節要因を除くと…」と説明
❌ 間違い2: 期間選択の根拠がない
問題:
「なんとなく3ヶ月移動平均を使っている」
「前任者がそうしていたから」
正しいアプローチ:
目的から逆算:
・季節性を除去したい → 12ヶ月MA
・短期トレンドを見たい → 3ヶ月MA
・バランス重視 → 6ヶ月MA
データ特性を確認:
・月次データで年次季節性 → 12ヶ月
・週次データで週次季節性 → 7日
・日次データで曜日効果 → 7日
対策:
・期間選択の理由をドキュメント化
・複数期間を試して比較
・経営層に説明できるようにする
「なんとなく3ヶ月移動平均を使っている」
「前任者がそうしていたから」
正しいアプローチ:
目的から逆算:
・季節性を除去したい → 12ヶ月MA
・短期トレンドを見たい → 3ヶ月MA
・バランス重視 → 6ヶ月MA
データ特性を確認:
・月次データで年次季節性 → 12ヶ月
・週次データで週次季節性 → 7日
・日次データで曜日効果 → 7日
対策:
・期間選択の理由をドキュメント化
・複数期間を試して比較
・経営層に説明できるようにする
❌ 間違い3: 移動平均だけで予測する
問題:
「来月の売上は今月の3ヶ月MAと同じ」
→ これは予測ではなく、過去の平均
移動平均の限界:
・過去のデータしか見ていない
・トレンドを外挿できない
・季節性を考慮した予測ができない
正しい使い方:
・移動平均 = 平滑化・トレンド把握
・予測 = 指数平滑法、ARIMA、Prophet
(STEP 34参照)
対策:
・目的を明確に(分析 or 予測)
・予測には適切な手法を選択
・移動平均は「現状把握」に使う
「来月の売上は今月の3ヶ月MAと同じ」
→ これは予測ではなく、過去の平均
移動平均の限界:
・過去のデータしか見ていない
・トレンドを外挿できない
・季節性を考慮した予測ができない
正しい使い方:
・移動平均 = 平滑化・トレンド把握
・予測 = 指数平滑法、ARIMA、Prophet
(STEP 34参照)
対策:
・目的を明確に(分析 or 予測)
・予測には適切な手法を選択
・移動平均は「現状把握」に使う
❌ 間違い4: 異常値を無視して移動平均を計算
問題:
「コロナで4月の売上が0円だった」
「その月を含めて移動平均を計算」
→ 3ヶ月後まで移動平均が歪む
影響:
・3ヶ月MA: 3ヶ月間影響
・12ヶ月MA: 12ヶ月間影響
→ 長期MAほど影響が長引く
対策:
・異常値を検出して除外or補正
・前年同月の値で代替
・線形補間で埋める
・異常値を含む期間を明記
「コロナで4月の売上が0円だった」
「その月を含めて移動平均を計算」
→ 3ヶ月後まで移動平均が歪む
影響:
・3ヶ月MA: 3ヶ月間影響
・12ヶ月MA: 12ヶ月間影響
→ 長期MAほど影響が長引く
対策:
・異常値を検出して除外or補正
・前年同月の値で代替
・線形補間で埋める
・異常値を含む期間を明記
チェックリスト
✅ 移動平均・季節調整を使う前の確認
データ確認:
□ 欠損値はないか?
□ 異常値はないか?
□ データは十分な期間があるか?
(12ヶ月MA → 最低24ヶ月必要)
目的確認:
□ 平滑化?トレンド把握?予測?
□ 季節性を除去する必要があるか?
□ 短期と長期どちらを重視?
手法選択:
□ 期間の根拠は説明できるか?
□ 加法モデルか乗法モデルか?
□ 結果は常識的に正しいか?
報告準備:
□ 元データと加工後の両方を示せるか?
□ 手法の限界を説明できるか?
□ 経営層に理解してもらえるか?
□ 欠損値はないか?
□ 異常値はないか?
□ データは十分な期間があるか?
(12ヶ月MA → 最低24ヶ月必要)
目的確認:
□ 平滑化?トレンド把握?予測?
□ 季節性を除去する必要があるか?
□ 短期と長期どちらを重視?
手法選択:
□ 期間の根拠は説明できるか?
□ 加法モデルか乗法モデルか?
□ 結果は常識的に正しいか?
報告準備:
□ 元データと加工後の両方を示せるか?
□ 手法の限界を説明できるか?
□ 経営層に理解してもらえるか?
📝 STEP 35 のまとめ
✅ このステップで学んだこと
- 移動平均の目的: ノイズを除去しトレンドを把握
- 単純移動平均(SMA): シンプルで説明しやすい
- 加重移動平均(WMA): 最近のデータを重視
- 中心化移動平均: 季節分解で正確な結果を得る
- 季節分解: トレンド + 季節性 + 残差に分解
- 加法/乗法モデル: データ特性で使い分け
- 季節指数: 各月の季節効果を数値化
- 季節調整: 季節要因を除去し真の実力を見る
💡 実務での判断フロー
1. データの季節性を確認
・グラフで周期的なパターンがあるか
・季節指数の変動幅が15以上か
2. 季節性がある場合
・12ヶ月移動平均でトレンド把握
・季節指数を計算
・季節調整済みデータで分析
3. 季節性がない場合
・3〜6ヶ月移動平均でノイズ除去
・そのまま分析可能
4. 報告時のポイント
・元データと季節調整済み両方を提示
・「季節要因を除くと…」と明示
・経営層は両方の視点を求める
📝 練習問題
問題 1
基礎
以下の売上データについて、3ヶ月単純移動平均を計算してください。
以下の売上データについて、3ヶ月単純移動平均を計算してください。
1月: 100万円
2月: 110万円
3月: 105万円
4月: 115万円
5月: 120万円
6月: 125万円
【解答】
3ヶ月移動平均:
1月: – (データ不足)
2月: – (データ不足)
3月: (100 + 110 + 105) / 3 = 105.0万円
4月: (110 + 105 + 115) / 3 = 110.0万円
5月: (105 + 115 + 120) / 3 = 113.3万円
6月: (115 + 120 + 125) / 3 = 120.0万円
1月: – (データ不足)
2月: – (データ不足)
3月: (100 + 110 + 105) / 3 = 105.0万円
4月: (110 + 105 + 115) / 3 = 110.0万円
5月: (105 + 115 + 120) / 3 = 113.3万円
6月: (115 + 120 + 125) / 3 = 120.0万円
解説:
計算のポイント:
1. 窓の動き
3月: [1月, 2月, 3月] の平均
4月: [2月, 3月, 4月] の平均
→ 窓が1ヶ月ずつ右にスライド
2. 平滑化効果
元データ: 100→110→105→115→120→125
(3月に一時的な下落)
移動平均: 105→110→113→120
(滑らかな上昇トレンド)
3. 欠損値
最初の2期間は計算不可(NaN)
→ 3期間分のデータが必要なため
1. 窓の動き
3月: [1月, 2月, 3月] の平均
4月: [2月, 3月, 4月] の平均
→ 窓が1ヶ月ずつ右にスライド
2. 平滑化効果
元データ: 100→110→105→115→120→125
(3月に一時的な下落)
移動平均: 105→110→113→120
(滑らかな上昇トレンド)
3. 欠損値
最初の2期間は計算不可(NaN)
→ 3期間分のデータが必要なため
問題 2
基礎
3ヶ月間のデータ(100, 110, 120万円)について、
3ヶ月間のデータ(100, 110, 120万円)について、
単純移動平均(SMA)と加重移動平均(WMA、重み1:2:3)を
それぞれ計算してください。
どちらの値が大きくなりますか?その理由も説明してください。
【解答】WMA(113.3万円) > SMA(110.0万円)
SMAの計算:
= (100 + 110 + 120) / 3
= 330 / 3
= 110.0万円
WMAの計算(重み1:2:3):
= (1×100 + 2×110 + 3×120) / (1+2+3)
= (100 + 220 + 360) / 6
= 680 / 6
= 113.3万円
= (100 + 110 + 120) / 3
= 330 / 3
= 110.0万円
WMAの計算(重み1:2:3):
= (1×100 + 2×110 + 3×120) / (1+2+3)
= (100 + 220 + 360) / 6
= 680 / 6
= 113.3万円
理由:
データは上昇トレンド(100→110→120)
SMA: すべて同じ重み
→ 古い100も最新の120も同じ影響力
WMA: 最新データを重視
→ 120に重み3(最大)
→ 100に重み1(最小)
→ 上昇トレンドをより反映
結論:
・上昇トレンド → WMA > SMA
・下降トレンド → WMA < SMA
・横ばい → WMA ≒ SMA
SMA: すべて同じ重み
→ 古い100も最新の120も同じ影響力
WMA: 最新データを重視
→ 120に重み3(最大)
→ 100に重み1(最小)
→ 上昇トレンドをより反映
結論:
・上昇トレンド → WMA > SMA
・下降トレンド → WMA < SMA
・横ばい → WMA ≒ SMA
問題 3
応用
ある商品の月別売上と季節指数が以下の通りです。
ある商品の月別売上と季節指数が以下の通りです。
2024年12月の実績: 240万円
12月の季節指数: 120
11月の季節調整済み売上: 190万円
この商品の12月の季節調整済み売上を計算し、
11月と比較した真の成長率を求めてください。
【解答】季節調整済み = 200万円、成長率 = +5.3%
1. 季節調整済み売上の計算
季節調整済み = 実績 ÷ (季節指数 / 100)
= 240 ÷ (120 / 100)
= 240 ÷ 1.2
= 200万円
2. 前月比成長率
成長率 = (今月 – 前月) / 前月 × 100%
= (200 – 190) / 190 × 100%
= 10 / 190 × 100%
= +5.3%
季節調整済み = 実績 ÷ (季節指数 / 100)
= 240 ÷ (120 / 100)
= 240 ÷ 1.2
= 200万円
2. 前月比成長率
成長率 = (今月 – 前月) / 前月 × 100%
= (200 – 190) / 190 × 100%
= 10 / 190 × 100%
= +5.3%
解説:
なぜ季節調整が重要か:
単純比較(誤り):
11月 → 12月: 売上が大幅増!
→ でも12月は季節的に売れやすい月
→ 季節要因を含んだ見かけの成長
季節調整後(正しい):
11月: 190万円
12月: 200万円
→ 真の成長は+5.3%のみ
実務での報告例:
「12月の売上は240万円でしたが、
季節要因を除くと実質200万円相当です。
11月比+5.3%の成長となります。」
単純比較(誤り):
11月 → 12月: 売上が大幅増!
→ でも12月は季節的に売れやすい月
→ 季節要因を含んだ見かけの成長
季節調整後(正しい):
11月: 190万円
12月: 200万円
→ 真の成長は+5.3%のみ
実務での報告例:
「12月の売上は240万円でしたが、
季節要因を除くと実質200万円相当です。
11月比+5.3%の成長となります。」
問題 4
応用
あなたはアイスクリーム店の店長です。
あなたはアイスクリーム店の店長です。
以下の月別売上データがあります。
1月: 60万円、7月: 150万円、年間平均: 100万円
質問1: 7月の季節指数を計算してください。
質問2: 加法モデルと乗法モデル、どちらが適切ですか?
質問3: その理由を説明してください。
【解答】
質問1: 7月の季節指数
季節指数 = 月平均 / 年間平均 × 100
= 150 / 100 × 100
= 150
(1月の季節指数 = 60/100×100 = 60)
季節指数 = 月平均 / 年間平均 × 100
= 150 / 100 × 100
= 150
(1月の季節指数 = 60/100×100 = 60)
質問2: 乗法モデルが適切
質問3: 理由
アイスクリーム店の特徴:
1. 季節変動が非常に大きい
・1月: 60万円(最低)
・7月: 150万円(最高)
・変動幅: 90万円(150%の差)
2. 成長すると変動幅も拡大する
・現在: 1月60万、7月150万(差90万)
・売上2倍になると:
1月120万、7月300万(差180万)
→ 変動幅も2倍に
3. 乗法モデルが適切な理由
・季節変動が「割合」で一定
・7月は常に「平均の1.5倍」
・1月は常に「平均の0.6倍」
・売上規模が変わっても比率は同じ
1. 季節変動が非常に大きい
・1月: 60万円(最低)
・7月: 150万円(最高)
・変動幅: 90万円(150%の差)
2. 成長すると変動幅も拡大する
・現在: 1月60万、7月150万(差90万)
・売上2倍になると:
1月120万、7月300万(差180万)
→ 変動幅も2倍に
3. 乗法モデルが適切な理由
・季節変動が「割合」で一定
・7月は常に「平均の1.5倍」
・1月は常に「平均の0.6倍」
・売上規模が変わっても比率は同じ
❓ よくある質問
Q1: 移動平均の期間はどう決めればいいですか?
目的とデータの特性で決定します。
基本的な考え方:
・短期トレンド把握 → 3ヶ月
・中期トレンド把握 → 6ヶ月
・季節性除去 → 12ヶ月
実務での使い分け:
・月次レポート: 3ヶ月MA
・四半期レポート: 6ヶ月MA
・年次計画: 12ヶ月MA
ベストプラクティス:
3ヶ月と12ヶ月の両方を併記して、
短期と長期の視点を提供する
基本的な考え方:
・短期トレンド把握 → 3ヶ月
・中期トレンド把握 → 6ヶ月
・季節性除去 → 12ヶ月
実務での使い分け:
・月次レポート: 3ヶ月MA
・四半期レポート: 6ヶ月MA
・年次計画: 12ヶ月MA
ベストプラクティス:
3ヶ月と12ヶ月の両方を併記して、
短期と長期の視点を提供する
Q2: 加法モデルと乗法モデル、どちらを使えばいいですか?
季節変動の大きさの変化で判断します。
加法モデルを使う場合:
・季節変動が金額ベースで一定
・例: 毎年冬に+20万円
・売上規模が安定している
乗法モデルを使う場合:
・季節変動が割合で一定
・例: 冬は売上の1.2倍
・売上が成長/縮小している
判断方法:
1. グラフで季節変動の幅を確認
2. 年々変動幅が拡大 → 乗法
3. 変動幅が一定 → 加法
4. 迷ったら両方試して比較
加法モデルを使う場合:
・季節変動が金額ベースで一定
・例: 毎年冬に+20万円
・売上規模が安定している
乗法モデルを使う場合:
・季節変動が割合で一定
・例: 冬は売上の1.2倍
・売上が成長/縮小している
判断方法:
1. グラフで季節変動の幅を確認
2. 年々変動幅が拡大 → 乗法
3. 変動幅が一定 → 加法
4. 迷ったら両方試して比較
Q3: 季節調整は必ず必要ですか?
季節性が強い場合は必須です。
季節調整が必要な場合:
・季節指数の変動幅が15以上
・前年同月比を正確に計算したい
・月次の成長率を評価したい
・KPIモニタリング
季節調整が不要な場合:
・季節性がほとんどない
・年間を通じて安定
・B2Bビジネスの一部
実務のコツ:
元データと季節調整後の両方を見る。
経営層には両方を提示するのがベスト。
季節調整が必要な場合:
・季節指数の変動幅が15以上
・前年同月比を正確に計算したい
・月次の成長率を評価したい
・KPIモニタリング
季節調整が不要な場合:
・季節性がほとんどない
・年間を通じて安定
・B2Bビジネスの一部
実務のコツ:
元データと季節調整後の両方を見る。
経営層には両方を提示するのがベスト。
Q4: Excelでできる一番簡単な季節調整は?
AVERAGE関数で12ヶ月移動平均を計算するのが最も簡単です。
手順:
1. D7セルに =AVERAGE(C2:C13) と入力
2. 下にコピー
3. これで季節性を除いたトレンドが見える
より正確な季節調整:
1. 季節比率 = 実績 / 12ヶ月MA
2. 月別に平均を取る
3. 実績 / 季節比率 = 季節調整済み
ツールの活用:
・データ分析ツールパックの「移動平均」
・期間=12で年次季節性を除去
手順:
1. D7セルに =AVERAGE(C2:C13) と入力
2. 下にコピー
3. これで季節性を除いたトレンドが見える
より正確な季節調整:
1. 季節比率 = 実績 / 12ヶ月MA
2. 月別に平均を取る
3. 実績 / 季節比率 = 季節調整済み
ツールの活用:
・データ分析ツールパックの「移動平均」
・期間=12で年次季節性を除去
学習メモ
ビジネスデータ分析・意思決定 - Step 35
📋 過去のメモ一覧
▼