LESSON

外部要因の分析

「需要は店の中だけで決まるわけじゃない。外の世界が大きく影響する。」

田中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分