店舗の売り上げ高を予測する

# 目次
[1.はじめに](#1はじめに)
[2.実行環境・Pythonのバージョン](#2実行環境pythonのバージョン)
[3.目的](#3目的)
[4.使用するデータセットについて](#4使用するデータセットについて)
[5.実装](#5実装)
[6.データの可視化・前処理](#6データの可視化前処理)
[7.分析手法の検討](#7分析手法の検討)
[8.結果](#8結果)
[9.今後の展望](#9今後の展望)

# 1.はじめに 
はじめまして。私はAidemyでデータ分析講座を受講しています。
現在、マーケティング関係の仕事をしており、企業の売上データを分析し、
新製品の販売予測や、販売戦略の立案に従事しています。
今後、機械学習の手法を応用し、より精度の高い分析をしたいと考え、受講を決意しました。
データ分析講座の学びの成果として、本記事を作成しました。
よろしくお願い致します。

# 2.実行環境、Pythonのバージョン
【実行環境】
Google colaboratory
【バーション】
Python 3.9.16

# 3.目的
時系列分析を利用して、エクアドルに拠点を置く大手食料品小売業者 Corporación Favorita のデータから、店舗の売上を予測します。

# 4.使用するデータセットについて
Kaggleのコンペ『Store Sales – Time Series Forecasting』のデータセットを
活用します(https://www.kaggle.com/competitions/store-sales-time-series-forecasting/data)。

# 5.実装
まずは、必要なライブラリをimportします。
```python
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt 
```

# 6.データの可視化・前処理
続いて、データの内容を確認します。
用意されているデータセットをimportします。
```python
train_df = pd.read_csv('/content/drive/MyDrive/Dataset/train.csv')
test_df = pd.read_csv('/content/drive/MyDrive/Dataset/test.csv')
store_df = pd.read_csv('/content/drive/MyDrive/Dataset/stores.csv')
transactions_df = pd.read_csv('/content/drive/MyDrive/Dataset/transactions.csv')
oil_df = pd.read_csv('/content/drive/MyDrive/Dataset/oil.csv')
holidays_df = pd.read_csv('/content/drive/MyDrive/Dataset/holidays_events.csv')
```
trainデータの中身を可視化してみます。
```python
train_df
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/79566e3a-e6d3-3071-b6e0-0360150d1a5e.png)

date別、str_nbr別、family別の売り上げ高が列挙されています。
データを見ると、売上が0の日もあるので、売上高が0の行を除外します。
```python
train_df[train_df.sales>0]
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/698ab17f-d943-2bea-38f9-04163132e154.png)

続いて、store_nbrの数や種類を確認します。
```python
np.sort(train_df.store_nbr.unique())
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/2f289fe6-d6df-6add-d223-cbcbd181df88.png)
store_nbrは1~54の全54個あることがわかりました。

同じように、familyの中身も確認します。
```python
train_df.family.unique()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/1fafd028-db3e-6231-99bb-86111515df09.png)
familyは全33個のデータからなることがわかりました。

trainデータの要約情報と、基本統計量を確認します。
```python
train_df.info()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/bcfd33d1-e3ba-b309-b563-4ad87f13e059.png)


```python
train_df.describe()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/a795df8a-306a-03e5-1994-36243a2c6df0.png)

データの概要を掴むことが出来たので、分析しやすくなるように、データを加工していきます。
まずは、店舗ごとの売り上げを合計します。
店舗数が多いので、ここでは先頭10行のみ表示します。

```python
train_df.groupby(by='store_nbr')['sales'].sum().head(10)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/6e3c070e-b7d3-cbbe-382a-83d9efcc0d28.png)

店舗1を例に、日別の売上を可視化してみます。
```python
import plotly.express as px
fig = px.scatter(train_df[train_df.store_nbr==1], x="date", y="sales")
fig.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/6c52aace-e714-03d8-e636-61724dc1b943.png)
2016年4月に、売上が大きく上昇していますが、それはエクアドルで発生した地震の影響によるものと思われます。

続いて、datetimeモジュールを用いて、データを変換します。
```python
train_df['month'] = pd.to_datetime(train_df['date']).dt.month
train_df['day'] = pd.to_datetime(train_df['date']).dt.day
train_df['day_name'] = pd.to_datetime(train_df['date']).dt.day_name()
train_df['year'] = pd.to_datetime(train_df['date']).dt.year
train_df.head(5)
```

店舗別・月別の売上を可視化します。
```python
table = pd.pivot_table(train_df, values ='sales', index =['store_nbr'],
                         columns =['month'], aggfunc = np.sum)
fig, ax = plt.subplots(figsize=(15,12))         
sns.heatmap(table, annot=False, linewidths=.5, ax=ax, cmap="YlGnBu")
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/993f37b0-2330-b984-51e2-5c92fb68892a.png)
店舗の売り上げと、販売月には、特に相関は無さそうです。

商品の種類別の売り上げを可視化します。
```python
table1 = pd.pivot_table(train_df, values ='sales', index =['family'], aggfunc = np.sum)
fig, ax = plt.subplots(figsize=(10,10))         
sns.heatmap(table1, annot=True, linewidths=.5, ax=ax, cmap="YlGnBu")
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/8fce63b7-1113-7238-9a35-855848906ef6.png)
GROCERY1、BEVERAGES、PRODUCE、CLEANINGなど、一部の商品が全体の売上を引っ張っているようです。

それぞれの商品が、全体の売上にどれだけ貢献したかを計算します。
```python
total_sum = table1.sales.sum()
table1/total_sum
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/4c1d4ebb-dfde-b8e1-adf0-81667cd5b537.png)
売上の大きかったGROCERY1、BEVERAGES、PRODUCE、CLEANINGなどは、全体の売上への貢献度が高い傾向にあります。

次に、店舗1、店舗2を例に、プロモーションが売上に与える影響を確認します。
```python
#店舗1
import plotly.express as px
fig = px.scatter(train_df[train_df.store_nbr==1], x="onpromotion", y="sales")
fig.show()

#店舗2
import plotly.express as px
fig = px.scatter(train_df[train_df.store_nbr==2], x="onpromotion", y="sales")
fig.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/2a10eca8-e041-36bd-07a8-9eece80b024f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/51200075-3303-f979-2ab4-406cb3e19ada.png)
プロモーションが売上に与える影響はそれほど大きくなさそうです。

プロモーションされていた商品と、そうでない商品の数を可視化してみます。
```python
import plotly.express as px
df = px.data.tips()
fig = px.histogram(train_df[train_df.onpromotion<200], x="onpromotion", nbins=20)
fig.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/f6030458-d586-988e-90bd-7442699fe455.png)
プロモーションされていた商品の数は少ないようです。

今度は、曜日が、売上に与える影響を確認します。
```python
table3 = pd.pivot_table(train_df, values ='sales', index =['day_name'], aggfunc = np.sum)
fig, ax = plt.subplots(figsize=(10,10))         
sns.heatmap(table3, annot=True, linewidths=.5, ax=ax, cmap="YlGnBu")
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/12a9eaf4-06a2-dfc6-7545-7862830185d6.png)
週末（土日の）売上が高くなっているのがわかります。

同様に、販売年の、売上に与える影響を確認します。
```python
table_year = pd.pivot_table(train_df, values ='sales', index =['store_nbr'],
                         columns =['year'], aggfunc = np.sum)

fig, ax = plt.subplots(figsize=(15,12))         
sns.heatmap(table_year, annot=False, linewidths=.5, ax=ax, cmap="YlGnBu")
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/78bc1c1f-b88b-b52e-d4e4-e36fafb73005.png)
販売年による売り上げのバラつきはそれほど無さそうです。

先程、商品ごとの全体の売り上げへの影響を見たところ、一部の商品の売り上げ比率が高いことが分かったので、分析しやすくするため、familyをグループ分けします。
```python
family_map       = {'AUTOMOTIVE': 'rest',
                   'BABY CARE': 'rest',
                   'BEAUTY': 'rest',
                   'BOOKS': 'rest',
                   'CELEBRATION': 'rest',
                   'GROCERY II': 'rest',
                   'HARDWARE': 'rest',
                   'HOME AND KITCHEN I': 'rest',
                   'HOME AND KITCHEN II': 'rest',
                   'HOME APPLIANCES': 'rest',
                   'LADIESWEAR': 'rest',
                   'LAWN AND GARDEN': 'rest',
                   'LINGERIE': 'rest',
                   'MAGAZINES': 'rest',
                   'PET SUPPLIES': 'rest',
                   'PLAYERS AND ELECTRONICS': 'rest',
                   'SCHOOL AND OFFICE SUPPLIES': 'rest',
                   'SEAFOOD': 'rest',
                   'DELI': 'first_sec',
                    'EGGS': 'first_sec',
                    'FROZEN FOODS': 'first_sec',
                    'HOME CARE': 'first_sec',
                    'LIQUOR,WINE,BEER': 'first_sec',
                    'PREPARED FOODS': 'first_sec',
                    'PERSONAL CARE': 'first_sec',
                    'BREAD/BAKERY': 'third',
                    'MEATS': 'third',
                    'POULTRY': 'third',
                    'CLEANING':'fourth',
                    'DAIRY':'fourth',
                    'PRODUCE':'seventh',
                    'BEVERAGES':'fifth',
                    'GROCERY I': 'sixth'
                   }

train_df['new_family'] = train_df['family'].map(family_map)
train_df.head(5)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/3b06be1a-0c36-4a41-14e6-eb03b7465d23.png)

外れ値を除去します。
```python
#外れ値除去前
import plotly.express as px
fig = px.scatter(train_df[train_df.store_nbr==4], x="date", y="sales")
fig.show()

for i in range(1,len(train_df.store_nbr.unique())+1):
    val = train_df[train_df.store_nbr == i].sales.quantile(0.99)
    train_df = train_df.drop(train_df[(train_df.store_nbr==i) & (train_df.sales > val)].index)

#外れ値除去後
fig = px.scatter(train_df[train_df.store_nbr==4], x="date", y="sales")
fig.show()
```

【外れ値除去前】
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/55e356d4-ccd9-081c-ea80-3500a459a7b6.png)

【外れ値除去後】
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/a0c32b3c-e409-4af1-028c-c64f80e6c153.png)

storesデータの分析に移ります。
store_dfデータの型を確認します。
```python
store_df.shape
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/3e6695de-7a4f-4057-4799-b7542fb7d5e3.png)

train_dfとstore_dfを結合します。
```python
train_df = pd.merge(train_df, store_df, on='store_nbr', how='left') 
train_df.head(5)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/786fb226-ce30-bbdc-781c-58b97fe47dbc.png)

holidaysデータを分析します。 
holidaysデータの中身や型を確認します。
```python
holidays_df.head(5)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/f0191bde-ab36-ae0a-5d08-02f9827798ef.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/12b95e81-9785-7b25-4778-bce5794be298.png)

```python
holidays_df.shape
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/f0cdcee9-3bd4-d88d-b567-1776cef9afa3.png)

holidaysデータの中のlocaleを確認します。
```python
holidays_df.locale.unique()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/c9e8a938-ec8f-fff6-ce92-1236829fdde5.png)

同じく、holidaysデータの中のlocale_nameの内容を確認します。
```python
holidays_df.locale_name.unique()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/5ee1656a-f0f8-e796-f0c8-47235d5d9908.png)

storesデータの中のcityの内容を確認します。
```python
store_df.city.unique()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/032d2286-cd60-617a-be85-9dffb361a2de.png)

storesデータの中のstateの内容を確認します。
```python
store_df.state.unique()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/1017ea68-2906-c8d5-7d48-137a23c18788.png)

holidaysデータの中身を確認します。
```python
holidays_df.type.unique()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/abf42462-55d2-fbb1-9048-99f3825ff1e9.png)

holidays_dfの列名を変更します。
```python
holidays_df.rename(columns={'type': 'day_nature'},
          inplace=True, errors='raise')
```
休日の種類に応じて、新しいデータを作成します。
```python
holiday_loc = holidays_df[holidays_df.locale == 'Local']
holiday_reg = holidays_df[holidays_df.locale == 'Regional']
holiday_nat = holidays_df[holidays_df.locale == 'National']
holiday_loc.rename(columns={'locale_name': 'city'},
          inplace=True, errors='raise')
holiday_reg.rename(columns={'locale_name': 'state'},
          inplace=True, errors='raise')
```

先程作成した、holiday_locデータを可視化してみます。
```python
holiday_loc
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/d69453c0-afe7-297d-6c6c-4618efd4622b.png)
データを結合していきます。
```python
train_df = pd.merge(train_df, holiday_loc, on=['date', 'city'], how='left') 
train_df = train_df[~((train_df.day_nature == 'Holiday') & (train_df.transferred == False))]
train_df.drop(['day_nature', 'locale', 'description','transferred'], axis=1, inplace=True)
train_df = pd.merge(train_df, holiday_reg, on=['date', 'state'], how='left') 
train_df = train_df[~((train_df.day_nature == 'Holiday') & (train_df.transferred == False))]
train_df.drop(['day_nature', 'locale', 'description','transferred'], axis=1, inplace=True)
train_df = pd.merge(train_df, holiday_nat, on=['date'], how='left') 
train_df = train_df[~((train_df.day_nature == 'Holiday') & (train_df.transferred == False))]
train_df.drop(['day_nature', 'locale', 'description','transferred'], axis=1, inplace=True)
```



train_dfというデータから、 'id', 'date', 'family', 'month', 'day','city','state','type', 'cluster', 'locale_name', 'year' という列を削除します。
```python
train_df.drop(['id', 'date', 'family', 'month', 'day','city','state','type', 'cluster', 'locale_name', 'year'],axis=1, inplace=True)
```

train_df変数内のday_nameとnew_familyをダミー変数に変換します。
```python
train_df = pd.get_dummies(train_df, columns = ['day_name','new_family'])
train_df.reset_index(inplace=True)
train_df.drop(['index'],axis=1, inplace=True)
train_df.head(5)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/73aa971a-b95a-0f77-8bc5-3791d1da344d.png)


# 7.分析手法の検討
モデルをimportし、データを学習させます。
ここでは、ランダムフォレストよりも高い精度が出るとされている、XGboostを用います。

```python
from sklearn.linear_model import LinearRegression
from xgboost import XGBRegressor
from sklearn import preprocessing
from sklearn.model_selection import train_test_split


size = round(len(train_df)*0.7)
train = train_df.iloc[:size, :]
test = temp.iloc[size:, :]


for i in range(1,len(train_df.store_nbr.unique())+1):  
    temp_df = train_df[train_df.store_nbr == i]
    sale_out = temp_df[['sales']]
    globals()['max_%s' % i] = temp_df['onpromotion'].max()
    temp_df['onpromotion'] = (temp_df['onpromotion']/globals()['max_%s' % i])
    temp_df.onpromotion = np.where(temp_df.onpromotion<0, 0, temp_df.onpromotion)
    temp_df.drop(['sales','store_nbr'],axis=1, inplace=True)
    globals()['model_%s' % i] = XGBRegressor(verbosity=0)
    globals()['model_%s' % i].fit(temp_df, sale_out)
```

店舗別の売上高を予測します。
```python
temp_df.head(5)

backup_df_1 = pd.DataFrame()

for i in range(1,len(train_df.store_nbr.unique())+1):
    temp_df = test_df[test_df.store_nbr == i]
    sales_out = temp_df[['sales']]
    print(sales_out)
    temp_df['onpromotion'] = (temp_df['onpromotion']/globals()['max_%s' % i])
    temp_df.onpromotion = np.where(temp_df.onpromotion<0, 0, temp_df.onpromotion)
    save_id = temp_df[['id', 'sales']].reset_index()
    submit = globals()['model_%s' % i].predict(temp_df)
    save_id['sales2'] = submit
    display(save_id.head(3))
    df11 = pd.DataFrame(submit, columns = ['sales2'])
    backup_df = pd.concat([save_id[['id', 'sales']], df11], axis = 1, ignore_index = True)
    backup_df_1 = backup_df_1.append(backup_df, ignore_index=True)

backup_df_1.rename(columns={0 : "id", 1 : "sales", 2 : "sales_predict"}, inplace=True, errors='raise')
test_df = pd.merge(test_df, backup_df_1, on='id', how='left')

```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/a0bffd2c-4a79-d695-09c7-d7042117411a.png)



backup_dfの先頭5行を表示させます。
```python
backup_df_1.head(5)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/1c55a184-1265-dabf-aaa2-ebaf8dd426ba.png)

sample_dfのsales列の内容について、0より小さい行は0に変えておきます。
```python
sample_df = test_df[['id', 'sales_predict']]
sample_df.sales_predict = np.where(sample_df.sales_predict<0, 0, sample_df.sales_predict)
sample_df.head(5)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/b97add7d-4798-cd85-263c-fb8b3252b361.png)

最後に、RMSEによるモデルの精度評価を行います。
```python
from sklearn.metrics import mean_squared_error
from math import sqrt
rmse = sqrt(mean_squared_error(backup_df_1['sales'], backup_df_1['sales_predict']))
print(f"{RMSE} : rmse")
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3291892/ca5f83c5-fa66-9ca4-3e12-dde30f86b600.png)


# 8.結果
XGBoostを用いて、店舗の売上高を予測することができました。
今回のブログ作成を通し、一通りデータを取得し、前処理→モデルの選定→実装→予測までの流れを体験することができました。ただ、精度については、まだまだ改善の余地があると思いますので、引き続き勉強を続けていこうと思います。

# 9.今後の展望
今回、Aidemyでデータ分析講座を受講し、Pythonの基本的な文法や、ライブラリの使い方や、機械学習・ディープラーニングの概要、データ分析の流れ等を学ぶことができました。
今回学んだ技術を、今後は実務でも導入していきたいと考えておりますが、まだ理解が曖昧な箇所があったり、コーディングの経験が乏しい部分がありますので、データ分析講座の復習や、他の講座の受講による知識の定着と、Kaggleに参加し、モデルを構築する経験を積んで、さらなるスキルアップを図っていきたいと考えています。