本記事では学習のアウトプットとして、『Python実践データ分析100本ノック』に書かれている各ノックのコードのうち、難解と思われる部分の意味を解説していきます。 「本に書かれている解説だけでは理解が難しい(;一_一)」と感じた方、この記事を読んで理解の一助となれば幸いです。
Python実戦データ分析100本ノックは、データの集計や分析のためのpandasやグラフ描画に使用するmatplotlib、機械学習を行うためのscikit-learnなど、データ分析に欠かせない要素を実際に自分で手を動かしながら学ぶことができる本です。
私自身はpython初心者で、pythonを触ったことがありませんでしたが、自ら手を動かしてpythonを学習したいと思いこちらの本で学習しました。よければ手に取っていただきたいです。
今回は第10回として、ノック46-50 をやっていきます。
追記:現在第二版が2022年6月に出版されています。
本記事は第1版の内容の解説です。
第二版の解説記事もいずれ書かせていただこうと思っていますので今しばしお待ちください!
第5章 顧客の退会を予測する10本ノック
ノック46:文字列型の変数を処理できるように整形しよう
今回行う決定木は分離という教師あり学習の1つです。
分離というだけあって、目的変数には離散値を用います。今回は、退会フラグであるis_deleted列を目的変数に用います。説明変数には、campaign_name、class_name、gender、count_1、routine_flg、periodを用います。
#まずは、説明変数と目的変数のみを抜き出しています。
target_col=["campaign_name","class_name","gender","count_1","routine_flg","period","is_deleted"]
predict_data=predict_data[target_col]
predict_data.head()
機械学習にあたり、文字列でカテゴリー分けされているデータ(カテゴリカル変数)をダミー変数化する必要があります。
#get_dummies()メソッドは、文字列でカテゴリーデータ(例えば性別)を、男を0, 女を1のように変換できます。
#機械学習の前処理として使用されることが多いです。
#predict_dataのデータセットのうち、文字列でカテゴリー分けされているデータ(campagin_name,class_name,genderなど)をダミー変数化します。
predict_data=pd.get_dummies(predict_data)
predict_data.head()
#余分な列を削除します。
del predict_data["campaign_name_通常"]
del predict_data["class_name_ナイト"]
del predict_data["gender_M"]
predict_data.head()
機械学習の前処理として、カテゴリー関連のデータをダミー変数化することができました。
ノック47:決定木を用いて退会予測モデルを作成してみよう
#DecisionTreeクラスのインポートから始めます。
from sklearn.tree import DecisionTreeClassifier
import sklearn.model_selection
#loc関数でis_deleted列の値(0または1)ごとに退会顧客と継続顧客のデータをそれぞれexit,conti変数に格納します。
exit=predict_data.loc[predict_data["is_deleted"]==1]
#.sample(len(exit))ですが、sample関数はノック43から登場しています。ランダムにデータセットを抽出します。
#引数でlen(exit)を指定して、exitデータの件数だけランダムサンプリングを行います。
#これでexitデータとcontiデータの件数をそろえています。
conti=predict_data.loc[predict_data["is_deleted"]==0].sample(len(exit))
#説明変数Xにexitとcontiを結合したデータを指定します。
X=pd.concat([exit,conti],ignore_index=True)
#is_deleted列を目的変数yとしています。
y=X["is_deleted"]
#説明変数Xから目的変数に指定したyを省きます。
del X["is_deleted"]
#ノック38でも登場したtrain_test_split関数で、学習用データ(train)と評価用データ(test)の分割を行います。
#割合を指定しないので、学習長データ75%、評価用データ25%で分割されます。
X_train,X_test,y_train,y_test=sklearn.model_selection.train_test_split(X,y)
#ここからモデル構築を行っていきます。
#まず、DecisionTreeClassifierクラスを指定します。
#random_state=で乱数の固定化(乱数固定化は結果の再現性を確保するために必要です)を行います。
model=DecisionTreeClassifier(random_state=0)
#学習用データをモデルに学習させます。
model.fit(X_train,y_train)
#評価用データの予測を行います。
y_test_pred=model.predict(X_test)
#評価用データの予測結果の出力を行います。
print(y_test_pred)
#評価用データ(正解となる教師データ)と予測したデータをresults_testデータフレームに格納します。
results_test=pd.DataFrame({"y_test":y_test,"y_pred":y_test_pred})
#結果を頭出しして予測が正しいかを確認します。
results_test.head()
退会予測のための決定木モデルを作成し、予測を行うことができました。
ノック48:予測モデルの評価を行ない、モデルのチューニングをしてみよう
#評価用データ(正解となる教師データ)と予測したデータの値が等しいデータ(つまり正解しているデータ)件数をcorrectに格納します。
correct=len(results_test.loc[results_test["y_test"]==results_test["y_pred"]])
data_count=len(results_test)
#正解データ件数÷全体データ件数つまり、正解率を計算します。
score_test=correct/data_count
print(score_test)
正解率が出たことで予測の精度がある程度把握できますが、ノック38でも行ったように、学習用データで予測した精度と評価用データで予測した精度に差がないかを確認する必要があります。ノック38の精度確認結果と同様に、それぞれの精度の差が小さいのが理想です。
#ノック38と同様に、score()関数で学習用データを用いて決定係数を測定します。
print(model.score(X_test,y_test))
#評価用データを用いて決定係数を測定し、制度検証を行います。
print(model.score(X_train,y_train))
学習用データの結果が98%となっており、評価用デー多の結果が87%であることからも、過学習が疑われます。そこで、過学習を防ぐためにチューニングという調整作業を行って精度向上を図ります。チューニングでは、データを増やしたり、変数を見直したり、モデルパラメーターを変更したりします。今回は木構造の深さを浅くしてモデルをより簡素化する方針でパラメーターを変更します。緻密な木構造ではなくなってしまいますが、その分、学習用データに適合しすぎることはなくなるはずです。
#説明変数Xにexitとcontiを結合したデータを指定します。
X=pd.concat([exit,conti],ignore_index=True)
#is_deleted列を目的変数yとしています。
y=X["is_deleted"]
#説明変数Xから目的変数に指定したyを省きます。
del X["is_deleted"]
#train_test_split関数で、学習用データ(train)と評価用データ(test)の分割を行います。
X_train,X_test,y_train,y_test=sklearn.model_selection.train_test_split(X,y)
#ここからモデルの再構築を行っていきます。
#DecisionTreeClassifierクラスを指定します。
#先ほどは登場しませんでしたが、新たにmax_depth=という引数が増えています。
#これは木構造の深さを5に指定して、5階層までで木構造の構築を止めるという意味です。
#ちなみに、指定しないと木構造の深さが伸び放題になります。
model=DecisionTreeClassifier(random_state=0,max_depth=5)
#学習用データをモデルに再学習させます。
model.fit(X_train,y_train)
#ノック38と同様に、score()関数で学習用データを用いて決定係数を測定します。
print(model.score(X_test,y_test))
#評価用データを用いて決定係数を測定し、精度検証を行います。
print(model.score(X_train,y_train))
max_depthを指定しないと木構造の深さが無制限になります。木構造の深さが無制限になると学習用データへの適合率はどんどん向上しますが、過剰学習によって過学習となり、未知のデータへの予測精度がどんどん低下する原因になります。
ノック49:モデルに寄与している変数を確認しよう
#特徴量の重要度を調べていきます。
#"feature_name","coefficient"という列名で説明変数ごとの寄与変数の係数を確認してみます。
#feature_importances_では、重要変数を指定できます。
importance=pd.DataFrame({"feature_name":X.columns,"coefficient":model.feature_importances_})
importance
実行結果から、どの変数がどれほど寄与しているかを確認できます。coefficientが大きい値が寄与度が大きいので、1ヶ月前の利用回数が最も重要であると分かります。
ノック50:顧客の退会を予測しよう
ノック49まででモデルを作成し、精度が十分であることを確認できたので、モデルとなる人物を設定し、その人物の退会予測をモデルを使って実行してみます。
#モデルのインプットとなるのはモデル作成時に設定した説明変数です。
#説明変数にそれぞれ値を代入して、モデルとなる人物のデータをインプットします。
count_1=3
routine_flg=1
period=10
campaign_name="入会費無料"
class_name="オールタイム"
gender="M"
#カテゴリカル変数がインプットに含まれているので、ノック46と同様に、カテゴリカル変数→ダミー変数の変換を行います。
#if文を用いて、カテゴリカル変数であれば、ダミー変数のリストに変換して格納します。
if campaign_name=="入会費半額":
campaign_name_list=[1,0]
elif campaign_name=="入会費無料":
campaign_name_list=[0,1]
elif campaign_name=="通常":
campaign_name_list=[0,0]
if class_name=="オールタイム":
class_name_list=[1,0]
elif class_name=="デイタイム":
class_name_list=[0,1]
elif class_name=="ナイト":
class_name_list=[0,0]
if gender=="F":
gender_list=[1]
elif gender=="M":
gender_list=[0]
input_data=[count_1,routine_flg,period]
#extend()メソッドは、リストの末尾に別のリストを結合することができます。
#append()というメソッドを用いて要素を追加したことがありましたが、
#extendメソッドは要素でなく、リストやタプルを追加するという違いがあります。
input_data.extend(campaign_name_list)
input_data.extend(class_name_list)
input_data.extend(gender_list)
#ノック49でも使用したpredictメソッドを使い、予測値を計算します。
#モデルを使って、input_dataの退会予測を実行します。
print(model.predict([input_data]))
print(model.predict_proba([input_data])
実行すると、1行目で[1.]、2行目でそれぞれ0となる確率と1となる確率が出力されます。今回は、98%の確立で退会が予測されると解釈できます。これで退会予測ができましたね。
ここまでで、ノック46-50は完了です。お疲れ様でした。 今回は、教師あり学習の1つである決定木という分類を行いました。当月と1月前のデータを用いて将来の退会予測を行うことができましたね。次回は第6章に入ります。引き続きよろしくお願いいたします(^^)/