#はじめに
本稿は Amazon SageMakerでPythonデータサイエンス入門 の連載記事になります。
その1. Amazon SageMaker の環境構築、事前準備、基礎分析と可視化について
その2. 線形回帰を用いたお弁当の売り上げ予測 ←【本稿】
その3. 決定木を用いた銀行の顧客ターゲティング
以下の Udemy のコースをベースに記事を作成しています。
【ゼロから始めるデータ分析】 ビジネスケースで学ぶPythonデータサイエンス入門
本稿の内容
- 線形回帰(単回帰、重回帰分析)を用いたお弁当の売り上げ予測
- 特徴量(説明変数)の作成
予測とは
データを基にある値がどのような値になるかを推定することです。
予測を行うには予測対象と予測を行う上で有用なデータが必要となります。
分析業務では予測対象を目的変数、有用なデータを説明変数と言う場合が多いです。
また本稿では線形回帰を利用したお弁当の売上予測を行います。
線形回帰
回帰とはデータから適する関数を抽出する方法のことです。
線形回帰は回帰分析の手法の中で1次関数を利用して予測モデルを導出する手法です。
線形回帰を用いたお弁当の売り上げ予測
今回は skit-learn を用いて、線形回帰(単回帰分析、重回帰分析)を行います。
単回帰分析編
まずは、単回帰分析(説明変数が1つ)で、お弁当の売り上げを予測します。
- その1で作成した case1 フォルダーに新しい notebook を conda_python3 で作成してください。
モジュールのインポートと設定
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
%matplotlib inline
from sklearn.linear_model import LinearRegression as LR
データ(csvファイル)の読み込み
train=pd.read_csv("train.csv")
test=pd.read_csv("test.csv")
sample=pd.read_csv("sample.csv",header=None)
- trainの先頭行を確認しておきましょう。
train.head()
説明変数と目的変数の用意
- 説明変数は train から temperature を選択し、変数 trainX に代入します。
- 目的変数は train から y を選択し、変数 y に代入します。
trainX = train["temperature"]
y = train["y"]
- test からも同じ説明変数の temperature を選択し、変数名を testX に代入します。
testX=test["temperature"]
- 説明変数のデータの形を整えます。
※単回帰の場合のみ必要な操作となります。
trainX = trainX.values.reshape(-1,1)
testX = testX.values.reshape(-1,1)
回帰モデルの作成
- 回帰モデルの箱を作る。
モデルを作成するために用いる手法を変数(モデルの箱)に覚えさせます。
今回は model1 という変数にLinearRegression(線形回帰)という手法を覚えさせます。
model1 = LR()
- 単回帰モデルの作成
()の中に、説明変数、目的変数の順番に書くことで、 model1 に格納されている手法でモデルを作成します。
model1.fit(trainX,y)
#実行結果
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
- 作ったモデルの傾き、切片を確認します。
#傾き
model1.coef_
#切片
model1.intercept_
実行結果
array([-2.5023821])
134.79948383749922
お弁当の売り上げ予測を行う
- 予測を実行。
予測結果は変数 pred に格納します。
()内にはテスト用の説明変数 testX を入力すると、作成したモデル(model1)で y を予測します。
pred = model1.predict(testX)
- sample の中身を確認し、予測結果を代入します。
sample[0] には日付( test の日付と同じ)が入っているので、 sample[1] に予測結果を代入します。
sample.head()
sample[1] = pred
- sample を csv ファイルで書き出します。
オプションには、"submit1.csv"(ファイル名)、index=None、header=Noneを書きます。
sample.to_csv("submit1.csv",index=None,header=None)
SIGNATE に結果を投稿し、確認
保存したcsvファイルをSIGNATEに投稿して、精度を確認します。
- こちらからSIGNATEのサイトを開いて、Competitionsを選択。
- 練習問題を選択。
- 下にスクロールしてお弁当の需要予測を選択。
- 投稿を選択。
- ファイルは先ほど作成した submit1.csv を選択。
メモには使用した説明変数は temperature と書いておきます。
- これで投稿完了です。結果が出るまで2、3分あるので少し待ちましょう。
- 結果が出ました。
ここでは評価の数が小さいほど精度がいいということになります。
このモデルの精度は42ほどなので、まだまだというところです。
別の説明変数で予測をする
先ほどは説明変数をtemperatureでモデル作成、予測を行いましたが他のカラムを説明変数にしたら結果が変わるだろうと考えます。
ここでは試しに説明変数をkcalにしてモデル作成、予測を行います。
新たな説明変数を取り出す。
- 説明変数として kcal を選択し、それぞれ trainX 、 testX に代入します。
trainX = train["kcal"]
testX = test["kcal"]
欠損値の補間
- 欠損値の有無を確認します。
train.isnull().any()
実行結果
True
- 補間に用いる値を計算。
今回は、補間に kcal の平均値を用います。
avg = trainX.mean()
avg
実行結果
404.4096385542169
- trainX と testX の欠損値を補間します。
trainX = trainX.fillna(avg)
testX = testX.fillna(avg)
- trainXの中身をする。
trainX
実行結果
0 404.409639
1 404.409639
2 404.409639
3 404.409639
4 404.409639
5 404.409639
6 404.409639
7 404.409639
8 404.409639
9 404.409639
10 404.409639
11 404.409639
12 404.409639
13 404.409639
14 404.409639
15 404.409639
16 404.409639
17 404.409639
18 404.409639
19 404.409639
20 404.409639
21 404.409639
22 404.409639
23 404.409639
24 404.409639
25 404.409639
26 404.409639
27 404.409639
28 404.000000
29 462.000000
...
177 396.000000
178 385.000000
179 423.000000
180 405.000000
181 404.409639
182 412.000000
183 400.000000
184 410.000000
185 396.000000
186 398.000000
187 380.000000
188 440.000000
189 408.000000
190 405.000000
191 380.000000
192 385.000000
193 460.000000
194 450.000000
195 385.000000
196 404.409639
197 438.000000
198 430.000000
199 395.000000
200 400.000000
201 395.000000
202 408.000000
203 394.000000
204 404.409639
205 404.000000
206 398.000000
Name: kcal, Length: 207, dtype: float64
モデルの作成と予測
- ここから先の手順は先ほどの手順とほとんど同じなので、コードのみ記述します。
model2 = LR() #モデル名をmodel2とする
model2.fit(trainX,y)
pred2 = model2.predict(testX) #結果をpred2に代入
sample[1] = pred2
sample.to_csv("submit2.csv",index=None,header=None) #ファイル名はsubmit2.csvで出力
SIGNATEに結果を投稿し、確認
- 先ほどと同じ手順で sample2.csv を投稿する。
メモには説明変数は kcal と書いておきます。
- 結果が出たら結果を確認。
sample1.csv の結果と比べると若干精度が上がっていることがわかります。
このように説明変数を変えるだけで、同じ手法でも精度は変わってきます。
しかし、精度は39程度とまだまだです。
重回帰分析編
もっと良い精度のモデルを作成するために説明変数を増やして、重回帰分析をします。
データは単回帰分析で使ったデータと同じデータを使用します。
重回帰分析は別の notebook で行います。
同じ notebook で作業を行う方は、モジュールのインポートと設定、データの読み込みを飛ばしてください。
モジュールのインポートと設定
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
%matplotlib inline
from sklearn.linear_model import LinearRegression as LR
データ(csvファイル)の読み込み。
train=pd.read_csv("train.csv")
test=pd.read_csv("test.csv")
sample=pd.read_csv("sample.csv",header=None)
- trainのweek列の各値がそれぞれいくつあるか確認します。
train["week"].value_counts()
#実行結果
木 43
水 43
火 41
金 41
月 39
Name: week, dtype: int64
文字列データの加工(ダミ-変数化)
- まず、pandasでダミー変数を作成する、get_dummies関数について説明します。
- get_dummies関数
()内にカテゴリー変数(文字列)が含まれるデータを入力することで、カテゴリー変数をダミー変数(数値)に置き換えます。
pd.get_dummies(変数)
- 今回、説明変数は train から week と temperature を選択します。
しかし、 week には文字列(カテゴリー変数)が含まれるため、その文字列をダミー変数(数値)に置き換えます。
行末に.head()を付け加えて結果の確認もします。
pd.get_dummies(train["week"]).head()
説明変数と目的変数の取り出し
- 説明変数として、 train から week と temperature を抜きだし、ダミー変数化したものを変数 trainX に代入します。
代入後、 trainX の中身を確認しておきます。
trainX = pd.get_dummies(train[["week","temperature"]])
trainX.head()
目的変数として、 train から y を取り出し、変数yに代入。
y = train["y"]
test からも同じ説明変数の week と temperature を選択し、同様の手順で変数名 testX に代入し、確認します。
testX = pd.get_dummies(test[["week","temperature"]])
testX.head()
モデルの作成と予測
- ここから先の手順は単回帰分析の手順とほとんど同じなので、コードのみ記述します。
model1 = LR() #モデル名はmodel1とする
model1.fit(trainX,y)
pred = model1.predict(testX) #結果をpredに代入
sample[1] = pred
sample.to_csv("submit3.csv",index=None,header=None) #ファイル名はsubmit3.csvで出力
SIGNATE に結果を投稿し、確認
- 先ほどと同じ手順で sample3.csv を投稿します。
メモには説明変数は week と temperature と書いておきます。
- 結果が出たら結果を確認。
精度は kcal を説明変数にした時より、下がってしましました。
精度を上げるには説明変数をただ増やせばいいというわけではありません。
精度を上げるために、次は追加する説明変数をデータの特徴から考えて作ってみましょう。
特徴量(説明変数)を作って解析してみる。
- お弁当の売り上げの特徴を確認します。
y の折れ線グラフを描く。
見やすいように figsize=(12,5) としておきます。
x 軸が時間、 y 軸が売り上げ。
train["y"].plot(figsize=(12,5))
グラフから時間が進むにつれて、売り上げが少なくなっていることがわかります。
先ほどの予測では時間に関する特徴量が入っていなかったので、時間に関する特徴量を作成します。
特徴量の作成
- train の datetime から年と月のデータを取り出し、 train の新たなカラムとして追加します。
その後 train の中身を確認する。
train の datetime は"年-月-日"という文字列で構成されています。
年、月のカラム名はそれぞれ year 、 month とする。
年と月を取り出すために apply 関数と split 関数、 lambda 式を使用します。 - apply 関数
指定された列の各値に関数を適用させる関数。 - split 関数
文字列を指定された区切り文字で区切ってリスト化する関数。 - lambda 式
無名関数
x を引数、 y を戻り値とした無名の関数を作れます。
関数の引数として関数を使うときに用いられます。
データ.apply(関数)
文字列.split(区切り文字)
lambda x = y
- train の datetime 列に対して apply 関数で各値に関数を適用します。
適用する関数を無名関数として定義します。
その無名関数の中では、入力として入ってきた文字列を"-"で区切ってリストにするために split 関数を使用。
split 関数で文字列を区切って作成されたリストの[0]にある年のデータを train の新しいカラム year として追加します。
month についても同様です。
train["year"] = train["datetime"].apply(lambda x :x.split("-")[0])
train["month"] = train["datetime"].apply(lambda x :x.split("-")[1])
train.head()
- test の説明変数にも同様の手順を行います。
test["year"] = test["datetime"].apply(lambda x : x.split("-")[0])
test["month"] = test["datetime"].apply(lambda x : x.split("-")[1])
- train のデータ型を確認します。
train.info()
実行結果
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 207 entries, 0 to 206
Data columns (total 14 columns):
datetime 207 non-null object
y 207 non-null int64
week 207 non-null object
soldout 207 non-null int64
name 207 non-null object
kcal 166 non-null float64
remarks 21 non-null object
event 14 non-null object
payday 10 non-null float64
weather 207 non-null object
precipitation 207 non-null object
temperature 207 non-null float64
year 207 non-null object
month 207 non-null object
dtypes: float64(3), int64(2), object(9)
memory usage: 22.7+ KB
- train , test のデータの型を整数( int )に変換します。
train.info() で見た通り、先ほど追加した year と month は数値ではなく、文字列として保存されています。
データの型を変換したい場合は、astype 関数を使います。
オプション(かっこの中)には変換先のデータの型を入れます。
train["year"] = train["year"].astype(np.int)
train["month"] = train["month"].astype(np.int)
- データの型が変更されたか確認します。
train.info()
実行結果
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 207 entries, 0 to 206
Data columns (total 14 columns):
datetime 207 non-null object
y 207 non-null int64
week 207 non-null object
soldout 207 non-null int64
name 207 non-null object
kcal 166 non-null float64
remarks 21 non-null object
event 14 non-null object
payday 10 non-null float64
weather 207 non-null object
precipitation 207 non-null object
temperature 207 non-null float64
year 207 non-null int64
month 207 non-null int64
dtypes: float64(3), int64(4), object(7)
memory usage: 22.7+ KB
- testについても同様にデータ型を変更します。
test["year"] = test["year"].astype(np.int)
test["month"] = test["month"].astype(np.int)
説明変数と目的変数の用意
- train と test から説明変数の year 、 month を取り出し、新たなカラムとして追加します。
trainX = train[["year","month"]]
testX = test[["year","month"]]
- trainからyを取り出します。
y = train["y"]
モデルの作成と予測
- ここから先の手順は先ほどの手順とほとんど同じなので、コードのみ記述します。
model2 = LR() #モデル名はmodel2とする
model2.fit(trainX,y)
pred = model2.predict(testX) #結果をpredに代入
sample[1] = pred
sample.to_csv("submit4.csv",index=None,header=None) #ファイル名はsubmit4.csvで出力
SIGNATE に結果を投稿し、確認
- 先ほどと同じ手順で sample4.csv を投稿する。
メモには説明変数は year と month と書いておきます。
- 結果が出たら結果を確認。
今までより精度がかなり上がりました。
さらに精度を上げるために、新たな説明変数を追加してみましょう。
追加する特徴量の検討
先ほどは折れ線グラフから特徴を確認し、特徴量の作成を行いました。
次は、別の方法で特徴を確認し、特徴量を追加します。
実際のyの値と予測したyの差から特徴を確認する。
- trainXに対して予測を行い、trainXの予測値と、trainXの実際の値を比べることができます。
まず、trainXから先ほど作ったモデルを利用して予測値を求めます。
pred = model2.predict(trainX)
- 予測値predを新たなカラムとして追加します。
train["pred"] = pred
- 予測値と実際の値を引き算した値を新たなカラムとして追加します。
train["res"] = train["y"]-train["pred"]
- 先ほど追加したカラムでソートして中身を確認します。
大きな差があるデータからから共通する要素が見つけられれば、その要素を加えることで更に精度が良いモデルを作れる可能性があります。
train.sort_values(by="res")
- 実行結果を確認する。
結果の下のほうを見ると、誤差が大きい行にはお楽しみメニューであるのがわかります。
新たな特徴量を追加する準備
- 特徴量としてお楽しみメニューを使いするにはお楽しみメニューを数値化する必要があります。
具体的にはお楽しみメニューなら1、そうでないなら0という特徴量を作成したいと考えます。
特徴量を作成するために、関数を作成します。
関数の作成には def文 と if 文を使用します。 - def文
自分で関数を作成できます。 - if文
条件の結果によって処理を分けられます。
def 関数名(引数):
関数の処理
if 条件式1:
条件式1が真の時に実行する処理
elif 条件式2:
条件式1が偽で条件式2が真の時に実行する処理
elif 条件式3:
条件式1及び条件式2が偽で条件式3が真の時に実行する処理
else:
全ての条件式が偽の時に実行する処理
- お楽しみメニューなら1、そうでないなら0という関数を作成します。
def jisaku1(x):
if x=="お楽しみメニュー":
return 1
else :
return 0
- 作成した jisaku1 関数と apply 関数を使用して、新たなカラム fun を作成します。
train["fun"] = train["remarks"].apply(lambda x : jisaku1(x))
test["fun"] = train["remarks"].apply(lambda x : jisaku1(x))
- trainの中身を確認します。
train.sort_values(by="fun")
- 説明変数の取り出し。
今回は、year 、 month 、 fun 、 temperatureを説明変数として取り出します。
trainX = train[["year","month","fun","temperature"]]
testX = test[["year","month","fun","temperature"]]
モデルの作成と予測
- ここから先の手順は先ほどの手順とほとんど同じなので、コードのみ記述します。
model3 = LR() #モデル名はmodel3とする
model3.fit(trainX,y)
pred = model3.predict(testX) #結果をpredに代入
sample[1] = pred
sample.to_csv("submit5.csv",index=None,header=None) #ファイル名はsubmit5.csvで出力
SIGNATE に結果を投稿し、確認
- 先ほどと同じ手順で sample5.csv を投稿する。
メモには、説明変数はyear、month、fun、temperatureと書いておきます。
- 結果が出たら結果を確認。
今までで一番良い精度の予測ができました。
このように、関係する特徴量をよく考えて作ったり、追加したりすることで精度を上げることができます。
#おわりに
今回は実際にpythonでプログラミングをしながら、お弁当の売り上げ予測を線形回帰で行いました。
次のその3では決定木を用いた銀行の顧客ターゲティングについてまとめていきますので、よろしくお願いします。