ID-POSデータから、顧客の将来価値を測定する

こんにちは。データサイエンスチームのYamakawaです。
この記事は、NHN テコラス DATAHOTEL:確率統計・機械学習・ビッグデータを語る Advent Calendar 2017 24日目の記事です。

データサイエンスチームは日々、色々なお客様からご相談をいただいています。手元のメモを見ると一番多かったパターンは、小売業でID-POSデータを活用できていない、というものでした。

 

ID-POSデータとは

 

ID-POSデータとは何かの前にPOSデータとは何かを軽く見ていきます。

POSデータとは一言で言うと、店のレジで商品が販売されたときに記録されるデータです。
具体的には「何が、いつ、何個、いくらで売れたのか」が記録されます。
ただ、それだけでは十分じゃないケースが増えてきました。「誰が買ったのか」という顧客側の情報が無いのです。その情報が追加されると「ID-POS」になります。
皆さんの財布の中にポイントカードが一枚でもあれば、きっとそれはシステムの裏側でID-POSデータを作り出しています。

さて、消費者にとってはポイントが得られるなどのメリットがあるわけですが、ID-POSデータを入手することによって事業者にはどんなメリットがあるのでしょうか?

たとえば、ある店舗である消費者が今日1,000円の買い物をしたとしましょう。ここからわかることは大して多くありません。でも、その消費者が

1)昨日も一昨日も、ほぼ毎日1,000円の買い物をしている場合
2)月一回くらいしか来店しないが、3回に一回は50,000円ほどの買い物をしている場合
3)初めて店に来た場合

だったらどうでしょうか。
1)の消費者は頻繁に来てくださっているので、お友達紹介キャンペーンを企画したらすごい効果が生まれるかもしれません。
2)の消費者はビッグユーザーなので、掘り出し物の案内を差し上げると興味をもたれるかもしれません。
3)の消費者は一見さんなので、どういう方かわかりません。次回の来店を待ってどういうお客様か理解しましょう
といったアプローチが考えられます。

RFM分析

ID-POSデータを分析する際によく使われるフレームワークがRFM分析で、
R(Recency):もっとも最近購入された年月日
F(Frequency):過去1年などの一定期間における購入回数
M(Monetary):一定期間での購買金額合計
を消費者ごとに集計し、R,F,Mの値が高い消費者と低い消費者でアプローチを変えます。

先の例では、今日買い物をしている消費者なので、Rは良い値です。その上で
1)Fは高い、Mは低い
2)Fは低い、Mは高い
3)FもMも低い
となります。

RFM分析の難しさ

とまあ、こういった記述は、マーケティングの本を何冊か読めば必ず書いてあるくらいでして、RFM分析は非常に一般的な手法です。しかし、使いこなすのが難しい。筆者は過去に10社ほどの小売業のお客様と関わる機会がありましたが、RFM分析を十分なレベルで実施して施策に反映させているケースは皆無でした。

何故か?

確かに、以下のような指針は得られるのです

Rが高い顧客ほど将来の収益に貢献する可能性が高い
Rが低ければFやMが高くても他社に奪われ離反している可能性が高い
Rが同じならFが高いほど常連客になっている
Rが同じならFやMが高いほど購買力がある顧客
RやFが高くてもMが少ない顧客は購買力が低い
Fが低くMが高い顧客はRの高い方が良い顧客
Fが上がらないか下がっている顧客は他社に奪われている可能性が高い
RFMすべてが低い顧客は切り捨てることも検討
(参考:奥瀬喜之 久保山哲二(2012)『経済・経営・商学のための「実践データ分析」』講談社)

しかし、それ以上の具体的な指針、RやFやMの値がどれくらいであれば、どういう消費者であり、マーケティング施策に対してどれくらい効果が期待できるの数字を推測するのが難しいのです。

BYTDパッケージ

RFM分析が上手くできない、結果としてID-POSデータを持て余している小売業の方は多いのではないでしょうか。そんな方にお勧めなのがRのBYTDパッケージです。
これを用いると、ID-POSデータから「どの消費者が今後どれくらい来店し、いくらくらい使ってくれるか」を推測することが出来ます。
パッケージでは(そして後継のBYTBplusでは)様々な手法が扱えますが、ここでは一番計算量が少ないPareto/NBDを用います。
Pareto/NBDでは以下の3つの仮定を置いています。
[仮定1] 購買はポアソン・プロセスに従う
[仮定2] 顧客の生存時間は指数分布に従う
[仮定3] 顧客の異質性は2編量独立ガンマ分布を仮定する

この仮定が実態の消費者行動を表しているかは所論有って、結果他の手法も提案されているのですが、そちらに触れるとキリが無いので割愛します。

では、まず必要なライブラリをインストールしましょう。

library(BTYD)
library(dplyr)
library(magrittr)
library(knitr)

分析用のにはBTYDパッケージ組み込みのCD-NOWのデータを用います。
dc.ReadLines関数は、顧客ID、日付、売上がある行番号を指定して取り込みます。
dateがデフォルトではchrなので、date型に変換します。

cdnowElog <- system.file("data/cdnowElog.csv", package = "BTYD")
data <- read.csv(cdnowElog)
elog <- dc.ReadLines(cdnowElog, cust.idx = 2,
                     date.idx = 3, sales.idx = 5)
elog$date <- as.Date(elog$date, "%Y%m%d")
elog0 <- elog

後の処理用にelogデータを別名で保存しておきます。

前処理

同じ日に同じcustで2件以上のデータが存在します。
ここでは、消費者が一日あたりいくら購買したかを見たいので、salesをマージします。dc.MergeTransactionsOnSameDateはそのための関数です。

elog <- dc.MergeTransactionsOnSameDate(elog)
elog0 %>% count(cust) %>% set_colnames(c("cust", "n0")) -> cnt0
elog %>% count(cust) %>% left_join(cnt0) %>% mutate(dif = n0 - n) %>%
  filter(dif !=0) %>% head() %>%
  kable(format = "markdown")
rm(cnt0)
cust n n0 dif
1017 26 27 1
1059 2 3 1
1081 16 17 1
1129 12 13 1
1136 18 19 1
1166 16 17 1

custは消費者コード、nはマージ後のデータ件数、n0はマージする前のデータ件数です。
例えばcust:1059についてみてみましょう。

elog0 %>% filter(cust == 1059)
elog %>% filter(cust == 1059)
cust date sales
1059 1997-02-09 150.66
1059 1997-02-10 11.99
cust date sales
1059 1997-02-09 136.69
1059 1997-02-09 13.97
1059 1997-02-10 11.99

cust:1059は1997/2/9に二回来店しており、分析上は二回の売上の合計値150.66ドルを用います。
CD-DATAは18ヶ月から成るデータなので前半分、1997/09/30までのデータを学習用に用います。

end.of.cal.period <- as.Date("1997-09-30")
elog.cal <- elog[which(elog$date <= end.of.cal.period), ]
split.data <- dc.SplitUpElogForRepeatTrans(elog.cal)
clean.elog <- split.data$repeat.trans.elog

次に、顧客がいつ購入したかを示すマトリクスを作成します。

freq.cbt <- dc.CreateFreqCBT(clean.elog)
tot.cbt <- dc.CreateFreqCBT(elog)
cal.cbt <- dc.MergeCustomers(tot.cbt, freq.cbt)

例えば

cal.cbt[141:143, 1:3]
1997-01-08 1997-01-09 1997-01-10
141 0 0 0
142 1 1 0
143 0 0 0

142行目の1997/1/8と1997/1/9に「1」があるのは、この日にcust:142の消費者が購買を行ったことを意味します。

前処理の最後として、dc.BuildCBSFromCBTAndDates関数で、Frequency(x)、Recency(t.x)、合計時間(T.cal)の3つの列からなるデータフレームを作ります。

birth.periods <- split.data$cust.data$birth.per
last.dates <- split.data$cust.data$last.date
cal.cbs.dates <- data.frame(birth.periods, last.dates,
                            end.of.cal.period)
cal.cbs <- dc.BuildCBSFromCBTAndDates(cal.cbt, cal.cbs.dates,
                                      per="week")

推測

params <- pnbd.EstimateParameters(cal.cbs)

パラメータを推測します。

このパラメータを使うと、例えば新規の消費者が一年間で平均何回来店するかの値が導出できます。

pnbd.Expectation(params, t=52)

1.473という値が得られました。

マーケティング的には既存の消費者の挙動が気になるところです。
例えば、cuts:1516は、今後一年間でどういう消費傾向を示すでしょうか

x <- cal.cbs["1516", "x"]
t.x <- cal.cbs["1516", "t.x"]
T.cal <- cal.cbs["1516", "T.cal"]
pnbd.ConditionalExpectedTransactions(params, T.star = 52,
                                     x, t.x, T.cal)

結果、
顧客番号1516が、今後1年間で行う取引数の期待値は25.45回となります。

実際はどうだったか比較したいのですが、CD-NOWデータにある将来データは9が月分しかありません。
なので期間を39週間にしてやり直します。

pnbd.ConditionalExpectedTransactions(params, T.star = 39,
                                     x, t.x, T.cal)

顧客番号1516が、今後39週間で行う取引数の期待値は20.12回、期待購買金額は575ドルとなります。
実データでは31回、1050ドルだったので、控えめな見積もりがされていることが分かります。

精度を上げるための様々なモデルが提唱されています。他のモデルについても、別途ご紹介できればと思います。

AWS移行支援キャンペーン

あなたにおすすめの記事