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

2024/03/09に公開

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

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

目的

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

【リンク紹介】
【一覧】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 dateutil.relativedelta import relativedelta      # 日付に月単位で計算をする
%%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()

Knock21:データの読み込み

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

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

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

ここでは第3章で用いるデータをまとめて読み込んでいるため、テキストとは大きく異なるのでご注意ください。

%%time

uselog          = pd.read_csv('use_log.csv')
customer        = pd.read_csv('customer_master.csv')
class_master    = pd.read_csv('class_master.csv')
campaign_master = pd.read_csv('campaign_master.csv')

print()
collect()

読み込んだデータを表示してみます。

%%time

PrintColor(f'\n uselog')
print(len(uselog))
display(uselog.head())

PrintColor(f'\n customer')
print(len(customer))
display(customer.head())

PrintColor(f'\n class_master')
print(len(class_master))
display(class_master.head())

PrintColor(f'\n campaign_master')
print(len(campaign_master))
display(campaign_master.head())

print()
collect()

Knock22:顧客データを整理する

customerに会員区分のclass_masterとキャンペーン区分のcampaign_masterを結合する。

%%time

# customerとclass_masterを結合する
customer_join = pd.merge(customer,
                         class_master,
                         on  = 'class',
                         how = 'left',
                        )

# 次に、customerとcampaign_masterを結合する
customer_join = pd.merge(customer_join,       # customerにしないように注意
                         campaign_master,
                         on  = 'campaign_id',
                         how = 'left'
                        )

print()
collect()

結合結果を確認します。同時に結合前と結合後のデータの個数も出力し、変化していないことを確認します。

%%time

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

print(f'書き換え前:{len(customer)}')
print(f'書き換え後:{len(customer_join)}')

print()
collect()

次に欠損値の確認をします。

%%time

customer_join.isnull().sum()

Knock23:顧客データの基礎集計をする

顧客データを集計し、全体像を見ます。

%%time

# class_nameごとにグループ分けし、それぞれのグループのcustomer_idの個数をカウントする
customer_join.groupby('class_name').count()['customer_id']
%%time

# campagin_nameごとにグループ化し、それぞれのグループのcustomer_idの個数をカウントする
customer_join.groupby('campaign_name').count()['customer_id']
%%time

# genderごとにグループ化し、それぞれのグループのcustomer_idの個数をカウントする 
customer_join.groupby('gender').count()['customer_id']

%%time

# is_deletedごとにグループ化し、それぞれのグループのcustomer_idの個数をカウントする
customer_join.groupby('is_deleted').count()['customer_id']

次に、2018年4月1日から2019年3月31日までの入会人数を集計します。

%%time

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

PrintColor(f'\n len(customer_start)')
display(len(customer_start))

print()
collect()

Knock24:最新顧客データの基礎集計をする

最新月の顧客データの把握を行っていく。まず、最新月のユーザーのみに絞ります。

※単純にend_dateが欠損している人だけ選べばよいと思うかもしれませんが、サービスの規約として、

「月末までに申告することで、来月末に退会することができる」

というものがあります。つまり、月末に申請してしまうと来月までには退会できないため、最新月にカウントしなければなりません。

%%time

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())]

PrintColor(f'\n len(customer_newer)')
print(len(customer_newer))

print()
collect()

データの中身を確認する

%%time

PrintColor(f"\n customer_newer['end_date'].unique()")
display(customer_newer['end_date'].unique())

print()
collect()

次に会員区分、キャンペーン区分、、性別ごとに全体の数を把握する。

%%time

customer_newer.groupby('class_name').count()['customer_id']
%%time

customer_newer.groupby('campaign_name').count()['customer_id']
%%time

customer_newer.groupby('gender').count()['customer_id']

Knock25:利用履歴データを集計する

月利用回数の平均値、中央値、最大値、最小値と定期的に利用しているかのフラグを作成し、顧客データに追加します。
この操作はKnock8でも行いました。

%%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']

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

print()
collect()

さらに顧客ごとに絞り込んで平均値、中央値、最大値、最小値を集計します。

%%time

uselog_customer = uselog_months.groupby('customer_id').agg(['mean', 'median', 'max', 'min'])['count']
uselog_customer = uselog_customer.reset_index(drop = False)

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

print()
collect()

Knock26:利用履歴データから定期利用フラグを作成する

定期利用している顧客を特定していきます。例えば毎週同じ曜日に来ている顧客に着目します。

そこで、顧客ごとに月、曜日別に集計を行い、最大値が4以上の曜日が1ヶ月ででもあった顧客にフラグを立て、1とします。

%%time

# 曜日を数値化して格納する。月曜日が0、日曜日が6
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)

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

print()
collect()

【出力結果の意味について】

groupbyにて、'customer_id'毎に分けたものを、'年月'毎に分けさせ、

さらに'weekday'毎つまり0~6の数字毎に分けたうえで、他の列に対してカウントの指示を出しています。

(as_index = Falseによりindexは振り直しています)

よって、出力されたでーたの解釈として、例えばindex = 0のレコードは

「顧客:AS002855('customer_id')の2018年4月('年月')における土曜日('weekday'の5)の利用回数は、('log_id'のカウントを参考にすると)4回である」

というふうに読むことができます。このとき、利用回数としての参考列は'log_id'列ですが、

今回選出していない'usedate'列でも可能です。

次に顧客ごとの各月の最大値を取得し、最大値が4以上の場合はフラグを立てます。

%%time

uselog_weekday = uselog_weekday.groupby('customer_id', as_index = False)\
                                .max()\
                                [['customer_id', 'count']]

# フラグ列を作成
uselog_weekday['routine_flg'] = 0
# 条件('count'列が4未満)に対して偽であるならば、1に置き換える
uselog_weekday['routine_flg'] = uselog_weekday['routine_flg'].where(uselog_weekday['count'] < 4, 1)

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

print()
collect()

Knock27:顧客データと利用履歴データを結合する

uselog_customer, uselog_weekdayをcustomer_joinと結合します。

%%time

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'
                        )

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

print()
collect()

end_date列以外の列に欠損値がないか確認をします。

%%time

customer_join.isnull().sum()

Knock28:会員期間を計算する

会員期間の集計をするために、集計期間最終日を2019年4月30日とします。

%%time

# calc_date列を作成して'end_date'列の値で初期化
customer_join['calc_date'] = customer_join['end_date']
# 欠損値をdatetime型の20190430で補完する
customer_join['calc_date'] = customer_join['calc_date'].fillna(pd.to_datetime('20190430'))

# membership_period列を作成して0で初期化
customer_join['membership_period'] = 0
for i in range(len(customer_join)):
    # 加入年月から退会年月間の差分を年と月に分けて計算する
    delta = relativedelta(customer_join['calc_date'].iloc[i], customer_join['start_date'].iloc[i])
    # 会員期間を月単位で計算する
    customer_join.loc[i, 'membership_period'] = delta.years * 12 + delta.months


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

print()
collect()

Knock29:顧客行動の各種統計量を把握する

まずはcustomer_joinの

'mean(顧客の月内利用回数の平均)',

'median'(顧客の月ごとの利用回数の平均),

'max'(顧客の月ごとの利用回数の最大値),

'min(顧客の月ごとの利用回数の最小値)'

の基本統計量を確認します。

%%time

customer_join[['mean', 'median', 'max', 'min']].describe()

次に、routine_flgを集計します。

%%time

customer_join.groupby('routine_flg')\
             .count()\
             ['customer_id']

館員機関の分布をヒストグラムで描画します。

%%time

plt.hist(customer_join['membership_period'],
         bins = 100,
         #ec  = 'black' # bar毎に枠線を付けたい場合は指定する
        )

plt.xlabel('membership_period')
plt.ylabel('count')

Knock30:退会ユーザーと継続ユーザーの違いを把握する

まず退会ユーザーと継続ユーザーを分けて、descriveで比較します。

%%time

customer_end  = customer_join.loc[customer_join['is_deleted'] == 1]
customer_stay = customer_join.loc[customer_join['is_deleted'] == 0]

print()
collect()
%%time

PrintColor(f'\n 退会ユーザーの基本統計量')
display(customer_end.describe())

PrintColor(f'\n 継続ユーザーの基本統計量')
display(customer_stay.describe())

print()
collect()

customer_joinをcsvを出力します

%%time

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

print()
collect()

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

Discussion