TPTブログ

テックポート株式会社のブログです。 技術情報や製品・サービス情報、 また未経験社員がデータサイエンティストを 目指す奮闘記など、更新していきます。

お弁当の需要予測

f:id:tbtech:20190628115347p:plain
前回SIGNATEについて書きましたが、今回は実際にコードを書いて
投稿するまでをやっていきます!

Kaggleの方では分類問題に挑戦しましたが,こちらでは回帰の問題に挑戦します.
使用したデータセットはLearningの「お弁当の需要予測」です。
SIGNATEのLearningで最も参加者が多い,初心者向けのデータセットです.

データセット概要

signate.jp
とある会社のカフェフロアで販売されるお弁当の販売数を予測するものです.
お弁当の売上向上・廃棄削減のために,正確な需要予測をする必要があります.

評価方法

必ず確認したい項目として,評価方法があります。
作成したモデルを評価するためには評価関数を使用します.
今回は「RMSE(Root Mean Squared Error 平均二乗平方根誤差)」で評価します.

データ概要

データが取られた期間は.次の通りです.
学習用データ期間:2013年11月18日 ~ 2014年 9月30日
検証用データ期間:2014年10月 1日 ~ 2014年11月30日

提供されたデータは学習用と検証用が予め用意されていました.

データの内容は以下の通りでした.
f:id:tbtech:20190628140552p:plain
目的変数は「y」の販売数です.
「kcal」の説明欄に「欠損あり」と教えてくれています.

コード作成

データを開いて確認していきます.
f:id:tbtech:20190628142309p:plain
頭から5番目まで表示させました.
すると「kcal」以外にも「remerk」「event」「payday」にも
[NaN]があることがわかりました.
また「precipitation」は欠損値ではありませんが,「--」という記号が使われています.

データの説明には欠損ありと書かれているものと、そうでないものがありましたが
必ず全ての項目の欠損値を確認する必要がありますね.

欠損値を確認してみます.
各データの個数と、データ型を表示します。

train.info()

f:id:tbtech:20190628190358p:plain

前処理

datetimeの変換

まずは「datetime」を変換します。
[1][2]object型をdatetime型に変換します。
[3]「datetime」から新たに月のデータを抽出し、「month」に入れます。
[4]「datetime」は不要になったので削除します。

import datetime as dt
train['datetime'] = pd.to_datetime(train['datetime'])
train['month'] = train['datetime'].dt.month
train = train.drop(['datetime'],axis=1)
欠損値補完

「kcal」には41個の欠損値が含まれるので補完をします。
「kcal」がどのようなデータ分布になっているかをヒストグラムで確認します。
f:id:tbtech:20190628192918p:plain
きれいな山の形をしていますが、少し頂点が右にズレています。
なので今回は中央値で欠損値を補完します。

df['kcal'] = df['kcal'].fillna(df['kcal'].median(),)


「remarks」「event」「payday」には欠損値が含まれますが、
確認してみるとデータが欠けた訳ではありませんでした。
「remarks」は特記事項が無い日は空欄になりますし、「event」はイベントが無い日は空欄です。
「payday」は給料日には1が記され、その他の日は空欄です。
なので、何も無い日は空欄になっているので、そこを0で補完します。
さらに、「remarks」「event」が空欄でない日を1に置き換えます。

「remarks」の部分を「event」「payday」に変えたものをそのまま使います。

train = train.fillna({'remarks' : 0})
train.loc[train['remarks'] != 0, 'remarks'] = 1

次に「precipitation」には欠損値ではなく「--」が含まれています。
[1]「--」を0に置き換えます。
[2]データ型がobjectなのでfloat型に変換します。

train.loc[train['precipitation'] == '--', 'precipitation'] = 0
train['precipitation'] = pd.to_numeric(train['precipitation'])
カテゴリカル変換

カテゴリカル変数として「name」「week」「weather」があります。

まず「name」の中身を確認します。

train['name'].unique()

f:id:tbtech:20190628194348p:plain
こーんなに沢山出てきました!個数は156個!
これはOne-Hot表現にすることはできないので、「name」は削除します。

同様に「week」と「weather」の中身を確認すると、
「week」は「月,火,水,木,金」
「weather」は「快晴, 曇, 晴れ, 薄曇, 雨, 雪, 雷電」でした。
こちらはOne-Hot表現に変換します。
以下のコードだとデータ内の全てのカテゴリー変数をまとめて変換してくれます。

train = pd.get_dummies(train)

一先ず簡単なモデルで検証しますので、前処理はここで終わりです!

モデル定義

今回は回帰の問題なので、一番初歩的な線形回帰でモデルを作成します。

まずはxとtを定義し、訓練データとテストデータに分割します。
[1]「y」以外の変数をxにします。
[2]「y」をtにします。
[3]テストデータの割合が0.3になるように分割します。

x = train.drop('y',axis=1).values
t = train['y'].values
from sklearn import model_selection
x_train, x_test, t_train, t_test = model_selection.train_test_split(x, t, test_size=0.3, random_state=0)

モデルはLinearRegressionにします。.fitで学習を実行します。

from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(x_train, t_train)

結果

学習した結果を見ていきます。
まずは簡単に決定係数を見てみます。
.scoreとして、訓練データとテストデータのスコアを確認します。

model.score(x_train, t_train)
model.score(x_test, t_test)

結果は、
訓練:0.6645
テスト:0.5795
でした!んー悪いですね🤣

続いて評価関数RMSE(Root Mean Squared Error 平均二乗平方根誤差)で確認します。
評価関数では学習したモデルを使って予測した値と、実際の正解データ(実測値)を比較して評価します。
なので、まずはモデルを使って予測を行います。

t_pred_train = model.predict(x_train)
t_pred_test = model.predict(x_test)

評価関数に予測値と実測値を入れて評価値を算出します。
[1]評価関数MSEをインポートします。ScikitLearnにはRMSEのライブラリは無いようです。
[2][3]MSEのルートをnp.sqrtで取ります。そうするとRMSEの評価値が出ます。

from sklearn.metrics import mean_squared_error
rmse_train = np.sqrt(mean_squared_error(t_train, t_pred_train))
rmse_test = np.sqrt(mean_squared_error(t_test, t_pred_test))

結果は...
訓練:18.7479
テスト:21.8320
でした!悪いいいいいい🤪

人生そんな甘くないね。

応募ファイル作成

訓練結果はさて置き、とりあえずモデル作成できたので投稿してみます!
まずは与えられたtestデータを取り込み、trainデータと同様の前処理を行います。
前処理が終わったデータの列数を確認してみます。

test.shape

結果は(40, 18)でした。trainデータは20カラムあるので2列足りません。
データ内を確認してみると、weatherの雪と雷電が無かったため
One-Hot表現に変換した際にその分の列が減ってしまっていました。
なので、trainデータと同じになるように、雪と雷電の列を追加します。
雪と雷電は1日も無かったので、全て0で埋めます。

df['weather_雪'] = 0
df['weather_雷電'] = 0 

trainデータとtestデータの形を揃える事ができたので、
モデルを使ってtestデータから予測値を算出します。

pred = model.predict(test)

predの中に予測値を入れたので、これを応募ファイルの形に整えます。

コンペのページには応募ファイルの形式が書かれており、
検証用データの1列目にある日付をインデックスとし、2列目に予測値を記述し、
ヘッダー無しのcsvファイルと指定されています。

応募用に新しくDataFrameを作ります。
データにpredを入れ、indexには日付である'datetime'を指定します。

ans =pd.DataFrame(pred, index=df['datetime'])

中身を確認して問題がなければ、これをscvにして保存します。

()の中にファイル名を入れますが、その前にファイルパスを入れると
指定した場所に保存することができます。例:'data/dst/ans.csv'
また、header=Falseとするとヘッダー無しになります。

ans.to_csv('ans.csv', header=False)

投稿結果

結果は投稿してから数分後にメールが届き
コンペの「投稿済みファイル」にも結果が反映されます。
f:id:tbtech:20190701002054p:plain
結果は,,,
61.17656でした。
f:id:tbtech:20190701002702p:plain
ランキングはこのように、順位と評価値と投稿した回数も記されます。

ぐぅぅ( ノД`)
予想はしていましたが、ここまで悪いとは,,,
やっぱり、特徴量選択やパラメーラチューニングは大切ですね!

以上でSIGNATEに投稿するまでの流れをご説明しました!
モデルは全然いいものでは無いので、参考にはしないでくださいね(´ω`)
ここからもっとモデルを良くできるよう頑張ります!!