外部要因の分析
「需要は店の中だけで決まるわけじゃない。外の世界が大きく影響する。」
田中VPoEがエクアドルの経済ニュースを画面に映す。
「石油価格が下がれば経済が冷え込み消費が減る。祝日には買い物客が増える。地震が起きれば消費パターンが一変する。これらの外部要因をどう取り込むかが、予測精度の鍵だ。」
祝日・イベントの影響分析
Store Salesデータセットの祝日データは複雑な構造を持つ。
import pandas as pd
holidays = pd.read_csv('holidays_events.csv', parse_dates=['date'])
# 祝日のタイプ
print(holidays['type'].value_counts())
# Holiday 221
# Transfer 17
# Additional 15
# Bridge 10
# Event 16
# Work Day 3
# localeの分布
print(holidays['locale'].value_counts())
# National 174
# Regional 42
# Local 66
祝日タイプの整理
| タイプ | 説明 | 売上への影響 |
|---|---|---|
| Holiday | 通常の祝日 | 前日に買い込み、当日は閉店の場合も |
| Transfer | 振替休日 | 元の日付から振替先に影響が移動 |
| Additional | 追加休日 | 祝日と同様の影響 |
| Bridge | 飛び石連休の間の休日 | 連休として買い物需要が変化 |
| Event | イベント(地震など) | イベントにより異なる |
| Work Day | 振替出勤日 | 通常営業、祝日効果を打ち消す |
# 祝日前後の売上変動を分析
def analyze_holiday_effect(train, holidays, family='GROCERY I'):
"""祝日前後の売上変動を分析"""
daily = train[train['family'] == family].groupby('date')['sales'].sum()
national_holidays = holidays[
(holidays['locale'] == 'National') &
(holidays['type'] == 'Holiday')
]['date'].unique()
effects = []
for hol in national_holidays:
for offset in range(-3, 4):
target_date = hol + pd.Timedelta(days=offset)
if target_date in daily.index:
# 前後2週間の同曜日平均との比較
baseline_dates = [
target_date + pd.Timedelta(weeks=w)
for w in [-2, -1, 1, 2]
]
baseline = daily.reindex(baseline_dates).mean()
if baseline > 0:
ratio = daily[target_date] / baseline
effects.append({'offset': offset, 'ratio': ratio})
effects_df = pd.DataFrame(effects)
return effects_df.groupby('offset')['ratio'].mean()
effect = analyze_holiday_effect(train, holidays)
print("祝日からの日数と売上比率:")
print(effect)
# -1日: 1.25 ← 前日に買い込み
# 0日: 0.65 ← 当日は売上低下
# +1日: 1.10 ← 翌日に反動
石油価格の影響
oil = pd.read_csv('oil.csv', parse_dates=['date'])
# 欠損値の確認と補間
print(f"欠損率: {oil['dcoilwtico'].isna().mean():.1%}")
oil['dcoilwtico'] = oil['dcoilwtico'].interpolate(method='linear')
# 石油価格と売上の相関
daily_sales = train.groupby('date')['sales'].sum().reset_index()
merged = daily_sales.merge(oil, on='date', how='left')
merged['dcoilwtico'] = merged['dcoilwtico'].interpolate()
# 相関係数
corr = merged['sales'].corr(merged['dcoilwtico'])
print(f"売上と石油価格の相関: {corr:.3f}")
# ラグ付き相関(石油価格の変動が売上に反映されるまでの遅延)
for lag in [0, 7, 14, 30]:
merged[f'oil_lag_{lag}'] = merged['dcoilwtico'].shift(lag)
lag_corr = merged['sales'].corr(merged[f'oil_lag_{lag}'])
print(f"ラグ{lag:2d}日: 相関 = {lag_corr:.3f}")
石油価格の特徴量化
# 石油価格関連の特徴量
def create_oil_features(df, oil):
"""石油価格の特徴量を作成"""
df = df.merge(oil, on='date', how='left')
df['oil_price'] = df['dcoilwtico'].interpolate()
df['oil_ma7'] = df['oil_price'].rolling(7).mean() # 7日移動平均
df['oil_ma30'] = df['oil_price'].rolling(30).mean() # 30日移動平均
df['oil_change_7d'] = df['oil_price'].pct_change(7) # 7日変化率
df['oil_change_30d'] = df['oil_price'].pct_change(30) # 30日変化率
return df
プロモーション効果
# プロモーション中の商品数と売上の関係
promo_effect = train.groupby(['family', 'onpromotion']).agg(
avg_sales=('sales', 'mean'),
count=('sales', 'count')
).reset_index()
# カテゴリ別プロモーション効果
for family in ['GROCERY I', 'BEVERAGES', 'PRODUCE']:
family_data = train[train['family'] == family]
promo = family_data[family_data['onpromotion'] > 0]['sales'].mean()
no_promo = family_data[family_data['onpromotion'] == 0]['sales'].mean()
lift = (promo / no_promo - 1) * 100 if no_promo > 0 else 0
print(f"{family}: プロモーションリフト = {lift:.1f}%")
2016年エクアドル地震の影響
# 2016年4月16日 M7.8の地震
earthquake = pd.Timestamp('2016-04-16')
# 地震前後の売上推移
daily = train.groupby('date')['sales'].sum()
before = daily[(daily.index >= earthquake - pd.Timedelta(days=30)) &
(daily.index < earthquake)].mean()
after = daily[(daily.index >= earthquake) &
(daily.index < earthquake + pd.Timedelta(days=30))].mean()
print(f"地震前30日平均売上: {before:,.0f}")
print(f"地震後30日平均売上: {after:,.0f}")
print(f"変化率: {(after/before - 1)*100:.1f}%")
# 地震直後は売上が急増(備蓄需要)→ その後は減少
まとめ
| 項目 | ポイント |
|---|---|
| 祝日効果 | 前日に買い込み、当日は低下、翌日に反動 |
| 石油価格 | エクアドル経済と連動。ラグ付き相関を確認 |
| プロモーション | カテゴリにより効果が異なる |
| 地震 | 直後に急増、その後パターンが変化 |
チェックリスト
- 祝日データの構造と各タイプの意味を理解した
- 祝日前後の売上変動パターンを分析できる
- 石油価格の特徴量化手法を理解した
- プロモーション効果の分析方法を理解した
次のステップへ
外部要因の影響を把握したところで、次は演習でStore Salesデータを総合的に分析しよう。
推定読了時間: 30分