Store Salesデータセットの理解
「データを理解しないまま分析を始めるのは、地図なしで航海に出るようなものだ。」
田中VPoEがKaggleのコンペティションページを開く。
「Store Sales - Time Series Forecasting。エクアドルの大手スーパーマーケットチェーンCorporacion Favoritaの売上データだ。54店舗、33商品カテゴリ、約4年分。まずはデータ構造を徹底的に理解しよう。」
コンペティション概要
| 項目 | 内容 |
|---|---|
| コンペ名 | Store Sales - Time Series Forecasting |
| 目的 | 店舗×商品カテゴリの日次売上を予測 |
| 評価指標 | RMSLE(Root Mean Squared Logarithmic Error) |
| データ期間 | 2013年1月〜2017年8月 |
| 予測対象 | 2017年8月16日〜8月31日(16日間) |
RMSLE(評価指標)
import numpy as np
def rmsle(y_true, y_pred):
"""Root Mean Squared Logarithmic Error"""
return np.sqrt(np.mean((np.log1p(y_pred) - np.log1p(y_true)) ** 2))
# 特徴: 過大予測より過小予測にペナルティが大きい
# log変換するため、大きな売上値の影響が緩和される
データセットの構成
train.csv / test.csv
メインの売上データ。
| カラム | 型 | 説明 |
|---|---|---|
| id | int | 一意のID |
| date | date | 日付(2013-01-01 〜 2017-08-15) |
| store_nbr | int | 店舗番号(1〜54) |
| family | str | 商品カテゴリ(33種類) |
| sales | float | 売上数量(目的変数) |
| onpromotion | int | プロモーション中の商品数 |
import pandas as pd
train = pd.read_csv('train.csv', parse_dates=['date'])
print(f"レコード数: {len(train):,}")
print(f"期間: {train['date'].min()} 〜 {train['date'].max()}")
print(f"店舗数: {train['store_nbr'].nunique()}")
print(f"商品カテゴリ数: {train['family'].nunique()}")
print(f"時系列数: {train.groupby(['store_nbr', 'family']).ngroups}")
# レコード数: 3,000,888
# 期間: 2013-01-01 〜 2017-08-15
# 店舗数: 54
# 商品カテゴリ数: 33
# 時系列数: 1,782
stores.csv
店舗のメタデータ。
| カラム | 説明 |
|---|---|
| store_nbr | 店舗番号 |
| city | 都市名 |
| state | 州名 |
| type | 店舗タイプ(A〜E) |
| cluster | クラスター番号(1〜17) |
oil.csv
日次の石油価格データ。エクアドルは産油国であり、石油価格が経済全体に大きな影響を与える。
| カラム | 説明 |
|---|---|
| date | 日付 |
| dcoilwtico | WTI原油価格(USD/barrel) |
holidays_events.csv
祝日・イベントデータ。
| カラム | 説明 |
|---|---|
| date | 日付 |
| type | Holiday / Transfer / Additional / Bridge / Event / Work Day |
| locale | National / Regional / Local |
| locale_name | 適用地域名 |
| description | イベントの説明 |
| transferred | 振替されたかどうか |
transactions.csv
店舗ごとの日次取引数。
| カラム | 説明 |
|---|---|
| date | 日付 |
| store_nbr | 店舗番号 |
| transactions | 取引数 |
データの初期確認
# 基本統計量
print(train['sales'].describe())
# count 3,000,888
# mean 357.8
# std 1101.5
# min 0.0
# 25% 0.0
# 50% 11.0
# 75% 197.0
# max 125,497
# sales = 0のレコードが多い(約25%以上)
zero_sales = (train['sales'] == 0).mean()
print(f"sales=0の割合: {zero_sales:.1%}")
# 商品カテゴリ別の売上分布
category_sales = train.groupby('family')['sales'].mean().sort_values(ascending=False)
print(category_sales.head(10))
# GROCERY I が最大、次いで BEVERAGES, PRODUCE ...
データの注意点
- 欠損値: oil.csvに欠損あり。補間が必要
- ゼロ売上: 特定の店舗×カテゴリで常にゼロの組み合わせがある
- 2016年4月の地震: エクアドル大地震の影響で売上パターンが変化
- 祝日の複雑さ: 振替休日、地域限定祝日など複雑な構造
- プロモーション: onpromotionカラムは2014年以前に欠損が多い
まとめ
| 項目 | ポイント |
|---|---|
| データ規模 | 300万レコード、1,782時系列 |
| 目的変数 | sales(日次売上数量) |
| 評価指標 | RMSLE(対数変換した二乗平均平方根誤差) |
| 外部変数 | 石油価格、祝日、店舗情報、取引数 |
| 注意点 | ゼロ売上、地震影響、祝日の複雑さ |
チェックリスト
- Store Salesデータセットの各テーブルの役割を説明できる
- RMSLEの特徴を理解した
- データの主要な注意点を把握した
- 1,782時系列の構造(54店舗 x 33カテゴリ)を理解した
次のステップへ
データ構造を理解したところで、次は時系列データの分解手法を学び、トレンド・季節性・残差を分離しよう。
推定読了時間: 30分