🐍

(学習ログ)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_onright_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_onright_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_datetimedt.strftime、条件抽出の基本パターン
  • グループ集計の応用:属性別カウント、統計量算出、曜日別集計などの実践テクニック
  • フラグ作成ロジックwhereを使った条件付き値の置き換えによる定期利用判定

Discussion