(学習ログ)Python026:顧客データの結合・集計・利用パターン分析の基礎
1. はじめに
この記事では、複数のCSVファイルを結合し、顧客属性ごとの集計や利用ログの分析を行う方法を説明します。
実務でよく使われるデータ結合(pd.merge)、日付操作、グループ集計、そして顧客の「定期利用フラグ」を立てるロジックまで、一連の流れを実践的に解説します。
2. サンプルコード
コード全文
import pandas as pd
# ========== データ読み込み ==========
sample_usagelog = pd.read_csv('003_サンプル/usage_log.csv')
sample_customer_master = pd.read_csv('003_サンプル/customer_master.csv')
sample_class_master = pd.read_csv('003_サンプル/class_master.csv')
sample_campaign_master = pd.read_csv('003_サンプル/campaign_master.csv')
# ========== テーブル統合 ==========
# 顧客マスターにクラスマスターを結合
sample_customer_join = pd.merge(
sample_customer_master,
sample_class_master,
on="class",
how="left"
)
# さらにキャンペーンマスターを結合
sample_customer_join = pd.merge(
sample_customer_join,
sample_campaign_master,
on="campaign_id",
how="left"
)
# ========== 属性別の顧客数集計 ==========
sample_customer_join.groupby("class_name").count()["customer_id"]
sample_customer_join.groupby("campaign_name").count()["customer_id"]
sample_customer_join.groupby("gender").count()["customer_id"]
sample_customer_join.groupby("is_deleted").count()["customer_id"]
# ========== 日付列の変換と絞り込み ==========
# start_date列を文字列から日付型に変換
sample_customer_join["start_date"] = pd.to_datetime(sample_customer_join["start_date"])
# 2018年4月1日より後に開始した顧客のみを抽出
sample_customer_start = sample_customer_join.loc[
sample_customer_join["start_date"] > pd.to_datetime("20180401")
]
# 終了日での絞り込み(2019年3月31日以降または終了日がNULL)
sample_customer_join["end_date"] = pd.to_datetime(sample_customer_join["end_date"])
sample_customer_newer = sample_customer_join.loc[
(sample_customer_join["end_date"] >= pd.to_datetime("20190331")) |
(sample_customer_join["end_date"].isna())
]
# ========== 絞り込み後のデータ確認 ==========
sample_customer_newer["end_date"].unique()
sample_customer_newer.groupby("class_name").count()["customer_id"]
sample_customer_newer.groupby("campaign_name").count()["customer_id"]
sample_customer_newer.groupby("gender").count()["customer_id"]
# ========== 利用ログの年月集計 ==========
sample_usagelog["usedate"] = pd.to_datetime(sample_usagelog["usedate"])
sample_usagelog["年月"] = sample_usagelog["usedate"].dt.strftime("%Y%m")
# 年月×顧客IDごとに利用回数をカウント
sample_usagelog_months = sample_usagelog.groupby(
["年月", "customer_id"],
as_index=False
).count()
sample_usagelog_months.rename(columns={"log_id": "count"}, inplace=True)
del sample_usagelog_months["usedate"]
# ========== 顧客ごとの統計量 ==========
sample_usagelog_customer = sample_usagelog_months.groupby("customer_id")["count"].agg(
["mean", "median", "max", "min"]
)
sample_usagelog_customer = sample_usagelog_customer.reset_index(drop=False)
# ========== 曜日別利用パターン分析 ==========
sample_usagelog["weekday"] = sample_usagelog["usedate"].dt.weekday
# 顧客×年月×曜日ごとの利用回数
sample_usagelog_weekday = sample_usagelog.groupby(
["customer_id", "年月", "weekday"],
as_index=False
).count()[["customer_id", "年月", "weekday", "log_id"]]
sample_usagelog_weekday.rename(columns={"log_id": "count"}, inplace=True)
# 顧客ごとの「最大利用曜日回数」を取得
sample_usagelog_weekday = sample_usagelog_weekday.groupby(
"customer_id",
as_index=False
).max()[["customer_id", "count"]]
# 定期利用フラグの作成(週4回以上なら1)
sample_usagelog_weekday["routine_flag"] = 0
sample_usagelog_weekday["routine_flag"] = sample_usagelog_weekday["routine_flag"].where(
sample_usagelog_weekday["count"] < 4, 1
)
コード解説
ブロック1:データ読み込みとテーブル結合
何をしているか
4つのCSVファイルを読み込み、pd.mergeで順次結合しています。
なぜそうするか
顧客マスター単体では「class」や「campaign_id」が数値コードのままで分かりにくいため、マスターテーブルと結合して名称情報を補完します。how="left"を指定することで、顧客マスターのすべての行を残しつつ、対応する名称を追加できます。
初心者ポイント
on="class"は結合キーを指定するパラメータです。両方のDataFrameに同名の列がある場合に使います。もし列名が異なる場合はleft_onとright_onを使い分けます。
ブロック2:属性別の顧客数集計と日付絞り込み
何をしているか
groupbyで属性ごとの顧客数をカウントし、その後、日付列を変換して期間絞り込みを行っています。
なぜそうするか
集計により「どのクラスが多いか」「どのキャンペーン経由が多いか」といった全体像を把握できます。日付絞り込みは「アクティブな顧客」や「新規顧客」を抽出するための基本操作です。
初心者ポイント
pd.to_datetime()は文字列やオブジェクト型を日付型に変換します。これにより>や>=などの比較演算が正しく動作します。また、isna()はNULL値を判定するメソッドで、「終了日が未設定=継続中」を表現する際に便利です。
ブロック3:利用ログの年月集計と顧客別統計
何をしているか
利用ログに「年月」列を作成し、顧客×年月ごとの利用回数を集計。さらに顧客ごとの平均・中央値・最大・最小を算出しています。
なぜそうするか
月次の利用傾向を見ることで、「毎月安定して使っているか」「特定月だけ使ったか」を判別できます。統計量を算出することで、顧客の利用パターンを数値的に評価できます。
初心者ポイント
dt.strftime("%Y%m")は日付を任意の文字列形式に変換します。"%Y%m"は「202401」のような年月形式です。agg(["mean", "median", "max", "min"])は複数の集計を一度に実行できる便利な記法です。
ブロック4:曜日別利用パターンと定期利用フラグ
何をしているか
利用ログに「曜日」列を追加し、顧客×年月×曜日ごとの利用回数を集計。その後、各顧客の「最大利用曜日回数」を取得し、週4回以上なら定期利用フラグを立てています。
なぜそうするか
「毎週月曜に必ず来る」といった定期的な利用パターンを検出するためです。routine_flagが1の顧客は継続率が高いと予測でき、マーケティング施策の対象として有望です。
初心者ポイント
dt.weekdayは0(月曜)〜6(日曜)の整数を返します。where(条件, 置き換え値)は条件がFalseの行だけ値を置き換えるメソッドで、今回は「4未満でない=4以上」の行を1にしています。
実行結果例
属性別顧客数
sample_customer_join.groupby("class_name").count()["customer_id"]
class_name
ナイト会員 523
レギュラー会員 1289
デイ会員 412
Name: customer_id, dtype: int64
絞り込み後の顧客数
sample_customer_newer.groupby("gender").count()["customer_id"]
gender
F 1104
M 982
Name: customer_id, dtype: int64
顧客別利用統計
sample_usagelog_customer.head(3)
| customer_id | mean | median | max | min |
|---|---|---|---|---|
| 10001 | 8.5 | 8.0 | 15 | 3 |
| 10002 | 12.3 | 12.0 | 18 | 7 |
| 10003 | 5.2 | 5.0 | 9 | 2 |
定期利用フラグ
sample_usagelog_weekday.head(3)
| customer_id | count | routine_flag |
|---|---|---|
| 10001 | 3 | 0 |
| 10002 | 5 | 1 |
| 10003 | 4 | 1 |
3. エラーと確認のポイント
代表的なエラー
① KeyError: 'class'
KeyError: 'class'
原因
結合キーに指定した列名が、どちらか一方のDataFrameに存在しない。
対処法
df.columnsで列名を確認し、正しい列名を指定する。列名が異なる場合はleft_onとright_onを使う。
② TypeError: '>' not supported between instances of 'str' and 'Timestamp'
TypeError: '>' not supported between instances of 'str' and 'Timestamp'
原因
日付列が文字列型のまま比較演算を実行している。
対処法
pd.to_datetime()で日付型に変換してから比較する。
③ ValueError: cannot convert float NaN to integer
ValueError: cannot convert float NaN to integer
原因
NaN(欠損値)を含む列を整数型に変換しようとしている。
対処法
欠損値を埋める(fillna(0))か、Int64型(NaNを許容する整数型)を使う。
よく使う確認メソッド
① df.info() - データ型と欠損確認
sample_customer_join.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2224 entries, 0 to 2223
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 customer_id 2224 non-null int64
1 start_date 2224 non-null datetime64[ns]
2 end_date 1876 non-null datetime64[ns]
3 class 2224 non-null int64
4 class_name 2224 non-null object
用途
データ型と欠損数を一覧で確認できるため、最初に必ず実行すべきメソッドです。
② df.head() - 先頭データの確認
sample_usagelog_months.head(3)
| 年月 | customer_id | count |
|---|---|---|
| 201801 | 10001 | 8 |
| 201801 | 10002 | 12 |
| 201801 | 10003 | 5 |
用途
集計後のデータ構造を素早く確認できます。
③ df['列名'].unique() - ユニークな値の確認
sample_customer_newer["end_date"].unique()
array([NaT, '2019-04-15', '2019-05-20', ...], dtype='datetime64[ns]')
用途
絞り込み条件が正しく適用されているか、どんな値が残っているかを確認できます。
④ df.shape - 行数と列数の確認
print(sample_customer_join.shape)
print(sample_customer_newer.shape)
(2224, 8)
(2086, 8)
用途
絞り込み前後で何件残ったかを即座に把握できます。
4. まとめ
-
複数テーブルの結合:
pd.mergeを使った実務的なデータ統合手法 -
日付操作と絞り込み:
pd.to_datetime、dt.strftime、条件抽出の基本パターン - グループ集計の応用:属性別カウント、統計量算出、曜日別集計などの実践テクニック
-
フラグ作成ロジック:
whereを使った条件付き値の置き換えによる定期利用判定
Discussion