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

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

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

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

私自身はpython初心者で、pythonを触ったことがありませんでしたが、自ら手を動かしてpythonを学習したいと思いこちらの本で学習しました。よければ手に取っていただきたいです。

今回は第9回として、ノック41-45 をやっていきます。 

 

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

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

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

スポンサーリンク

第5章 顧客の退会を予測する10本ノック

ノック41:データを読み込んで利用データを整形しよう

import pandas as pd
customer=pd.read_csv('customer_join.csv')
uselog_months=pd.read_csv('use_log_months.csv')

#uselog_months(2018年4月から2019年3月のデータ)の年月列の固有値のみをリスト化し、year_monthsに格納します。
year_months=list(uselog_months["年月"].unique())

#DataFrame(行と列の二次元構造を持つデータ)を作成します。
uselog=pd.DataFrame()
for i in range(1,len(year_months)):

  #loc関数を用いて、uselog_monthsの2018年5月からのuselog_monthsデータをtmpに格納します。
  tmp=uselog_months.loc[uselog_months["年月"]==year_months[i]]

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

  #iで指定した年月から、1か月前のuselog_monthsのuselog_monthsデータをtmp_beforeに格納します。
  tmp_before=uselog_months.loc[uselog_months["年月"]==year_months[i-1]]

  #delでtmp_beforeの年月列を削除します。
  del tmp_before["年月"]

  #rename()でtmp_beforeのcountをcount_1で書き替えます。
  tmp_before.rename(columns={"count":"count_1"},inplace=True)

  #customer_idを共通キーとして、tmpとtmp_beforeを横方向に結合します。
  tmp=pd.merge(tmp,tmp_before,on="customer_id",how="left")

   #tmpをuselogデータフレームに結合します。
  uselog=pd.concat([uselog,tmp],ignore_index=True)
uselog.head()

前回行った教師あり学習の場合と異なり、当月と過去1か月分の利用回数を集計したデータを作成できました。

ノック42:退会前月の退会顧客データを作成しよう

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

#customerデータのis_deleted列が1(退会した顧客)となっている行のみを取り出してexit_customerに格納しています。
exit_customer=customer.loc[customer["is_deleted"]==1]

#exit_datという列を作り、初期化しておきます。
exit_customer["exit_date"]=None

#end_dateをdatetime型に変換します。
exit_customer["end_date"]=pd.to_datetime(exit_customer["end_date"])
for i in range(len(exit_customer)):

   #relativedelta(months=1)でひと月分を加算または減算できます。今回は-としているので減算しています。
   #end_dateから1か月前の日付をexit_dateに格納しています。
   exit_customer['exit_date'].iloc[i] = exit_customer['end_date'].iloc[i] - relativedelta(months=1)

#exit_dateをdatetime型に変換します。
exit_customer['exit_date'] = pd.to_datetime(exit_customer['exit_date'])

#exit_dateをstrftime関数で年月の形式にした後、年月という名前の変数に格納しています。
exit_customer['年月'] = exit_customer['exit_date'].dt.strftime('%Y%m')

#ノック17で登場したastype(“str”)は、対象のデータのデータ型を引数で指定した型(今回はstr型)に変換します。
uselog['年月'] = uselog['年月'].astype(str)

#customer_idと年月を共通キーとして、uselogとexit_customeを結合します。
exit_uselog=pd.merge(uselog,exit_customer,on=["customer_id","年月"],how="left")
print(len(uselog))
exit_uselog.head()

#欠損値を削除するdropna()ですが、特定の行・列に欠損値がある列・行を削除したいときsubset=で列名を指定指定することができます。
exit_uselog=exit_uselog.dropna(subset=["name"])
print(len(exit_uselog))
print(len(exit_uselog["customer_id"].unique()))
exit_uselog.head()

subset=”name”を指定しているので、customerデータのname列が欠損値であれば退会前月データと結合ができない不要なデータとみなして削除しています。

退会予測を行うにあたって必要な退会申請が実際に行われる退会前月のデータを作成することができました。

ノック43:継続顧客のデータを作成しよう

#customerデータのis_deleted列が0(継続顧客)となっている行のみを取り出してconti_customerに格納しています。
conti_customer=customer.loc[customer["is_deleted"]==0]

#customer_idを共通キーとして、uselogとexit_customerを結合します。
conti_uselog=pd.merge(uselog,conti_customer,on=["customer_id"],how="left")
print(len(conti_uselog))

#conti_uselogデータのname列が欠損値であれば不要なデータとみなして削除しています。
conti_uselog=conti_uselog.dropna(subset={"name"})
print(len(conti_uselog))

結果を出力してみると、下から二行目の処理によって、継続顧客のデータ件数が33851件から27422件に減っていることが分かります。

#sample()メソッドはpandas.DataFrameの行をランダムに並び替えることができます。
#引数でfrac=1とすると、すべての行数をランダムに並び替えることができます(ランダムサンプリング)。
conti_uselog=conti_uselog.sample(frac=1).reset_index(drop=True)

#drop_duplicates()メソッドは、重複した行を削除します。
#subset=で指定した列の重複を判定します。
conti_uselog=conti_uselog.drop_duplicates(subset="customer_id")
print(len(conti_uselog))
conti_uselog.head()

sample(frac=1)でランダムな並び替えを行い、drop_duplicateを行った理由は、退会データの件数が1104件なのに対し、継続顧客データの件数が27422件あり、件数に差があるため、継続顧客のサンプル数を調整して退会データの件数に合わせるためです。

#継続顧客のデータと退会顧客のデータを縦に結合します。
predict_data=pd.concat([conti_uselog,exit_uselog],ignore_index=True)
print(len(predict_data))
predict_data.head()

最後のブロックで、継続と退会の両方が混ざったデータを作成できました。

ノック44:予測する月の在籍期間を作成しよう

#在籍期間という時間軸をもった変数を用意するためにperiodという列を追加します。
predict_data["period"]=0

#predict_dataの年月を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"])
for i in range(len(predict_data)):

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

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

第5章後半で行う機械学習に必要な説明変数のデータ作成が完了しました。

ノック45:欠損値を除去しよう

#isna().sum()でpredict_dataの欠損値の数を確認します。
predict_data.isna().sum()

end_date, exit_data, count_1に欠損値があることが分かります。

#count_1 に欠損値があるデータのみ除外します。
predict_data=predict_data.dropna(subset=["count_1"])
predict_data.isna().sum()

count_1はtmp_beforeのcountだったことを考えると、前の月の利用回数が欠損値であり、前月の時点ですでに利用していない顧客のデータといえます。前の月の利用状況から予測を行いたいので、前の月からすでに利用を止めている人のデータは不要であるといえます。

欠損値を除去できました。

ここまでで、ノック41-45は完了です。お疲れ様でした。 今回は、機械学習の1つである、決定木という教師あり学習を行うにあたり必要なデータの整理や作成を行いました。次回は第5章(決定木)に入ります。引き続きよろしくお願いいたします(^^)/