featuretoolsとtpotを用いた需要予測(回帰分析)。①

## はじめに

初めまして、現在プログラミングスクールに在籍しているものです。
今回は成果物の1つとして、[Signateのこちらのコンペ](https://signate.jp/competitions/24)に参加致しました。

このコンペの内容を簡単にお伝えしますと、説明変数(<font color="red">**温度や曜日,メニュー名等**</font>) 
から 目的変数(<font color="red">**お弁当の売上数**</font>)を予測するものです。いわゆる教師有学習(回帰)です。
イメージとしてはscikit-learnのボストンの住宅価格予想をイメージして頂けると分かりやすいかも知れません。

初投稿故、駄文散文ご了承くださいませ。


### コンセプト
今回Qiitaに挑戦した理由は下記のとおりです。
- 私のアウトプットや言語化の為。
- これからQiitaの記事を書く為のステップアップの練習。ライティングスキル向上の為。

以上、この記事を閲覧するだけで初心者の方も簡単に理解し実装して頂ける事を目標と致します。


丁寧に解説するつもりですので多少長くなります。
少しでも読みやすくと思い3つにパートを分ける予定です。
今回はデータ確認と特徴変換を中心として記入します。



## 開発環境

<font color="red">**Jupyter lab**</font> ⇒少ないデータですが、今回の機械学習モデルは大幅な時間を要します。それ故google colabのように時間制限やサーバーに依存するものは推奨いたしません。

私の経験として、google colab無料版にて機械学習にかけた際に、2時間経過した後フリーズして泣きを見ました。その後Jupyter labにて一晩実行し提出しました。

- プロセッサ:Intel(R) Core(TM) i7-6600U CPU
- 実装RAM   :16.0 GB
- システム  :64 ビット オペレーティング システム、x64 ベース プロセッサ
- OS        :Windows 11 

## 今回使う、featuretoolsとtpotとは？

<font color="red">**featuretools**</font>：
特徴量生成自動ツールです。使い方は②で説明致します。[公式サイト](https://docs.featuretools.com/en/stable/)

<font color="red">**TPOT**</font>：
遺伝的プログラミングを用いて､機械学習モデルの選定やパラメータチューニングなどを自動で行ってくれる機械学習モデルです。[公式サイト](http://epistasislab.github.io/tpot/)

## 実際にやってみる。

### 1.データのインポートと確認。

- [こちら](https://signate.jp/competitions/24/data)からデータをダウンロードしてください。
![2022-06-22_14h30_42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/431a297f-0c6f-29e7-675b-4db1cc8eb95b.png)

ダウンロードした後は下記の手順に沿ってjupyter上で取り込み確認できるようにします。
<details><summary>①データインポート</summary><div>
このようにpd.read_csv()の中にパスを指定してあげてください。

```.tsx
import pandas as pd
train  = pd.read_csv("C:/Users/user/Desktop/****/****/****/train(1).csv")
test   = pd.read_csv("C:/Users/user/Desktop/****/****/****/test(2).csv")

```

:::note info
・パスは最初から記入してください。C:~から。
・2022年現在はパスの区切りは｢/｣です。コピーした際、[\]の場合は置き換えてください。
:::

</div></details>

- データの確認。

trainとtestの相違点や情報等を確認します。説明変数(各特徴)に関しては
[こちら](https://signate.jp/competitions/24/data)をご参照ください。precipitationの"--"と"0"の違いがピンと来ませんが､そこまで意識するものでもございません。私見ですが。
<details>
 <summary>①カラム確認｡</summary>
   <div>
 今回は便宜上1つのセルにまとめて記入しておりますが、実際にはそれぞれ個別のセルに記入し、
逐一実行して確認していただくのが良いかと思われます。

```.tsx
train.head(2)           #trainのカラム確認
test.head(2)            #testのカラム確認
train.shape,test.shape  #train/testの何行何列かの確認。
train.dtypes            #dfの型を確認。列情報､整数(int)なのか,objectなのか。

```
こちらが上記コードの情報です。
![2022-06-25_22h23_46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/fe7715c0-0983-ae15-7cac-dd9da12545f5.png)


更にdatetimeに着目してみましょう。train.tail(2)を見てみたところ､testの冒頭と繋がっている事がわかります｡
![2022-06-25_22h31_20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/82714b75-7f80-4051-4ea0-b87845cd6ffa.png)

- データの統合
上記を踏まえ、trainとtestのデータを統合して一括処理できるようにしましょう。
データの特徴把握や､整形をする際にtrainとtestを結合して１つのデータとします。
その方が一度のコードで済みますので2度手間になりません。
勿論1行1行にtrainとtestが判別できる処理は施します。これが下記の[flg]です。

```.tsx
#各々の行がtrainかtestか区別するためにflgを付与します。
train['flg'] = 1   #trainデータはflg 1。
test['flg'] = 0    #testデータはflg 0。

# trainデータとtestデータを結合
datas = pd.concat([train,test], axis=0, sort=False)
```
これでtrainとtestが合体したデータフレーム、dataが完成しました。確認してみましょう。
前半のflg=1 がtrain部分で、後半のflg =0 がtest部分です。
![2022-06-25_22h44_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/81cfa5d9-fa81-4a40-ebba-e9726a61c7ce.png)

   </div>
</details>


<details>
 <summary>②トレンドの確認と対処</summary>
   <div>

以上を鑑みてこの図のようにyの値を予測しましょう。
まずはデータを確認します。
![2022-06-22_21h09_21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/f4970ef7-9015-26eb-1289-e32966a9f2c1.png)
ここで着目して頂きたいポイントとして、前半と後半では傾向が異なるという点です。
私の場合、思い切って前半をまるっとカットしまいました。
カットする際の塩梅ですが、私の場合は2014年の4月一杯までの部分をカットしました。根拠は目視で5月から傾向が変わっていると感じたので。

```.tsx
datas = datas.loc[106:,]
```
![2022-06-23_11h09_30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/ad095188-0e51-f21e-ed0f-496de917e07f.png)
これがその結果です。
   </div>
</details>


- ①のまとめ
```.tsx
import pandas as pd

train  = pd.read_csv("C:/Users/user/Desktop/****/****/****/train (1).csv")
test   = pd.read_csv("C:/Users/user/Desktop/Jupyter/lab/成果物/test (2).csv")

# フラグを立てる⇒trainとtestの区別｡
train['flg'] = 1
test['flg'] = 0
# trainデータとtestデータを結合
datas = pd.concat([train,test], axis=0, sort=False)

datas = datas.loc[106:,]
```











### 2.データ前処理
まずは前処理するデータの確認。最初のステップとして欠損値を確認します。
```.tsx

datas.isnull().sum()   #データの欠損値を確認。

```
いくつか欠損地が見当たりますね。まずはこの欠損値を埋めましょう。
![2022-06-23_11h42_28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/c61cca2e-a8c0-b45b-e299-61905e83a350.png)



<details>
 <summary>①欠損値穴埋め</summary>
   <div>
欠損値を埋めていきます。

- まずはpaydayの要素を確認してみましょう。

```.tsx:
datas["payday"].value_counts()

# 結果
# 1.0    7
# Name: payday, dtype: int64
```
なるほど、1.0が7つあり他はNaNみたいですね。
そもそも<font color="red">1.0は何を表すのでしょうか</font>？確認してみましょう。
再度[カラム説明](https://signate.jp/competitions/24/data)で確認しますと、
`payday | boolean | 給料日フラグ（1:給料日）`
どうやら1.0は給料日のようです。それならば、対になるように給料日ではない日、つまりNanを<font color="red">[0]</font>
で埋めましょう。

- eventはいかがでしょうか。


```.tsx:
datas["event"].value_counts()

#ママの会             5
#キャリアアップ支援セミナー    5
#Name: event, dtype: int64
```
どうやらイベントがある日にそのイベント名が入力されているみたいです。
それならば、NaNはイベント無しと考えて良さそうです。イベント無しなのでそのまま<font color="red">「なし」</font>で埋めましょう。


- remarksも同様に確認します。


```.tsx:
datas["remarks"].value_counts()

#結果
#お楽しみメニュー           13
#料理長のこだわりメニュー        7
#手作りの味               1
#スペシャルメニュー（800円）     1
#近隣に飲食店複合ビルオープン      1
Name: remarks, dtype: int64
```
eventと同じ記載方法のようです。それならば同様に<font color="red">「なし」</font>で補完します。

```.tsx
datas.info()           #データの型やカラム数､欠損値を確認。
```
![2022-06-25_23h10_27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/47a41531-d7a0-54e9-8c8a-f934c6db578e.png)


上記を踏まえたうえで欠損地をこのように補います。
```.tsx
#給料
datas['payday'] = datas['payday'].fillna(0)         #欠損値を0で埋めます。

#イベント
datas['event'] = datas['event'].fillna('なし')      #欠損値をなしで埋めます。

#remarks
datas["remarks"] = datas["remarks"].fillna('なし')  #欠損値をなしで埋めます。

```
kcalの欠損値補完については平均値や中央値を用いましょう。
今回はグラフで可視化した際に、少し頂点が右にズレているので中央値で補完します。

 ```.tsx
# kcalの欠損値補完について
import seaborn as sns
plt.show()
sns.displot(train.kcal, bins =8,kde=False)
sns.set()
train['kcal'] = train[['kcal']].fillna(train['kcal'].median())
```
イメージはこちら。
![2022-06-23_19h12_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/60cf9a83-3346-35c3-ce0f-95c93166bdb1.png)

- それではこれで、すべての欠損値が補完できたはずです。確認してみましょう。

![2022-06-29_20h31_41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/40d6d5e1-cf86-7611-8aec-7c9f1d69ec6b.png)

eventには「なし」というカテゴリが追加され…、他欠損値もtestのy(40)以外埋まってますね。
因みにこれはこの値を予測するのが目標ですので､そのままNaNで問題ありません。むしろ前処理で補完したらいけません。

   </div>
</details>

<details>
 <summary>②特徴量生成</summary>
   <div>

 -  remarksの要素内容を確認してみましょう。

```.tsx
datas["remarks"].value_counts()
<a id="anchor1"></a>
'''
結果
お楽しみメニュー           13
料理長のこだわりメニュー        7
手作りの味               1
スペシャルメニュー（800円）     1
近隣に飲食店複合ビルオープン      1
Name: remarks, dtype: int64
'''
```
remarksの中にも様々な項目がある事がわかりますね。それぞれの項目の違いについてみてみましょう。
まずは必要なmodule import。これにより日本語で表示することができます。
 ```.tsx
!pip install japanize-matplotlib   #日本語モジュールインポート
import japanize_matplotlib         #そのモジュールをセット。
import matplotlib.pyplot as plt    
import seaborn as sns

```

それぞれの項目を箱ひげ図にて確認しましょう。
- 欠損値処理されたもの かつ,yの値のあるものからデータを見てみましょう。
つまり､欠損値処理が済んだtrainデータのことです。

```.tsx
df_train = datas[datas['flg']==1]
```

このように抽出することができます。datasのflgが1の部分を抜き出して df_trainという変数に格納しています。
 
```.tsx
fig, ax = plt.subplots(2,2,figsize=(12,7))
sns.boxplot(x="week",y="y",    data =df_train,ax=ax[0][0])
sns.boxplot(x="weather",y="y", data =df_train,ax=ax[0][1])
sns.boxplot(x="remarks",y="y", data =df_train,ax=ax[1][0])
sns.boxplot(x="event",y="y",   data =df_train,ax=ax[1][1])
plt.tight_layout()
```

意味としては､data =df_trainで適用するdataを指定します。今回はdf_trainです。
df_trainの中から、xとyを指定します。一番下の例ですと、
sns.boxplot(x="event",y="y",   data =df_train,ax=ax[1][1])
x⇒df_trainの"event"。y⇒df_trainの"event"という意味です。

![2022-06-26_19h19_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/f14c0540-a2bf-4f68-c388-e1a64cda5123.png)


分かったこと。
- お楽しみメニューの際に売上数が多い。
なのでお楽しみメニューの時は１、そうでないときは０のカラムを作成しましょう。
 ```.tsx
datas['fun'] = datas['remarks'].apply(lambda x : 1 if x.find("fun") >=0 else 0)
```
lambda式の復習です。
・1行づつremarksの要素がxに格納され、その度に「お楽しみメニュー」かそうでないかを場合分けしています。｢お楽しみメニュー｣であれば１，そうでなければ0と。
その内容を新しいカラム名["fun"]に格納しています。

- eventは特に意味はない。

 -  "name"の中からも何か特徴量を作成できないか検討してみましょう。
 同じように"name"の情報を見てみます。それでは上記通り､箱ひげ図で確認しますと、
 
```.tsx
sns.boxplot(x="name",y="y",   data =df_train)
```

![2022-06-26_19h49_28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/a6efaebf-efd7-1f15-c0df-bd44f77f5773.png)
このように文字が潰れて確認できません。この場合では下記のような箱ひげ図で確認してください。
詳しい説明は省きますが、jupyterとgoogle colabではコピペして頂けると表示されます。

 ```.tsx
import plotly
import plotly.graph_objs as go
# Google Colab. やJupyter Lab.でプロットするためには，以下を実行する．
import plotly.io as pio
pio.renderers.default = "colab"
plotly.__version__
import plotly.offline as offline

layout = go.Layout(
    autosize=False,
    width=950,
    height=500,
    margin=go.layout.Margin(
        l=80,
        r=50,
        b=50,
        t=10,
        pad=4),
    xaxis=dict(
        title='menu',
        titlefont=dict(
            size=14,
        ),
        showticklabels=True,
        tickangle=0,
        tickfont=dict(
            size=12,
        ),
    ),
    yaxis=dict(
        title='販売数(y)',
        titlefont=dict(
            size=14,
        ),
        showticklabels=True,
        tickfont=dict(
            size=10,
        ),

    ))

data = [go.Box( x=df_train['name'], y=df_train['y'] )]

fig = go.Figure(data=data, layout=layout)
offline.iplot(fig, filename='example', show_link=False, 
              config={"displaylogo":False, "modeBarButtonsToRemove":["sendDataToCloud"]})
```

![2022-06-26_19h52_18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/a06eb76c-8501-de62-5564-aa532b87bafe.png)


このようにメニュー名とyの相関関係を見てみると、カレーが人気メニューのように見えます。
カレーの日は売上が伸びるのですね。なのでメニューに｢カレー｣が含まれる場合に１、そうでないときは０のカラムを作成しましょう。

 ```.tsx
datas['curry'] = datas['name'].apply(lambda x : 1 if x.find("カレー") >=0 else 0)
```

カラム["fun"]と基本的構造は同じで、["name"]からの要素を１行づつ、取り出し、｢カレー｣という文字列が含まれているかどうかを確認しています。

   </div>
</details>

<details>

 <summary>③One-Hot Encoding</summary>
   <div>
['week','weather',"precipitation"]はOne-Hot Encodingにしましょう。
項目により特徴量(列)が大量になってしまう可能性がありますが、今回の学習方法TPOTに関してはさほど心配する必要はございません。

```.tsx
one_hot_columns = ['week','weather',"precipitation"]#encordingしたい要素達。

datas = pd.get_dummies(datas, dummy_na=False, columns=one_hot_columns)
```

<font color= “red”>**- One-Hot Encoding**</font>
各変数に対して該当するカテゴリかどうかを0,1で表現したベクトルに変換する処理のことです。
例えば天気が｢晴れ、曇り、雨｣という3つのカテゴリをもつ変数があるとすれば、
その変数に対してOne-hot encodingを行うと晴れ？曇り？雨？という3つの特徴量が作成され、 該当部分に１がつき、その他は0になります。図で可視化するとより理解して頂けると思います。

![2022-06-27_13h32_41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/fc71b5da-b685-8414-3504-1c4b9ee325ca.png)


   </div>
</details>

 -  ②のまとめ

```.tsx
#給料
datas['payday'] = datas['payday'].fillna(0)         

#イベント
datas['event'] = datas['event'].fillna('なし')      

#remarks
datas["remarks"] = datas["remarks"].fillna('なし')  

datas['fun'] = datas['remarks'].apply(lambda x : 1 if x.find("fun") >=0 else 0)

datas['curry'] = datas['name'].apply(lambda x : 1 if x.find("カレー") >=0 else 0)

one_hot_columns = ['week','weather',"precipitation"]#encordingしたい要素達。

datas = pd.get_dummies(datas, dummy_na=False, columns=one_hot_columns)
```
![2022-06-27_15h54_44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2719278/e204ec99-a7d5-397d-cfe0-31b7b38d44af.png)



いかがでしょうか？次回はfeaturetoolsを用いてさらに特徴量を増やしていきたいと思います。
ここまで確認できましたら、[次のステップ](https://qiita.com/The_Boys/private/452b26d08dff5118f262)に行きましょう。