リクルートのまとめ

まだ解法のまとめ中なので、随時更新していきます。

## 大会の概要

客の予約時のデータと実際に来店した際のデータを使用して、将来の各レストランに訪れる客の数を予測するというもの。

## 評価指標

このコンペでの評価指標は`RMSLE`でした。<br>

$$
\sqrt{\frac{1}{n} \sum_{i=1}^{n} (log(p_{i}+1) - log(a_{i}+1))^{2}}
$$

この指標は`RMSE`と比較して以下の違いが存在します。

### under-estimate vs over-estimate

予測値が実際の値よりも低い場合と、高い場合を見てみます。

- 実測値=1000, 予測値=600
    - RMSE=400, RMSLE=0.5108
- 実測値=1000, 予測値=600
    - RMSE=400, RMSLE=0.3365

となっており予測値が実際の値よりも低い場合により大きな損失を与えます。<br>
これは店舗の在庫などが余ってしまわないような状況を想定していると考えられます。

### magnitude of numbers

予測値と実際の値との差が同じで、元の数値の大きさが異なる場合を考えてみます。

- 実測値=1000, 予測値=1400
    - RMSE=400, RMSLE=0.3365
- 実測値=10000, 予測値=10400
    - RMSE=400, RMSLE=0.0392

これは実際の店舗を考えてみると、元の店舗のキャパシティが大きければ大きいほど、ある程度誤差に寛容にできることを意味しています。

## データ

データの構造は以下のようになっています。

- 予約データ(HPG)
    - Store id
    - 来店した時間
    - 予約が行われた時間
    - 予約した人数

- レストランの情報(HPG & AIR)
    - Store id
    - 店舗のジャンル（例：Italyan / French）
    - 場所
    - 緯度
    - 経度

- 来店者の情報(AIR)
    - 日付と時間
    - Store id
    - 来店者数

- カレンダーの情報
    - 日付
    - 曜日
    - 休日かどうか

ホットペッパー(HPG)の情報とairレストラン(AIR)の情報を合わせたものです。

それぞれ店舗の数は以下のように確認を行います。

```python
print(os.listdir("../input"))
>> ['air_store_info.csv', 'hpg_store_info.csv', 'sample_submission.csv', 
    'date_info.csv', 'hpg_reserve.csv', 'air_visit_data.csv', 
    'air_reserve.csv', 'store_id_relation.csv']

df_air_store = pd.read_csv('air_store_info.csv')
print(len(df_air_store['air_store_id'].unique()))
>> 829

df_hpg_store = pd.read_csv('hpg_store_info.csv')
print(df_hpg_store['hpg_store_id'].unique())
>> 4690
```

## 特徴量の可視化

### 与えられたデータの期間

`sample_submission`のデータに含まれている`id`には店舗のIDと日付の情報が混ざっており、まずはこれを分けた後に日付の情報などを確認します。

```python
df_train = pd.read_csv('air_visit_data.csv')
df_test  = pd.read_csv('sample_submission.csv')

df_test['air_store_id'] = df['id'].str[:-11]
df_test['visit_date'] = df['id'].str[-10:]
df_test.drop('id', axis='columns', inplace=True)

print('訓練データの開始日は', df_train.visit_date.iloc[0])
>> '2016-01-13'
print('訓練データの終了日は', df_train.visit_date.iloc[-1])
>> '2017-04-22'
print('テストデータの開始日は', df_test.visit_date.iloc[0])
>> '2017-04-23'
print('テストデータの終了日は', df_test.visit_date.iloc[-1])
>> '2017-05-31'
```

問題点としては、テストデータの期間に**ゴールデンウィーク**が含まれていることであり、この期間の処理には注意を払う必要があります。

### 実際の来店者数

実際の来店者数と予測数を確認してみる。

```python
#Visitor each day
f,ax = plt.subplots(1,1,figsize=(15,8))
plt1 = df_train_air.groupby(['visit_date'], as_index=False).agg({'visitors': np.sum})
plt1=plt1.set_index('visit_date')
plt1.plot(color='salmon', kind='area', ax=ax)
plt.ylabel("Sum of Visitors")
plt.title("Visitor and Reservations")
```

![日付ごとの来客数.png](https://qiita-image-store.s3.amazonaws.com/0/194085/03b81967-d039-04da-83ef-08265c6efcda.png)


途中で`visitors`が急に増加していることと、季節や週レベルでのトレンドが存在していることが確認できます。<br>
つまり、それらの特徴量を組み込むと有効だと思えます。

### 時間帯ごとに

```python
air_reserve = pd.read_csv('../input/air_reserve.csv')
air_reserve['visit_hour'] = pd.to_datetime(air_reserve['visit_datetime']).dt.hour
air_reserve.groupby('visit_hour')['reserve_visitors'].sum().plot.bar()
```

![時間帯ごとの予約客数.png](https://qiita-image-store.s3.amazonaws.com/0/194085/82a1c7ce-ddb5-9ac3-1b57-6e174d4cefe0.png)


### 店舗のジャンル

ジャンルごとの店舗数は以下のように確認します。

```python
air_store.air_genre_name.value_counts().sort_values().plot(kind='barh')
```

![じぇんるごとの店舗数.png](https://qiita-image-store.s3.amazonaws.com/0/194085/a81fc009-1dac-134b-431a-11363d839010.png)


居酒屋と喫茶店が多くを占めています。

### 店舗の位置

店舗がより多く存在している地点は以下のように確認します。

```python
air_store.air_area_name.value_counts()[:15].sort_values().plot(kind='barh')
```

![場所ごとの店舗数.png](https://qiita-image-store.s3.amazonaws.com/0/194085/b778b963-aff4-ef07-2e90-d3895d738c5b.png)


## 解法

### 検証データの作成法

このデータセットは時系列なので、通常の`k-fold`を使用することができません。もしランダムにデータを抽出してしまうと、過去のデータと未来のデータが混じってしまい、リークが生じていますからです。<br>

そこで時系列に沿ってデータを分割していきます。

![時系列の分割法.png](https://qiita-image-store.s3.amazonaws.com/0/194085/55ba4d1a-1de7-9bbb-39f7-f21f4b93d6dd.png)


### 使用された特徴量

- store_id
- year, month, day of month, day of week
- 来店客の統計情報
    - 日曜でのMin, Max, Mean, Median, ...

- 休日フラグ
- 予約数
- 予約時と来店時の時間差
- 移動平均などのWindow情報
- 近隣の店舗の人気度
- 県名と市区町村名
- 経度、緯度
- ...

### 参考資料

- [Kaggle Meetup](https://www.youtube.com/watch?v=6llLC4M3dMo)
- [Kaggle Day](https://www.youtube.com/watch?v=_IXKmcUuIfo)
- [Mesen, Luis PREDICT-454](https://www.youtube.com/watch?v=KSHE7aLqoOI)
- [1st place solution](https://www.kaggle.com/pureheart/1st-place-lgb-model-public-0-470-private-0-502/comments)
- [7th place solution](https://www.kaggle.com/c/recruit-restaurant-visitor-forecasting/discussion/49259)
- [8th place solution](https://www.kaggle.com/c/recruit-restaurant-visitor-forecasting/discussion/49166)
- [12th place solution](https://www.kaggle.com/c/recruit-restaurant-visitor-forecasting/discussion/49251)
- [Recruit Restaurant EDA](https://www.kaggle.com/fabiendaniel/recruit-restaurant-eda)
- [A Japanese Journey](https://www.kaggle.com/asindico/a-japanese-journey)
- [Recruit Visual EDA Itadakimasu!](https://www.kaggle.com/faizalabdkadir/recruit-visual-eda-itadakimasu)
- [Time Series Cross-Validation evolution, xgboost, features](https://www.kaggle.com/c/recruit-restaurant-visitor-forecasting/discussion/46602)