❏ 第2部 実践編①:機械学習

第3章  顧客の全体像を把握する10本ノック


この章の目的

・本章では、機械学習を行う前段階の、人間の手による分析を適切に行うためのデータ加工技術について学び、顧客行動の分析、把握を行っていく

 ノウハウを習得する。これによって、今、取り扱っているデータがどのようなものであるかを把握することができ、どのような機械学習の手法を

 用いれば良い結果が出せるのかを判断できるようになる


ノック21:データを読み込んで把握しよう

ポイント

・最初は先頭数行を表示させ、どの様なデータ列が存在するか、それぞれのデータ列の関係性など、データの大枠を掴むことが重要

# ジムの利用履歴(期間は2018年4月~2019年3月)
uselog = pd.read_csv('use_log.csv')
uselog.head()
  log_id customer_id usedate
0 L00000049012330 AS009373 2018-04-01
1 L00000049012331 AS015315 2018-04-01
2 L00000049012332 AS040841 2018-04-01
3 L00000049012333 AS046594 2018-04-01
4 L00000049012334 AS073285 2018-04-01
# 会員区分データ(オールタイム、デイタイム等)
class_master = pd.read_csv('class_master.csv')
class_master.head()
  class class_name price
0 C01 オールタイム 10500
1 C02 デイタイム 7500
2 C03 ナイト 6000

・name値は塗りつぶしている。これを「マスキング」と呼ぶ

# 2019年3月末時点での会員データ
customer = pd.read_csv('customer_master.csv')
customer.head()
  customer_id name class gender start_date end_date campaign_id is_deleted
0 OA832399 XXXX C01 F 2015-05-01 00:00:00 NaN CA1 0
1 PL270116 XXXXX C01 M 2015-05-01 00:00:00 NaN CA1 0
2 OA974876 XXXXX C01 M 2015-05-01 00:00:00 NaN CA1 0
# キャンペーン区分データ(入会費無料等)
campaign_master = pd.read_csv('campaign_master.csv')
campaign_master.head()
  campaign_id campaign_name
0 CA1 通常
1 CA2 入会費半額
2 CA3 入会費無料


ノック22:顧客データを整形しよう

ポイント

・顧客データを主にジョインを行い、会員区分や金額等が分かるようにデータを整形する

・データ整形とは、データ分析に向けてデータの中身を整理したり、分析手法に合わせてデータを編集することを言う

customer_join = pd.merge(customer, class_master, on="class", how="left")
customer_join = pd.merge(customer_join, campaign_master, on="campaign_id", how="left")
customer_join.head()
  customer_id name class gender start_date end_date campaign_id is_deleted class_name price campaign_name
0 OA832399 XXXX C01 F 2015-05-01 00:00:00 NaN CA1 0 オールタイム 10500 通常
1 PL270116 XXXXX C01 M 2015-05-01 00:00:00 NaN CA1 0 オールタイム 10500 通常
2 OA974876 XXXXX C01 M 2015-05-01 00:00:00 NaN CA1 0 オールタイム 10500 通常
3 HD024127 XXXXX C01 F 2015-05-01 00:00:00 NaN CA1 0 オールタイム 10500 通常
4 HD661448 XXXXX C03 F 2015-05-01 00:00:00 NaN CA1 0 ナイト 6000 通常

 

ポイント

・end_dateに欠損値が入っていること以外は比較的綺麗なデータであることが確認できる

・end_dateにおいては、退会してないユーザーはend_dateは保持していないため、欠損値となっていることが考えられる

customer_join.isnull().sum()

customer_id     0

name        0

class       0

gender         0

start_date       0

end_date     2842

campaign_id     0

is_deleted      0

class_name    0

price       0

campaign_name 0

dtype: int64


ノック23:顧客データの基礎集計をしよう

ポイント

・まずは顧客データを集計して、全体像をみてみる(会員区分別、キャンペーン別、入会/退会別、男女別等)

・続いて、いろいろ仮説をたててみる(キャンペーンはいつ行われているのか、性別と会員クラスの関係、今年度の入会人数など)

・こうした仮説や疑問点を集計して確認するのはもちろんのこと、現場の人にヒアリングすることで理解が進むことが多々ある

# 会員区分別
customer_join.groupby("class_name").count()["customer_id"]

# キャンペーン別
customer_join.groupby("campaign_name").count()["customer_id"]

# 男女別
customer_join.groupby("gender").count()["customer_id"]

# 入会/退会別
customer_join.groupby("is_deleted").count()["customer_id"]

ポイント

・入会人数を集計

customer_join["start_date"] = pd.to_datetime(customer_join["start_date"])
customer_start = customer_join.loc[customer_join["start_date"] > pd.to_datetime("20180401")]
print(len(customer_start))
1361

ノック24:最新顧客データの基礎集計をしてみよう

ポイント

・最新月(2019年03月)時点では在籍していたユーザーを抽出

・loc()は、[(条件1) | (条件2)]でOR条件が可能

・NaTは、datetime型の欠損値という意味。このデータにおいては退会していない顧客を示す

customer_join["end_date"] = pd.to_datetime(customer_join["end_date"])
customer_newer = customer_join.loc[(customer_join["end_date"] >= pd.to_datetime("20190331")) | (customer_join["end_date"].isna()) ]
print(len(customer_newer))
customer_newer["end_date"].unique()

 2953

array( [ 'NaT', '2019-03-31T00:00:00.000000000'], dtype='datetime64[ns]' )


ノック25:利用履歴データを集計しよう

ポイント

・groupby( )を使うと、デフォルトでグループラベルが index になる。index にしたく無い場合は as_index=False を指定する

・groupby( )は、集計単位(年月、customer_id)以外のカラム(log_id、usedate)全てが集計される

・rename(columns={"log_id":"count"}は、列名をlog_idからcountへ置換する

・rename( )は、デフォルトでは元のpandas.DataFrameは変更されず、新しいpandas.DataFrameが返されるが、

 引数inplaceをTrueにすると、元のpandas.DataFrameが変更される

・del uselog_months["usedate"]は、usedate列を削除する

1 uselog["usedate"] = pd.to_datetime(uselog["usedate"])
2 uselog["年月"] = uselog["usedate"].dt.strftime("%Y%m")
3 uselog_months = uselog.groupby(["年月","customer_id"], as_index=False).count()
4 uselog_months.rename(columns={"log_id":"count"}, inplace=True)
5 del uselog_months["usedate"]
6 uselog_months.head()

2行目のuselog.head()

  log_id customer_id usedate 年月
0 L00000049012330 AS009373 2018-04-01 201804
1 L00000049012331 AS015315 2018-04-01 201804
2 L00000049012332 AS040841 2018-04-01 201804
3 L00000049012333 AS046594 2018-04-01 201804
4 L00000049012334 AS073285 2018-04-01 201804

3行目のuselog_months.head()

  年月 customer_id log_id usedate
0 201804 AS002855 4 4
1 201804 AS009013 2 2
2 201804 AS009373 3 3
3 201804 AS015315 6 6
4 201804 AS015739 7 7

6行目のuselog_months.head()

  年月 customer_id count
0 201804 AS002855 4
1 201804 AS009013 2
2 201804 AS009373 3
3 201804 AS015315 6
4 201804 AS015739 7

 

ポイント

・顧客毎の月内の利用回数の集計

・平均値(mean)、中央値(median)、最大値(max)、最小値(min)

・reset_index( )は、indexの振り直しを行っている

uselog_customer = uselog_months.groupby("customer_id").agg(["mean","median","max","min"])["count"]
uselog_customer = uselog_customer.reset_index(drop=False)
uselog_customer.head()
  customer_id mean median max min
0 AS002855 4.500000 5.0 7 2
1 AS008805 4.000000 4.0 8 1
2 AS009013 2.000000 2.0 2 2
3 AS009373 5.083333 5.0 7 3
4 AS015233 7.545455 7.0 11 4

ノック26:利用履歴データから定期利用フラグを作成しよう

ポイント

・weekday( )は、週ナンバーを計算する。0から6が付与され、それぞれ月曜から日曜に相当する

・groupby( )では、顧客・年月・週ナンバー毎にlog_id数を集計

・rename( )で、log_idをcountへカラム名変更している

uselog["weekday"] = uselog["usedate"].dt.weekday
uselog_weekday = uselog.groupby(["customer_id","年月","weekday"], as_index=False).count()[["customer_id","年月","weekday","log_id"]]
uselog_weekday.rename(columns={"log_id":"count"}, inplace=True)
uselog_weekday.head()
  customer_id 年月 weekday count
0 AS002855 201804 5 4
1 AS002855 201805 2 1
2 AS002855 201805 5 4
3 AS002855 201806 5 5
4 AS002855 201807 1 1

 

ポイント

・groupby( )で、顧客毎に月内に特定の曜日で最も利用した回数を取得

・uselog_weekday["routine_flg"] = 0は、当データフレームに列を追加している。初期値に0を設定

・where( )で、条件がTrueだと元のデータを保持し、Falseの場合、other(カンマの右側)で指定された処理を実行するか値を代入します

・routine_flgは、顧客毎に月内に特定の曜日に利用している回数が4週以上の顧客は「1:定期的に利用しているユーザー」を設定

uselog_weekday = uselog_weekday.groupby("customer_id", as_index=False).max()[["customer_id", "count"]]
uselog_weekday["routine_flg"] = 0
uselog_weekday["routine_flg"] = uselog_weekday["routine_flg"].where(uselog_weekday["count"]<4, 1)
uselog_weekday.head()
  customer_id count routine_flg
0 AS002855 5 1
1 AS008805 4 1
2 AS009013 2 0
3 AS009373 5 1
4 AS015233 5 1

ノック27:顧客データと利用履歴データを結合しよう

ポイント

特になし

customer_join = pd.merge(customer_join, uselog_customer, on="customer_id", how="left")
customer_join = pd.merge(customer_join, uselog_weekday[["customer_id", "routine_flg"]], on="customer_id", how="left")
customer_join.head()
  customer_id name class gender start_date end_date campaign_id is_deleted class_name price campaign_name mean median max min routine_flg
0 OA832399 XXXX C01 F 2015-05-01 NaT CA1 0 オールタイム 10500 通常 4.833333 5.0 8 2 1
1 PL270116 XXXXX C01 M 2015-05-01 NaT CA1 0 オールタイム 10500 通常 5.083333 5.0 7 3 1
2 OA974876 XXXXX C01 M 2015-05-01 NaT CA1 0 オールタイム 10500 通常 4.583333 5.0 6 3 1
                                 

ノック28:会員期間を計算しよう

ポイント

・処理内容

 1行目 日付の比較で使用するrelativedeltaは、ライブラリをインポート

 2行目 日付計算用の列を追加

 3行目 fillna( )は、欠損値を他の値に置換(穴埋め)する。欠損値に2019年4月30日を設定

 4行目 会員期間の結果を格納する列を追加

 5行目 データフレームの行毎にループ

 6行目 iloc( )は、行番号で任意のデータを取り出す( それに対して loc( )は行、列で指定する)。経過期間を求める(例:下記)

 6行目 relativedelta(years=+1, months=+11, days=+29)の場合、( 1年 * 12 ) + ( 11ヶ月 ) = 23ヶ月

1 from dateutil.relativedelta import relativedelta
2 customer_join["calc_date"] = customer_join["end_date"]
3 customer_join["calc_date"] = customer_join["calc_date"].fillna(pd.to_datetime("20190430"))
4 customer_join["membership_period"] = 0
5 for i in range(len(customer_join)):
6     delta = relativedelta(customer_join["calc_date"].iloc[i], customer_join["start_date"].iloc[i])
7     customer_join["membership_period"].iloc[i] = delta.years*12 + delta.months
8 customer_join.head()
  customer_id name class gender start_date end_date campaign_id is_deleted class_name price campaign_name mean median max min routine_flg calc_date membership_period
0 OA832399 XXXX C01 F 2015-05-01 NaT CA1 0 オールタイム 10500 通常 4.833333 5.0 8 2 1 2019-04-30 47
1 PL270116 XXXXX C01 M 2015-05-01 NaT CA1 0 オールタイム 10500 通常 5.083333 5.0 7 3 1 2019-04-30 47
2 OA974876 XXXXX C01 M 2015-05-01 NaT CA1 0 オールタイム 10500 通常 4.583333 5.0 6 3 1 2019-04-30 47

ノック29:顧客行動の各種統計量を把握しよう

ポイント

・列名のmeanは顧客の月内平均利用回数。行にあるmeanは顧客の月内平均利用回数の平均

customer_join[["mean", "median", "max", "min"]].describe()
  mean median max min
count 4192.000000 4192.000000 4192.000000 4192.000000
mean 5.333127 5.250596 7.823950 3.041269
std 1.777533 1.874874 2.168959 1.951565
min 1.000000 1.000000 1.000000 1.000000
25% 4.250000 4.000000 7.000000 2.000000
50% 5.000000 5.000000 8.000000 3.000000
75% 6.416667 6.500000 9.000000 4.000000
max 12.000000 12.000000 14.000000 12.000000

ポイント

・1:定期的に利用しているユーザーの方が多い

customer_join.groupby("routine_flg").count()["customer_id"]

routine_flg

0    779

1  3413

Name: customer_id, dtype: int64

 

ポイント

・会員期間をヒストグラムで表示

・10ヶ月以内のユーザーが多く、それ以降はほぼ横ばい。これは、短期でユーザーが離れていく業界であることを示唆している

・棒一本一本のデータが独立しているのが棒グラフ、連続しているのがヒストグラム

import matplotlib.pyplot as plt
%matplotlib inline
plt.hist(customer_join["membership_period"])

ノック30:退会ユーザーと継続ユーザーの違いを把握しよう

ポイント

・退会ユーザーと継続ユーザーに分けて比較

・退会ユーザーは月内の利用回数の平均値が継続ユーザーよりも低く出ている。中央値、最大値、最小値も同様

・routine_flgの平均値に大きく差がでている。継続ユーザーは0.98と多くのユーザーが定期的に利用していることが伺える。

 退会ユーザーは0.45となり、およそ半分くらいのユーザーはランダムに利用していると考えられる

customer_end = customer_join.loc[customer_join["is_deleted"]==1]
customer_end.describe()
  is_deleted price mean median max min routine_flg membership_period
count 1350.0 1350.000000 1350.000000 1350.000000 1350.000000 1350.000000 1350.000000 1350.000000
mean 1.0 8595.555556 3.865474 3.621852 6.461481 1.821481 0.456296 8.026667
std 0.0 1949.163652 1.246385 1.270847 2.584021 0.976361 0.498271 5.033692
min 1.0 6000.000000 1.000000 1.000000 1.000000 1.000000 0.000000 1.000000
25% 1.0 6000.000000 3.000000 3.000000 4.000000 1.000000 0.000000 4.000000
50% 1.0 7500.000000 4.000000 4.000000 7.000000 2.000000 0.000000 7.000000
75% 1.0 10500.000000 4.666667 4.500000 8.000000 2.000000 1.000000 11.000000
max 1.0 10500.000000 9.000000 9.000000 13.000000 8.000000 1.000000 23.000000
customer_stay = customer_join.loc[customer_join["is_deleted"]==0]
customer_stay.describe()
  is_deleted price mean median max min routine_flg membership_period
count 2842.0 2842.000000 2842.000000 2842.000000 2842.000000 2842.000000 2842.000000 2842.000000
mean 0.0 8542.927516 6.030288 6.024279 8.471147 3.620690 0.984166 23.970443
std 0.0 1977.189779 1.553587 1.599765 1.571048 2.030488 0.124855 13.746761
min 0.0 6000.000000 3.166667 3.000000 5.000000 1.000000 0.000000 1.000000
25% 0.0 6000.000000 4.833333 5.000000 7.000000 2.000000 1.000000 12.000000
50% 0.0 7500.000000 5.583333 5.500000 8.000000 3.000000 1.000000 24.000000
75% 0.0 10500.000000 7.178030 7.000000 10.000000 5.000000 1.000000 35.000000
max 0.0 10500.000000 12.000000 12.000000 14.000000 12.000000 1.000000 47.000000

・customer_joinをcsv出力

customer_join.to_csv("customer_join.csv", index=False)

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


この章の目的

・本章では、前章で事前分析を行ったスポージジムの会員の行動情報を用いて、機械学習による予測を行っていく

・クラスタリングという手法を用いることで、会員をグルーピングしていくことができ、それぞれのグループの行動パターンを掴むことで、

 将来予測を精度よく行うことが可能になる

 

・機械学習とは

 機械学習とは、コンピュータにデータを学習させ特徴を導き出し、その特徴を用いて未来への予測・判断などに活用する事を指す

 機械学習の主軸は未来の予測にある。機械学習は大きく分けると教師あり学習、教師なし学習、強化学習の3つに分けられ、

 それぞれの学習方法ごとに、複数のアルゴリズムが存在する


ノック31:データを読み込んで確認しよう

ポイント

特になし

import pandas as pd
uselog = pd.read_csv("use_log.csv")
customer = pd.read_csv("customer_join.csv")

ノック32:クラスタリングで顧客をグループ化しよう

ポイント

・customer変数の列を絞り込む

customer_clustering = customer[["mean", "median", "max", "min", "membership_period"]]
customer_clustering.head(3)
  mean median max min membership_period
0 4.833333 5.0 8 2 47
1 5.083333 5.0 7 3 47
2 4.583333 5.0 6 3 47

 

ポイント

・クラスター分析

 大きな集団の中から、似たもの同士を集めてグループに分ける統計的な分析手法

 性別や年齢層等と違った外的基準がはっきりしていないデータを分類する場合に用いる場合が多い

 教師なし学習(正解データがない状態でデータを分類する手法)に分類される

 階層的クラスター分析と非階層的クラスター分析がある

 ビジネスシーンで使用される例として、セグメントの可視化(現状把握)、教師あり学習の特徴量生成(将来予測)などがある

 

・K-means法(K平均法)

 非階層的クラスター分析(似たデータ同士が同じクラスターになるよう全体を分割)の一つ

 最もオーソドックスなクラスタリング手法

 変数間の距離をベースにグループ化を行う

 あらかじめグルーピングしたい数を指定する

 

・教師なし学習

 教師なし学習とは、教師データを学習せずに与えられたデータから規則性などの意味のある情報を見つけ出す手法

 代表的なものとしてクラスリングがある

 教師データが用意できない場合で非常に効果的で、探索的なデータ解析で多く使用されている

 ただ、正解データがないためモデルの精度が決められず、そのグループの分類が正しいかの判断ができず、より人間の知見が重要になってきている

 

・処理内容

 1,2行目 K-means法や標準化を使用するために、scikit-learn(サイキット・ラーン)というライブラリをインポート

 3,4行目 customer_clusteringに対して標準化を実行し、新たにcustomer_clustering_scに格納

 5行目  K-meansのモデル構築を行っている。まず、クラスタ数に4を指定し、作成するモデル定義を行っている

 6行目  KMeans クラスのメソッド fit( )で、クラスタリングの計算を実行(モデル構築)

 7行目  clusters.labels_は、各データのクラスタ番号のリスト。それをcustomer_clusteringに反映

 8行目  0~3の4グループ作成されている

 

・参考先

 階層的    :クラスター分析の手法②(階層クラスター分析)サイト

 非階層的   :クラスター分析の手法③(非階層クラスター分析) サイト

 k-means法  :【10分で分かる!】ビジネスで使えるクラスター分析を解説!非階層のk-means法とは? youtube

 fit_transfor( ) :fit、transform、fit_transformの意味を、正規化の例で解説 - 具体例で学ぶ数学 サイト 

 

1 from sklearn.cluster import KMeans
2 from sklearn.preprocessing import StandardScaler
3 sc = StandardScaler()
4 customer_clustering_sc = sc.fit_transform(customer_clustering)

5 kmeans = KMeans(n_clusters=4, random_state=0)
6 clusters = kmeans.fit(customer_clustering_sc)
7 customer_clustering["cluster"] = clusters.labels_
8 print(customer_clustering["cluster"].unique())
9 customer_clustering.head()

[0 3 2 1]

  mean median max min membership_period cluster
0 4.833333 5.0 8 2 47 0
1 5.083333 5.0 7 3 47 0
2 4.583333 5.0 6 3 47 0
3 4.833333 4.5 7 2 47 0
4 3.916667 4.0 6 1 47 0

 

ノック33:クラスタリング結果を分析しよう

ポイント

・データフレームのcolumns( )で、列名を変更。角括弧の中に列名を指定することで全ての列名を変更

customer_clustering.columns = ["月内平均値", "月内中央値", "月内最大値", "月内最小値", "会員期間", "cluster"]
customer_clustering.groupby("cluster").count()
  月内平均値 月内中央値 月内最大値 月内最小値 会員期間
cluster          
0 840 840 840 840 840
1 1249 1249 1249 1249 1249
2 771 771 771 771 771
3 1332 1332 1332 1332 1332

ポイント

・各グループ毎の平均値を集計

・グループ毎に集計を行うと、グループの特徴が見えてくる

 ・各グループの特徴

 グループ0:会員期間が短く利用率が高い(入会した後も、やるきが継続できている人達)

 グループ2:会員期間が短いのに利用率が低い(入会したが、やる気が継続できなかった人達)

 グループ1:会員期間が一番長い(長く利用してても週一利用の安定した人達)

 グループ3:会員期間が二番目に長い(グループ1より期間が短く、やるきがまだ高い人達)

 

・グループの特徴がわかれば、グループ毎に別の施策を打つことが可能になる

・mean,median,max,min,membership_period 以外に、さらに特徴的な変数を組み込むことで、より複雑なグルーピングも可能になる

customer_clustering.groupby("cluster").mean()
  月内平均値 月内中央値 月内最大値 月内最小値 会員期間
cluster          
0 8.061942 8.047024 10.014286 6.175000 7.019048
1 4.677561 4.670937 7.233787 2.153723 36.915933
2 3.065504 2.900130 4.783398 1.649805 9.276265
3 5.539535 5.391141 8.756006 2.702703 14.867868

ノック34:クラスタリング結果を可視化してみよう

ポイント

・次元削除

 ここまで扱ってきたクラスタリングに使用した5つつの変数を二次元上にプロットする場合、次元削除を行う

  教師なし学習の一種で、情報をなるべく失わないように変数を削除して、新しい軸を作り出すこと

 これによって、5つの変数を2つの変数で表現することができ、グラフ化することが可能になる

 次元削除の代表的な手法は、主成分分析

・PCA

 PCAとは、次元削除の1つの手法で、日本語では主成分分析という

 多次元のデータを低次元で扱うことができるようになるのでメモリ節約になる

 多次元のデータを2次元、3次元にすることによって、簡単にデータを可視化することができる

 PCAを実施する前に、特徴量のスケール(範囲)を合わせるため標準化をする必要がある

 

 

・処理内容

   1行目    主成分分析のライブラリをインポート

   2行目    customer_clustering_scは、ノック32で標準化したを変数。クラスタリングのINPUTゲータ

   3行目    モデルを定義。n_componentsは主成分の数(圧縮先の次元数)

   4,5行目 主成分分析を実行。fit( )してtransform( )する意味は前述のノック32と同様

   6行目   2次元に主成分分析したデータをpca_dfとしてデータフレームに格納

   7行目    主成分分析のデータフレームにクラスタリング情報を付加(「変数確認①」参照)

 10行目 2次元に主成分分析した結果(0列と1列)をクラスタリング毎に散布図にプロット(「変数確認②③」参照)

 

・結果を見ると、綺麗に色分けされており、情報を残したまま綺麗に削除できていることがわかる

 一般的には、作成された2つの軸(変数)が、どの変数から成り立っているかを分析していくと、軸の意味づけが可能になる

 

・参考先

 PCA    :次元削減とは?PCA(主成分分析)を理解する サイト

 1 from sklearn.decomposition import PCA
 2 X = customer_clustering_sc
 3 pca = PCA(n_components=2)
 4 pca.fit(X)
 5 x_pca = pca.transform(X)
 6 pca_df = pd.DataFrame(x_pca)
 7 pca_df["cluster"] = customer_clustering["cluster"]

 8 import matplotlib.pyplot as plt
 9 %matplotlib inline
10 for i in customer_clustering["cluster"].unique():
11    tmp = pca_df.loc[pca_df["cluster"]==i]
12    plt.scatter(tmp[0], tmp[1])

変数確認①

pca_df["cluster"] = customer_clustering["cluster"]
pca_df.head()
  0 1 cluster
0 -0.819982 -1.959097 1
1 -0.707922 -1.799857 1
2 -1.061499 -1.659826 1
3 -1.160764 -1.810139 1
4 -2.017132 -1.670101 1

変数確認②

customer_clustering["cluster"].unique()

array([1, 2, 3, 0])

 

変数確認③

for i in customer_clustering["cluster"].unique():
    tmp = pca_df.loc[pca_df["cluster"]==i]
    print(tmp.head(2))

      0     1   cluster

0  -0.819982 -1.959097      1

1  -0.707922 -1.799857      1

       0     1  cluster

708  -2.365997 0.562809     2

729  -2.365997 0.562809     2



ノック35:クラスタリング結果をもとに退会顧客の傾向を把握しよう

ポイント

・concat( )

 customer_clusteringにcustomerを結合。indexで紐付いているのでconcatで列の結合が可能(横方向の結合)

 axis=1は、列名をキーにして横方向に結合する

 

・参考先

 concat( )  :【TIPS】Pandasのconcat関数でDataFrameを結合する方法まとめ サイト

customer_clustering = pd.concat([customer_clustering, customer], axis=1)
customer_clustering.head(3)
  月内平均値 月内中央値 月内最大値 月内最小値 会員期間 cluster customer_id name class gender ... class_name price campaign_name mean median max min
0 4.833333 5.0 8 2 47 1 OA832399 XXXX C01 F ... オールタイム 10500 通常 4.833333 5.0 8 2
1 5.083333 5.0 7 3 47 1 PL270116 XXXXX C01 M ... オールタイム 10500 通常 5.083333 5.0 7 3
2 4.583333 5.0 6 3 47 1 OA974876 XXXXX C01 M ... オールタイム 10500 通常 4.583333 5.0 6 3

後方の列を一部省略

 

ポイント

・cluster, is_deleted毎にcustomer_idの件数を集計している

・グループ0,1は継続顧客が多く、グループ2は退会顧客が多く、グループ3はバランス良く含まれている事が分かる

・下記はノック33で得られたクラスタリングの各特徴。その特徴と今回の集計結果は紐づいている事が分かる

 グループ0:会員期間が短く利用率が高い(入会した後も、やるきが継続できている人達)

 グループ2:会員期間が短いのに利用率が低い(入会したが、やる気が継続できなかった人達)

 グループ1:会員期間が一番長い(長く利用してても週一利用の安定した人達)

 グループ3:会員期間が二番目に長い(グループ1より期間が短いく、やるきがまだ高い人達)

・クラスタリングによるグループ化を行うことで、顧客の特徴を掴むことができる。さらに、分析の目的に応じて様々な切り口で分析を進めるとよい

1 customer_clustering.groupby(["cluster","is_deleted"],as_index=False).count()[["cluster","is_deleted","customer_id"]]
2 customer_clustering.groupby(["cluster","routine_flg"],as_index=False).count()[["cluster","routine_flg","customer_id"]]

1行目

  cluster is_deleted customer_id
0 0 0 821
1 0 1 19
2 1 0 1231
3 1 1 18
4 2 1 771
5 3 0 790
6 3 1 542

2行目

  cluster routine_flg customer_id
0 0 0 52
1 0 1 788
2 1 0 2
3 1 1 1247
4 2 0 499
5 2 1 272
6 3 0 226
7 3 1 1106

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

ポイント

・顧客の過去の行動データから翌月の利用回数を予測する場合、「教師あり学習の回帰」を用いる

・ここでは、過去6ヶ月の利用データを用いて、翌月の利用データを予測する

・2018年5月~10月の6ヶ月の利用データと2018年11月の利用回数を教師データとして学習に使う

 

・教師あり学習

 あらかじめ正解がわかっているデータ(教師データ)を用いて予測を行う

 分類と回帰がある。来月の利用回数の予測は回帰。退会するか/しないかの離散値の場合は分類になる

 

 

・回帰

 機械学習における回帰とは、「連続値を使い、ある数値から別の数値を予測すること」を意味する

 例えば、過去の気温から明日の気温を予測など

 回帰の特徴は、「データがないところまで予測できる」こと。それにより過去のデータから今後の数値を予測することが可能になる

 

・回帰と分類の違い

 回帰は連続値を使って別の数値を予測する。分類は非連続値つまり離散値を使って振り分ける

 回帰は数量を扱う学習方法。分類は「画像に写っているのが犬か猫か判定する」など分析したいデータが属するカテゴリー等を判定する手法

 回帰は数値を予測するもの、分類は振り分けるものと覚える

 

・線を引く

 学習させるということは線を引くことにあたる。分類の場合は綺麗に分割できる線。回帰の場合は綺麗に説明できる線を引くことにある

 この線を学習によって引いていくことで未知なデータが来た際に予測が可能になる

 分類のアルゴリズム:決定木、ロジスティック回帰、ランダムフォレストなど

 回帰のアルゴリズム:重回帰が有名

# ノック25と同様(年月、customer_id毎の利用回数を集計)
uselog["usedate"] = pd.to_datetime(uselog["usedate"])
uselog["年月"] = uselog["usedate"].dt.strftime("%Y%m")
uselog_months = uselog.groupby(["年月","customer_id"], as_index=False).count()
uselog_months.rename(columns={"log_id":"count"}, inplace=True)
del uselog_months["usedate"]
uselog_months.head()

ポイント

・次に、当月から過去5ヶ月分の利用回数と、翌月の利用回数を付与

・処理内容

   1行目 年月リストを生成。len(year_months)は12

   3行目 添字 i が6~11で繰り返される。range( )は、range( start, stop )でstart ≦ i < stopの意味

   4行目 添字 i の年月のデータ郡をtmp(データフレーム)へ格納

   6行目 添字 j が1~6で繰り返される

   7行目 添字 i の年月の1ヶ月前~6ヶ月前のデータ郡を変数tmp_before(データフレーム)へ格納

     i=201810のときは、i-j=201809,201808,201807,201806,201805,201804(※1)

     i=201811のときは、i-j=201810,201809,201808,201807,201806,201805

   8行目 tmp_before["年月"]列を削除

   9行目 j=1のときは、列名を"count"から"count_0"に変更

 10行目 tmpに1ヶ月前~6ヶ月前の集計をマージする(count_pred列が予測したい月のデータ、count_0以降が過去6ヶ月のデータ)

 11行目 predict_dataにtmpをユニオンしていく

 13行目 dropna( )は、欠損値を含むデータを除去する

 14行目 reset_index( )は、indexを初期化している

 1 year_months = list(uselog_months["年月"].unique())
 2 predict_data = pd.DataFrame()
 3 for i in range(6, len(year_months)):
 4     tmp = uselog_months.loc[uselog_months["年月"]==year_months[i]]
 5     tmp.rename(columns={"count":"count_pred"}, inplace=True)
 6     for j in range(1, 7):
 7         tmp_before = uselog_months.loc[uselog_months["年月"]==year_months[i-j]]
 8         del tmp_before["年月"]
 9         tmp_before.rename(columns={"count":"count_{}".format(j-1)}, inplace=True)
10         tmp = pd.merge(tmp, tmp_before, on="customer_id", how="left")
11     predict_data = pd.concat([predict_data, tmp], ignore_index=True)
12 predict_data.head()

13 predict_data = predict_data.dropna()
14 predict_data = predict_data.reset_index(drop=True)
15 predict_data.head()
  年月 customer_id count_pred count_0 count_1 count_2 count_3 count_4 count_5
0 201810 AS002855 3 7.0 3.0 5.0 5.0 5.0 4.0
1 201810 AS009373 5 6.0 6.0 7.0 4.0 4.0 3.0
2 201810 AS015315 4 7.0 3.0 6.0 3.0 3.0 6.0
3 201810 AS015739 5 6.0 5.0 8.0 6.0 5.0 7.0
4 201810 AS019860 7 5.0 7.0 4.0 6.0 8.0 6.0
... ... ... ... ... ... ... ... ... ...
15108 201903 TS995299 3 3.0 5.0 4.0 5.0 4.0 5.0
15109 201903 TS998593 8 7.0 8.0 7.0 9.0 9.0 9.0
15110 201903 TS999079 3 2.0 6.0 9.0 6.0 6.0 4.0
15111 201903 TS999231 6 6.0 3.0 8.0 5.0 5.0 4.0
15112 201903 TS999855 4 4.0 7.0 5.0 4.0 4.0 5.0

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

ポイント

・特徴となるデータとして会員期間を付与する

・以下はどちらも結果値は同じ

 delta = relativedelta(predict_data["now_date"][i], predict_data["start_date"][i])

 delta = relativedelta(predict_data["now_date"].iloc[i], predict_data["start_date"].iloc[i])

 

・処理内容

 1行目 customerのstart_dateをpredict_dataに付与

 2行目 文字列型である年月をdatetime型に変換(yyyy-mm-01)。3行目のstart_dateも同様

 7行目 年月(now_date)とstart_dateの差から会員期間を月単位で算出した period を付加 ※relativedeltaについてはノック28を参照

1 predict_data = pd.merge(predict_data, customer[["customer_id", "start_date"]], on = "customer_id", how = "left")
2 predict_data["now_date"] = pd.to_datetime(predict_data["年月"], format="%Y%m")
3 predict_data["start_date"] = pd.to_datetime(predict_data["start_date"])
4 from dateutil.relativedelta import relativedelta
5 predict_data["period"] = None 
6 for i in range(len(predict_data)):
7     delta = relativedelta(predict_data["now_date"][i], predict_data["start_date"][i])
8     predict_data["period"].iloc[i] = delta.years*12 + delta.months
9 predict_data.head()
  年月 customer_id count_pred count_0 count_1 count_2 count_3 count_4 count_5 start_date now_date period
0 201810 AS002855 3 7.0 3.0 5.0 5.0 5.0 4.0 2016-11-01 2018-10-01 23
1 201810 AS009373 5 6.0 6.0 7.0 4.0 4.0 3.0 2015-11-01 2018-10-01 35
2 201810 AS015315 4 7.0 3.0 6.0 3.0 3.0 6.0 2015-07-01 2018-10-01 39
3 201810 AS015739 5 6.0 5.0 8.0 6.0 5.0 7.0 2017-06-01 2018-10-01 16
4 201810 AS019860 7 5.0 7.0 4.0 6.0 8.0 6.0 2017-10-01 2018-10-01 12

 


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

ポイント

・今回は2018年4月以降に新規に入った顧客に絞ってモデル作成を行う

・古くからいる顧客は、入店時期の利用履歴データが無い、利用回数が安定している(予測する必要性がない)ため除外してモデルを作成

・使用する回帰モデルは、scikit-learn(サイキット・ラーン)のLinearRegression(リニア・リグレッション)

 

・LinearRegression(リニア・リグレッション)

 LinearRegressionは、線形回帰モデルと呼ばれ、非常にシンプルな式で表される

 データを学習用データと評価用データに分割して、学習を行うようにする

 

・線形回帰とは(※1より)

 回帰分析とは、ある変数を用いて他の変数を説明(予測)するモデルを作ること

 例えば、ある日のアイスコーヒーの売上高をその日の気温で説明したいというときに使う

 上記は、(アイスコーヒーの売上高)=(ある定数)+(ある係数)✕(気温)といった式で、

 その日の気温からそのアイスコーヒーの売上高を説明するモデルを作ることができる

 アイスコーヒの売上高を「目的変数」、気温を「説明変数」という

 気温とアイスコーヒーの売上高の関係性を図にマッピングした時、各点からの誤差が小さいような回帰線を作ることで線形モデルを作る事が

 できる。これを最小二乗法という

 説明変数が1つのときを単回帰線分析、説明変数が複数あるときを重回帰分析とよぶ

 

・線形回帰の注意点(※1より)

 変数が多くなれば売上高を正確に説明できる可能性も高まるが、あまりにも変数同士の相関が高い変数(例えば摂氏と華氏など)を用いてし

 まうと多重共線性という問題が起きてモデルが安定しなくなる

 そのモデルによってどれだけ目的変数を説明することができるかを示す指標を寄与率という。変数を増やすと寄与率が高くなる。

 寄与率は、現状のデータへどれだけフィッティングしたモデルを作れているかを示しているが、現状のデータへのフィッティングを過剰に求

 め過ぎると、未知データを上手く予測できないモデルになってしまう(過学習)

 AICはデータ数 ✕ log(1-寄与率)+2✕(説明変数の数)で表し、AICが低ければ低いほど良いモデルであると評価される

 無駄に説明変数を増やしすぎるとAICが増加してしまう構造になっている

・処理内容

 1行目 2018年4月以降二新規に入った顧客に絞り込んでいる

 2行目 sklearnで線形回帰を行うためのライブラリ

 3行目 学習用データと評価用データに分割するためのライブラリ

 4行目 モデルの初期化

 5行目 予測に使う変数(説明変数)を定義

 6行目 予測したい変数(目的変数)を定義

 7行目 train_test_split( )は、学習用データと評価用データに分割。

                  分割の比率は無指定の場合、学習用データ75%、評価用データ25%

 8行目 学習用データを用いてモデルを作成

変数 学習 評価
説明変数 X_train X_test
目的変数 y_train y_test
  75% 25%

 

・7行目の出力:

X_train(説明変数.学習), X_test(説明変数.評価),

y_train(目的変数.学習), y_test(目的変数.評価)


 

・学習用データと評価用データ(※2も参照)

 学習に用いたデータに過剰適合してしまうと、未知なデータに対応できなくなる(過学習)

 そのため、学習用データで学習を行い、モデルにとって未知のデータである評価用データで精度の検証を行う

 

・参考先

 線形回帰分析  :【10分で分かる】回帰分析について解説!線形回帰分析を基本に少しだけ応用手法も触れおこう! youtube※1)

 線形回帰分析  :第3章 機械学習(教師あり学習)サイト※1)

 train_test_split  :train_test_splitでデータ分割を行う【sklearn】 サイト(※2)

1 predict_data = predict_data.loc[predict_data["start_date"]>=pd.to_datetime("20180401")]
2 from sklearn import linear_model
3 import sklearn.model_selection
4 model = linear_model.LinearRegression()
5 X = predict_data[["count_0","count_1","count_2","count_3","count_4","count_5","period"]]
6 y = predict_data["count_pred"]
7 X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X,y)
8 model.fit(X_train, y_train)

ポイント

・構築したmodelに、学習用データと評価用データを渡すとscoreが算出される

・どちらもおよそ60%程度の精度を示している

・より詳細にモデルの評価を行う方法に残差プロットなどがある

・これで回帰予測モデルが構築できた

・変数を変えたり、データの抽出条件を変えたりすることで、精度も変わるのでいろいろ試してみるのが良い

print(model.score(X_train, y_train))
print(model.score(X_test, y_test))

0.6242678289581058

0.5612298022171376


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

ポイント

・精度の高い予測モデルを構築しても、それを説明できないと現場で運用する側は納得できず、導入が見送られることがある

・説明変数ごとに、モデルに寄与している変数の係数(寄与度)を出力

 

・実行結果

 count_0が最も大きく、過去に遡るほど係数が小さくなる傾向にあることがわかる。

 つまり、直近の利用回数が翌月の利用回数に大きく影響しているということ

coef = pd.DataFrame({"feature_names":X.columns, "coefficiet":model.coef_})
coef
  feature_names coefficiet
0 count_0 0.334927
1 count_1 0.172017
2 count_2 0.174583
3 count_3 0.206956
4 count_4 0.087693
5 count_5 0.055801
6 period 0.090430

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

ポイント

・顧客2人分の利用履歴データを作成(過去6ヶ月分の利用回数、会員期間はどちらも8ヶ月)

x1 = [3, 4, 4, 6, 8, 7, 8]  # 1ヶ月前, 2ヶ月前, 3ヶ月前, 4ヶ月前, 5ヶ月前, 6ヶ月前, 会員期間
x2 = [2, 2, 3, 3, 4, 6, 8]
x_pred = [x1, x2]
x_pred

[[3, 4, 4, 6, 8, 7, 8], [2, 2, 3, 3, 4, 6, 8]]  

 

ポイント

・modelを用いて予測を行う

・1人目は3.9回、2人目は2.0回来ると予測された

・実際には、構築したモデルはシステムに組み込まれ、当月が締まった段階で、過去のデータから予測が行われ翌月の利用回数を一斉に集計する

 仕組みになると考えられる

・自動で予測が行えることで、翌月に行う様々な施策に活用していくこができる

model.predict(x_pred)

array([3.90828584, 2.0039648 ])


・uselog_monthsをcsv出力

uselog_months.to_csv("use_log_months.csv", index=False)

出力データサンプル ----------------------
年月,customer_id,count 201804,AS002855,4 201804,AS009013,2 201804,AS009373,3

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


この章の目的

・本章では、既に退会してしまった顧客と継続して利用している顧客のデータを用いて、どういった顧客が退会してしまうのかを予測する流れを、

 「決定木」と呼ばれる教師あり学習の分類アルゴリズムを用いて学ぶ

 

・決定木(※1より)

 シンプルな手法だが、わかりやすく原因の分析を行うことができることから、ビジネスの現場では頻繁に用いられる

 木構造でデータを部類する手法(目的変数:会員OR非会員の判別を例にすると、顧客→性別で分類(男か女か)→年齢で分類(子供か大人か))

 部類するときの箱(例の男、女)をノード、最後のノード(それ以上分類されない)をターミナルノードという

 決定木には分類手法によっていくつかのアルゴリズムがあり、CART(カート)が最も一般的

 決定木は、予測における初期分析や、現状のデータの構造把握などに用いられる(例えばECサイトで顧客が購入する/しないのモデルを作成する

 場合、行動データ(購買履歴やウェブ閲覧履歴など)や属性データ(年齢や性別など)などから、男性で20代で閲覧回数が月10回以上は購入す

 ると判別するなどと「分類ルールを可視化」することができる)

 決定木単体では高い精度は見込めないが、決定木をアンサンブル学習したランダムフォレストやxgboost(エックスジーブースト)と組み合わせる

 ことで最強の精度をたたき出せる

 

・参考先

 決定木  :【10分で分かる!】決定木とは?利用場面やランダムフォレスト・Xgboostなどの応用手法についても見ていこう! youtube※1)


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

ポイント

・ノック36と類似した処理

・当月とその1ヶ月前の利用回数を集計したデータを作成。count_0は当月、count_1はcount_0の1ヶ月前

・ほんの数ヶ月で辞めてしまう顧客も多いので、過去6ヶ月分のデータからの予測では意味がない。なので当月と前月のデータを利用する

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

year_months = list(uselog_months["年月"].unique())
uselog = pd.DataFrame()
for i in range(1, len(year_months)):
    tmp = uselog_months.loc[uselog_months["年月"]==year_months[i]]
    tmp.rename(columns={"count":"count_0"}, inplace=True)
    tmp_before = uselog_months.loc[uselog_months["年月"]==year_months[i-1]]
    del tmp_before["年月"]
    tmp_before.rename(columns={"count":"count_1"}, inplace=True)
    tmp = pd.merge(tmp, tmp_before, on="customer_id", how="left")
    uselog = pd.concat([uselog, tmp], ignore_index=True)
uselog.head()
  年月 customer_id count_0 count_1
0 201805 AS002855 5 4.0
1 201805 AS009373 4 3.0
2 201805 AS015233 7 NaN
3 201805 AS015315 3 6.0
4 201805 AS015739 5 7.0

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

ポイント

・このジムは、月末までに退会申請を提出することで、翌月に退会できる。例えば、2018年9月30日で退会した顧客がいたとした場合は8月には退会

 申請を出しており、9月のデータを用いても退会を未然に防ぐことはできない。そこで、退会月を2018年8月として、その1ヶ月前の7月のデータ

 から8月に退会申請を出す確率を予測する

・まず、退会した顧客に絞り込み、end_date列の1ヶ月前の年月を取得し、ノック41で整形したuselogとcustomer_id、年月をキーにして結合する

 

・処理内容

   5行目 end_dateの1ヶ月前の日付をexit_dateに設定

   9行目 exit_date(退会前月)の年月とcustomer_idをキーにしてuselogをベースにした結合

 12行目 退会前月データと結合できないデータを除去

 1 from dateutil.relativedelta import relativedelta
 2 exit_customer = customer.loc[customer["is_deleted"]==1]
 3 exit_customer["exit_date"] = None
 4 exit_customer["end_date"] = pd.to_datetime(exit_customer["end_date"])
 5 for i in range(len(exit_customer)):
       exit_customer["exit_date"].iloc[i] = exit_customer["end_date"].iloc[i] - relativedelta(months=1)
 6 exit_customer["exit_date"] = pd.to_datetime(exit_customer["exit_date"])
 7 exit_customer["年月"] = exit_customer["exit_date"].dt.strftime("%Y%m")
 8 uselog["年月"] = uselog["年月"].astype(str)
 9 exit_uselog = pd.merge(uselog, exit_customer, on=["customer_id", "年月"], how="left")

12 exit_uselog = exit_uselog.dropna(subset=["name"]) 13 print(len(exit_uselog)) 14 print(len(exit_uselog["customer_id"].unique())) 15 exit_uselog.head()

1104

1104

  年月 customer_id count_0 count_1 name class gender start_date end_date campaign_id ... price campaign_name mean median max min routine_flg calc_date membership_period exit_date
19 201805 AS055680 3 3.0 XXXXX C01 M 2018-03-01 2018-06-30 CA1 ... 10500.0 通常 3.000000 3.0 3.0 3.0 0.0 2018-06-30 3.0 2018-05-30
57 201805 AS169823 2 3.0 XX C01 M 2017-11-01 2018-06-30 CA1 ... 10500.0 通常 3.000000 3.0 4.0 2.0 1.0 2018-06-30 7.0 2018-05-30
110 201805 AS305860 5 3.0 XXXX C01 M 2017-06-01 2018-06-30 CA1 ... 10500.0 通常 3.333333 3.0 5.0 2.0 0.0 2018-06-30 12.0 2018-05-30

5 rows × 22 columns


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

ポイント

・退会顧客データの方が少ないので、継続顧客データをアンダーサンプリングする

 

・アンダーサンプリング

 少数派のデータ件数に合うように多数派データからランダムに抽出する方法。下記の4~5行目

 

 

・4行目 全件シャッフルしてインデックスを初期化

・5行目 customer_idが重複しているデータは最初のデータのみ取得

・6行目 継続顧客データと退会顧客データを縦に結合

1 conti_customer = customer.loc[customer["is_deleted"]==0]
2 conti_uselog = pd.merge(uselog, conti_customer, on=["customer_id"], how="left")
3 conti_uselog = conti_uselog.dropna(subset=["name"])
4 conti_uselog = conti_uselog.sample(frac=1).reset_index(drop=True)
5 conti_uselog = conti_uselog.drop_duplicates(subset="customer_id")
6 predict_data = pd.concat([conti_uselog, exit_uselog],ignore_index=True)

7 predict_data.loc[:,["年月","customer_id","count_0","count_0","end_date","exit_date"]].sample(frac=1).head()
  年月 customer_id count_0 count_0 end_date exit_date
2556 201812 OA764380 4 4 NaN NaT
1347 201812 OA377888 8 8 NaN NaT
2123 201808 HD888412 7 7 NaN NaT
3700 201901 AS660812 3 3 2019-02-28 00:00:00 2019-01-28
3937 201902 TS486027 2 2 2019-03-31 00:00:00 2019-02-28

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

ポイント

・ノック37と同様

・start_dateから年月を変換したnow_dateまでの期間をperiodとして追加。この値を機械学習の説明変数として使用する

predict_data["period"] = 0
predict_data["now_date"] = pd.to_datetime(predict_data["年月"], format="%Y%m")
predict_data["start_date"] = pd.to_datetime(predict_data["start_date"])
for i in range(len(predict_data)):
    delta = relativedelta(predict_data["now_date"][i], predict_data["start_date"][i])
    predict_data["period"][i] = int(delta.years*12 + delta.months)
predict_data.head()

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

ポイント

・ノック36と同様に、機械学習は欠損値があると対応できないので必ず除去する

・isna( )は、欠損値かどうか判定

・end_date、exit_date、count_1に欠損値がある

predict_data.isna().sum()

ポイント

・end_date、exit_dateは退会顧客データしか持っていない

・継続顧客データ、退会顧客データの両方が持っているcount_1が欠損しているデータで欠損値データを除外

predict_data = predict_data.dropna(subset=["count_1"])

ノック46:文字列型の変数を処理できるように整形しよう

ポイント

・性別などのカテゴリー関連のデータをカテゴリカル変数という(入会キャンペーン区分、会員区分、性別などの選択肢)

・ノック26で定期利用フラグ(routine_flg)を作成したようにフラグ化することをダミー変数化という

・今回の予測に使用するデータに絞り込む

 説明変数 ・・・ 1ヶ月前の利用回数、入会キャンペーン区分、会員区分、性別、定期利用フラグ、在籍期間

 目的変数 ・・・ 退会フラグ

target_col = ["campaign_name","class_name","gender","count_1","routine_flg","period","is_deleted"]
predict_data = predict_data[target_col]
predict_data.head()
  campaign_name class_name gender count_1 routine_flg period is_deleted
0 入会費半額 デイタイム F 4.0 1.0 15 0.0
1 通常 オールタイム F 8.0 1.0 5 0.0
2 通常 オールタイム M 7.0 1.0 21 0.0
3 通常 オールタイム M 8.0 1.0 21 0.0
4 通常 オールタイム M 6.0 1.0 11 0.0

 

ポイント

・get_dummies( )は、一括でダミー変数化を行う

predict_data = pd.get_dummies(predict_data)
predict_data.head()
  period is_deleted campaign_name_入会費半額 campaign_name_入会費無料 campaign_name_通常 class_name_オールタイム class_name_デイタイム class_name_ナイト gender_F gender_M
0 4 0.0 0 0 1 0 0 1 0 1
1 21 0.0 0 0 1 1 0 0 1 0
2 31 0.0 0 0 1 0 1 0 1 0
3 32 0.0 0 0 1 1 0 0 1 0
4 6 0.0 0 1 0 1 0 0 0 1

 

ポイント

・「データ分析は、データ加工が8割」とよく言われる

・会員区分は入会費半減でも入会費無料でもなければ通常会員であると判断できる(campaign_name_通常列は不要)

 gender_Fだけで性別は判断できる(gender_M列は不要)。よって各組1列を削除する

predict_data = pd.get_dummies(predict_data)
del predict_data["campaign_name_通常"]
del predict_data["class_name_ナイト"]
del predict_data["gender_M"]
predict_data.head()
  count_1 routine_flg period is_deleted campaign_name_入会費半額 campaign_name_入会費無料 class_name_オールタイム class_name_デイタイム gender_F
0 6.0 1.0 12 0.0 1 0 1 0 0
1 6.0 1.0 7 0.0 0 1 0 1 0
2 5.0 1.0 34 0.0 0 0 1 0 0
3 5.0 1.0 44 0.0 0 0 1 0 0
4 4.0 1.0 32 0.0 0 0 0 0 0

ノック47:決定木を用いて退会予測モデルを作成しよう

ポイント

・処理内容

 1行目 決定木を使用するためのライブラリ

 4行目 退会と継続のデータ件数を揃えている。継続のデータからランダムに抽出して50対50になるようにしている

 6行目 is_deleted変数を目的変数の y として設定

 7行目 is_deleted変数を削除したデータを説明変数の X として設定

 以外の行は、ノック38を参照

 

・出力結果

 1 が退会、0 は継続と予測されたのを意味する

 1 from sklearn.tree import DecisionTreeClassifier
 2 import sklearn.model_selection
 3 exit = predict_data.loc[predict_data["is_deleted"]==1]
 4 conti = predict_data.loc[predict_data["is_deleted"]==0].sample(len(exit))
 5 X = pd.concat([exit, conti], ignore_index=True)
 6 y= X["is_deleted"]
 7 del X["is_deleted"]
 8 X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X,y)
 9 model = DecisionTreeClassifier(random_state=0)
10 model.fit(X_train, y_train)
11 y_test_pred = model.predict(X_test)
12 print(y_test_pred)

[0. 0. 0. 1. 0. 0. 1. 0. 0. 0. 0. 0. 1. 0. 1. 0. 1. 1. 1. 1. 0. 1. 0. 0.

0. 1. 1. 1. 0. 0. 1. 0. 1. 0. 0. 0. 1. 0. 1. 0. 1. 0. 1. 1. 1. 1. 0. 0.

0. 1. 0. 1. 1. 0. 0. 1. 1. 0. 1. 0. 1. 0. 1. 0. 0. 0. 0. 1. 0. 1. 1. 1.

1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 1. 1. 0. 1. 0. 0. 0. 0. 0. 0. 1. 0. 0. 1.

0. 1. 1. 1. 0. 1. 0. 0. 0. 1. 0. 0. 0. 0. 1. 0. 1. 1. 0. 0. 0. 1. 1. 1.

0. 0. 1. 1. 0. 1. 1. 0. 0. 0. 0. 0. 0. 1. 1. 1. 0. 0. 1. 0. 0. 1. 0. 0.

1. 1. 1. 0. 0. 1. 1. 1. 1. 1. 0. 0. 0. 1. 1. 1. 1. 1. 0. 0. 1. 0. 0. 1.

1. 0. 0. 0. 0. 0. 1. 1. 1. 1. 1. 1. 1. 0. 0. 1. 0. 0. 0. 0. 0. 1. 1. 1.

1. 0. 0. 0. 0. 0. 1. 0. 0. 1. 0. 1. 1. 1. 1. 1. 0. 1. 0. 1. 1. 0. 1. 0.

1. 1. 1. 0. 0. 1. 0. 1. 0. 1. 1. 0. 1. 0. 1. 1. 1. 1. 0. 1. 0. 0. 1. 0.

1. 1. 1. 1. 0. 0. 0. 0. 1. 0. 0. 0. 1. 1. 0. 1. 0. 1. 0. 1. 1. 0. 0. 0.

0. 1. 1. 1. 0. 1. 0. 0. 0. 0. 0. 0. 1. 0. 1. 1. 0. 1. 0. 1. 0. 0. 1. 1.

1. 1. 1. 0. 0. 0. 0. 1. 1. 0. 0. 1. 1. 1. 1. 0. 0. 0. 1. 0. 0. 0. 0. 0.

1. 1. 1. 1. 0. 1. 1. 0. 1. 1. 1. 0. 1. 0. 0. 1. 1. 1. 1. 0. 0. 0. 1. 1.

1. 1. 1. 0. 0. 0. 0. 0. 1. 0. 0. 0. 1. 1. 0. 0. 0. 0. 1. 1. 1. 0. 0. 1.

0. 0. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 1. 1. 1. 0. 1. 1. 0. 0.

0. 1. 0. 0. 1. 0. 1. 1. 0. 1. 0. 1. 1. 0. 1. 0. 1. 1. 1. 0. 0. 0. 0. 1.

0. 1. 0. 0. 1. 1. 1. 0. 0. 0. 1. 1. 1. 1. 0. 0. 0. 0. 1. 1. 1. 0. 0. 1.

1. 0. 0. 1. 0. 0. 1. 1. 0. 0. 0. 1. 0. 0. 1. 0. 1. 0. 0. 0. 1. 0. 1. 0.

1. 0. 0. 0. 0. 1. 0. 0. 0. 1. 0. 1. 0. 0. 1. 1. 1. 1. 1. 0. 0. 0. 0. 1.

0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 1. 1. 1. 0. 1. 0. 0. 0. 1. 0. 0. 0. 1. 1.

1. 0. 1. 1. 0. 0. 0. 1. 0. 0. 1. 0. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0.]

 

ポイント

・実際に正解との比較を行う

 y_test(目的変数の評価用データ。正解データ) y_test_pred(X_test(説明変数の評価用データ)の予測結果)

 正解との比較を行うため、データフレームに格納

results_test = pd.DataFrame({"y_test":y_test ,"y_pred":y_test_pred})
results_test.head()
  y_test y_pred
1493 0.0 0.0
2030 0.0 0.0
850 1.0 0.0
858 1.0 1.0
1244 0.0 0.0

ノック48:予測モデルの評価を行い、モデルのチューニングをしてみよう

ポイント

・result_testデータを集計して正解率を算出

・正解しているデータは、result_testデータのy_test列とy_pred列が一致しているデータ件数。その件数を全体のデータ件数で割ったものが正解率

・出力結果は、0.889 つまり89%程度の精度

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)

0.8897338403041825

 

ポイント

・学習用データで予測した精度と評価用データで予測した精度の差が小さいのが理想

・理想に近いか、score( )を用いて学習用と評価用の精度を算出して確認

・結果、学習用データを用いた場合は98%、評価用データを用いた予測精度は89%。学習用データに適合しすぎているので過学習傾向にある

print(model.score(X_test, y_test))
print(model.score(X_train, y_train))

0.8897338403041825

0.9771863117870723

 

ポイント

・モデルのパラメータ(max_depth)で木構造の深さを浅くしてみる。浅くすることでモデルは簡易化できる

X = pd.concat([exit, conti], ignore_index=True)
y= X["is_deleted"]
del X["is_deleted"]
X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X,y)
model = DecisionTreeClassifier(random_state=0, max_depth=5)
model.fit(X_train, y_train)
print(model.score(X_test, y_test))
print(model.score(X_train, y_train))

0.9334600760456274

0.9283903675538656


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

ポイント

・ノック39の回帰の時とは違い、model.feature_importances_で重要変数を取得できる

・ここでは扱わないが、決定木は木構造の可視化も行うことができる。木構造の可視化を行うことでより直感的にモデルの理解が可能となり、

 説明もしやすくなる。graphviz などのライブラリを用いれば比較的簡単に描画できる

importance = pd.DataFrame({"feature_names":X.columns, "coefficiet":model.feature_importances_})
importance
  feature_names coefficiet
0 count_1 0.356015
1 routine_flg 0.128628
2 period 0.510894
3 campaign_name_入会費半額 0.001536
4 campaign_name_入会費無料 0.000000
5 class_name_オールタイム 0.000000
6 class_name_デイタイム 0.000000
7 gender_F 0.002926

ノック50:顧客の退会を予測しよう

ポイント

・入力情報

 1ヶ月前の利用回数(count_1):3回

 定期利用者(routing_flg):1

 在籍期間(period):10

 キャンペーン区分(campaign_name):入会非無料

 会員区分(classs_name):オールタイム

 性別(gender):M(男性)

・カテゴリカル変数を if文で分岐し、ダミー変数を作成し、1つのリストに格納

・extend( )は、リストの末尾に新たな要素を追加する

count_1 = 3
routing_flg = 1
period = 10
campaign_name = "入会費無料"
class_name = "オールタイム"
gender = "M"

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, routing_flg, period] input_data.extend(campaign_name_list) input_data.extend(class_name_list) input_data.extend(gender_list)

ポイント

・作成したデータをもとに予測を実施し、1(退会)が予測された

・予測は、1か0の結果だけでなく、予測確率で表すこともできる。今回は 98% の確率で退会と予測された

print(model.predict([input_data]))
print(model.predict_proba([input_data]))

[1.]

[[0.01204819 0.98795181]]


ノックXX:

ポイント

predict_data.isna().sum()