【Python】実践データ分析100本ノック 第4章

2024/03/13に公開

この記事は、現場で即戦力として活躍することを目指して作られた現場のデータ分析の実践書である「Python 実践データ分析 100本ノック(秀和システム社)」で学んだことをまとめています。

ただし、本を参考にして自分なりに構成などを変更している部分が多々あるため、ご注意ください。細かい解説などは是非本をお手に取って読まれてください。

目的

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

【リンク紹介】
【一覧】Python実践データ分析100本ノック
これまで書いたシリーズ記事一覧

Import

%%time

import pandas as pd
from colorama import Fore, Style, init                # Pythonの文字色指定ライブラリ
from IPython.display import display_html, clear_output
from gc import collect                                # ガーベッジコレクション
import matplotlib.pyplot as plt
%matplotlib inline

from sklearn import linear_model
import sklearn.model_selection
from sklearn.cluster import KMeans                    # K-means法
from sklearn.preprocessing import StandardScaler      # 標準化によるスケーリング
from sklearn.decomposition import PCA                 # 主成分分析
from dateutil.relativedelta import relativedelta      # 日付に月単位で計算をする

# 警告(worning)の非表示化→ただし実際の業務では悪手となるので非推奨
#import warnings
#warnings.filterwarnings('ignore')

[注意]
今回Knock32のコードでwarningが出てしまうので、対策としてQiitaの@mickie895さんの記事を参考にさせてもらい、回避することとしました。

%%time

# テキスト出力の設定
def PrintColor(text:str, color = Fore.GREEN, style = Style.BRIGHT):
    print(style + color + text + Style.RESET_ALL);

# displayの表示設定
pd.set_option('display.max_columns', 50);
pd.set_option('display.max_rows', 50); 

print()
collect()

Knock431:データの読み込み

必要なデータを読み込んでいきます。

なお、テキストのデータダウンロードは以下に格納されています。

https://www.shuwasystem.co.jp/support/7980html/6727.html

%%time

uselog   = pd.read_csv('use_log.csv')
customer = pd.read_csv('customer_join.csv')

print()
collect()

読み込んだデータの欠損値を確認します。

%%time

PrintColor(f'\n uselog.isnull().sum()')
display(uselog.isnull().sum())

PrintColor(f'\n customer.isnull().sum()')
display(customer.isnull().sum())

print()
collect()

Knock32:クラスタリングで顧客をグループ化する

クラスタリングに用いる変数として、mean, median, max, min, membership_periodを採用する。

%%time

customer_clustering = customer[['mean', 'median', 'max', 'min', 'membership_period']]

# 確認
PrintColor(f'\n customer_clustering')
display(customer_clustering.head())

print()
collect()

K-means法を用いてクラスタリングを行います。まずはクラスタリングを行うデータを標準化を用いてスケーリングします。

%%time

# 標準化
sc                     = StandardScaler()                         # インスタンス化
customer_clustering_sc = sc.fit_transform(customer_clustering)    # 学習し、標準化を行う

# K-means法
kmeans              = KMeans(n_clusters   = 4,                    # クラスタ数を4に指定
                             random_state = 0,
                             n_init       = 'auto'                # 設定しないとwarningが出る
                            )
clusters            = kmeans.fit(customer_clustering_sc)          # クラスタリングの実行
customer_clustering = customer_clustering.assign(cluster = clusters.labels_)    # 実行結果を格納

print()
collect()

ユニーク値と格納データを確認します。

%%time

PrintColor(f"\n customer_clustering['cluster'].unique()")
display(customer_clustering['cluster'].unique())

PrintColor(f"\n customer_clustering")
display(customer_clustering.head())

print()
collect()

Knock:33クラスタリング結果を分析する

まずは、データ件数を確認します。

%%time

# カラム名も少し変更する
customer_clustering.columns = ['月内平均値','月内中央値', '月内最大値', '月内最小値', '会員期間', 'cluster']

# クラスターごとにグループ分けしてデータ件数を出力する
PrintColor(f"\n customer_clustering.groupby('cluster').count()")
display(customer_clustering.groupby('cluster').count())

# クラスターごとにグループ分けして平均値を算出する
PrintColor(f"\n customer_clustering.groupby('cluster').mean()")
display(customer_clustering.groupby('cluster').mean())

print()
collect()

Knock34:クラスタリング結果を可視化する

今回クラスタリングで採用した変数が5つであり、通常なら5次元としてみなければなりませんが、主成分分析を用いて次元削除を行い2次元上に描画していきます。

%%time

X   = customer_clustering_sc
pca = PCA(n_components = 2)    # インスタンス化

pca.fit(X)
x_pca             = pca.transform(X)
pca_df            = pd.DataFrame(x_pca)
pca_df['cluster'] = customer_clustering['cluster']    # ラベリングするための列を追加

print()
collect()
%%time

for i in customer_clustering['cluster'].unique():
    tmp = pca_df.loc[pca_df['cluster'] == i]
    plt.scatter(tmp[0], tmp[1])

# グラフを保存
plt.savefig('fig34.png')

print()
collect()

Knock35:クラスタリング結果をもとに退会顧客の傾向を把握する

%%time

customer_clustering = pd.concat([customer_clustering, customer], axis = 1)

display(customer_clustering.groupby(['cluster', 'is_deleted'], as_index = False)\
                           .count()[['cluster', 'is_deleted', 'customer_id']]
       )

print()
collect()
%%time

display(customer_clustering.groupby(['cluster', 'routine_flg'], as_index = False)\
                           .count()[['cluster', 'routine_flg', 'customer_id']]
       )

print()
collect()

Knock36:翌月の利用回数予測を行うための準備を行う

当月を2018年10月とし、顧客の過去の行動データから翌月2018年11月の利用回数を予測して行きます。予測にあたり、今回は回帰を用いることとします。

まずはuselogデータを用いて年月、顧客毎に集計を行います。

この操作はKnock8Knock25でも行いました。

%%time

# object型をdatetime型に変換
uselog['usedate'] = pd.to_datetime(uselog['usedate'])
# dt.strftimeっを用いて年月を抽出し、別の列として作成
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']

print()
collect()
%%time

# 確認用
PrintColor(f'\n uselog_months')
display(uselog_months)

print()
collect()

このデータに当月2018年10月から過去5ヶ月分の利用回数と、翌月の利用回数を付与していきます。

%%time

year_months  = list(uselog_months['年月'].unique())

# 確認用
PrintColor(f'\n year_month')
display(year_months)

print()
collect()
%%time

# uselog_monthsデータと付与するデータを結合したデータを格納するDataFrameを用意する
predict_data = pd.DataFrame()

# 2018年10月から半年間の月を一つずつ取り出す
for i in range(6, len(year_months)):
    tmp = uselog_months.loc[uselog_months['年月'] == year_months[i]].copy()
    tmp.rename(columns = {'count' : 'count_pred'}, inplace = True)
    
    # リストの最初から6番目までをjに渡す。つまり2018年4月~2018年10月
    for j in range(1, 7):
        tmp_before = uselog_months.loc[uselog_months['年月'] == year_months[i - j]].copy()

        del tmp_before['年月']
        
        tmp_before.rename(columns = {'count' : 'count_{}'.format(j - 1)}, inplace = True)
        # 横方向に結合し、列をループのたびに増やしていく
        tmp = pd.merge(tmp,
                       tmp_before,
                       on  = 'customer_id',
                       how = 'left'
                      )

    # 集計が済むたびに縦に結合していく
    predict_data = pd.concat([predict_data, tmp], ignore_index = True)

print()
collect()
%%time

PrintColor(f'\n predict_data')
display(predict_data.head())

print()
collect()

今回欠損値を持つレコードは除外します。

%%time

predict_data = predict_data.dropna()
predict_data = predict_data.reset_index(drop = True)

PrintColor(f'\n predict_data')
display(predict_data)

print()
collect()

Knock37:特徴となる変数を付与する

まずcustomerのstart_date列をpredict_dataに付与します。

%%time

predict_data = pd.merge(predict_data,
                        customer[['customer_id', 'start_date']],
                        on  = 'customer_id',
                        how = 'left'
                       )

PrintColor(f'\n predict_data')
display(predict_data.head())

print()
collect()

次に年月とstart_dateの差から、会員期間を月単位で作成します。

月の計算はKnock28でも用いたrelativedeltaを用います。

%%time

predict_data['now_date']   = pd.to_datetime(predict_data['年月'], format = '%Y%m')
predict_data['start_date'] = pd.to_datetime(predict_data['start_date'])

predict_data['period'] = 0  # テキストはNoneを指定しているが、Knock28と同様に0で初期化した

for i in range(len(predict_data)):
    delta = relativedelta(predict_data.loc[i, 'now_date'], predict_data.loc[i, 'start_date'])
    predict_data.loc[i, 'period'] = delta.years * 12 + delta.months

PrintColor(f'\n predict_data')
display(predict_data.head())

print()
collect()

Knock38:来月の利用回数予測モデルを作成する

データを学習用データと評価(テスト)用データに分割して、学習を行います。

今回使用するモデルは、scikit-learnのLinearRegression(線形回帰モデル)です。

%%time

predict_data = predict_data.loc[predict_data['start_date'] >= pd.to_datetime('20180401')]

# 線形回帰モデルをインスタンス化
model = linear_model.LinearRegression()

# 説明変数を定義
X = predict_data[['count_0',
                  'count_1',
                  'count_2',
                  'count_3',
                  'count_4',
                  'count_5',
                  'period',
                 ]]

# 目的変数を定義
y = predict_data['count_pred']

X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, random_state = 0)
model.fit(X_train, y_train)

print()
collect()
%%time

# 学習用データでの精度を出す
PrintColor(f'\n train_score')
print(model.score(X_train, y_train))

# テストデータでの精度を出す
PrintColor(f'\n test_score')
print(model.score(X_test, y_test))

print()
collect()

Knock39:モデルに寄与している変数を確認する

%%time

coef = pd.DataFrame({'feature_names' : X.columns, 'coefficient' : model.coef_})

display(coef)

print()
collect()

Knock40:来月の利用回数を予測する

%%time

# 予測するデータを2つ用意する
x1 = [3, 4, 4, 6, 8, 7, 8]
x2 = [2, 2, 3, 3, 4, 6, 8]

x_pred = pd.DataFrame(data = [x1, x2],
                      columns = ['count_0',
                                 'count_1',
                                 'count_2',
                                 'count_3',
                                 'count_4',
                                 'count_5',
                                 'period',
                                ],
                     )
PrintColor(f'\n predict')
display(model.predict(x_pred))

print()
collect()

\bf{\textcolor{red}{記事が役に立った方は「いいね」を押していただけると、すごく喜びます \ 笑}}
ご協力のほどよろしくお願いします。

Discussion