【Python実践データ分析100本ノック】ノック36-40

python実践データ分析100本ノック解説 python データ分析
スポンサーリンク

本記事では学習のアウトプットとして、『Python実践データ分析100本ノック』に書かれている各ノックのコードのうち、難解と思われる部分の意味を解説していきます。 「本に書かれている解説だけでは理解が難しい(;一_一)」と感じた方、この記事を読んで理解の一助となれば幸いです。

Python実戦データ分析100本ノックは、データの集計や分析のためのpandasや、グラフ描画に使用するmatplotlib、機械学習を行うためのscikit-learnなど、データ分析に欠かせない要素を実際に自分で手を動かしながら学ぶことができる本です。

今回は第8回として、ノック36-40をやっていきます。

 

追記現在第二版が2022年6月に出版されています

本記事は第1版の内容の解説です

第二版の解説記事もいずれ書かせていただこうと思っていますので今しばしお待ちください! 

スポンサーリンク

第4章 顧客の行動を予測する10本ノック

ノック36:翌月の利用回数予測を行うためのデータ準備をしよう

ノック36からは教師あり学習の内の1つである回帰を行います。教師あり学習というのは、教師(正解となるデータ)を用いて予測を行います回帰では、過去のデータを教師として、未来の予測を行うことができます

こちらの記事で線形回帰について解説しています。

おおまかな流れとしては、以下の6つのステップになります。

  1. 教師となる過去のデータを整える
  2. 特徴となるデータを作成する
  3. 予測モデルを作成する
  4. データセットを学習用データと評価用データに分割する
  5. 評価用データでモデルの精度を検証する
  6. モデルを用いて未知のデータの予測を行う。

#ノック8と同様にusedateを日時を表す文字列datetimeに変換し、strftime関数で年月の形式にした後、年月という変数に格納しています。
uselog["usedate"]=pd.to_datetime(uselog["usedate"])
uselog["年月"]=uselog["usedate"].dt.strftime("%Y%m")

#groupby()で"年月"そしてcustomer_idごとに集計し、uselog_monthという変数に格納します。
uselog_months=uselog.groupby(["年月","customer_id"],as_index=False).count()

#ノック25と同様にrename()でlog_idをcountで書き替えます。
uselog_months.rename(columns={"log_id":"count"},inplace=True)

#delでusedateを削除します。
del uselog_months["usedate"]
uselog_months.head()
#uselog_months(2018年4月から2019年3月のデータ)の年月列の固有値のみをリスト化してyear_months変数に入れます。
year_months=list(uselog_months["年月"].unique())

#DataFrame(行と列の二次元構造を持つデータ)を作成します。
predict_data=pd.DataFrame()

#range()でyear_monthsの6番目からyear_monthsの最後の要素まで、
#つまり、2018年10月から2019年3月までを指定します。
for i in range(6,len(year_months)):
 
  #uselog_monthsの2018年10月から2019年3月までの利用データをtmpに格納します。
  tmp=uselog_months.loc[uselog_months["年月"]==year_months[i]]

  #ノック25と同様にrename()でcountをcount_predで書き替えます。
  tmp.rename(columns={"count":"count_pred"},inplace=True) 
  for j in range(1,7):
  
    #iで指定した年月から、ループカウンタ変数jを使って6か月前まで遡ってuselog_monthsの6か月前までの利用データをtmp_beforeに格納します。
    tmp_before=uselog_months.loc[uselog_months["年月"]==year_months[i-j]]
 
    #delでtmp_beforeの年月列を削除します。
    del tmp_before["年月"]
  
    #format関数を使うと、()で指定した変数(今回はj-1)を文字列{}に埋め込むことができます。
    #つまり、tmp_beforeのcount列の名前をcount_0からcount_6まで格納します。
    tmp_before.rename(columns={"count":"count_{}".format(j-1)},inplace=True)
 
    #customer_idを共通キーとして、tmpとtmp_beforeを横方向に結合します。
    tmp=pd.merge(tmp,tmp_before,on="customer_id",how="left")
    tmp_before.head()
    
  #tmpをpredict_dataデータフレームに結合します。
  predict_data=pd.concat([predict_data,tmp],ignore_index=True)
predict_data.head()

#機械学習では欠損値を処理する必要があります。今回は、dropna()で欠損値を含むデータを除去します。
predict_data=predict_data.dropna()

#reset_index関数でインデックスをリセットします。
#今回は欠損値を含んでいた列を削除したいので、drop=Trueとしています。
predict_data=predict_data.reset_index(drop=True)
predict_data.head()

教師あり学習の最初のステップとして、教師となるデータを整理しました。

ノック37:特徴となる変数を付与しよう

#customer_idを共通キーとして、predict_dataにcustomerテーブルのstart_dateを結合します。
predict_data=pd.merge(predict_data,customer[["customer_id","start_date"]],on="customer_id",how="left")
predict_data.head()
#年月を文字列datetimeに変換し、strftime関数で年月の形式にした後、now_dateという変数に格納しています。
predict_data["now_date"]=pd.to_datetime(predict_data["年月"],format="%Y%m")

#start_dateも文字列datetime型に変換します。
predict_data["start_date"]=pd.to_datetime(predict_data["start_date"])

#ノック28から利用しているrelativedeltaメソッドを用いて月の加算・減算を行っていきます。
from dateutil.relativedelta import relativedelta

#会員期間を算出するため、算出結果を格納するためのperiodという列を作り、初期化しておきます。
predict_data["period"]=None

#predict_dataの行数の分だけ繰り返し処理を行います。
for i in range(len(predict_data)):

  #now_dateとstart_dateを減算して、deltaという変数に格納します。
  delta=relativedelta(predict_data["now_date"][i],predict_data["start_date"][i])

  #単位を月の単位に直しながら会員期間を計算し、periodに格納します。
  predict_data["period"][i]=delta.years*12+delta.months
predict_data.head()

回帰に用いる会員期間というデータを算出し、predict_dataに格納できました。

ノック38:来月の利用回数予測モデルを作成しよう

いよいよ予測モデルを作成していきます。今回は線形回帰モデルのLinearRegressionという、シンプルな式で予測を行います。

回帰では、説明変数目的変数という2つの変数が存在します。「結果は原因によって決まる」という認識のもと、y=〇x+△という式で表すとき、予測したいyが目的変数、予測に使うxが説明変数となります


教師あり学習では、学習用データと評価用データにデータを分割する必要があります。これは、評価用データでモデルの精度の評価を行うためでもありますが、過学習を防ぐために必要です。過学習とは、学習データに適合しすぎることで、ほかのデータを用いたときに著しく精度が低下する現象のことです。

#2018年4月以降に入会した顧客のみを対象として学習させます。
predict_data=predict_data.loc[predict_data["start_date"]>=pd.to_datetime("20180401")]

#sklearnで線形回帰を行うためのモデルを読み込みます。
from sklearn import linear_model
import sklearn.model_selection

#LinearRegression クラスを指定します。
model=linear_model.LinearRegression()

#説明変数にpredict_dataのcount_0からperiodまでを指定します。
X=predict_data[["count_0","count_1","count_2","count_3","count_4","count_5","period"]]

#目的変数にはcount_predを指定します。
#つまり、過去の利用回数データを説明変数に、未来の利用回数を目的変数に指定しています。
y=predict_data["count_pred"]

#train_test_split関数は、分割の割合を指定しながら簡単に学習用データ(train)と評価用データ(test)の分割が可能になります。
#割合を指定しない場合、学習長データ75%、評価用データ25%で分割します。
X_train,X_test,y_train,y_test=sklearn.model_selection.train_test_split(X,y)

#モデルに学習させます。
model.fit(X_train,y_train)

#score()関数で決定係数を出力します。これで予測値xと正解値yの相関を測ることができます。
#学習用データを用いて決定係数を測定します。
print(model.score(X_train,y_train))

#評価用データを用いて決定係数を測定し、制度検証を行います。
print(model.score(X_test,y_test))

決定係数は0~1の範囲内で値をとり、1に近ければ近いほどモデルが与えられたデータに当てはまっているという解釈になります。

学習用データと評価用データそれぞれのscore結果からは60%程度の精度を示しています。学習用データが評価用データと比較して著しく高い場合は過学習と判断しますが、今回は同程度の結果だったということで、まずまずの精度であると判断できます。

学習用データと評価用データを分割した後、予測モデルを作成し、モデルの精度検証を行いました。

ノック39:モデルに寄与している変数を確認しよう

#"feature_name","coefficient"という列名で説明変数ごとの寄与変数の係数を確認してみます。
#coef_では、回帰変数を指定できます。
coef=pd.DataFrame({"feature_name":X.columns,"coefficient":model.coef_})
coef

寄与している変数とその係数を確認できます。寄与が大きければ大きいほど、説明変数として影響度が大きいという意味です。結果からは、count_0が最大なので、過去に遡っていくほど(count_xのxが大きくなるほど)寄与が小さくなると解釈でき、これは、直近の利用回数が翌月の利用回数に最も影響しているという解釈になります。

ノック40:来月の利用回数を予測しよう

ノック39まででモデルを作成し、精度が十分であることを確認できたので、2人の顧客の利用回数をモデルに入れて予測してみます。

#1人目は6か月前から3回,4回,4回,6回,8回,7回,8回利用している顧客を想定しています。
x1=[3,4,4,6,8,7,8]

#2人目は6か月前から2回,2回,3回,3回,4回,6回,8回利用している顧客を想定しています。
x2=[2,2,3,3,4,6,8]

#リストに格納して新しいデータを作成しています。
x_pred=[x1,x2]

#predict() メソッドは、学習が終わったモデルに、新たな入力値を与えて予測値を計算させるときに使用します。
#モデルを使って、x_predデータの2人の顧客の利用回数を予測します。
model.predict(x_pred)

2人の顧客の利用回数の予測結果が戻り値として表示され、予測結果が分かりました。

ここまでで、ノック36-40は完了です。お疲れ様でした。 今回は、教師あり学習の1つである回帰を行いました。過去のデータを用いて将来の予測を行うことができましたね。次回は第5章(決定木)に入ります。引き続きよろしくお願いいたします(^^)/