價值? 成本? 讓RFM模型輕輕鬆鬆評估一切!(附實現程式碼)

在RFM模型「系列 1」-常貴客?新客? 讓RFM模型簡簡單單解釋一切!(附實現程式碼),我們利用了R與F的交叉分析,簡簡單單推導出不同客群的行銷方案。 並在假設條件下,該商場可以利用R與F的交叉分析,在一年多賺進168萬元的淨利

  而本次我們將專注在RFM模型中的消費金額(Monetary)分析,並回答系列1的三個問題:

  1. 有這麼多顧客區隔,更細部來說,這些區隔都值得我注意嗎? 難道一次客真的要放棄嗎?可以使用消費金額(Monetary)及成本評估顧客終生價值嗎?那該如何評估?
  2. 如何整合時間序列分析?難道時間序列沒有其他用途嗎?

  在開始進行消費金額(Monetary)分析前,要請讀者先行了解四個概念

  1. 商品毛利(Gross Margin, GM):每一件產品的毛利;簡單來說,就是「產品營收」- 「進貨的成本」。
  2. 顧客終身價值(Customer Lifetime Value, CLV):每一個顧客為我們貢獻的收入;簡單來說,就是對每個顧客購買的每一件商品進行「商品毛利」之加總。
  3. 顧客購買(取得)成本(Customer Acquisition Cost, CAC):為了要賣出商品,在每一個顧客上所花費的成本。
  4. 顧客獲利率(CLV/CAC Ratio):每一CAC可以為我們賺進多少的CLV。
哪些顧客區隔應該要放棄? 應該要保留?
有了上述概念後,接下來,讓我們來回答上述問題:「有這麼多顧客區隔邊界,更細部來說,這些區隔都值得我注意嗎? 難道一次客真的要放棄嗎?可以使用消費金額(Monetary)及成本評估顧客終生價值嗎?那該如何評估?」
資料:
 我們一樣透過上一篇文章的賣場資料,接著本篇在加入每個顧客的CAC(CAC資料下載)與CLV資料(直接以程式碼透過商品毛利計算)。
首先來看一下上一篇R與F交叉分析的資料格式:
將原始資料轉換成R與F模型可分析的形式

那我們再將CLV與CAC的資料加入考量,其中我們假定三樣商品的商品毛利:

  • 瓶裝水 = 10
  • 牛奶麵包 = 50
  • 高麗菜 = 10

最終生成下述每個顧客CAC與CLV的資料。

每個顧客的CAC與CLV資料結構

最後結合CLV與CAC的資料表就產生出來了!也就是在這張資料表中,除了可以進行R與F的交叉分析外,我們亦可開始進行「消費金額(Monetary)」的分析啦!

複習一下R與F的「交叉分析」資料表

將消費金額相關變數加入,得到RFM全員到齊的資料表!

消費金額(Monetary)模型分析:
圖片X軸為CLV與CAC之比較,而Y軸對CLV來說則為營收金額的概念,對CAC來說則為成本的概念。藍色X軸則為R,藍色Y軸則為F。除此之外,我們還將「長條圖」的粗細以購買人數多寡表示之,越粗,代表越多人在此區隔購買,越細,則代表越少人在該區域購買。
有了顧客終生價值(CLV)與成本(CAC)的比較圖,讀者可以想想,如果你是行銷資料科學家或專業經理人,有了Monetary相關的資料,接著可能會做些甚麼?

  首先,我們發現:

  1. 常貴客確實為最賺錢的區隔
  2. 新顧客隨著購買頻率的增加,CLV有逐漸上漲的趨勢
  3. 先前客部份則非常有趣,在31–55天內,消費達5次以上的顧客,人數多,且金額甚至高過16–22天內,消費達5次以上的顧客
  4. 一次客則發現成本花費高,且顧客消費效益不高…,但是人數似乎又是最多的區隔…

那… 所以哪些區隔要放棄?哪些要保留?

這時候,就需要使用到顧客獲利率(CLV/CAC Ratio)來做更直觀的計算,讓我們更清楚目前商場整體獲利狀況:

從顧客獲利率比較圖中,我們可以非常清楚了解幾個思考方向:
總體
我們可以看到紅色區隔均是投入1塊錢成本,反而無法回本的區隔,所以我們就可以考慮不再繼續投資。或者不再繼續投資「嗎」?
常貴客分析
  1. 在常貴客0–7天內消費5次以上的顧客中,我們每投入1塊錢的成本,就會賺得3.13元,這群人總共有14人,平均消費天數為3.93天,購買頻率為7.14,也就是一天平均消費1.82次,那一天就是 14* 1.82* (3.13–1) = 54.2元的淨收。
  2. 在常貴客16–22天內消費5次以上的顧客中,我們每投入1塊錢的成本,就會賺得4.67元,這群人總共有5人,平均消費天數為18.4天,購買頻率為6.4,也就是一天平均消費0.35次,那一天就是 5 * 0.35 * (3.67–1) = 4.67元的淨收。
  3. 在常貴客0–7天內消費5次的顧客中,我們每投入1塊錢的成本,就會賺得2.44元,這群人總共有17人,平均消費天數為3.82天,購買頻率為5,也就是一天平均消費0.77次,那一天就是 17* 0.77* (2.44–1) = 18.72元的淨收。
如果不考量該商場的其他布局策略,純粹以數字來評斷,讀者有沒有看出些什麼?
在常貴客的第1點與第3點比較可發現,第1點的人數明顯不足,如果能利用行銷活動將第3點的5個人轉換到第1點,如果根據過往經驗,將成本變高提高18%,花1.15元能一樣能賺得3.13元,且能夠吸引5個人,我們將可以一天多賺7.55元。
原樣:14* 1.82* (3.13–1) +17* 0.77* (2.44–1) = 73.12
轉換後=(14+5)人* 1.82* (3.13–1.18) + (17-5)人* 0.77* (2.44-1) = 80.67
如果我們將人數「簡單」的放大10倍:
原樣:140* 1.82* (3.13-1) + 170 * 0.77* (2.44–1) =731.22
轉換後=(140+50)人* 1.82* (3.13–1.5) + (170-50)人* 0.77* (2.44-1) = 806.69
這樣一天就多賺了75.47元,一個月就是2264元。
哇! 算出來了! 好棒棒!
但… 恩…有這樣簡單「嗎」?
這時候讀者心中一定激起了一個至關重要的疑問:「這看起來就是一個時間的綜整結果,能不能給我一個細部的趨勢變化圖? 更有利於我做行銷資源非配,而非一概而論?
如何整合時間序列分析?
這時候就要綜整第一與第二個問題:「將消費金額(Monetary)以時間序列的方式進行趨勢整合,看出不同顧客區隔的趨勢分析,了解在每一個顧客區隔再投入資源不便的狀況下,哪一個區隔在未來是有發展性的 」。
要進行時間序列的消費金額分析,我們可將顧客的第一次購買日期當作基準,統計在這往後,該顧客的購買頻率(F)、最近一次消費天數(R)、CAC與CLV的總平均,如此我便能知道:
  1. 每個月份消費者的購買軌跡;例如:cliendId 1 的消費者自從2017–02就有消費紀錄,而最近有5次消費、最近一次購買是則是8天前。如此便知在2017–02時至今日,他依舊是我的常貴客。
  2. 我們還能知道每個顧客區隔中,到底是哪一個月份進來購買的顧客會是我們最值得投資的目標對象。
  3. 再來就是,哪幾顧客區隔我們應該可以考量繼續或放棄投入資源。

資料表 — 時間序列的消費金額分析

  我們假設報告的時間點先以為2017–04–11為基底,將時間序列的消費金額分析製作出來後,我們可以很清楚看到,在本月的營收:

  1. 最近一次購買天數0–7天且購買頻率4次的常貴客的數字從2017–01的155元平均顧客淨利一直到2017–03的370平均顧客淨利,有持續增長的趨勢存在,且該區隔的逐月新進顧客增長許多,建議可以將較多的資源比重依照月份淨值多寡進行行銷資源分配。
  2. 再來看看最近一次購買天數0–7天且購買頻率2次的潛在客,很清楚發現,我們可以將有限的行銷資源投放到2017–02與2017–04該區隔新進顧客,排除2017–01及2017–03的顧客。就算要重新喚這群淨值為負的顧客,也可以將淨值-28的2017–03的顧客當作資源投放的優先考量。所以並非顧客獲利率大於1以上,我們就一定要將所有資源投入到整體顧客區隔,而是可以根據月份等商場注重的變數做「區隔再區隔」,精準鎖定行銷資源的投放標的。
  3. 再來看一個例子,我們再看到最近一次購買天數31–55天且購買頻率2次的一次客,在四月份時,有2017–02進來消費的這群一次客產生15元的淨值,然而這一顧客區隔以總體來說,他的總顧客獲利率竟然是0.96,雖然2017–02這群一次客貢獻淨值很低,但是相對有機會將他們拉抬上來,成為潛在顧客。
  4. 再看最近一次購買天數16–22天且購買頻率2次的潛在顧客,雖然總顧客獲利率是0.9,但是其逐月新進顧客貢獻的淨值接有所改善,如果商場放棄掉他們,是不是又可惜了些?
所以我們從單點的顧客獲利率圖時間序列的消費金額分析圖中,給我們的insight便是可以依照時間點的不同而妥當的去分配自己的行銷資源,而非總顧客獲利率<1就全盤放棄考量,那除了時間外,我可不可以在依照其他變數進行「區隔再區隔」? 當然可以!時間序列可被替換成「品牌、品質、滿意程度等對商品利益有所影響的變數,精緻化行銷資源」,由此可知,商場必須要將顧客區分得更細緻,方能妥善運用行銷資源,將行銷利益最大化!

作者:鍾皓軒(臺灣行銷研究有限公司 共同創辦人)

附錄:

# 載入library
library(dplyr)
library(reshape2)
library(ggplot2)
library(stringr)

# 讀取資料
orders = read.csv('orders.csv')
cac = read.csv('cac.csv') %>% .[-1]

# 計算clv
gr.margin <- data.frame(product=c('瓶裝水', '牛奶麵包', '高麗菜'), grossmarg=c(10, 50, 10))
# calculating customer lifetime value
orders <- merge(orders, gr.margin, by='product')

clv <- orders %>%
  group_by(clientId) %>%
  summarise(clv=sum(grossmarg)) %>%
  ungroup()

####gist2############


# 假設 2017 - 4 -11 為報告日期
today <- as.Date('2017-04-11', format='%Y-%m-%d')

# processing data
orders <- dcast(orders, orderId + clientId + gender + orderdate ~ product, value.var='product', fun.aggregate=length)
orders$orderdate = as.Date(orders$orderdate)

orders <- orders %>%
  group_by(clientId) %>%
  mutate(frequency=n(),
         recency=as.numeric(today-orderdate)) %>%
  filter(orderdate==max(orderdate)) %>%
  filter(orderId==max(orderId)) %>%
  ungroup()

# 切割頻率
orders.segm <- orders %>%
  mutate(buy_freq=ifelse(between(frequency, 1, 1), '1',
                         ifelse(between(frequency, 2, 2), '2',
                                ifelse(between(frequency, 3, 3), '3',
                                       ifelse(between(frequency, 4, 4), '4',
                                              ifelse(between(frequency, 5, 5), '5', '>5')))))) %>%
  
  
  # 切割近因畫出邊界
  mutate(segm.rec=ifelse(between(recency, 0, 7), '0-7 天',
                         ifelse(between(recency, 8, 15), '8-15 天',
                                ifelse(between(recency, 16, 22), '16-22 天',
                                       ifelse(between(recency, 23, 30), '23-30 天',
                                              ifelse(between(recency, 31, 55), '31-55 天', '>55 天')))))) %>%
  # 把商品放入變數中
  mutate(cart=paste(ifelse(瓶裝水!=0, '、瓶裝水', ''),
                    ifelse(牛奶麵包!=0, '、牛奶麵包', ''),
                    ifelse(高麗菜!=0, '、高麗菜', ''), sep='')) %>%
  arrange(clientId)

# '瓶裝水','牛奶麵包','高麗菜'
# 定義邊界的順序
orders.segm$buy_freq <- factor(orders.segm$buy_freq, levels=c('>5', '5', '4', '3', '2', '1'))
orders.segm$segm.rec <- factor(orders.segm$segm.rec, levels=c('>55 天', '31-55 天', '23-30 天', '16-22 天', '8-15 天', '0-7 天'))
orders.segm$cart = str_split_fixed(orders.segm$cart, '、', 2)[,2]


# 將 CAC與CLV結合進去
orders.segm <- merge(orders.segm, cac, by='clientId')
orders.segm <- merge(orders.segm, clv, by='clientId')

lcg.clv <- orders.segm %>%
  group_by(segm.rec, buy_freq) %>%
  summarise(quantity=n(),
            # calculating cumulative CAC and CLV
            cac=sum(cac),
            clv=sum(clv)) %>%
  ungroup() %>%
  # calculating CAC and CLV per client
  mutate(cac_mean=round(cac/quantity, 2),
         clv_mean=round(clv/quantity, 2))

# clv/cac ratio
lcg.clv$ratio = round(lcg.clv$clv/lcg.clv$cac, 2)

lcg.clv <- melt(lcg.clv, id.vars=c('segm.rec', 'buy_freq', 'quantity'))


howard_theme <- function(base_size = 12, base_family = "sans"){
  theme_minimal(base_size = base_size, base_family = base_family) +
    theme(
      axis.text.x = element_text(size=20, angle = 65, vjust = 1, hjust=1),
      axis.text.y = element_text(size=20),
      axis.title = element_text(size = 20),
      panel.grid.major = element_line(color = "grey"),
      panel.grid.minor = element_blank(),
      panel.background = element_rect(fill = "aliceblue"),
      strip.background = element_rect(fill = "navy", color = "navy", size = 1),
      strip.text = element_text(face = "bold", size = 10, color = "white"),
      legend.position = "right",
      legend.justification = "bottom",
      legend.background = element_blank(),
      legend.text=element_text(size=15),
      panel.border = element_rect(color = "grey", fill = NA, size = 0.05),
      title = element_text(size = 15),
      plot.caption=element_text(size = 10)
    )
}


ggplot(lcg.clv[lcg.clv$variable %in% c('clv', 'cac'), ], aes(x=variable, y=value, fill=variable)) +
  theme_bw() +
  theme(panel.grid = element_blank())+
  geom_bar(stat='identity', alpha=0.6, aes(width=quantity/max(quantity))) +
  geom_text(aes(y=value, label=value), size=4) +
  facet_grid( buy_freq~ segm.rec) +
  ggtitle("顧客終生價值(CLV)與成本(CAC)的加總比較") + howard_theme()+
  xlab("CLV與CAC之比較") + 
  ylab("$錢$")


ggplot(lcg.clv[lcg.clv$variable %in% c('clv_mean', 'cac_mean'), ], aes(x=variable, y=value, fill=variable)) +
  theme_bw() +
  theme(panel.grid = element_blank())+
  geom_bar(stat='identity', alpha=0.6, aes(width=quantity/max(quantity))) +
  geom_text(aes(y=value, label=value), size=4) +
  facet_grid(buy_freq ~ segm.rec) +
  ggtitle("顧客終生價值(CLV)與成本(CAC)- 顧客平均價值")+
  howard_theme()+
  xlab("CLV與CAC之比較") + 
  ylab("$錢$")

ggplot(lcg.clv[lcg.clv$variable %in% c('ratio'), ], aes(x=variable, y=value, fill=variable)) +
  theme_bw() +
  theme(panel.grid = element_blank())+
  geom_bar(stat='identity', alpha=0.6, aes(width=quantity/max(quantity),fill = value > 1) ) +
  geom_text(aes(y=value, label=value), size=4) +
  facet_grid(buy_freq ~ segm.rec) +
  ggtitle("顧客獲利率比較圖") + howard_theme()+
 guides(fill=FALSE)+
  xlab("比例變數") + 
  ylab("顧客獲利率")

# 平均顧客獲利率
# mean(lcg.clv[lcg.clv$variable %in% c('ratio'), ]$value)
# 
# 
# a = orders.segm[orders.segm$buy_freq == '>5' & orders.segm$segm.rec == '0-7 天',]
# 
# 19*(mean(a$frequency)/mean(a$recency))*2.13 + 12* 0.77* (2.44-1)
# 
# 
# 17*1.73 * (3.13-1)
# (14+5)* 1.82* (3.13-1.5) + (17-5)* 0.77* (2.44-1)
# 
# 19*(mean(a$frequency)/mean(a$recency))*2.13 +12* 0.77* (2.44-1)
# 
# org = 14* 1.82* (3.13-1) + 17 * 0.77* (2.44-1)
# change = 19*(mean(a$frequency)/mean(a$recency))*(3.13-1.18) +12* 0.77* (2.44-1)
# org = 140* 1.82* (3.13-1) + 170 * 0.77* (2.44-1)
# change = 190*(mean(a$frequency)/mean(a$recency))*(3.13-1.18) +120* 0.77* (2.44-1)
# org - change






# 讀取資料
orders = read.csv('orders.csv')
# 假設 2017 - 4 -11 為報告日期
today <- as.Date('2017-04-11', format='%Y-%m-%d')

# calculating customer lifetime value
orders <- merge(orders, gr.margin, by='product')
orders$orderdate = as.Date(orders$orderdate)

customers <- orders %>%
  # combining products and summarising gross margin
  group_by(orderId, clientId, orderdate) %>%
  summarise(grossmarg=sum(grossmarg)) %>%
  ungroup() %>%
  # calculating frequency, recency, average time lapses between purchases and defining cohorts
  group_by(clientId) %>%
  mutate(frequency=n(),
         recency=as.numeric(today-max(orderdate)),
         av.gap=round(as.numeric(max(orderdate)-min(orderdate))/frequency, 0),
         cohort=format(min(orderdate), format='%Y-%m')) %>%
  ungroup() %>%
  # calculating CLV to date
  group_by(clientId, cohort, frequency, recency, av.gap) %>%
  summarise(clv=sum(grossmarg)) %>%
  arrange(clientId) %>%
  ungroup()

# 切割頻率
customer_orders.segm <- customers %>%
  mutate(buy_freq=ifelse(between(frequency, 1, 1), '1',
                         ifelse(between(frequency, 2, 2), '2',
                                ifelse(between(frequency, 3, 3), '3',
                                       ifelse(between(frequency, 4, 4), '4',
                                              ifelse(between(frequency, 5, 5), '5', '>5')))))) %>%
  
  
  # 切割近因畫出邊界
  mutate(segm.rec=ifelse(between(recency, 0, 7), '0-7 天',
                         ifelse(between(recency, 8, 15), '8-15 天',
                                ifelse(between(recency, 16, 22), '16-22 天',
                                       ifelse(between(recency, 23, 30), '23-30 天',
                                              ifelse(between(recency, 31, 55), '31-55 天', '>55 天')))))) 




# 定義邊界的順序
customer_orders.segm$buy_freq <- factor(customer_orders.segm$buy_freq, levels=c('>5', '5', '4', '3', '2', '1'))
customer_orders.segm$segm.rec <- factor(customer_orders.segm$segm.rec, levels=c('>55 天', '31-55 天', '23-30 天', '16-22 天', '8-15 天', '0-7 天'))
customer_orders.segm <- merge(customer_orders.segm, cac, by='clientId')

lcg.coh <- customer_orders.segm %>%
  group_by(cohort, segm.rec, buy_freq) %>%
  # calculating cumulative values
  summarise(quantity=n(),
            cac=sum(cac),
            clv=sum(clv),
            av.gap=sum(av.gap)) %>%
  ungroup() %>%
  # calculating average values
  mutate(av.cac=round(cac/quantity, 2),
         av.clv=round(clv/quantity, 2),
         av.gap=round(av.gap/quantity, 2),
         diff=av.clv-av.cac)

ggplot(lcg.coh, aes(x=cohort, fill=cohort)) +
  theme_bw() +
  theme(panel.grid = element_blank())+
  geom_bar(aes(y=diff), stat='identity', alpha=0.5) +
  geom_text(aes(y=diff, label=round(diff,0)), size=4) +
  facet_grid(buy_freq ~ segm.rec) +
  theme(axis.text.x=element_text(angle=90, hjust=.5, vjust=.5, face="plain")) +
  ggtitle("時間序列的消費金額分析 - CLV與CAC之差異平均價值")+
  xlab("時間序列") + 
  ylab("CLV與CAC之差異平均價值")+
  howard_theme()+
  guides(fill=FALSE)

# 
# a = lcg.coh[lcg.coh$cohort=='2017-02' ,  ]
# sum(a$diff)
# 
# b = lcg.coh[lcg.coh$cohort=='2017-02' ,  ]
# sum(b$diff)
# 
Scroll to Top