课程
意大利经济学家维弗雷多·帕累托提出:80% 的结果来自 20% 的原因,这就是著名的 80/20 法则或帕累托法则。类似地,企业 80% 的业务往往来自 20% 的客户。企业需要识别这些顶级客户,并与他们保持关系以确保持续的收入。为了与客户维持长期关系,企业需要制定忠诚度计划,例如折扣、优惠、优惠券、积分和礼品。
获取新客户比留住现有客户更昂贵,因为您无需再次投入资源、时间和精力去获取新客户,您只需要让现有客户满意。业务分析师使用 CLTV(客户终身价值)准确计算获客成本。CLTV 表示在整个关系期间来自客户的总收入。CLTV 有助于企业将重心放在那些未来能带来更多收入的潜在客户上。
客户终身价值(CLTV)
“客户终身价值是一种货币数值,代表客户在整个关系期间将为公司带来的收入或利润总额”。CLTV 展示了获取长期客户相较于短期客户的影响。客户终身价值(CLV)可以帮助您回答每家公司在销售方面最重要的问题:
- 如何识别最有利润的客户?
- 公司如何提供最佳产品并获得最多利润?
- 如何对盈利客户进行细分?
- 需要投入多少预算来获取客户?
计算客户终身价值
计算 CLTV 有很多方法。每个人对此都有自己的看法。为了计算 CLTV,我们需要客户的历史数据,但对于新客户则无法直接计算。为了解决这个问题,业务分析师会开发机器学习模型来预测新客户的 CLTV。下面来看看一些 CLTV 的计算方法:
1)您可以在给定周期内累加来自客户的利润/收入。例如,如果某位客户过去 3 年一直与您保持合作,您可以将这 3 年内的所有利润相加。您可以按年、半年或月度计算平均利润,但使用这种方法无法为新客户构建预测模型。
2)为现有客户建立回归模型。取最近六个月的数据作为自变量,过去三年的总收入作为因变量,并据此建立回归模型。


3)也可以使用 RFM(Recency、Frequency、Monetary,即最近一次购买、购买频率和购买金额)进行 CLTV 实现。更多详情可参考我的教程。
4)使用以下等式:CLTV =((平均订单金额 × 购买频率)/ 流失率)× 利润率。
Customer Value = Average Order Value * Purchase Frequency
-
平均订单金额(AOV):平均订单金额是总收入与订单总数的比值。AOV 表示客户每个订单的平均消费金额。
Average Order Value = Total Revenue / Total Number of Orders -
购买频率(PF):购买频率是订单总数与客户总数的比值。它表示每位客户的平均下单次数。
Purchase Frequency = Total Number of Orders / Total Number of Customers -
流失率:流失率是指未再次下单的客户占比。
-
客户生命周期:客户生命周期是客户持续下单的一段时间长度。
Customer Lifetime=1/Churn Rate -
复购率:复购率定义为下单次数超过一次的客户数与唯一客户数之比。示例:如果某个月共有 10 位客户,其中有 4 位回购,则复购率为 40%。
Churn Rate= 1-Repeat Rate
在 Python 中实现 CLTV(使用公式)
导入所需库
#import modules
import pandas as pd # for dataframes
import matplotlib.pyplot as plt # for plotting graphs
import seaborn as sns # for plotting graphs
import datetime as dt
import numpy as np
加载数据集
首先使用 pandas 的 CSV 读取函数加载所需的 Online Retail 数据集。您可以从这里下载数据。
data = pd.read_excel("Online_Retail.xlsx")
data.head()
| InvoiceNo | StockCode | Description | Quantity | InvoiceDate | UnitPrice | CustomerID | Country | |
|---|---|---|---|---|---|---|---|---|
| 0 | 536365 | 85123A | WHITE HANGING HEART T-LIGHT HOLDER | 6 | 2010-12-01 08:26:00 | 2.55 | 17850.0 | United Kingdom |
| 1 | 536365 | 71053 | WHITE METAL LANTERN | 6 | 2010-12-01 08:26:00 | 3.39 | 17850.0 | United Kingdom |
| 2 | 536365 | 84406B | CREAM CUPID HEARTS COAT HANGER | 8 | 2010-12-01 08:26:00 | 2.75 | 17850.0 | United Kingdom |
| 3 | 536365 | 84029G | KNITTED UNION FLAG HOT WATER BOTTLE | 6 | 2010-12-01 08:26:00 | 3.39 | 17850.0 | United Kingdom |
| 4 | 536365 | 84029E | RED WOOLLY HOTTIE WHITE HEART. | 6 | 2010-12-01 08:26:00 | 3.39 | 17850.0 | United Kingdom |
去重
有时您会拿到杂乱的数据集,需要处理重复记录,否则会影响分析结果。在 Python 中,pandas 提供了 drop_duplicates() 函数,用于删除重复记录。
filtered_data=data[['Country','CustomerID']].drop_duplicates()
开始挖掘数据洞察
#Top ten country's customer
filtered_data.Country.value_counts()[:10].plot(kind='bar')
<matplotlib.axes._subplots.AxesSubplot at 0x7fe677a887f0>

在该数据集中,您可以看到大多数客户来自 “United Kingdom”。因此,您可以筛选出英国客户的数据。
uk_data=data[data.Country=='United Kingdom']
uk_data.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 495478 entries, 0 to 541893
Data columns (total 8 columns):
InvoiceNo 495478 non-null object
StockCode 495478 non-null object
Description 494024 non-null object
Quantity 495478 non-null int64
InvoiceDate 495478 non-null datetime64[ns]
UnitPrice 495478 non-null float64
CustomerID 361878 non-null float64
Country 495478 non-null object
dtypes: datetime64[ns](1), float64(2), int64(1), object(4)
memory usage: 34.0+ MB
pandas 的 describe() 函数可方便地获取各种汇总统计信息。它会返回数据的计数、均值、标准差、最小值、最大值以及分位数。
uk_data.describe()
| Quantity | UnitPrice | CustomerID | |
|---|---|---|---|
| count | 495478.000000 | 495478.000000 | 361878.000000 |
| mean | 8.605486 | 4.532422 | 15547.871368 |
| std | 227.588756 | 99.315438 | 1594.402590 |
| min | -80995.000000 | -11062.060000 | 12346.000000 |
| 25% | 1.000000 | 1.250000 | 14194.000000 |
| 50% | 3.000000 | 2.100000 | 15514.000000 |
| 75% | 10.000000 | 4.130000 | 16931.000000 |
| max | 80995.000000 | 38970.000000 | 18287.000000 |
这里可以看到,有些客户的下单数量为负数,这是不可能的。因此,您需要筛选数量大于 0 的记录。
uk_data = uk_data[(uk_data['Quantity']>0)]
uk_data.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 486286 entries, 0 to 541893
Data columns (total 8 columns):
InvoiceNo 486286 non-null object
StockCode 486286 non-null object
Description 485694 non-null object
Quantity 486286 non-null int64
InvoiceDate 486286 non-null datetime64[ns]
UnitPrice 486286 non-null float64
CustomerID 354345 non-null float64
Country 486286 non-null object
dtypes: datetime64[ns](1), float64(2), int64(1), object(4)
memory usage: 33.4+ MB
筛选所需列
在这里,您可以筛选计算 CLTV 所需的列。您只需要这五列:CustomerID、InvoiceDate、InvoiceNo、Quantity 和 UnitPrice。
- CustomerID 用于唯一标识您的客户。
- InvoiceDate 帮助您计算客户与您的产品保持关系的天数。
- InvoiceNo 帮助您统计发生交易的次数(频率)。
- Quantity 是每笔交易中购买的商品数量。
- UnitPrice 是客户购买的每个单位的价格,可用于计算总购买金额。
uk_data=uk_data[['CustomerID','InvoiceDate','InvoiceNo','Quantity','UnitPrice']]
#Calulate total purchase
uk_data['TotalPurchase'] = uk_data['Quantity'] * uk_data['UnitPrice']
接下来,您将执行以下操作:
- 计算每位客户最近一次购买日期与最早购买日期之间的天数。
- 计算每位客户的订单数。
- 计算每位客户的购买总金额。
uk_data_group=uk_data.groupby('CustomerID').agg({'InvoiceDate': lambda date: (date.max() - date.min()).days,
'InvoiceNo': lambda num: len(num),
'Quantity': lambda quant: quant.sum(),
'TotalPurchase': lambda price: price.sum()})
uk_data_group.head()
| InvoiceDate | InvoiceNo | Quantity | TotalPurchase | |
|---|---|---|---|---|
| CustomerID | ||||
| 12346.0 | 0 | 1 | 74215 | 77183.60 |
| 12747.0 | 366 | 103 | 1275 | 4196.01 |
| 12748.0 | 372 | 4596 | 25748 | 33719.73 |
| 12749.0 | 209 | 199 | 1471 | 4090.88 |
| 12820.0 | 323 | 59 | 722 | 942.34 |
重命名列
# Change the name of columns
uk_data_group.columns=['num_days','num_transactions','num_units','spent_money']
uk_data_group.head()
| num_days | num_transactions | num_units | spent_money | |
|---|---|---|---|---|
| CustomerID | ||||
| 12346.0 | 0 | 1 | 74215 | 77183.60 |
| 12747.0 | 366 | 103 | 1275 | 4196.01 |
| 12748.0 | 372 | 4596 | 25748 | 33719.73 |
| 12749.0 | 209 | 199 | 1471 | 4090.88 |
| 12820.0 | 323 | 59 | 722 | 942.34 |
使用以下公式计算 CLTV:
CLTV = ((Average Order Value x Purchase Frequency)/Churn Rate) x Profit margin.
Customer Value = Average Order Value * Purchase Frequency
1. 计算平均订单金额
# Average Order Value
uk_data_group['avg_order_value']=uk_data_group['spent_money']/uk_data_group['num_transactions']
uk_data_group.head()
| num_days | num_transactions | num_units | spent_money | avg_order_value | |
|---|---|---|---|---|---|
| CustomerID | |||||
| 12346.0 | 0 | 1 | 74215 | 77183.60 | 77183.600000 |
| 12747.0 | 366 | 103 | 1275 | 4196.01 | 40.737961 |
| 12748.0 | 372 | 4596 | 25748 | 33719.73 | 7.336756 |
| 12749.0 | 209 | 199 | 1471 | 4090.88 | 20.557186 |
| 12820.0 | 323 | 59 | 722 | 942.34 | 15.971864 |
2. 计算购买频率
purchase_frequency=sum(uk_data_group['num_transactions'])/uk_data_group.shape[0]
3. 计算复购率和流失率
# Repeat Rate
repeat_rate=uk_data_group[uk_data_group.num_transactions > 1].shape[0]/uk_data_group.shape[0]
#Churn Rate
churn_rate=1-repeat_rate
purchase_frequency,repeat_rate,churn_rate
(90.37107880642694, 0.9818923743942872, 0.018107625605712774)
4. 计算利润率
利润率是常用的盈利能力比率,表示总销售额中有多少百分比转化为收益。假设我们的业务在总销售额上约有 5% 的利润。
# Profit Margin
uk_data_group['profit_margin']=uk_data_group['spent_money']*0.05
uk_data_group.head()
| num_days | num_transactions | num_units | spent_money | avg_order_value | profit_margin | |
|---|---|---|---|---|---|---|
| CustomerID | ||||||
| 12346.0 | 0 | 1 | 74215 | 77183.60 | 77183.600000 | 3859.1800 |
| 12747.0 | 366 | 103 | 1275 | 4196.01 | 40.737961 | 209.8005 |
| 12748.0 | 372 | 4596 | 25748 | 33719.73 | 7.336756 | 1685.9865 |
| 12749.0 | 209 | 199 | 1471 | 4090.88 | 20.557186 | 204.5440 |
| 12820.0 | 323 | 59 | 722 | 942.34 | 15.971864 | 47.1170 |
5. 计算客户终身价值
# Customer Value
uk_data_group['CLV']=(uk_data_group['avg_order_value']*purchase_frequency)/churn_rate
#Customer Lifetime Value
uk_data_group['cust_lifetime_value']=uk_data_group['CLV']*uk_data_group['profit_margin']
uk_data_group.head()
| num_days | num_transactions | num_units | spent_money | avg_order_value | profit_margin | CLV | cust_lifetime_value | |
|---|---|---|---|---|---|---|---|---|
| CustomerID | ||||||||
| 12346.0 | 0 | 1 | 74215 | 77183.60 | 77183.600000 | 3859.1800 | 3.852060e+08 | 1.486579e+12 |
| 12747.0 | 366 | 103 | 1275 | 4196.01 | 40.737961 | 209.8005 | 2.033140e+05 | 4.265538e+07 |
| 12748.0 | 372 | 4596 | 25748 | 33719.73 | 7.336756 | 1685.9865 | 3.661610e+04 | 6.173424e+07 |
| 12749.0 | 209 | 199 | 1471 | 4090.88 | 20.557186 | 204.5440 | 1.025963e+05 | 2.098545e+07 |
| 12820.0 | 323 | 59 | 722 | 942.34 | 15.971864 | 47.1170 | 7.971198e+04 | 3.755789e+06 |
CLTV 的预测模型
现在来构建 CLTV 的预测模型。
这里,您将使用线性回归模型来预测 CLTV。
首先使用上面加载并筛选后的数据。
uk_data.head()
| CustomerID | InvoiceDate | InvoiceNo | Quantity | UnitPrice | TotalPurchase | month_yr | |
|---|---|---|---|---|---|---|---|
| 0 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 6 | 2.55 | 15.30 | Dec-2010 |
| 1 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 6 | 3.39 | 20.34 | Dec-2010 |
| 2 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 8 | 2.75 | 22.00 | Dec-2010 |
| 3 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 6 | 3.39 | 20.34 | Dec-2010 |
| 4 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 6 | 3.39 | 20.34 | Dec-2010 |
从 InvoiceDate 中提取月份和年份。
uk_data['month_yr'] = uk_data['InvoiceDate'].apply(lambda x: x.strftime('%b-%Y'))
uk_data.head()
| CustomerID | InvoiceDate | InvoiceNo | Quantity | UnitPrice | TotalPurchase | month_yr | |
|---|---|---|---|---|---|---|---|
| 0 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 6 | 2.55 | 15.30 | Dec-2010 |
| 1 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 6 | 3.39 | 20.34 | Dec-2010 |
| 2 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 8 | 2.75 | 22.00 | Dec-2010 |
| 3 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 6 | 3.39 | 20.34 | Dec-2010 |
| 4 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 6 | 3.39 | 20.34 | Dec-2010 |
数据透视表将列作为输入,并将条目分组为二维表,从而对数据进行多维度汇总。
sale=uk_data.pivot_table(index=['CustomerID'],columns=['month_yr'],values='TotalPurchase',aggfunc='sum',fill_value=0).reset_index()
sale.head()
| month_yr | CustomerID | Apr-2011 | Aug-2011 | Dec-2010 | Dec-2011 | Feb-2011 | Jan-2011 | Jul-2011 | Jun-2011 | Mar-2011 | May-2011 | Nov-2011 | Oct-2011 | Sep-2011 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 12346.0 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 77183.60 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| 1 | 12747.0 | 0.00 | 301.70 | 706.27 | 438.50 | 0.00 | 303.04 | 0.00 | 376.30 | 310.78 | 771.31 | 312.73 | 675.38 | 0.00 |
| 2 | 12748.0 | 1100.37 | 898.24 | 4228.13 | 1070.27 | 389.64 | 418.77 | 1113.27 | 2006.26 | 1179.37 | 2234.50 | 10639.23 | 2292.84 | 6148.84 |
| 3 | 12749.0 | 0.00 | 1896.13 | 0.00 | 763.06 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 859.10 | 572.59 | 0.00 | 0.00 |
| 4 | 12820.0 | 0.00 | 0.00 | 0.00 | 210.35 | 0.00 | 170.46 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 343.76 | 217.77 |
将所有月份的销售额求和。
sale['CLV']=sale.iloc[:,2:].sum(axis=1)
sale.head()
| month_yr | CustomerID | Apr-2011 | Aug-2011 | Dec-2010 | Dec-2011 | Feb-2011 | Jan-2011 | Jul-2011 | Jun-2011 | Mar-2011 | May-2011 | Nov-2011 | Oct-2011 | Sep-2011 | CLV |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 12346.0 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 77183.60 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 77183.60 |
| 1 | 12747.0 | 0.00 | 301.70 | 706.27 | 438.50 | 0.00 | 303.04 | 0.00 | 376.30 | 310.78 | 771.31 | 312.73 | 675.38 | 0.00 | 4196.01 |
| 2 | 12748.0 | 1100.37 | 898.24 | 4228.13 | 1070.27 | 389.64 | 418.77 | 1113.27 | 2006.26 | 1179.37 | 2234.50 | 10639.23 | 2292.84 | 6148.84 | 32619.36 |
| 3 | 12749.0 | 0.00 | 1896.13 | 0.00 | 763.06 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 859.10 | 572.59 | 0.00 | 0.00 | 4090.88 |
| 4 | 12820.0 | 0.00 | 0.00 | 0.00 | 210.35 | 0.00 | 170.46 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 343.76 | 217.77 | 942.34 |
选择特征
在这里,您需要将给定列划分为两类变量:因变量(或目标变量)和自变量(或特征变量)。选择最近 6 个月作为自变量。
X=sale[['Dec-2011','Nov-2011', 'Oct-2011','Sep-2011','Aug-2011','Jul-2011']]
y=sale[['CLV']]
划分数据
为了评估模型表现,将数据集划分为训练集和测试集是一个好策略。
使用 train_test_split() 函数来划分数据集。您需要传入 3 个参数:特征、目标和测试集大小。另外,您可以使用 random_state 作为种子值以保持可复现性,这意味着无论何时划分数据,都不会影响结果。如果 random_state 为 None,则随机数生成器会使用 np.random 随机选择记录。这意味着如果不设置种子,每次结果都不同。
#split training set and test set
from sklearn.cross_validation import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y,random_state=0)
模型开发
首先导入线性回归模块并创建线性回归对象。然后使用 fit() 函数在训练集上拟合模型,并使用 predict() 函数在测试集上进行预测。
# import model
from sklearn.linear_model import LinearRegression
# instantiate
linreg = LinearRegression()
# fit the model to the training data (learn the coefficients)
linreg.fit(X_train, y_train)
# make predictions on the testing set
y_pred = linreg.predict(X_test)
# print the intercept and coefficients
print(linreg.intercept_)
print(linreg.coef_)
[208.50969617]
[[0.99880551 0.80381254 1.60226829 1.67433228 1.52860813 2.87959449]]
模型对数据的拟合效果如何?
为了评估线性模型的整体拟合度,我们使用 R-squared(决定系数)。R-squared 是模型解释方差的比例,取值介于 0 和 1 之间。值越高越好,因为这表示模型解释的方差越大。
from sklearn import metrics
# compute the R Square for model
print("R-Square:",metrics.r2_score(y_test, y_pred))
R-Square: 0.9666074402817512
该模型的 R-squared 较高(0.96)。说明模型对数据拟合更好。
模型评估
针对回归问题,常用以下评估指标(Ritchie Ng):
- 平均绝对误差(MAE)是误差绝对值的平均值。
- 均方误差(MSE)是误差平方的平均值。
- 均方根误差(RMSE)是误差平方平均值的平方根。
# calculate MAE using scikit-learn
print("MAE:",metrics.mean_absolute_error(y_test,y_pred))
#calculate mean squared error
print("MSE",metrics.mean_squared_error(y_test, y_pred))
# compute the RMSE of our predictions
print("RMSE:",np.sqrt(metrics.mean_squared_error(y_test, y_pred)))
MAE: 595.0282284701234
MSE 2114139.8898678957
RMSE: 1454.0082151995896
RMSE 比 MSE 和 MAE 更常用,因为 RMSE 与 y 的量纲一致,便于解释。
CLTV 的优缺点
CLTV 有助于您制定有效的业务计划,并为业务扩张提供机会。CLTV 能构建有意义的客户细分,这些细分有助于识别不同细分人群的需求。
客户终身价值是一种工具,而非策略。CLTV 可以找出最具利润的客户,但如何从他们身上获利,取决于您的策略。一般而言,CLTV 模型容易被误解和误用。对 CLTV 的痴迷可能会让人产生思维“盲点”:公司只专注于寻找最佳客户群、集中资源并重复业务,但同样重要的是也要关注其他客户。
结论
恭喜您读到本教程的最后!
在本教程中,您详细了解了客户终身价值的相关内容。您学习了什么是客户终身价值、计算 CLTV 的方法、在 Python 中从零实现 CLTV、CLTV 的预测模型,以及 CLTV 的优缺点。同时,您还复习了 pandas 的一些基础概念,如 groupby 和数据透视表,用于对所选数据的列和行进行汇总。
希望您现在可以将 CLTV 的概念用于分析您自己的数据集。感谢阅读!
如果您想进一步学习如何在 Python 中分析客户数据,请参加 DataCamp 的 Customer Analytics & A/B Testing in Python 课程。