初心者によるKaggleメモ#2 将来の売り上げ予測

[前回のタイタニックチュートリアルの記事](https://qiita.com/simonritchie/items/71159a3db996818ee9f4)が中途半端ですが、仕事で時系列データによる将来の予測のタスクが来たので、勉強がてら他の時系列のKaggleコンテストを触ってみます。

# 対象のコンテスト

[Store Item Demand Forecasting Challenge | Predict 3 months of item sales at different stores](https://www.kaggle.com/c/demand-forecasting-kernels-only) 

# コンテスト内容

- 与えられるのは5年分の各ショップの売り上げデータ。
- ショップ数10、商品数50。
- 3か月先の各ショップ別・商品別の売り上げを予測するのが目的。
- 賞金などはなく、スキルアップなどが目的のコンペ。

# データを確認してみる

## KaggleのAPIを使ってみる

APIがあるようなので、データを落としたりが楽にできないか少し触ってみます。（クラウド上のカーネルでの対応が楽になるのを期待しつつ）

とりあえず、Azure Notebooks環境で進めてみます。

ライブラリをインストール。

```
!pip install kaggle
```

```
Successfully installed kaggle-1.4.2
```

認証用のファイルをダウンロードします。
Kaggleのアカウントのページ（https://www.kaggle.com/<ユーザー名>/account）にアクセスします。

APIのところの「Create New API Token」をクリック。トークンのJSONファイルがダウンロードされます。

<img width="511" alt="20180721_1.png" src="https://qiita-image-store.s3.amazonaws.com/0/228778/9c654fbd-b492-532c-b425-95fe7e84cf17.png">

Kaggleのライブラリモジュールをimportすると、「ここに認証用のファイルを設置してね」とか「JSONの権限を600に変えてね」といったエラー内容や警告がでるので、そちらの指示に従って対応します。

Azure Notebooksの場合、「/home/nbuser/.kaggle/」以下に設置するように指示が出ました。

```py
import kaggle
```

```py
import os
os.makedirs('/home/nbuser/.kaggle/')
```

```
!cp ../auth/kaggle.json /home/nbuser/.kaggle/kaggle.json
!chmod 600 /home/nbuser/.kaggle/kaggle.json
```

（クラウド環境だと、消えてしまったりしそうなので、あとでスクリプト書いておこうと考えつつ・・）

[データダウンロード関係のドキュメント](https://github.com/Kaggle/kaggle-api#download-competition-files)を見てみます。
どうやらコマンドライン経由で扱う模様・・

-c 引数に対象コンペのURLに含まれるID的な文字列（今回は https://www.kaggle.com/c/demand-forecasting-kernels-only の demand-forecasting-kernels-only ）、-p引数にダウンロード先のディレクトリを指定します。

```
!kaggle competitions download -c demand-forecasting-kernels-only -p ./
```

```
Downloading sample_submission.csv.zip to .
  0%|                                                | 0.00/101k [00:00<?, ?B/s]
100%|█████████████████████████████████████████| 101k/101k [00:00<00:00, 486kB/s]
Downloading test.csv.zip to .
  0%|                                                | 0.00/135k [00:00<?, ?B/s]
100%|█████████████████████████████████████████| 135k/135k [00:00<00:00, 398kB/s]
Downloading train.csv.zip to .
 97%|████████████████████████████████████▉ | 3.00M/3.08M [00:00<00:00, 3.64MB/s]
100%|███████████████████████████████████████| 3.08M/3.08M [00:04<00:00, 804kB/s]
```

zipで落ちてくる模様。
解凍しておきます。

```py3
import zipfile
file_list = [
    'sample_submission.csv.zip',
    'train.csv.zip',
    'test.csv.zip']
for file_name in file_list:
    with zipfile.ZipFile(file=file_name) as target_zip:
        target_zip.extractall()
```

## train.csv

```py
import pandas as pd
train_df = pd.read_csv('./train.csv')
```

```py3
train_df.head()
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>date</th>
      <th>store</th>
      <th>item</th>
      <th>sales</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>2013-01-01</td>
      <td>1</td>
      <td>1</td>
      <td>13</td>
    </tr>
    <tr>
      <th>1</th>
      <td>2013-01-02</td>
      <td>1</td>
      <td>1</td>
      <td>11</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2013-01-03</td>
      <td>1</td>
      <td>1</td>
      <td>14</td>
    </tr>
    <tr>
      <th>3</th>
      <td>2013-01-04</td>
      <td>1</td>
      <td>1</td>
      <td>13</td>
    </tr>
    <tr>
      <th>4</th>
      <td>2013-01-05</td>
      <td>1</td>
      <td>1</td>
      <td>10</td>
    </tr>
  </tbody>
</table>


```py3
print(train_df.tail().to_html())
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>date</th>
      <th>store</th>
      <th>item</th>
      <th>sales</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>912995</th>
      <td>2017-12-27</td>
      <td>10</td>
      <td>50</td>
      <td>63</td>
    </tr>
    <tr>
      <th>912996</th>
      <td>2017-12-28</td>
      <td>10</td>
      <td>50</td>
      <td>59</td>
    </tr>
    <tr>
      <th>912997</th>
      <td>2017-12-29</td>
      <td>10</td>
      <td>50</td>
      <td>74</td>
    </tr>
    <tr>
      <th>912998</th>
      <td>2017-12-30</td>
      <td>10</td>
      <td>50</td>
      <td>62</td>
    </tr>
    <tr>
      <th>912999</th>
      <td>2017-12-31</td>
      <td>10</td>
      <td>50</td>
      <td>82</td>
    </tr>
  </tbody>
</table>

約91万行のデータ。

```py3
train_df.describe().loc[:, ['sales']]
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>sales</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>count</th>
      <td>913000.000000</td>
    </tr>
    <tr>
      <th>mean</th>
      <td>52.250287</td>
    </tr>
    <tr>
      <th>std</th>
      <td>28.801144</td>
    </tr>
    <tr>
      <th>min</th>
      <td>0.000000</td>
    </tr>
    <tr>
      <th>25%</th>
      <td>30.000000</td>
    </tr>
    <tr>
      <th>50%</th>
      <td>47.000000</td>
    </tr>
    <tr>
      <th>75%</th>
      <td>70.000000</td>
    </tr>
    <tr>
      <th>max</th>
      <td>231.000000</td>
    </tr>
  </tbody>
</table>

```py3
train_df.info()
```

```
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 913000 entries, 0 to 912999
Data columns (total 4 columns):
date     913000 non-null object
store    913000 non-null int64
item     913000 non-null int64
sales    913000 non-null int64
dtypes: int64(3), object(1)
memory usage: 27.9+ MB
```

欠損値などはない模様。

## test.csv

```py3
test_df = pd.read_csv('test.csv')
test_df.head()
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>id</th>
      <th>date</th>
      <th>store</th>
      <th>item</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0</td>
      <td>2018-01-01</td>
      <td>1</td>
      <td>1</td>
    </tr>
    <tr>
      <th>1</th>
      <td>1</td>
      <td>2018-01-02</td>
      <td>1</td>
      <td>1</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2</td>
      <td>2018-01-03</td>
      <td>1</td>
      <td>1</td>
    </tr>
    <tr>
      <th>3</th>
      <td>3</td>
      <td>2018-01-04</td>
      <td>1</td>
      <td>1</td>
    </tr>
    <tr>
      <th>4</th>
      <td>4</td>
      <td>2018-01-05</td>
      <td>1</td>
      <td>1</td>
    </tr>
  </tbody>
</table>

```py3
test_df.tail()
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>id</th>
      <th>date</th>
      <th>store</th>
      <th>item</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>44995</th>
      <td>44995</td>
      <td>2018-03-27</td>
      <td>10</td>
      <td>50</td>
    </tr>
    <tr>
      <th>44996</th>
      <td>44996</td>
      <td>2018-03-28</td>
      <td>10</td>
      <td>50</td>
    </tr>
    <tr>
      <th>44997</th>
      <td>44997</td>
      <td>2018-03-29</td>
      <td>10</td>
      <td>50</td>
    </tr>
    <tr>
      <th>44998</th>
      <td>44998</td>
      <td>2018-03-30</td>
      <td>10</td>
      <td>50</td>
    </tr>
    <tr>
      <th>44999</th>
      <td>44999</td>
      <td>2018-03-31</td>
      <td>10</td>
      <td>50</td>
    </tr>
  </tbody>
</table>

```py3
test_df.info()
```

```
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 45000 entries, 0 to 44999
Data columns (total 4 columns):
id       45000 non-null int64
date     45000 non-null object
store    45000 non-null int64
item     45000 non-null int64
dtypes: int64(3), object(1)
memory usage: 1.4+ MB
```

## sample_submission.csv

```py
sample_submission_df = pd.read_csv('./sample_submission.csv')
sample_submission_df.head()
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>id</th>
      <th>sales</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0</td>
      <td>52</td>
    </tr>
    <tr>
      <th>1</th>
      <td>1</td>
      <td>52</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2</td>
      <td>52</td>
    </tr>
    <tr>
      <th>3</th>
      <td>3</td>
      <td>52</td>
    </tr>
    <tr>
      <th>4</th>
      <td>4</td>
      <td>52</td>
    </tr>
  </tbody>
</table>

```py
sample_submission_df.info()
```

```
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 45000 entries, 0 to 44999
Data columns (total 2 columns):
id       45000 non-null int64
sales    45000 non-null int64
dtypes: int64(2)
memory usage: 703.2 KB
```

# Prophetを使ってみる

知り合いからFacebookのライブラリのProphet良さそうと伺ったのと、シンプルで馴染むAPIだったので、一旦Prophetを使ってみます。

## インストール周り

```
!pip install fbprophet
```

pystanに依存するため、一度エラーが出ますがそのままpystanのインストールも含め、インストールが進んでいきました。

```
ModuleNotFoundError: No module named 'pystan'
...
Installing collected packages: pystan, fbprophet
```

> ※2017/9/26現在ではAnacondaのfbprohetがver0.1.1なのでpip install推奨 一回エラー出るかもしれないけど、pystanが入ればインストール成功します。
[Prophet利用方法](http://sasamm.hatenablog.com/entry/2018/01/04/170608)

インストールに少し時間がかかります。

```
Successfully installed fbprophet-0.3.post2 pystan-2.17.1.0
```

## 一部のストア・商品で動かしてみる

ストアID = 1, 商品ID = 1 のデータフレームで、Prophetを動かしてみます。

```py
store_1_df = train_df[
    (train_df.store == 1) & (train_df.item == 1)]
```

```py
store_1_df.head(n=3)
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>date</th>
      <th>store</th>
      <th>item</th>
      <th>sales</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>2013-01-01</td>
      <td>1</td>
      <td>1</td>
      <td>13</td>
    </tr>
    <tr>
      <th>1</th>
      <td>2013-01-02</td>
      <td>1</td>
      <td>1</td>
      <td>11</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2013-01-03</td>
      <td>1</td>
      <td>1</td>
      <td>14</td>
    </tr>
  </tbody>
</table>

```py
store_1_df.info()
```

```
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1826 entries, 0 to 1825
Data columns (total 4 columns):
date     1826 non-null object
store    1826 non-null int64
item     1826 non-null int64
sales    1826 non-null int64
dtypes: int64(3), object(1)
memory usage: 151.3+ KB
```

```py
renamed_store_1_df.head(n=2)
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>ds</th>
      <th>y</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>2013-01-01</td>
      <td>13</td>
    </tr>
    <tr>
      <th>1</th>
      <td>2013-01-02</td>
      <td>11</td>
    </tr>
  </tbody>
</table>

一旦、引数でのパラメーター調整などせずに対応します。

```py3
from fbprophet import Prophet
m = Prophet()
m.fit(df=renamed_store_1_df)
```

3か月分、90行分必要なので、periods=90と指定します。

```py
future_df = m.make_future_dataframe(periods=90)
forecast_df = m.predict(df=future_df)
```

yhatというカラム名で、推論結果が設定されます。

```py
forecast_df.loc[:, ['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail()
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>ds</th>
      <th>yhat</th>
      <th>yhat_lower</th>
      <th>yhat_upper</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>1911</th>
      <td>2018-03-27</td>
      <td>20.606651</td>
      <td>14.875017</td>
      <td>26.001466</td>
    </tr>
    <tr>
      <th>1912</th>
      <td>2018-03-28</td>
      <td>21.284127</td>
      <td>15.653048</td>
      <td>26.926783</td>
    </tr>
    <tr>
      <th>1913</th>
      <td>2018-03-29</td>
      <td>21.995847</td>
      <td>16.337424</td>
      <td>27.573980</td>
    </tr>
    <tr>
      <th>1914</th>
      <td>2018-03-30</td>
      <td>23.612987</td>
      <td>18.062670</td>
      <td>28.965417</td>
    </tr>
    <tr>
      <th>1915</th>
      <td>2018-03-31</td>
      <td>25.626790</td>
      <td>20.105935</td>
      <td>31.187498</td>
    </tr>
  </tbody>
</table>


ドキュメントに、

> as well as columns for components and uncertainty intervals.
[Quick Start](https://facebook.github.io/prophet/docs/quick_start.html)

とあり、yhat_lowerとyhat_upperの範囲は信頼区間的なもののようです。

matplotlibのfigureを返す関数が用意されているので、そちらでプロットしてみます。

```py
m.plot(fcst=forecast_df)
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/228778/21a45c34-dedb-a82f-79c3-834a88a64dd4.png)

使い方がシンプルでいいですね。

# 同様に、全ストア・全商品で試してみる

```py
store_id_arr = train_df.store.unique()
store_id_arr
```

```
array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10])
```

```py
item_id_arr = train_df.item.unique()
item_id_arr
```

```
array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17,
       18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
       35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50])
```

推論結果などの保存先のディレクトリを用意しておきます。

```py
import os

CSV_DIR = './csv/'
PLOT_DIR = './plot/'

if not os.path.exists(CSV_DIR):
    os.makedirs(CSV_DIR)
if not os.path.exists(PLOT_DIR):
    os.makedirs(PLOT_DIR)
```

対象のストア・商品の推論結果とプロットを保存する関数を用意しておきます。

```py
from datetime import datetime

def predict_and_save_result(store_id, item_id):
    """
    対象のストア、商品に対して推論を行い、結果のデータ
    のCSVとプロットの保存を行う。
    
    CSV保存先 : ./csv/<ストアのID>_<商品のID>.csv
    プロット保存先 : ./plot/<ストアのID>_<商品のID>.png
    
    Parameters
    ----------
    store_id : int
        推論対象のストアのID。
    item_id : int
        推論対象の商品のID。
    """
    print(
        datetime.now(), 'store_id :', store_id,
        'item_id :', item_id, 'prediction started.')
    
    csv_path = CSV_DIR + '%s_%s.csv' % (store_id, item_id)
    plot_path = PLOT_DIR + '%s_%s.png' % (store_id, item_id)
    
    # すでに結果が存在すれば処理は行わない。
    if (os.path.exists(csv_path) and
            os.path.exists(plot_path)):
        return
    
    target_df = train_df[
        (train_df.store == store_id) & (train_df.item == item_id)]
    renamed_df = target_df.rename(
        columns={'date': 'ds', 'sales': 'y'})
    renamed_df = renamed_df.loc[:, ['ds', 'y']]
    m = Prophet()
    m.fit(df=renamed_df)
    future_df = m.make_future_dataframe(periods=90)
    forecast_df = m.predict(df=future_df)
    figure = m.plot(fcst=forecast_df)
    
    forecast_df.to_csv(
        csv_path, index=False, encoding='utf-8')
    figure.savefig(fname=plot_path)
```

試しにstore_id=1, item_id=1で動かしてみて、結果を表示してみます。

```
!pip install pillow
```

```
Requirement already satisfied: pillow in /home/nbuser/anaconda3_501/lib/python3.6/site-packages (4.3.0)
```

```py
predict_and_save_result(store_id=1, item_id=1)
img = Image.open(fp=PLOT_DIR + '1_1.png')
img
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/228778/28953cfd-e34c-f85f-28bf-f4c7d05093f0.png)

```py
img.close()

saved_df = pd.read_csv(CSV_DIR + '1_1.csv')
saved_df.tail().loc[:, ['ds', 'yhat']]
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>ds</th>
      <th>yhat</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>1911</th>
      <td>2018-03-27</td>
      <td>20.606651</td>
    </tr>
    <tr>
      <th>1912</th>
      <td>2018-03-28</td>
      <td>21.284127</td>
    </tr>
    <tr>
      <th>1913</th>
      <td>2018-03-29</td>
      <td>21.995847</td>
    </tr>
    <tr>
      <th>1914</th>
      <td>2018-03-30</td>
      <td>23.612987</td>
    </tr>
    <tr>
      <th>1915</th>
      <td>2018-03-31</td>
      <td>25.626790</td>
    </tr>
  </tbody>
</table>

大丈夫そうです。
大雑把に計った感じでは、一通り処理が終わるまでは何時間も必要になりそうな印象です。
仕事だと並列化などを検討するところですが、今回は手間なので処理が終わるまで待つようにします。

```py
for store_id in store_id_arr:
    for item_id in item_id_arr:
        predict_and_save_result(
            store_id=store_id, item_id=item_id)
```

# 各商品まとめた画像を作ってみる

変な推論になっているデータがないか大雑把に確認するため、各ストアごとに各プロットを小さく縦10 x 横5でまとめた画像を生成してみてみます。

プロット画像のサイズ確認 :

```py3
from PIL import Image
img = Image.open(PLOT_DIR + '1_1.png')
img.width
```

```
720
```

```py3
img.height
```

```
432
```

```py3
img.close()
```

縦横の比率を保ったまま、1画像あたり幅180px高さ108pxとし、合計で幅900px、高さ1080pxの画像をストアごとに作ります。

```py3
def make_store_all_plot_img(store_id):
    """
    対象のストアの各商品ごとのプロットをまとめた1つの
    画像を生成する。
    
    Parameters
    ----------
    store_id : int
        対象のストアのID（1～10）。
    
    Returns
    -------
    img : Image
        対象のストアの50商品分のプロットをまとめた画像。
        縦10 x 横5画像で、W900px, H1080pxの画像となる。
    """
    unit_width = 180
    unit_height = 108
    img = Image.new(
        mode='RGB',
        size=(unit_width * 5, unit_height * 10),
        color='#FFFFFF')
    for i, item_id in enumerate(item_id_arr):
        unit_plot_file_path = PLOT_DIR + '%s_%s.png' % \
            (store_id, item_id)
        unit_img = Image.open(fp=unit_plot_file_path)
        unit_img = unit_img.resize(
            size=(unit_width, unit_height),
            resample=Image.BILINEAR)
        x_index = i % 5
        y_index = int(i / 5)
        img.paste(
            im=unit_img,
            box=(x_index * unit_width, y_index * unit_height))
        unit_img.close()
    return img
```

```py3
make_store_all_plot_img(store_id=1)
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/228778/1215bb4c-e007-62c4-bb15-5d8c004991dc.png)

ここでは省略しますが、一応他のショップのものも確認しておきます。

大雑把にですが、大丈夫そうなので、次のステップに進みます。

# 提出用のデータを用意する。

提出用のデータは、idとsalesカラムのみ必要となります。
test.csvには、id, date, store, itemカラムがあり、推論結果はstoreとitemごとに、各日と推論結果を含んだCSVとなっています。

そのため、提出用のCSVを用意するために、date, store, itemの値からidを取得できるように、多次元の連想配列を用意しておきましょう。

dict[date][store][item] = id といった具合に、各次元のキーにそれぞれ日付、ストアID、商品ID、値にidカラムの値を設定します。
通常の辞書の場合、存在しないキーの部分に毎回新しい辞書を追加したりと手間なので、defaultdictを使います。

```py3
from collections import defaultdict
test_data_defaultdict = defaultdict(
    lambda: defaultdict(lambda: defaultdict(int)))
```

また、データフレームのままループを回すと遅いので、一旦ndarrayに変換してからループを回します。（そうすることで、一瞬で辞書への値の設定が終わります）

```py
test_arr = test_df.as_matrix()
test_arr
```

```
array([[0, '2018-01-01', 1, 1],
       [1, '2018-01-02', 1, 1],
       [2, '2018-01-03', 1, 1],
       ...,
       [44997, '2018-03-29', 10, 50],
       [44998, '2018-03-30', 10, 50],
       [44999, '2018-03-31', 10, 50]], dtype=object)
```

```py
for target_row_arr in test_arr:
    id_ = int(target_row_arr[0])
    date = str(target_row_arr[1])
    store =  int(target_row_arr[2])
    item = int(target_row_arr[3])
    
    test_data_defaultdict[date][store][item] = id_
```

用意した辞書を参照する形で、各推論結果のデータフレームにidカラムを追加し、ストア・商品別に分かれている分を一つのデータフレームにまとめます。

```py3
def get_target_data_id(date, store_id, item_id):
    """
    対象の日付、ストアのID、商品のIDから、該当するidを
    取得する。
    
    Parameters
    ----------
    date : str or Timestamp
        対象の日付。
    store_id : int
        対象のストアのID。
    item_id : int
        対象の商品のID。
        
    Returns
    -------
    id_ : int
        該当する（提出用のデータに紐づく）id。
        該当する値が存在しない場合は-1が返却される。
    """
    date = str(date)
    store_id = int(store_id)
    item_id = int(item_id)
    if not test_data_defaultdict[date][store_id].has_key(item_id):
        return -1
    return test_data_defaultdict[date][store_id][item_id]

predicted_df_list = []
for store_id in store_id_arr:
    print(datetime.now(), 'store_id :', store_id, 'started.')
    for item_id in item_id_arr:
        
        target_store_csv_path = CSV_DIR + '%s_%s.csv' % \
            (store_id, item_id)
        target_store_predicted_df = pd.read_csv(
            target_store_csv_path)
        target_store_predicted_df['id'] = target_store_predicted_df['ds'].apply(
            get_target_data_id,
            store_id=store_id, item_id=item_id)
        predicted_df_list.append(target_store_predicted_df)
```

```
2018-07-28 07:02:40.583967 store_id : 1 started.
2018-07-28 07:02:47.156170 store_id : 2 started.
2018-07-28 07:02:54.077372 store_id : 3 started.
2018-07-28 07:03:00.129491 store_id : 4 started.
2018-07-28 07:03:05.877288 store_id : 5 started.
2018-07-28 07:03:13.579948 store_id : 6 started.
2018-07-28 07:03:21.454486 store_id : 7 started.
2018-07-28 07:03:28.574409 store_id : 8 started.
2018-07-28 07:03:35.764472 store_id : 9 started.
2018-07-28 07:03:44.597948 store_id : 10 started.
```

```py3
overall_df = pd.concat(
    predicted_df_list, ignore_index=True)
```

推論対象の日付範囲のデータのみにスライスします。

```py3
overall_df = overall_df[overall_df.ds >= '2018-01-01']
len(overall_df)
```

```
45000
```

必要なカラムのみにスライスします。

```py3
overall_df = overall_df.loc[:, ['yhat', 'id']]
```

test.csvのデータとidカラムを使って結合します。

```py3
submission_df = pd.merge(
    left=test_df, right=overall_df, how='inner', on='id')
```

yhatとなっているカラム名を、提出用にsalesとリネームします。

```py3
submission_df.rename(columns={'yhat': 'sales'}, inplace=True)
```

提出用のCSVがidとsalesカラムのみ必要なので、そのようにスライスします。

```
submission_df = submission_df.loc[:, ['id', 'sales']]
```

salesカラムが推論結果のままで、浮動小数点数を含んでいるので整数にキャストしておきます。

```py3
import numpy as np

submission_df.sales = submission_df.sales.astype(
    np.int, copy=False)
```

結果を確認してみます。

```py
submission_df.head()
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>id</th>
      <th>sales</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0</td>
      <td>12</td>
    </tr>
    <tr>
      <th>1</th>
      <td>1</td>
      <td>15</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2</td>
      <td>16</td>
    </tr>
    <tr>
      <th>3</th>
      <td>3</td>
      <td>16</td>
    </tr>
    <tr>
      <th>4</th>
      <td>4</td>
      <td>18</td>
    </tr>
  </tbody>
</table>

```py3
submission_df.describe()
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>id</th>
      <th>sales</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>count</th>
      <td>45000.000000</td>
      <td>45000.000000</td>
    </tr>
    <tr>
      <th>mean</th>
      <td>22499.500000</td>
      <td>50.478556</td>
    </tr>
    <tr>
      <th>std</th>
      <td>12990.525394</td>
      <td>25.119587</td>
    </tr>
    <tr>
      <th>min</th>
      <td>0.000000</td>
      <td>8.000000</td>
    </tr>
    <tr>
      <th>25%</th>
      <td>11249.750000</td>
      <td>30.000000</td>
    </tr>
    <tr>
      <th>50%</th>
      <td>22499.500000</td>
      <td>47.000000</td>
    </tr>
    <tr>
      <th>75%</th>
      <td>33749.250000</td>
      <td>67.000000</td>
    </tr>
    <tr>
      <th>max</th>
      <td>44999.000000</td>
      <td>149.000000</td>
    </tr>
  </tbody>
</table>


train.csvのほうのデータ全体だと、平均52、25%パーセンタイルが30、中央値47、75％パーセンタイルが70となっており、推論結果と比較してみてもとりあえずは大丈夫そうです。（少なくとも間違ったカラムを連結して桁が違うといったことは無さそうです）

# 提出！

```py
submission_df.to_csv('submission.csv', index=False, encoding='utf-8')
```

今回のコンテストではCSVのほかにカーネルの提出も必要なので、用意したノートを調整し、そちらも提出します。（やり方を調べつつ・・）

New Kernelのボタンを押すとKaggleのKernelのノートが起動するようです。

<img width="626" alt="20180728_1.png" src="https://qiita-image-store.s3.amazonaws.com/0/228778/ded7267c-b0b5-d264-4cbc-130ac9050567.png">

右上の方にある雲のアイコンで、ノートファイルをアップできるので、そちらでここまでに書いたノートをアップします。

<img width="435" alt="20180728_2.png" src="https://qiita-image-store.s3.amazonaws.com/0/228778/9e6587bc-e281-1f36-99d7-f49be4de7a74.png">

データのダウンロードをAPIを利用していたので、そこをKaggle上のカーネルで参照できるように設定します。

右のほうにある Add Dataset ボタンを押すと、コンテストごとのアップしたいデータセットを検索できるようです。（便利！）

<img width="625" alt="20180728_3.png" src="https://qiita-image-store.s3.amazonaws.com/0/228778/878db928-c85a-cc9e-e5e7-2cf17c27d32f.png">

データセットを設定した後は、../input/というフォルダで対象のコンテストのデータセットが格納されるようなので、ノートのCSV読み込みの個所をそちらに調整します。

```py
train_df = pd.read_csv('../input/train.csv')
...
test_df = pd.read_csv('../input/test.csv')
...
sample_submission_df = pd.read_csv('../input/sample_submission.csv')
```

なお、ProphetやPILなどはすでにKaggleカーネルにインストール済みなようです。楽でいいですね。

また、メモリも17GB、ディスクもそのカーネルで1GBまで、GPUも使えるようで、Colaboratoryレベル・・と驚きましたが、よく調べたらKaggleはGoogleが買収済みなようで・・納得です:sweat:

このスペックだったら最初からKaggle Kernel上で作業すればよかった。


提出の際にカーネルが実行される必要があり、二度手間なので、次回からは積極的にこちらのカーネルも使っていこうと思います・・

ほかになにか調整が必要であれば調整を行い、そののちにCommit & Runボタンを押します。
なお、提出方法の資料のところに、「Publish」ボタンを押してね、といった記載がありましたが、どうやら古い記述のようで、現在はCommit & Runボタンになっているようです。（しばらくPublishボタンを探してしまった・・）

あとは、保存済みのsubmission.csvを、APIを使って投稿します。(認証周りは前述したデータダウンロード部分のものを参考にしてください。)

```
!kaggle competitions submit -c demand-forecasting-kernels-only -f submission.csv -m "The Prophet libs predictions"
```

※補足 : 上記コマンド実行後、「Successfully submitted to Store Item Demand Forecasting Challenge」と出るものの、My Submissionsページのリストに反映されない・・
提出方法のページのスクショが古く該当するUIが見つからないのと、提出関係のページには「Use the kaggle API to make a submission」と説明が・・
現在調査中。
（まあでも、勉強用ですし、ここまででも一旦はいいような気もします・・）