Python: lifetimesで顧客のトランザクション数を予測する

前回紹介したBG/NBDなど、顧客の未来のライフタイムバリュー (ここではトランザクション数) を予測するモデルを、簡単に利用できるPythonパッケージ lifetimes を紹介します。

lifetimes

lifetimesは、顧客の未来のトランザクションを計算するためためのパッケージです。
BG/NBD以外にも、ベースとなっているPareto/NBDや、トランザクション数ではなく金額を直接的に予測するGamma-Gammaモデル (提案論文) なども提供されています。

github.com

lifetimesを使った予測

$ pip install lifetimes

データセット

今回は、UCI Machine Learning Repositoryで提供されているECサイトの取引データを使って予測を行っていきます。

2010/12/1〜2011/12/9に発生した取引 (=トランザクション) 約54万件 (キャンセル含む) が収録されており、顧客ID・取引日時・金額などの情報を含んでいます。

UCI Machine Learning Repository: Online Retail Data Set

最初に、このデータをPandasのDataFrameにロードします。

import pandas as pd
import urllib.request

url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/00352/Online%20Retail.xlsx'
path = './OnlineRetail.xlsx'

# xlsxのダウンロード
urllib.request.urlretrieve(url, path)

# DataFrameにロード
df = pd.read_excel(path)
df.head()

で、ちょこっと加工して、顧客ID、購入日時、合計金額のレコードに集約します。

  • キャンセルのトランザクション (InvoiceNoが'C'で始まる) はここで除去してしまいます
    • キャンセルされた取引がどの購入取引と紐付いているのかがわからないため
  • 簡単化のため、購入日時 (InvoiceDate) が同じ場合は同じトランザクションとしてます
# キャンセル分は、削除
df = df.loc[df['SubTotalPrice'] > 0.0]

# 合計金額を計算
df['SubTotalPrice'] = df['Quantity'] * df['UnitPrice']

# 顧客ID, 購入日時, 金額 のレコードを生成
transaction_df = df.groupby(['CustomerID', 'InvoiceDate'])['SubTotalPrice'].sum().reset_index()

将来のトランザクション数の予測 (BG/NBDモデル)

トランザクション数をBG/NBDモデルを使って予測していきます。

復習となりますが、BG/NBDでは、顧客ごとに3つのデータを使います。詳しくは前回の投稿をご覧ください。

  • T: 最初にトランザクションしてから現在までの期間 (日数)
  • frequency: 期間T中のトランザクション数
  • reacency: 期間T中で最後にトランザクションしてから現在までの期間 (日数)

トランザクションデータから上の3つの項目を持つデータを作り、BG/NBDモデルを学習させます。

  • PandasのDataFrameに対応したインタフェースとなってます
  • トランザクションごとにレコードとなっているデータでも、lifetimes.utils.summary_data_from_transaction_dataメソッドを使うと、上3つのカラムを持つ顧客ID単位のテーブルに集約してくれます (自前でGroupbyする必要がありません)
    • observation_period_endは2011/11/30として、ちょうど1年分となるように調整してます
# transaction_dfから、frequency, recency, Tを抽出
from lifetimes.utils import summary_data_from_transaction_data

summary_df = summary_data_from_transaction_data(
    transaction_df, 'CustomerID', 'InvoiceDate', 
    observation_period_end='2011-11-30'
)
summary_df.head()

モデルを生成して学習します。

  • BG/NBDモデルはlifetimes.filters.BetaGeoFilter
    • 他のモデルもlifetimes.filters.BaseFitterを継承したモデルとして実装されてます
    • penalizer_coefは正則化の係数で、ここでは0に設定することで正則化無しのモデルとしてます
# BG/NBDモデルを学習する
from lifetimes import BetaGeoFitter

bgf_model = BetaGeoFitter(penalizer_coef=0.0)

bgf_model.fit(summary_df['frequency'], summary_df['recency'], summary_df['T'])
# <lifetimes.BetaGeoFitter: fitted with 4297 subjects, a: 0.00, alpha: 70.93, b: 3.13, r: 0.83>

学習の結果、各パラメータは以下の値で推定されています。

パラメータ
r 0.83
alpha 70.96
a 0.00
b 2.00

モデルの可視化をしていきます。

1日後の購入数はplot_frequency_recency_matrixメソッドで可視化できます。

import matplotlib
from lifetimes.plotting import plot_frequency_recency_matrix

plot_frequency_recency_matrix(bgf_model)

f:id:ohke:20190302104443p:plain

生存確率はplot_probability_alive_matrixです。recencyが小さいほど・frequencyが大きいほど、確率が高く計算されることがわかります。

from lifetimes.plotting import plot_probability_alive_matrix

plot_probability_alive_matrix(bgf_model)

f:id:ohke:20190302104456p:plain

このモデルを使って予測していきます。BetaGeoFilterのconditional_expected_number_of_purchases_up_to_timeメソッドで、未来の期間t (日) でのトランザクション数を予測できます。

  • ここではt=7 (12/1〜12/7の1週間) を指定してます
# 未来の7日間 (2011-12-01〜2011-12-07) のトランザクション数を顧客ごとに予測
t = 7
summary_df['predicted_purchases'] = bgf_model.conditional_expected_number_of_purchases_up_to_time(
    t, summary_df['frequency'], summary_df['recency'], summary_df['T']
)

# 予測トランザクション数が多いトップ5
summary_df.sort_values(by='predicted_purchases', ascending=False).head()

実際に2011/12/1〜2011/12/7に発生したトランザクション数のTOP5と比較すると、5人中2人が含まれており、傾向レベルでは予測できていそうです。

# 実際のトランザクション数TOP5を表示
transaction_df.loc[
    (transaction_df['InvoiceDate'] >= '2011-12-01') &
    (transaction_df['InvoiceDate'] < '2011-12-07')
].groupby('CustomerID').count().sort_values(by='InvoiceDate', ascending=False)['InvoiceDate'].head()
# CustomerID
# 15856.0    6
# 14911.0    5
# 12748.0    5
# 12569.0    4
# 13089.0    3

まとめ

lifetimesパッケージを使うことで、顧客のトランザクション数を予測できることを確認しました。