原文:
annas-archive.org/md5/42d9640be578d0082a7e1589f07dd9bf
译者:飞龙
第五章:使用价格需求弹性选择最佳价格
需求的价格弹性衡量的是产品消费量对价格变化的响应程度。如果价格调整导致供给或需求发生显著变化,则商品是有弹性的。如果商品的价格调整对需求或供给没有显著影响,则其为无弹性。产品的弹性受替代品可获得性的影响。如果该产品是必需品且没有合适的替代品,价格上涨时需求不会改变,从而使其具有无弹性。
在本章中,我们将学习以下内容:
-
什么是价格弹性,如何利用它来最大化收入?
-
探索数据以确定定价模式及其周围的消费者行为
-
确定不同产品的需求曲线
-
优化价格以最大化所有商品的收入
在这种情况下,我们将使用餐车销售数据来分析不同定价策略的影响。
技术要求
为了能够跟随本章的步骤,你需要满足以下要求:
-
一个运行 Python 3.7 或更高版本的 Jupyter Notebook 实例。如果你有 Google Drive 账户,也可以使用 Google Colab 笔记本来运行这些步骤。
-
理解基本的数学和统计学概念。
理解价格需求弹性
价格弹性概念用于解释销售的商品数量对价格上涨或下跌的响应程度。这对于需要预测价格变动如何影响公司财务的经理来说,具有重要价值。
从数学角度来看,其表达式如下:
这里,每个术语代表以下内容:
-
Q:需求商品的数量
-
P:需求商品的价格
价格弹性是衡量需求量对价格变化敏感度的指标,几乎所有商品在价格上涨时需求都会下降,但某些商品的需求下降幅度更大。价格弹性在保持其他因素不变的情况下,衡量价格上涨 1%时需求量的百分比变化。当弹性为-2 时,价格每上涨 1%,需求量就会下降 2%。在某些特定情况下,价格弹性为负数。当商品的弹性为 2 时,几乎可以肯定地表明该弹性的正式定义为-2。
更多弹性(more elastic)意味着商品的弹性更大,无论符号如何。需求法则的两个不常见例外,费布伦商品和吉芬商品,是两类具有正弹性的商品。当商品的需求的弹性绝对值小于 1 时,需求被认为是缺乏弹性的,意味着价格变化对需求量的影响相对较小。如果商品的需求弹性大于 1,则被认为是有弹性的。一种需求弹性为-2 的商品,其数量下降是价格上涨的两倍,而需求弹性为-0.5 的商品,其数量下降仅为价格上涨的一半。
当价格选择使得弹性恰好为 1 时,收入最大化。税收对商品的影响(或“负担”)可以通过商品的弹性来预测。为了确定价格弹性,采用了多种研究技术,如测试市场、历史销售数据分析和联合分析。
探索数据
实验的初始数据源是位于办公楼外的食品卡车销售情况。由于成本压力,审查过高的价格至关重要。食品卡车的经营者必须了解价格上涨如何影响店内汉堡的需求。换句话说,要确定价格能够提高多少,了解汉堡的价格弹性至关重要。实际上,价格弹性衡量的是产品价格对需求的影响程度。
我们将在下一个示例中使用以下 Python 模块:
-
Pandas:一个用于数据分析和数据处理的 Python 包。
-
NumPy:这是一个支持大型多维数组和矩阵的库,并提供大量高层次的数学函数来操作这些数组。
-
statsmodels:一个 Python 包,提供对 SciPy 的补充,用于统计计算,包括描述性统计和统计模型的估计与推断。它提供了用于估计多种不同统计模型的类和函数。它是一个用于统计方法和描述性统计的 Python 包,以及其他模型的统计模型。
-
Seaborn 和 Matplotlib:用于有效数据可视化的 Python 包。
-
以下代码块将加载所有所需的包,加载数据并显示数据的前五行:
import pandas as pd
import numpy as np
import statsmodels.api as sm
from statsmodels.formula.api import ols
import matplotlib.pyplot as plt
import seaborn as sns; sns.set(style="ticks",
color_codes=True)
data = pd.read_csv('foodcar_data.csv',
parse_dates=['DATE'])
data.head()
这将产生下一个数据框,展示了包含食品卡车销售的商品交易数据以及其他一些外部变量的数据:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_1.jpg
图 5.1:待分析的食品卡车销售数据
数据包含以下变量:
-
SELLER:销售商品的标识符
-
CAT:表示商品是单独销售(0)还是作为组合的一部分销售(2)的变量
-
ITEM_ID:售出的物品标识符
-
ITEM_NAME:物品的全名
-
DATE:物品销售的时间
-
YEAR:从日期中提取的年份
-
HOLIDAY:布尔变量,指示当天是否为节假日
-
WEEKEND:布尔变量,指示是否为周末
-
SCHOOLBREAK:布尔变量,指示是否为学校假期
-
AVG_TEMPERATURE:当天的温度(华氏度)
-
在下一个命令中,我们将进行描述性统计分析,之前已经删除了ITEM_ID列,因为尽管它被理解为一个数值变量,但它代表的是一个分类维度。
data.drop(['ITEM_ID'],axis=1).describe()
这将产生以下输出:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_2.jpg
图 5.2:数据的统计描述摘要
-
我们感兴趣的一件事是,我们将在数据中找到的不同类型的产品。在这种情况下,我们将只选择SELLER、ITEM_ID和ITEM_NAME数据,并删除重复项:
d = data[['SELLER','ITEM_ID','ITEM_NAME']].drop_duplicates()
print(d)
这将产生以下输出:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_3.jpg
图 5.3:正在出售的产品
-
通过将这些数据转换为虚拟变量,我们可以看到,卖家列实际上展示了不同的产品组合,其中一些是单独出售的,而另一些是作为组合出售的,例如4104,它是一个汉堡和一瓶水:
pd.concat([d.SELLER, pd.get_dummies(d.ITEM_NAME)], axis=1).groupby(d.SELLER).sum().reset_index(drop=True)
这将产生以下输出:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_4.jpg
图 5.4:产品组合销售
-
我们开始探索数据的一种方式是运行配对图。这个 Seaborn 图可以让我们看到值的分布以及变量之间的关系。如果我们想在第一眼看到它们之间是否存在关系,它非常有用:
sns.pairplot(data[['PRICE','QUANTITY']],height=5,aspect=1.2)
这将产生以下输出:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_5.jpg
图 5.5:价格与数量的配对图
一眼看去,我们可以看到价格与数量之间有许多重叠的趋势。我们还可以看到两个非常明显的组,一个位于左下象限,另一个位于右上象限。
-
我们可以使用 Seaborn 直方图绘图并将
CAT
变量作为色调,来探讨价格与CAT
变量之间的关系,进一步分析这些差异。下一段代码正是做了这一点,首先创建了一个matplotlib
图形,然后用定义好的直方图填充它。这对于某些需要此设置来正确定义图形大小的 Seaborn 图非常有用:f, ax = plt.subplots(figsize=(10, 6))
fig = sns.histplot(x='PRICE',data=data,hue='CAT',
palette=['red','blue'])
这将产生以下输出:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_6.jpg
图 5.6:按 CAT 区分的价格直方图
从前面的图表中我们可以看到,CAT
值为2的产品价格比其他产品更高。
-
这些是作为套餐出售的产品,因此这是一个合理的结果。我们可以通过运行新的散点图进一步探索这些价格差异——这次,我们将寻找
PRICE
、QUANTITY
和ITEM_NAME
之间的关系:sns.pairplot(data[['PRICE','QUANTITY','ITEM_NAME']], hue = 'ITEM_NAME', plot_kws={'alpha':0.1},height=5,aspect=1.2)
这将产生以下输出:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_7.jpg
图 5.7:价格、数量和商品名称的散点图
我们现在可以更详细地看到每个商品的价格差异。在这个案例中,汉堡是价格最高的,而水是最便宜的(这很合理)。有趣的是,对于相同的商品,我们看到不同的价格,并且销量也有所不同。
我们可以得出结论,必须通过对每个商品进行明确区分来分析价格差异,因为我们很可能会对每个商品有不同的需求弹性和最优定价。
-
下一个代码块将创建一个
10
x6
英寸的 Matplotlib 图形,并填充一个 Seaborn 直方图,展示每个类别中商品名称的分布:f, ax = plt.subplots(figsize=(10, 6))
fig = sns.histplot(x='ITEM_NAME',data=data,hue='CAT', palette=['red','blue'])
这将产生以下输出:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_8.jpg
图 5.8:每个类别的商品名称直方图
从这个直方图中,我们可以看到汉堡是唯一属于 0 类别的商品,因此它是单独销售的。这意味着可乐、水和咖啡与汉堡一起作为套餐销售。这是有用的信息,它展示了食品车老板的销售方式,从而帮助我们思考更好的定价或产品组合方式,以提供给客户。
-
下一个代码块将过滤数据,仅包含
CAT
=2
的商品,并创建一个 Seaborn 关系图来解释价格与销量之间的关系:d = data[data['CAT']==2]
sns.relplot(x=d['PRICE'],y=d['QUANTITY'],height=7,aspect=1.2)
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_9.jpg
图 5.9:CAT = 2 商品的价格与质量关系
在这里显示的关系图中,我们可以看到属于第二类商品(即作为套餐出售的商品)之间价格和质量的关系。除了那些价格明显高于其他数据点的离群值外,大多数商品的价格都处于某个范围内。虽然我们同时观察了多个商品,但通过意识到价格较低的商品售出数量较多,我们仍然可以看到一定的关系。接下来,我们的任务是深入挖掘这些关系的具体细节,以便能够确定最大化收益的价格。
-
在下一个代码块中,我们将尝试捕捉价格和销量随时间变化的情况。我们将创建一个新的数据集,并以标准化的方式查看价格和数量,通过减去均值并除以范围:
d = data[['DATE','PRICE','QUANTITY']].sort_values( ['DATE'], ascending=True)
d['PRICE'] = (d['PRICE'] - d['PRICE'].mean())/((d['PRICE'].max() - d['PRICE'].min()))
d['QUANTITY'] = (d['QUANTITY'] - d['QUANTITY'].mean())/((d['QUANTITY'].max() - d['QUANTITY'].min()))
-
一旦我们将价格和数量标准化到-1 到 1 的范围内,我们将把
d['DATE']
设置为索引,最后应用滚动平均来平滑曲线,并使用 pandas DataFrame 对象的绘图方法:d.index = d['DATE']
d = d.drop(['DATE'],axis=1)
d.rolling(window=60).mean().plot(figsize=(20,8))
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_10.jpg
图 5.10:价格与质量关系随时间变化
有趣的是,降价始终会导致销售数量的增加。在这种情况下,我们可以看到最大幅度的降价发生在 2020 年上半年,这导致了销售数量的增加。我们需要问的问题是,这次降价所丧失的收入是否已经通过销量的增加得到了弥补。
-
在下一个代码块中,我们将深入探讨不同变量与价格之间的相关性。为此,我们将使用 pandas DataFrame 的
corr()
方法,以及 NumPy 库来创建一个掩码,以“遮盖”对角线以上的重复值,还将使用matplotlib
库创建一个12
x12
英寸的图形,并用 Seaborn 热图进行填充:import numpy as np
df_corr = data.drop(['DATE','SELLER','STORE'],axis=1).corr()
mask = np.triu(np.ones_like(df_corr, dtype=bool))
df_corr = df_corr.mask(mask).round(3)
fig, ax = plt.subplots(figsize=(12,12))
sns.heatmap(df_corr, annot=True,ax=ax)
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_11.jpg
图 5.11:变量之间的相关性
这里展示的数据给了我们一个关于变量如何相关的概念。我们可以看到价格与温度之间存在负相关关系(也许在天气炎热时人们不太愿意买咖啡和汉堡组合),与学校假期之间有正相关关系(也许孩子们在这些日子里也会购买产品),而与周末之间存在负相关关系,这可能表明食品车的位置在周末的流量较少。
请谨慎看待这些结论,因为我们需要逐项逐案例分析来验证这些假设,并始终记住最大原则:相关性不代表因果关系。
现在我们已经探索了数据,以理解看似相同的商品之间的价格差异,以及了解这种分布的样子,我们可以尝试估算需求曲线。了解需求曲线可以帮助我们建立价格和销售数量之间的关系,这也是我们将在下一节中做的事情。
寻找需求曲线
经济学中的需求曲线是一个图表,描绘了某种商品的价格与在该价格下所需数量之间的关系。个别需求曲线用于描述个别消费者之间的价格与数量交互,而市场需求曲线则用于描述所有消费者(即市场需求曲线)。
一般认为需求曲线向下倾斜是由于需求法则的作用。对于大多数商品来说,价格上升时需求下降。但在一些特殊情况下,这一法则并不适用。这些情况包括投机泡沫、费布伦商品和吉芬商品,当价格上涨时,消费者反而更倾向于购买这些商品。
需求曲线与供给曲线结合使用,以确定均衡价格。在这一理想点,买卖双方已经达成了对商品或服务实际价值的共识,从而使我们能够生产出足够的商品以满足需求,而不会出现短缺或过剩。
在代码中探索需求曲线
为了找到每个商品的需求曲线,首先,我们将每个商品的数据隔离到一个单独的数据框中:
-
在下一段代码中,我们为
burger_2752
创建一个数据框,深入探讨这个特定商品的价格与需求关系,因为我们怀疑每个商品都有其特定的需求曲线:burger_2752 = data[data['ITEM_ID']==2752].drop(['
ITEM_ID','ITEM_NAME'],axis=1)
burger_2752.head()
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_12.jpg
图 5.12:汉堡数据
-
一旦我们隔离了数据,我们可以开始确定价格与数据之间的关系。为此,我们将使用
statsmodels
包,特别是将"QUANTITY ~ PRICE"
作为 OLS 函数的参数。这样,OLS 函数将Quantity
视为因变量,Price
视为自变量。我们也可以传入其他变量作为因变量,但目前我们只关注Price
:model = ols("QUANTITY ~ PRICE", burger_2752).fit()
-
一旦模型正确拟合数据,我们可以打印出给定商品的关系斜率,
价格弹性
,以及 OLS 模型的其他参数:price_elasticity = model.params[1]
print("Price elasticity of the product: " + str(
price_elasticity))
print(model.summary())
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_13.jpg
图 5.13:汉堡 OLS 模型总结
分析模型表现的一种方法是查看截距和误差的回归图。这些散点图帮助我们理解线性回归中因变量和自变量之间的关系。
-
下一段代码将创建一个尺寸为
12
x8
英寸的 Matplotlib 图形,并使用statsmodels
创建模型的偏回归图:fig = plt.figure(figsize=(12,8))
fig = sm.graphics.plot_partregress_grid(model, fig=fig)
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_14.jpg
图 5.14:汉堡 OLS 模型偏回归图
-
我们分析的下一步是创建一个函数来打包模型的创建,确定价格弹性,并返回价格弹性和模型本身。该函数以每个商品的数据作为参数:
def create_model_and_find_elasticity(data):
model = ols("QUANTITY ~ PRICE", data).fit()
price_elasticity = model.params[1]
print("Price elasticity of the product: " + str(price_elasticity))
print(model.summary())
fig = plt.figure(figsize=(12,8))
fig = sm.graphics.plot_partregress_grid(model, fig=fig)
return price_elasticity, model
-
现在我们已经定义了函数,我们将创建两个字典来存储结果:一个用于弹性,另一个用于模型本身。之后,我们将遍历数据中所有独特的项目,将函数应用于数据子集,最后存储每个项目的结果:
elasticities = {}
models = {}
for item_id in data['ITEM_ID'].unique():
print('item_id',item_id)
price_elasticity, item_model =
create_model_and_find_elasticity(data[data[
'ITEM_ID']==item_id])
elasticities[item_id] = price_elasticity
models[item_id]= item_model
-
运行所有唯一项之后,我们可以打印每个项目的弹性结果。
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_15.jpg
图 5.15: 产品项目弹性
现在我们已经确定了每个项目的价格弹性,我们可以模拟不同的可能价格,并找到能够最大化收入的点。
使用需求曲线优化收入
一旦我们建立了价格与数量之间的关系,就可以模拟每一个可能价格的收入。为此,我们将找到每个项目的最低和最高价格,设定一个阈值,创建可能价格的区间,并使用存储的模型来预测销售数量。下一步是通过将价格与数量相乘来确定总收入。需要注意的是,在这种分析中,通常最好查看收入而非利润,因为大多数时候我们没有每个项目的成本数据。我们将通过以下步骤探索如何进行:
-
下一个代码块将获取
burger_2752
的数据,确定上下价格边界,使用 NumPy 的范围创建一个区间,最后,使用训练好的模型预测销售数量,从而预测收入:start_price = burger_2752.PRICE.min() - 1
end_price = burger_2752.PRICE.max() + 10
test = pd.DataFrame(columns = ["PRICE", "QUANTITY"])
test['PRICE'] = np.arange(start_price, end_price,0.01)
test['QUANTITY'] = models[2752].predict(test['PRICE'])
test['REVENUE'] = test["PRICE"] * test["QUANTITY"]
test.head()
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_16.jpg
图 5.16: 每个价格下预测的收入的前几行
-
为了能够直观地呈现变量之间的关系,无论度量单位如何,我们将通过减去均值并除以范围来标准化数据变量。最后,我们将使用绘图方法来可视化数据:
test['PRICE'] = (test['PRICE']-test['PRICE'].mean())/(test['PRICE'].max()-test['PRICE'].min())
test['QUANTITY'] = (test['QUANTITY']-test['QUANTITY'].mean())/(test['QUANTITY'].max()-test['QUANTITY'].min())
test['REVENUE'] = (test['REVENUE']-test['REVENUE'].mean())/(test['REVENUE'].max()-test['REVENUE'].min())
test.plot(figsize=(12,8),title='Price Elasticity - Item 2752)
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_17.jpg
图 5.17: burger_2752 需求曲线
从需求曲线中我们可以看到,这个项目是非弹性的,意味着即使价格上涨,这种产品的销售仍然会继续增加。为了获得更清晰的图像,我们将对另一个项目重复相同的练习。
-
我们将使用
coffee
数据并重复相同的练习来可视化需求曲线:coffee_3052 = data[data['ITEM_ID']==3052]
start_price = coffee_3052.PRICE.min() - 1
end_price = coffee_3052.PRICE.max() + 10
test = pd.DataFrame(columns = ["PRICE", "QUANTITY"])
test['PRICE'] = np.arange(start_price, end_price,0.01)
test['QUANTITY'] = models[3052].predict(test['PRICE'])
test['REVENUE'] = test["PRICE"] * test["QUANTITY"]
test['PRICE'] = (test['PRICE']-test['PRICE'].mean())/(test['PRICE'].max()-test['PRICE'].min())
test['QUANTITY'] = (test['QUANTITY']-test['QUANTITY'].mean())/(test['QUANTITY'].max()-test['QUANTITY'].min())
test['REVENUE'] = (test['REVENUE']-test['REVENUE'].mean())/(test['REVENUE'].max()-test['REVENUE'].min())
test.plot(figsize=(12,8),title='Price Elasticity - Item 3052')
通过运行之前的代码,我们可以看到需求曲线是凹形的,且弹性为负,这意味着如果价格上涨,销售的单位数会减少。虽然这会由于销售单位减少而导致收入下降,但也意味着由于价格上涨,收入会增加。
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_18.jpg
图 5.18: coffee_3052 需求曲线
- 现在,我们可以将这个过程转换为一个可以应用于每个项目数据的函数,从而获得需求曲线,并确定最优价格。
确定最优价格的方式非常简单。我们只需要在where
语句中找到最大值,这样就能返回收入最高时的价格:
def find_optimal_price(data, model,item_id):
start_price = data.PRICE.min() - 1
end_price = data.PRICE.max() + 10
test = pd.DataFrame(columns = ["PRICE", "QUANTITY"])
test['PRICE'] = np.arange(start_price, end_price,0.01)
test['QUANTITY'] = model.predict(test['PRICE'])
test['REVENUE'] = test["PRICE"] * test["QUANTITY"]
test['P'] = (test['PRICE']-test['PRICE'].mean())/(test['PRICE'].max()-test['PRICE'].min())
test['Q'] = (test['QUANTITY']-test['QUANTITY'].mean())/(test['QUANTITY'].max()-test['QUANTITY'].min())
test['R'] = (test['REVENUE']-test['REVENUE'].mean())/(test['REVENUE'].max()-test['REVENUE'].min())
test[['P','Q','R']].plot(figsize=(12,8),title='Price Elasticity - Item'+str(item_id))
ind = np.where(test['REVENUE'] == test['REVENUE'].max())[0][0]
values_at_max_profit = test.drop(['P','Q','R'],axis=1).iloc[[ind]]
values_at_max_profit = {'PRICE':values_at_max_profit['PRICE'].values[0],'QUANTITY':values_at_max_profit['QUANTITY'].values[0],'REVENUE':values_at_max_profit['REVENUE'].values[0]}
return values_at_max_profit
-
现在我们已经有了确定最大利润的函数,我们可以计算所有项目的最优价格并将其存储在一个字典中:
optimal_price = {}
for item_id in data['ITEM_ID'].unique():
print('item_id',item_id)
optimal_price[item_id] =
find_optimal_price(data[data['ITEM_ID']==item_id],
models[item_id],item_id)
在对数据中的所有项目运行此代码后,我们可以确定那些能够最大化食品卡车收入的参数。
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_19.jpg
图 5.19:item_6249 的需求曲线
我们可以看到,剩下的另外两个待分析的项目,其弹性也为负值,这意味着价格越高,销售的单位数量就越少。
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_20.jpg
图 5.20:item_4273 的需求曲线
-
现在我们已经得到了最优价格,我们可以打印出导致收入最大化的参数。接下来的代码块遍历了
optimal_price
字典:for item_id in optimal_price:
print(item_id,optimal_price[item_id])
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_05_21.jpg
图 5.21:每个项目的最优参数
通过这种方式,我们可以确定最优价格,无论项目的特性如何,以及客户愿意为每个项目支付的金额。
总结
在本章中,我们深入探讨了项目价格与销售数量之间的关系。我们研究了不同项目有不同的需求曲线,这意味着在大多数情况下,价格越高,售出的商品数量越少,但并非总是如此。价格与销售数量之间的关系可以通过价格弹性来建模,它能帮助我们了解价格上涨时,商品销售数量会减少多少。
我们查看了食品卡车的销售数据,旨在确定每个项目的最佳价格,并发现这些项目具有不同的弹性,对于每个项目,我们可以确定一个价格,该价格将最大化收入。
在下一章,我们将专注于改进我们捆绑和推荐产品的方式,通过分析市场购物篮来推荐经常一起购买的有意义的产品。
第六章:产品推荐
产品推荐本质上是一个过滤系统,旨在预测并展示用户可能感兴趣的商品。它用于生成推荐,保持用户对产品和服务的兴趣,并向他们提供相关建议。在本章中,我们将学习如何做到以下几点:
-
识别正在减少销售的客户
-
为尚未购买的产品向客户提供个性化产品建议
-
基于已购买产品创建具体的产品推荐,使用市场篮子分析和 Apriori 算法
让我们确定理解这些步骤并跟随本章的需求。
本章内容包括以下主题:
-
目标是识别回购量下降的客户
-
理解产品推荐系统
-
使用 Apriori 算法进行产品捆绑
技术要求
为了能够跟随本章的步骤,您需要满足以下要求:
-
需要运行 Python 3.7 及以上版本的 Jupyter Notebook 实例。如果你有 Google Drive 账号,也可以使用 Google Colab Notebook 来运行这些步骤。
-
具备基本的数学和统计学概念。
目标是识别回购量下降的客户
企业的一个重要方面是,老客户的购买量总是超过新客户,因此密切关注老客户并在发现他们行为发生变化时采取行动是至关重要的。我们可以做的其中一项就是识别出购买模式下降的客户,并为他们推荐他们还没有购买的新产品。在这种情况下,我们将查看消费品配送中心的数据,识别这些购买量下降的客户:
-
首先,我们将导入必要的库,包括:用于数据处理的 pandas,处理缺失值和掩码的 NumPy,以及用于协同过滤产品推荐的 scikit-surprise。
-
我们将探索数据,确定合适的策略将数据标准化为正确的格式。
-
一旦数据结构化,我们将设置线性回归模型,确定具有负斜率的客户,以识别消费模式下降的客户。这些信息将帮助我们为这些客户制定具体的行动计划,避免客户流失。
让我们从以下步骤开始:
-
我们的第一步是加载这些包并安装
scikit-surprise
包,用于协同过滤,这是一种根据类似用户的评分来过滤出用户可能喜欢的项目的方法。它通过将一小部分与特定用户口味相似的用户行为联系起来,为用户推荐产品:!pip install --upgrade openpyxl scikit-surprise
import pandas as pd
import numpy as np
-
为了提高可读性,我们将限制最多显示的行数为 20 行,设置最多显示 50 列,并将浮动数值显示为精确到 2 位小数:
pd.options.display.max_rows = 20
pd.options.display.max_columns = 50
pd.options.display.precision = 2
-
现在我们可以加载要分析的数据:
df = pd.read_csv('/content/distributed_products.csv')
df.head()
在下图中,我们展示了按周期销售商品的历史销售情况,包括客户和产品的详细信息以及销售数量:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_1.jpg
图 6.1:消费品交易数据
数据由不同客户的购买订单组成,涵盖不同的产品和不同的周期。数据中有一个周期列,记录了购买发生的年份和月份。
-
我们可以通过查看列列表继续探索数据:
df.columns.tolist()
>>> ['period', 'sub_market', 'client_class', 'division', 'brand','cat', 'product', 'client_code', 'client_name', 'kgs_sold']
-
现在,让我们查看总客户数进行分析:
len(df.client_name.unique())
>>> 11493
在这个案例中,我们有接近 12,000 个客户。为了本章的演示目的,我们将重点关注最重要的客户,基于消费最多的标准。
-
现在,我们将找出那些销售逐渐减少的客户。我们将通过汇总信息,列出那些总购买千克数最高的客户,以确定最佳客户。我们将使用
groupby
方法按周期求和,计算每个客户在每个周期购买的千克数:kgs_by_period = df[['period','client_name','kgs_sold']]
kgs_by_period = kgs_by_period.groupby(['
period','client_name']).sum().reset_index()
kgs_by_period.head()
在下图中,我们可以看到按客户和周期
划分的商品总千克数:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_2.jpg
图 6.2:按客户和周期聚合的商品总重量(单位:千克)
-
现在我们有了所有客户的列表,我们将按每个周期的购买次数对他们进行特征化:
unique_clients = kgs_by_period.client_name.value_counts().reset_index()
unique_clients.columns = ['client_name','purchases']
unique_clients.head()
在下一个数据框中,我们可以看到每个客户的购买次数:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_3.jpg
图 6.3:按购买次数统计的用户数据
-
现在,我们将按购买次数筛选出前五名客户,保留在 8 个周期中至少有 5 次购买的客户。这个限制是为了找到那些大部分时间是常规客户的客户,限值是随意设定的:
unique_clients = unique_clients[unique_clients.purchases>5]
-
现在,我们可以再次查看客户的总数:
unique_clients.shape
>>> (7550, 2)
如我们所见,大多数客户有超过 5 个周期的购买记录,因此我们减少了约 30%的总用户数。
-
现在,我们将列出所有周期中销售的商品总千克数,筛选出购买周期少于 5 个周期的客户:
kgs_by_client = df[['client_name','kgs_sold']]
kgs_by_client = kgs_by_client[kgs_by_client.client_name.isin(unique_clients.client_name)]
-
现在,为了获得所有周期中销售的商品总千克数,我们将使用
groupby
方法并按升序排序值:kgs_by_client = kgs_by_client.groupby(['client_name']).sum().reset_index()
kgs_by_client.columns = ['client','total_kgs']
kgs_by_client = kgs_by_client.sort_values([
'total_kgs'],ascending= False)
-
作为下一步,仅为了可视化和演示,我们将客户限制为前 25 名客户:
kgs_by_client = kgs_by_client.head(25)
kgs_by_client.head()
然后,我们可以看到按总千克数排序的前 25 名客户:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_4.jpg
图 6.4:购买千克数最多的客户
-
现在我们有了按千克数销售最多的客户的信息,我们可以创建一个直方图来理解他们的消费模式。我们将使用
plot
方法为 pandas 数据框创建一个柱状图:kgs_by_client.plot(kind='bar',x='client',y='total_kgs',figsize=(14,6),rot=90)
这将得到以下结果:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_5.jpg
图 6.5:销售千克数最多的客户图表
-
为了捕捉那些消费水平下降的客户,我们将创建一个掩码,筛选出所有客户中最顶尖的,来可视化每个客户和期间购买的千克数:
mask = kgs_by_period.client_name.isin(kgs_by_client.client)
kgs_by_period = kgs_by_period[mask]
kgs_by_period = kgs_by_period.sort_values([
'kgs_sold'],ascending=False)
kgs_by_period
这是在按重量筛选出顶尖客户后的过滤数据:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_6.jpg
图 6.6:按期间和客户销售的千克数
-
最后,我们将透视 DataFrame 以进行可视化,并将 NaN 值填充为 0,因为这表示客户在该期间没有购买任何商品:
dfx = kgs_by_period.pivot(index='period',columns=
'client_name',values='kgs_sold').reset_index()
dfx.columns.name = ''
dfx = dfx.fillna(0)
dfx
下一个 DataFrame 已经透视过,并且更适合用于机器学习模型:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_7.jpg
图 6.7:透视数据
-
现在,我们可以可视化整个期间的消费情况:
import seaborn as sns
import matplotlib.pyplot as plt # visualization
f, ax = plt.subplots(figsize=(20, 6))
# Load the long-form example gammas dataset
g = sns.lineplot(data=dfx.drop(['period'],axis=1))
# Put the legend out of the figure
g.legend(loc='center left', bbox_to_anchor=(1, 0.5))
折线图让我们一目了然地看到销售量最大的客户:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_8.jpg
图 6.8:按客户和期间销售的千克数折线图
-
为了识别那些有下降趋势的曲线,我们将确定每月销售额的斜率和标准差。这将帮助我们通过观察斜率识别消费下降的客户,并识别那些消费波动较大的用户:
from scipy import stats
results = []
for i in range(1,dfx.shape[1]):
client = dfx.columns[i]
slope, intercept, r_value, p_value, std_err = stats.linregress(dfx.index,dfx.iloc[0:,i])
results.append([client,slope,std_err])
print('Client Name:',client,'; Buy Tendency (Slope):',round(slope,3),'; Common Standard deviation:',round(std_err,3))
从打印结果中可以看到,一些客户的斜率为负数,表示他们的消费模式显示出月度购买量的下降:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_9.jpg
图 6.9:客户购买趋势的斜率
在这种情况下,值是以绝对值显示的,但更好的做法是将其显示为每个客户中位购买量的百分比,以保持一致性。你可以应用这个更改并评估差异。
-
接下来,我们将把结果存储在 DataFrame 中,并用它来可视化结果:
results_df = pd.DataFrame(results).dropna()
results_df.columns = ['client','slope','std']
results_df.index = results_df.client
results_df = results_df.drop(['client'],axis=1)
results_df.head()
DataFrame 显示了客户以及回归模型估算的参数:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_10.jpg
图 6.10:最终斜率和标准差
-
现在我们的信息已经整齐地结构化,我们可以创建一个 seaborn 热力图,更直观地展示结果数据:
f, ax = plt.subplots(figsize=(12, 12))
sns.heatmap(results_df, annot=True)
结果为以下输出:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_11.jpg
图 6.11:斜率和偏差热力图
从数据中我们可以看到,一些客户的月度购买量明显下降,而有些客户则逐渐增加了购买量。查看标准差也很有帮助,可以找到这些客户购买模式的波动性。
现在我们了解了每个客户的表现,可以通过为他们提供量身定制的推荐,针对销售下降的客户采取行动。在下一部分中,我们将根据客户的购买模式训练推荐系统。
理解产品推荐系统
现在我们已经识别出消费下降的客户,我们可以为他们创建特定的产品推荐。你如何推荐产品?在大多数情况下,我们可以使用推荐系统,这是一种过滤系统,旨在预测并显示用户可能想购买的产品,从而形成产品建议。k 最近邻方法和潜在因子分析是协同过滤中使用的两种算法,潜在因子分析是一种统计方法,用于找出相关变量的群体。此外,使用协同过滤时,系统会学习两个或多个物品共同购买的可能性。推荐系统的目标是以用户喜欢的方式提供产品的用户友好推荐。协同过滤方法和基于内容的方法是实现这一目标的两大类技术。
向客户推荐相关产品的重要性至关重要,因为企业可以通过推荐系统个性化客户体验,推荐那些根据客户消费模式对他们最有意义的产品。为了提供相关的产品推荐,推荐引擎还使公司能够分析客户过去和现在的网页活动。
推荐系统有很多应用,其中一些最著名的包括视频和音频服务的播放列表生成器、在线商店的产品推荐器、社交媒体平台的内容推荐器以及开放网页内容推荐器。
总结来说,推荐引擎提供基于每个客户需求和兴趣的个性化直接推荐。机器学习正在被用来改进在线搜索,因为它基于用户的视觉偏好而非产品描述提供建议。
创建推荐系统
我们训练推荐系统的第一步是捕捉客户的消费模式。在接下来的示例中,我们将关注客户在不同时间段内购买的产品:
dfs = df[['client_name','product']].groupby(['client_name','product']).size().reset_index(name='counts')
dfs = dfs.sort_values(['counts'],ascending=False)
dfs.head()
这会产生以下输出:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_12.jpg
图 6.12:客户购买的产品
我们将使用 0 到 1 之间的评分标准来训练推荐系统,因此需要对这些值进行缩放。现在我们可以看到一些客户始终购买某些产品,因此我们将使用sklearn
的最小-最大缩放器来调整比例。
在机器学习中,我们通过生成新值来规范化数据,保持整体分布,并调整数据中的比例;规范化可以防止使用原始数据以及解决众多数据集问题。运用多种方法和算法还可以提高机器学习模型的效率和准确性。
来自 scikit-learn 的MinMaxScaler
可以用于将变量缩放到一个范围内。需要注意的是,变量的分布应该是正态分布。MinMaxScaler
保留了原始分布的形状,确保原始数据中的信息没有实质性改变。请记住,MinMaxScaler
不会降低异常值的重要性,且最终生成的特征默认范围为 0 到 1。
哪个缩放器——MinMaxScaler
还是StandardScaler
——更优?对于符合正态分布的特征,StandardScaler
很有帮助。当上下边界从领域知识中得到了很好的定义时,可以使用MinMaxScaler
(例如 RGB 颜色范围内从 0 到 255 的像素强度)。
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
dfs['count_sc'] = scaler.fit_transform(dfs[['counts']])
dfs = dfs.drop(['counts'],axis=1)
现在我们已经标准化了值,可以开始构建推荐系统了。在这里,我们将使用 SVDpp 算法,它是 SVD 的扩展,考虑了隐式评分。SVD 作为协同过滤机制被应用于推荐系统。矩阵中的每一行代表一个用户,每一列代表一件商品。用户为物品提供的评分构成了矩阵的元素。
SVD 的通用公式是:
M=UΣV**ᵗ
其中:
-
M 是我们想要分解的原始矩阵,它是用户和他们购买的产品的密集矩阵
-
U 是左奇异矩阵(列是左奇异向量)
-
Σ是一个对角矩阵,包含奇异特征值
-
V 是右奇异矩阵(列是右奇异向量)
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_13.jpg
图 6.13:协同过滤矩阵分解
scikit-surprise
包高效地实现了 SVD 算法。我们无需重新发明轮子,就可以使用简单易用的 Python 模块SurpriseSVD
快速构建基于评分的推荐系统。当使用像 SVD 这样的模型时,SurpriseSVD
还提供了对矩阵因子的访问,使我们能够直观地看到数据集中对象之间的关系:
-
我们将从导入库开始:
from surprise import SVDpp
from surprise.model_selection import cross_validate
from surprise import Reader, Dataset
-
现在,我们将初始化阅读器,并将其范围设置为 0 到 1:
reader = Reader(rating_scale=(0,1))
-
然后,我们可以使用
Dataset
方法从包含标准化产品值计数的数据框加载数据:data = Dataset.load_from_df(dfs, reader)
-
最后,我们可以实例化 SVD 算法并在数据上进行训练:
algo = SVDpp()
algo.fit(data.build_full_trainset())
训练过程应该需要几分钟,具体取决于您的硬件规格,但一旦完成,我们就可以开始使用它来进行预测。
-
我们将从选择一个特定的用户开始,并筛选出他们尚未购买的所有产品,以便为他们提供更推荐的产品:
usr = 'LA TROYA'
# Filter the products that the client is already buying
user_prods = dfs[dfs.client_name==usr]['product'].unique().tolist()
prods = dfs[dfs.client_name!=usr]['product'].unique().tolist()
prods = [p for p in prods if p not in user_prods]
-
现在我们已经确定了用户未购买的产品,让我们看看算法如何为这个特定的用户评分:
my_recs = []
for iid in prods:
my_recs.append((iid, algo.predict(uid=usr,iid=iid).est))
-
上述代码将遍历以下数据中的产品,并创建一个包含推荐值最高产品的数据框:
dk = pd.DataFrame(my_recs)
dk.columns = ['product', 'rating']
dk = dk.sort_values('rating',ascending= False).reset_index(drop=True)
dk.head()
下一个数据框展示了我们为客户推荐的产品:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_14.jpg
图 6.14:客户推荐的产品
-
现在我们已经为单个用户确定了这一点,我们可以将其推断到其余的客户。我们将只保留前 20 个推荐,因为产品数量过于庞大:
dki_full = pd.DataFrame()
# For top 25 clients
users = kgs_by_period.client_name.unique()
for usr in users:
print(usr)
user_prods = dfs[dfs.client_name==usr]['product'].unique().tolist()
prods = dfs[dfs.client_name!=usr]['product'].unique().tolist()
prods = [p for p in prods if p not in user_prods]
my_recs = []
for iid in prods:
my_recs.append((iid, algo.predict(uid=usr,iid=iid).est))
dk = pd.DataFrame(my_recs)
dk.columns = ['product', 'rating']
dk = dk.sort_values('rating',ascending= False).reset_index(drop=True)
dk['client'] = usr
dki_full = pd.concat([dki_full, dk.head(20)])
这个脚本将允许我们遍历我们的客户,并为每个客户生成一份推荐产品的清单。在这种情况下,我们正在进行特定的分析,但这可以实现为一个实时交付这些结果的管道。
现在我们有了数据,可以为每个客户提供量身定制的产品推荐,推荐他们尚未购买的产品。我们还可以推荐与他们已经购买的产品互补的产品,接下来我们将在下一部分做这些。
使用 Apriori 算法进行产品捆绑
目前,我们专注于那些减少购买的客户,为他们提供针对那些他们未购买的产品的特定优惠,但我们也可以改善已经是忠实客户的人的结果。我们可以通过进行市场购物篮分析,提供与他们消费模式相关的产品,从而提高他们购买的产品数量。为此,我们可以使用多种算法。
最受欢迎的关联规则学习方法之一是 Apriori 算法。它识别数据集合中的事物,并将它们扩展为越来越大的项目组合。Apriori 被用于数据集中的关联规则挖掘,寻找多个经常出现的事物集合。它扩展了项集之间的联系和关联。这就是你经常在推荐网站上看到的“You may also like”建议的实现,它们正是该算法的结果。
Apriori 是一种用于关联规则学习和频繁项集挖掘的算法,应用于关系数据库。只要这些项集在数据库中出现得足够频繁,它就会通过检测频繁的单个项目并将其扩展为越来越大的项集来向前推进。Apriori 算法通常用于事务型数据库,这些数据库通过 Apriori 方法挖掘频繁项集和关联规则。“支持度”、“提升度”和“置信度”作为参数被使用,其中支持度是某项物品出现的可能性,而置信度则是条件概率。项集由事务中的物品组成。该算法使用“连接”和“剪枝”两个步骤来减少搜索空间。它是发现最频繁项集的迭代方法。在关联规则学习中,数据集中的项被识别,数据集会被扩展为包括越来越大的物品组合。
Apriori 方法是市场购物篮分析中常用的算法,它是一种广泛使用的关联规则算法。该方法有助于发现交易中频繁出现的项集,并揭示这些项之间的关联规律。
使用 Apriori 进行市场购物篮分析
对于此分析,我们将使用 UCI ML 库中找到的独立数据(archive.ics.uci.edu/ml/datasets/Online+Retail
):
-
我们通过导入包并加载数据开始分析。记得在运行这段代码之前安装
mlxtend
模块,否则会遇到Module Not Found错误:from mlxtend.frequent_patterns import apriori, association_rules
data = pd.read_csv('/content/Online Retail.csv',encoding='iso-8859-1')
data.head()
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_15.jpg
图 6.15: 在线零售数据
这份国际数据集包含了 2010 年 12 月 1 日至 2011 年 12 月 9 日间,一家英国注册的非店面互联网零售商所进行的每一笔交易。该公司主要提供各种场合的一次性礼品,拥有大量批发商客户。
-
我们开始探索数据的各列:
data.columns
>>> Index(['InvoiceNo', 'StockCode', 'Description', 'Quantity', 'InvoiceDate','UnitPrice', 'CustomerID', 'Country'],dtype='object')
数据包含了包含代码和日期信息的交易销售数据,但我们现在不需要这些信息。相反,我们将重点关注描述、数量和价格。
-
我们可以查看数据的统计汇总:
data.describe()
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_16.jpg
图 6.16: 描述性统计汇总
-
为了评估分类变量,我们使用聚焦于 DataFrame 对象列的
describe
方法:data.describe(include='O')
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_17.jpg
图 6.17: 描述性分类汇总
这些信息展示了每个对象列的一些计数,并表明最常见的国家是英国,正如预期的那样。
-
我们还将探索不同地区的交易,以更好地理解数据:
data['Country'].value_counts().head(10).plot(kind='bar',figsize=(12,6))
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_18.jpg
图 6.18: 数据中的市场
我们可以确认绝大多数交易发生在英国,其次是德国和法国。
-
为了提高可读性,我们将去除描述中的多余空格:
data['Description'] = data['Description'].str.strip()
-
现在,我们将删除发票号中包含 NaN 的行,并将它们转换为字符串以进行分类处理:
data = data[~data['InvoiceNo'].isna()]
data['InvoiceNo'] = data['InvoiceNo'].astype('str')
-
目前,我们将专注于非信用交易,因此我们将删除所有信用交易:
data = data[~data['InvoiceNo'].str.contains('C')]
-
我们将通过查看英国的关联规则开始分析:
data_uk = data[data['Country'] =="United Kingdom"]
basket_uk = data_uk.groupby(['InvoiceNo', 'Description'])['Quantity'].sum()
basket_uk = basket_uk.unstack().reset_index().fillna(0)
basket_uk = basket_uk.set_index('InvoiceNo')
basket_uk.head()
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_19.jpg
图 6.19: 英国市场购物篮
我们可以看到每张发票上购买的产品的密集矩阵。
-
我们将对法国的交易进行相同的操作:
basket_fr = data[data['Country'] =="France"]
basket_fr = basket_fr.groupby(['InvoiceNo', 'Description'])['Quantity'].sum()
basket_fr = basket_fr.unstack().reset_index().fillna(0)
basket_fr = basket_fr.set_index('InvoiceNo')
-
最后,我们将对德国的数据进行相同的操作:
basket_de = data[data['Country'] =="Germany"]
basket_de = basket_de.groupby(['InvoiceNo', 'Description'])['Quantity'].sum()
basket_de = basket_de.unstack().reset_index().fillna(0)
basket_de = basket_de.set_index('InvoiceNo')
-
现在,我们将定义热编码函数,使数据适应相关库的要求,因为它们需要离散的值(0 或 1):
basket_uk = (basket_uk>0).astype(int)
basket_fr = (basket_fr>0).astype(int)
basket_de = (basket_de>0).astype(int)
-
一旦我们将结果编码为一热编码器,我们就可以开始为每个市场建立模型:
frq_items_uk = apriori(basket_uk, min_support = 0.01, use_colnames = True)
-
一旦模型建立完成,我们就可以查看英国市场发现的关联规则:
# Collecting the inferred rules in a dataframe
rules_uk = association_rules(frq_items_uk, metric ="lift", min_threshold = 1)
# rules_uk = rules_uk.sort_values(['confidence', 'lift'], ascending =[False, False])
rules_uk.head()
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_20.jpg
图 6.20:英国关联规则
如果更详细地检查英国交易的规则,就会发现英国人通常购买各种颜色的茶盘。这可能是因为英国人非常喜欢喝茶,并且经常为各种场合收集不同颜色的茶具。
-
现在我们将对法国的数据进行相同的操作:
frq_items_fr = apriori(basket_fr, min_support = 0.05, use_colnames = True)
# Collecting the inferred rules in a dataframe
rules_fr = association_rules(frq_items_fr, metric ="lift", min_threshold = 1)
rules_fr = rules_fr.sort_values(['confidence', 'lift'], ascending =[False, False])
rules_fr.head()
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_21.jpg
图 6.21:法国关联规则
从这些数据中可以明显看出,纸盘、杯子和餐巾在法国经常一起购买。这是因为法国人每周至少会与朋友和家人聚会一次。此外,由于法国政府已经禁止在国内使用塑料,公民必须购买纸质替代品。
-
最后,我们将为德国数据建立模型:
frq_items_de = apriori(basket_de, min_support = 0.05, use_colnames = True)
# Collecting the inferred rules in a dataframe
rules_de = association_rules(frq_items_de, metric ="lift", min_threshold = 1)
rules_de = rules_de.sort_values(['confidence', 'lift'], ascending =[False, False])
rules_de.head()
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_06_22.jpg
图 6.22:德国关联规则
先前的数据向我们展示,大多数项目与交付成本相关,因此这可能表明德国的交易大多由单一商品构成。
总结
在本章中,我们学习了如何识别销售量逐渐减少的客户,以便根据他们的消费模式为他们提供特定的产品推荐。我们通过观察给定周期内的历史销售斜率来识别销售下降,并使用 SVD 协同过滤算法为客户未购买的产品创建个性化推荐。
作为下一步,为了提高现有客户的忠诚度,我们探索了使用 Apriori 算法进行市场篮子分析,并能够根据购买的特定产品提供产品推荐。
在下一章中,我们将深入探讨如何识别流失客户的共同特征,以便通过更深入理解我们的客户流失来补充这些方法。
第三部分:运营和定价优化
本书的最后部分将介绍如何优化业务运营。我们将从理解市场和客户转向,深入探讨如何调整运营以提高利润率。这将通过改善定价策略、优化促销使用以及最终提升数字营销策略以接触更多潜在客户来实现。
本部分涵盖以下章节:
-
第七章,预测客户流失
-
第八章,通过客户细分分组用户
-
第九章,利用历史 Markdown 数据预测销售
-
第十章,网站分析优化
-
第十一章,在商业中创建数据驱动的文化
第七章:预测客户流失
流失率是一个用来衡量在一定时间框架内有多少客户或员工离开公司的指标。它也可以指因客户流失而损失的资金总额。公司流失率的变化可能会提供关于公司有价值的信息。通过客户流失分析,可以了解不再购买更多商品或服务的消费者数量或比例。
在本章中,我们将了解流失(churn)的概念以及它在商业中的重要性。接着,我们将准备数据进行进一步分析,并创建分析模型来确定理解流失模式时需要考虑的最重要因素。最后,我们将学习如何创建机器学习模型,以预测可能流失的客户。
本章包含以下内容:
-
理解客户流失
-
探索客户数据
-
探索变量关系
-
预测可能流失的用户
技术要求
为了能够按照本章中的步骤进行操作,您需要满足以下要求:
-
运行 Python 3.7 及以上版本的 Jupyter Notebook 实例。如果你有 Google Drive 账户,也可以使用 Google Colab 笔记本来运行这些步骤。
-
了解基础的数学和统计概念。
理解客户流失
在商业中,未能成为回头客的付费客户的数量被称为客户流失,也称为客户流失率。这里的流失是指在预定时间段内发生的可衡量的变化率。
分析流失的原因、与客户互动、教育客户、了解哪些客户处于风险中、识别最有价值的客户、提供激励措施、选择正确的目标受众以及提供更好的服务是减少客户流失的一些策略。
降低流失率至关重要,因为它会增加客户获取成本(CAC)并降低收入。实际上,维护和改善现有客户关系的成本远低于吸引新客户的成本。你失去的客户越多,你就需要花费更多的资金来吸引新客户,以弥补失去的收入。你可以使用以下公式来计算 CAC:CAC 通过将销售和营销成本除以新客户的数量来计算。客户回归率是指返回你公司并继续购买的消费者的比例。
这与流失率不同,后者衡量的是在一定时间内失去的客户数量。默认情况下,流失率高的公司会有较低的客户回归率。
现在我们已经大致了解了通过识别导致客户流失的模式所获得的商业价值,在接下来的部分中,我们将开始探索数据及其变量。
探索客户数据
我们的目标是创建一个模型,用于估算客户流失的可能性,数据来自电信客户。这是为了回答消费者停止使用该服务的可能性有多大这个问题。
最初,数据会进行探索性分析。了解每一列的数据类型是过程中的第一步,之后将对变量进行必要的调整。
为了探索数据,我们将绘制流失变量与其他构成数据集的重要因素之间的关系。在提出模型之前,进行这项工作是为了初步了解变量之间的潜在关系。
在进行描述性统计时,采用了细致的方法,主要关注客户基于一项或多项属性的差异。现在,主要关注的变量是流失,因此为此生成了一组新的有趣图表。
为了检查变量,我们必须处理非结构化数据并调整数据类型;第一步是探索数据。本质上,我们将了解数据分布并为聚类分析整理数据。
在接下来的示例中,我们将使用以下 Python 模块:
-
Pandas:用于数据分析和数据操作的 Python 包。
-
NumPy:这是一个为大型、多维数组和矩阵提供支持的库,还包含了广泛的高阶数学函数,用于处理这些数组。
-
statsmodels:一个 Python 包,它为统计计算提供了 SciPy 的补充,包括描述性统计和统计模型的估计与推断。它提供了用于估计多种统计模型的类和函数。
-
Seaborn、mpl_toolkits 和 Matplotlib:用于高效数据可视化的 Python 包。
现在,我们将按照以下步骤开始分析:
-
第一部分的代码块中,我们将加载刚才提到的所有必需包,包括我们将要使用的函数,如
LabelEncoder
、StandardScaler
和KMeans
:import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import os
-
为了提高可读性,我们将限制最大行数为
20
,将最大列数设置为50
,并显示保留2
位小数的浮动数据:pd.options.display.max_rows = 20
pd.options.display.max_columns = 50
pd.options.display.precision = 2
path = 'customer_churn.csv'
data = pd.read_csv(path)
data.head()
这段代码将加载数据并展示数据的前几行:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_1.jpg
图 7.1:客户数据
-
现在,我们可以查看 DataFrame 中的列:
data.columns
为了获取每一列的类型和缺失值数量,我们可以使用info
方法:
data.info()
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_2.jpg
图 7.2:Pandas 列数据类型
-
我们可以看到,尽管我们没有
null
值需要填补,但大多数变量是分类变量——这意味着我们需要将它们转换为boolean
数值列,才能使用机器学习模型或聚类方法。第一步是将TotalCharges
转换为数值数据类型:data.TotalCharges = pd.to_numeric(data.TotalCharges, errors='coerce')
上述代码将变量转换为数值型变量,强制任何错误值,而不是失败。
我们可以通过再次使用info
方法查看转换结果:
data.info()
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_3.jpg
图 7.3: 修正后的数据类型
结果转换成功,但生成了 10 个null
值,我们可以稍后删除这些值。
-
现在,我们将确定需要转换为虚拟变量以便更好分析的所有分类列:
object_cols = [c for c in data.drop(['customerID'],axis=1).columns if data[c].dtype=='O']
object_cols
-
有几列可以通过 1 和 0 来轻松表示,因为这些列的值是
Yes
或No
;我们将确定哪些变量只有这两种选项,然后将这些值映射到它们的数值对应值:yn_cols = []
# Iterate over the column names
For c in object_cols:
# count the unique values by accessing the column
val_counts = data[c].value_counts()
# If the count of unique values is equal to two, we assume that it's a Yes/No column
if len(val_counts.index)==2 and all(val_counts.index.isin(['No', 'Yes'])):
print(c)
print(data[c].value_counts().to_string())
yn_cols.append(c)
-
上述代码将生成一组
Yes
/No
的分类列,我们可以将这些数据映射为 1 和 0:# Iterate over the yes/no column names
for c in yn_cols:
# Normalize the column values by lowering them and mapping them to new values.
data[c] = data[c].str.lower().map({'yes': 1, 'no': 0})
我们现在可以重新评估这些替换的结果:
data.head()
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_4.jpg
图 7.4: 列转换为规范化布尔列
我们现在可以使用describe
方法查看数值变量的分布,以更好地理解数据:
data.describe()
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_5.jpg
图 7.5: 数据的统计描述
有趣的是,在这里,有 27%的客户流失,这个比例非常大。在其他情况下,这些值往往会低得多,这使得数据集高度不平衡,并且需要调整分析方法以应对这些不平衡。幸运的是,这里并非如此,因为客户流失的发生频率足够具有代表性。然而,不平衡的数据集要求我们更深入地审视评估模型时使用的度量标准。如果我们仅仅关注准确率,在我们的案例中,模型如果只是输出最常见的变量(客户不流失),其准确率将是 73%。这就是为什么我们需要添加更多的性能指标,比如精确率、召回率,以及两者的结合,如 F1 得分,特别是查看混淆矩阵,以找出每个类别正确预测案例的比例。
-
我们现在可以可视化一些分类变量的分布,考虑到用户流失的情况。我们可以使用 Seaborn 的
countplot
来完成这个操作:import seaborn as sns
import matplotlib.pyplot as plt
f, ax = plt.subplots(figsize=(10, 6))
pl = sns.countplot(x=data["InternetService"],hue=data["Churn"])
pl.set_title("InternetService vs Churn")
pl.set_xlabel("InternetService")
pl.set_ylabel("Count")
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_6.jpg
图 7.6: 客户互联网合同与流失
我们可以看到,选择光纤服务的用户与没有互联网服务或仅有 DSL 服务的用户在流失率上存在很大的差异。
-
这些信息可以用于深入分析原因,或制定新的促销活动以应对这一情况:
f, ax = plt.subplots(figsize=(10, 6))
pl = sns.countplot(x=data["MultipleLines"],hue=data["Churn"])
pl.set_title("MultipleLines vs Churn")
pl.set_xlabel("MultipleLines")
pl.set_ylabel("Count")
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_7.jpg
图 7.7: 客户电话合同与流失率
我们可以看到,选择多个线路的客户与不选择多个线路的客户在流失率上存在一定差异。没有多个线路的客户似乎流失更多。这些差异需要通过 t 检验或其他假设检验方法来验证,以确定各组均值之间的实际差异。
-
下一步是可视化合同与流失率之间的关系:
f, ax = plt.subplots(figsize=(10, 6))
pl = sns.countplot(x=data["Contract"],hue=data["Churn"])
pl.set_title("Contract vs Churn")
pl.set_xlabel("Contract")
pl.set_ylabel("Count")
这里的代码将展示合同类型与流失率之间的条形图:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_8.jpg
图 7.8: 客户合同类型与流失率
在这里,相比于拥有 1 年或 2 年合同的客户,按月合同的客户流失率极高。这些信息可以用来制定营销策略,尝试将按月合同转化为 1 年或 2 年的合同。
-
最后,我们对最后一个变量的探索聚焦于支付方式类型。下一个图表将展示流失率与使用的支付方式类型之间的关系:
f, ax = plt.subplots(figsize=(10, 6))
pl = sns.countplot(x=data["PaymentMethod"],hue=data["Churn"])
pl.set_title("PaymentMethod vs Churn")
pl.set_xlabel("PaymentMethod")
pl.set_ylabel("Count")
这里的代码将展示支付方式与流失率之间的条形图:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_9.jpg
图 7.9: 客户支付方式与流失率
在这里,使用电子支票而非其他支付方式的客户之间的差异是显而易见的。
接下来,我们将使用 Seaborn 的pairplot
以及相关分析来探索不同变量之间的关系。
探索变量关系
探索变量如何共同变化可以帮助我们确定隐藏的模式,这些模式支配着客户的行为:
-
我们的第一步将使用 Seaborn 方法绘制一些关系图,主要是数值连续变量之间的关系,如服务年限、每月费用和总费用,使用流失率作为
hue
参数:g = sns.pairplot(data[['tenure','MonthlyCharges', 'TotalCharges','Churn']], hue= "Churn",palette= (["red","blue"]),height=6)
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_10.jpg
图 7.10: 连续变量关系
从分布情况来看,流失的客户通常具有较低的服务年限,通常每月费用较低,且总费用较低。
-
现在,我们可以最终转换并确定将要转化为虚拟变量的对象列:
object_cols = [c for c in data.drop(['customerID'],axis=1).columns if data[c].dtype=='O']
object_cols
-
一旦确定了这些列,我们可以使用
get_dummies
函数,并创建一个仅包含数值型变量的新 DataFrame:df_dummies = pd.get_dummies(data[object_cols])
data = pd.concat([data.drop(object_cols+['Churn'],axis=1),df_dummies,data[['Churn']]],axis=1)
data.head()
上述代码将展示重新结构化后的数据:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_11.jpg
图 7.11:重构后的数据
在这里,数据能够有效地描述每个客户的描述性水平,从而使信息以数值形式而非分类形式表示。这些因素包括任期、订阅类型、费用、通话历史和人口统计等。之所以需要以数值形式表示维度,是因为大多数机器学习算法要求数据以这种方式表示。
-
下一步,我们将研究变量之间的关系,以确定它们之间最重要的相关性:
import numpy as np
from matplotlib import colors
df_corr = data.corr()
mask = np.triu(np.ones_like(df_corr, dtype=bool))
df_corr = df_corr.mask(mask).round(2)
fig, ax = plt.subplots(figsize=(25,25))
sns.heatmap(df_corr, annot=True,ax=ax)
代码确定这些相关性并构建一个三角形数据集,我们可以将其绘制为热力图,以清晰地可视化变量之间的关系。
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_12.jpg
图 7.12:变量相关性
在这里,我们可以可视化整个变量相关性的集合,但我们可能只关注与目标变量相关的部分。我们可以看到,一些依赖其他变量的变量与此变量的相关性为 1——例如,在internet contract = no
的情况下,它与streaming service = no
的相关性为 1。这是因为如果没有互联网合同,很明显就无法使用需要互联网合同的流媒体服务。
-
我们可以通过查看与此变量相关的相关性来实现这一点:
churn_corr = data.corr()['Churn'].sort_values(ascending = False)
churn_corr.plot(kind='bar',figsize=(20,8))
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_13.jpg
图 7.13:与流失最相关的变量
这些信息非常有用,因为它不仅确定了与较高流失率相关的变量,还指出了能够降低流失率的变量,例如任期时间和拥有 2 年合同。建议在这里删除流失变量,因为该变量与自身的相关性为 1,且会扭曲图形。
完成这一步 EDA 后,我们将开发一些预测模型并进行比较。
预测将流失的用户
在这个例子中,我们将训练逻辑回归、随机森林和支持向量机(SVM)机器学习模型,预测基于观察到的变量将会流失的用户。我们需要首先对变量进行缩放,使用 sklearn 的MinMaxScaler
功能来实现:
-
我们将从逻辑回归开始,并将所有变量缩放到 0 到 1 的范围内:
from sklearn.preprocessing import MinMaxScaler
y = data['Churn'].values
x = data.drop(columns = ['customerID','Churn']).fillna(0)
scaler = MinMaxScaler(feature_range = (0,1))
x_scaled = scaler.fit_transform(x)
x_scaled = pd.DataFrame(x_scaled,columns=x.columns)
x_scaled.head()
前面的代码将创建x
和y
变量,其中我们只需要对x
进行缩放。
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_14.jpg
图 7.14:模型输入特征
在逻辑回归中,缩放变量非常重要,以确保所有变量都在 0 到 1 的范围内。
-
接下来,我们可以通过分割数据来训练逻辑回归模型,首先获得一个验证集:
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn import metrics
x_train, x_test, y_train, y_test = train_test_split(
x_scaled, y, test_size=0.3, random_state=101)
model = LogisticRegression()
result = model.fit(x_train, y_train)
preds_lr = model.predict(x_test)
最后,我们可以打印出预测准确性:
print(metrics.accuracy_score(y_test, preds_lr))
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_15.jpg
图 7.15:逻辑回归模型准确性
我们在模型中获得了不错的准确性。
-
我们还可以获得所有变量的权重,以衡量它们在预测模型中的重要性:
weights = pd.Series(model.coef_[0],index=x_scaled.columns)
pd.concat([weights.head(10),weights.tail(10)]).sort_values(ascending = False).plot(kind='bar',figsize=(16,6))
上述代码将创建一个数据框,显示 10 个权重最正向和 10 个权重最负向的变量。
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_16.jpg
图 7.16:模型特征重要性
有趣的是,看到总费用和服务年限,尤其是后者,在回归模型中的重要性。通过查看变量之间的相关性,这些变量的重要性得到了验证。一个重要的下一步将是深入分析这些变量与流失变量之间的关系,以理解这种关系背后的机制。
-
我们可以创建混淆矩阵,以可视化每个类别预测的性能:
from sklearn.metrics import classification_report, confusion_matrix
print(confusion_matrix(y_test,preds_lr))
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_17.jpg
图 7.17:模型混淆矩阵
在混淆矩阵中,列中显示的是不同的类别(无流失和流失),而行中则是按相同顺序预测的结果(无流失,流失)。对角线上的值表示真正的正例,预测为该类别且实际也是该类别。对角线外的值则表示预测错误的值。在我们的案例中,我们正确分类了 1401
个无流失的案例,错误地将 265
个无流失分类为流失,将 145
个流失预测为无流失,正确分类了 302
个流失的案例。从混淆矩阵中可以看出,模型在预测最常见的情况(即无流失)方面表现良好,但几乎三分之一的类别预测错误,这对我们预测非常重要。
- 我们的下一步是创建一个由多个决策树组成的分类系统,称为随机森林。它通过使用自助法(bagging)和特征随机性在生成每个单独的树时,尝试产生一个相互独立的决策树森林,这比单棵树更准确。
在接下来的代码中,我们将使用来自 sklearn 的 RandomForestClassifier
类,并在数据上进行训练:
from sklearn.ensemble import RandomForestClassifier
model_rf = RandomForestClassifier(n_estimators=750 , oob_score = True, random_state =50, max_features = "auto",max_leaf_nodes = 15)
model_rf.fit(x_train, y_train)
-
最后,我们训练了模型,并可以进行预测:
preds_rfc = model_rf.predict(x_test)
print(metrics.accuracy_score(y_test, preds_rfc))
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_18.jpg
图 7.18:随机森林模型准确度
我们得到了一个与回归模型非常相似的准确度。
-
让我们来看一下模型的变量重要性,作为下一步:
importances = model_rf.feature_importances_
weights_rf = pd.Series(importances,index=x_scaled.columns)
pd.concat([weights_rf.head(10),weights.tail(10)]).sort_values(ascending = False).plot(kind='bar',figsize=(16,6))
上述代码将显示模型中 10 个最正向和 10 个最负向的权重。
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_19.jpg
图 7.19:模型特征重要性
这个模型有不同的排序,在极端情况下,月度合同和 2 年合同排在前列:
print(confusion_matrix(y_test,preds_rfc))
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_20.jpg
图 7.20:模型混淆矩阵
该模型更好地预测了我们的目标变量,这使得它更适合我们的需求。
-
接下来,我们将训练一种监督式机器学习技术,称为支持向量分类器(SVC),它常用于分类问题。SVC 通过将数据点映射到高维空间,然后定位最佳超平面,将数据分为两类:
from sklearn.svm import SVC
model_svm = SVC(kernel='linear')
model_svm.fit(x_train,y_train)
preds_svm = model_svm.predict(x_test)
metrics.accuracy_score(y_test, preds_svm)
这里的代码将模型拟合到数据中,并打印出准确率得分。
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_21.jpg
图 7.21:模型准确率得分
-
准确率得分仍然在其他模型的相同范围内,因此让我们来看看模型的绝对权重重要性:
pd.Series(abs(model_svm.coef_[0]), index=x_scaled.columns).nlargest(10).plot(kind='barh',figsize=(10,8))
这里的代码将提示模型的 10 个最重要的变量。
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_22.jpg
图 7.22:模型特征重要性
我们可以看到,任期和总费用是模型中重要的变量,这也是我们在其他模型中看到的。这种可视化的缺点是我们无法看到这些重要性的方向性。
-
让我们看看预测目标变量的表现:
print(confusion_matrix(y_test,preds_svm))
下一步是混淆矩阵,这将帮助我们更准确地确定哪些标签我们正确地预测了。
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_07_23.jpg
图 7.23:模型混淆矩阵
该模型在预测客户是否流失方面的准确性低于随机森林。
将混淆矩阵的不同视角结合起来总是很有帮助的,因为如果数据集不平衡,仅凭准确率无法作为性能度量。
总结
在本章中,我们分析了一个非常常见的商业案例:客户流失。理解其原因,以及能够采取预防措施避免流失,可以为公司创造大量收入。
在我们分析的示例中,我们看到了如何清洗数据集中的变量,正确表示它们并为机器学习做准备。将变量与我们分析的目标变量进行关系可视化,有助于我们更好地理解问题。最后,我们训练了几个机器学习模型,随后分析了它们在预测目标变量时的表现。
在下一章中,我们将更加关注如何理解变量如何影响数据段,并将具有相似特征的用户分组,以便更好地理解他们。
第八章:使用客户细分对用户进行分组
为了更好地理解消费者需求,我们需要了解我们的客户具有不同的消费模式。每一类特定产品或服务的消费者群体可以根据年龄、婚姻状况、购买力等因素划分为不同的细分群体。在本章中,我们将对来自一家超市的消费者数据进行探索性分析,然后应用聚类技术将其分成具有相似消费模式的群体。这些知识将帮助我们更好地理解他们的需求,创造独特的优惠,并更有效地进行精准营销。在本章中,我们将学习以下内容:
-
了解客户细分
-
探索有关客户数据库的数据
-
应用特征工程来标准化变量
-
使用 K-means 聚类创建用户细分
-
描述这些群体的共同特征
让我们看看理解步骤并跟随本章的要求。
技术要求
为了能够跟随本章的步骤,您需要满足以下要求:
-
运行 Python 3.7 及以上版本的 Jupyter Notebook 实例。如果您有 Google Drive 账户,也可以使用 Google Colab notebook 运行步骤。
-
了解基本的数学和统计学概念。
-
一个 Kaggle 账户—您必须同意我们将获取数据的比赛条款和条件,您可以在此处找到:
www.kaggle.com/datasets/imakash3011/customer-personality-analysis
。
了解客户细分
客户细分是将客户根据共同特征分类的实践,以便企业能够有效且恰当地向每个群体进行市场营销。在企业对企业(B2B)市场营销中,一家公司可能会根据多种标准将其客户群划分为几个组别,如地理位置、行业、员工人数以及公司商品的历史购买记录。
企业通常根据人口统计学特征(如年龄、性别、婚姻状况、地理位置(城市、郊区或农村)和生活阶段(单身、已婚、离婚、空巢、退休))将客户群体划分为不同的细分市场。客户细分要求企业收集关于其客户的数据,评估这些数据,并寻找可用来建立细分市场的趋势。
职位名称、地点和购买的产品——例如——是从购买数据中可以获取的帮助企业了解其客户的一些细节。这些信息可能通过查看客户的系统记录发现。使用选择加入邮件列表的在线营销人员可能会根据吸引客户的选择加入优惠将营销通讯划分为不同类别。例如,其他数据——例如消费者的人口统计信息,如年龄和婚姻状况——则需要通过不同的方法收集。
在消费品领域,其他典型的信息收集方法包括:
-
与客户的面对面访谈
-
在线调查
-
在线营销和网站流量信息
-
焦点小组
所有组织,无论大小、行业,还是在线销售或面对面销售,都可以使用客户细分。这一过程从获取和评估数据开始,最终通过采取适当有效的行动来利用所获得的信息。
在本章中,我们将对一家杂货店数据库中的客户记录执行无监督的聚类分析。为了最大化每位客户对公司的价值,我们将细分客户群体,根据特定需求和消费者行为调整产品。满足不同客户需求的能力也会使公司受益。
探索数据
理解客户细分的第一步是了解我们将使用的数据。第一阶段是探索数据,检查我们必须处理的变量,处理非结构化数据并调整数据类型。我们将为聚类分析构建数据,并了解数据的分布情况。
在下一个示例中我们将使用以下 Python 模块进行分析:
-
Pandas:用于数据分析和数据处理的 Python 包。
-
NumPy:这是一个为大规模、多维数组和矩阵提供支持的库,并附带大量的高阶数学函数,用于操作这些数组。
-
scipy
用于统计计算,包括描述性统计和统计模型的估算与推断。它提供用于估算多种统计模型的类和函数。 -
Yellowbrick:一个用于视觉分析和诊断工具的 Python 包,旨在通过 scikit-learn 促进 机器学习(ML)。
-
Seaborn, mpl_toolkits 和 Matplotlib:用于有效数据可视化的 Python 包。
现在我们将开始分析,采用以下步骤:
-
以下代码块将加载之前提到的所有必要包,包括我们将使用的函数,例如
LabelEncoder
、StandardScaler
和Kmeans
:import numpy as np
import pandas as pd
import datetime
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib import colors
from matplotlib.colors import ListedColormap
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from yellowbrick.cluster import KElbowVisualizer
from sklearn.cluster import KMeans
from mpl_toolkits.mplot3d import Axes3D
from sklearn.cluster import AgglomerativeClustering
-
为了提高可读性,我们将限制显示的最大行数为 20,设置最大列数为 50,并将浮动数值显示为保留两位小数:
pd.options.display.max_rows = 20
pd.options.display.max_columns = 50
pd.options.display.precision = 2
-
接下来,我们将加载数据,这些数据存储在本地数据文件夹中。文件采用 CSV 格式,使用制表符作为分隔符。我们将数据读取到一个 Pandas DataFrame 中,并打印数据的形状,以及显示前几行:
path = "data/marketing_campaign.csv"
data = pd.read_csv(path, sep="\t")
print("Data Shape", data.shape)
data.head()
这会生成以下输出:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_1.jpg
图 8.1:用户数据
-
为了全面了解我们将采取的清洗数据集的步骤,让我们查看使用
describe
方法生成的数据的统计摘要:data.describe()
这会生成以下输出:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_2.jpg
图 8.2:描述性统计摘要
-
为了获取更多关于特征的信息,我们可以使用
info
方法显示null
值的数量和数据类型:data.info()
这会生成以下输出:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_3.jpg
图 8.3:列数据类型和缺失值
从之前使用 Pandas DataFrame 的describe
和info
方法显示的输出中,我们可以看到以下内容:
-
Income
列中有 26 个缺失值 -
名为
Dt_Customer
的日期变量,表示客户加入数据库的日期,并未被解析为DateTime
-
我们的 DataFrame 中有一些类别特征,其
dtype
为对象类型,我们稍后需要将它们编码为数值特征,以便能够应用聚类方法。
-
为了处理缺失值,我们将删除缺失收入值的行,因为收入是描述客户的重要变量:
data = data.dropna()
print("Data Shape", data.shape)
-
我们将使用
pd.to_datetime
Pandas 方法解析日期列。需要注意的是,该方法会自动推断日期格式,但如果需要,我们也可以指定格式:data["Dt_Customer"] = pd.to_datetime(data["Dt_Customer"])
-
在解析日期之后,我们可以查看最新和最早注册客户的值:
str(data["Dt_Customer"].min()),str(data["Dt_Customer"].max())
>>>> ('2012-01-08 00:00:00', '2014-12-06 00:00:00')
-
在下一步中,我们将创建一个基于
Dt_Customer
的特征,表示客户注册公司数据库的天数,相对于数据库中第一个注册的用户,尽管我们也可以使用今天的日期。我们这样做是因为我们分析的是历史记录,而不是最新数据。Customer_For
特征是客户注册日期减去日期列中的最小值,可以解释为客户开始在商店购物以来的天数,相对于最后记录的日期:data["Customer_For"] = data["Dt_Customer"]-data["Dt_Customer"].min()
data["Customer_For"] = data["Customer_For"].dt.days
-
现在,我们将探索类别特征中的唯一值,以更清楚地了解数据:
data["Marital_Status"].value_counts().plot.bar(figsize=(12,6),title = 'Categories in the feature Marital_Status:')
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_4.jpg
图 8.4:婚姻状况
在这里,我们可以看到有几种婚姻状况,这可能是由于数据采集时自由文本输入造成的。我们将需要标准化这些值。
-
接下来,我们将使用
value_counts
方法查看Education
特征中的值,并使用 Pandas 的plot
方法绘制条形图:data["Education"].value_counts().plot.bar(figsize=(12,6),title = 'Categories in the feature Education:')
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_5.jpg
图 8.5:教育水平值
再次,我们可以看到自由文本输入的影响,因为有几个值具有相同的基本含义;因此,我们也需要对它们进行标准化处理。
在接下来的部分,我们将应用特征工程来结构化数据,以便更好地理解和处理数据。
特征工程
为了能够正确分析数据以及建模集群,我们需要清理和结构化数据——这个步骤通常被称为特征工程——因为我们需要根据分析计划重新结构化一些变量。
在这一部分,我们将执行下一步操作,清理和结构化一些数据集特征,目标是简化现有变量,并创建更易于理解的特征,以正确描述数据:
-
使用
Year_Birth
特征创建一个Age
变量,表示每位客户的出生年份。 -
创建一个
Living_With
特征,以简化婚姻状况,描述夫妻的居住情况。 -
创建一个
Children
特征,表示家庭中孩子的总数——即儿童和青少年。 -
按照产品类型汇总支出,以更好地捕捉消费者行为。
-
使用一个名为
Is_Parent
的特征表示父母身份。
所以,让我们应用这里提到的步骤来结构化数据:
-
首先,让我们从客户当前的年龄开始,使用
pd.to_datetime
方法获取当前年份和客户的出生年份:data["Age"] = pd.to_datetime('today').year -
data["Year_Birth"]
-
现在,我们将使用
sum
方法对选定列进行求和,并沿列轴汇总支出数据:prod_cols = ["MntWines","MntFruits","MntMeatProducts",
"MntFishProducts","MntSweetProducts","MntGoldProds"]
data["Spent"] = data[prod_cols].sum(axis=1)
-
作为下一步,我们将婚姻状况的值映射到不同的编码中,以简化含义相近的术语。为此,我们定义一个映射字典,并用它来替换
marital_status
列中的值,创建一个新特征:marital_status_dict= {"Married":"Partner",
"Together":"Partner",
"Absurd":"Alone",
"Widow":"Alone",
"YOLO":"Alone",
"Divorced":"Alone",
"Single":"Alone",}
data["Living_With"] = data["Marital_Status"].replace(marital_status_dict)
-
接下来,我们通过将家庭中孩子和青少年的总数相加,创建一个
Children
特征:data["Children"]=data["Kidhome"]+data["Teenhome"]
-
现在,我们使用关系和儿童数据建模家庭成员总数:
data["Family_Size"] = data["Living_With"].replace({"Alone": 1, "Partner":2})+ data["Children"]
-
最后,我们在一个新变量中捕捉父母身份:
data["Is_Parent"] = (data.Children> 0).astype(int)
-
现在,我们将教育水平分为三个组以便简化:
edu_dict = {"Basic":"Undergraduate","2n Cycle":"Undergraduate", "Graduation":"Graduate", "Master":"Postgraduate", "PhD":"Postgraduate"}
data["Ed_level"]=data["Education"].replace(edu_dict)
-
现在,为了简化,我们使用映射字典将列重命名为更易于理解的术语:
col_rename_dict = {"MntWines": "Wines",
"MntFruits":"Fruits",
"MntMeatProducts":"Meat",
"MntFishProducts":"Fish",
"MntSweetProducts":"Sweets",
"MntGoldProds":"Gold"}
data = data.rename(columns=col_rename_dict)
-
现在,我们将删除一些冗余特征,专注于最清晰的特征,包括我们刚刚创建的特征。最后,我们将使用
describe
方法查看统计描述分析:to_drop = ["Marital_Status", "Dt_Customer",
"Z_CostContact", "Z_Revenue", "Year_Birth", "ID"]
data = data.drop(to_drop, axis=1)
data.describe()
-
统计数据显示,
Income
和Age
特征中存在一些不一致的情况,我们将通过可视化来更好地理解这些不一致之处。我们从Age
的直方图开始:data["Age"].plot.hist(figsize=(12,6))
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_6.jpg
Figure 8.6: Age data
我们可以看到有一些异常值,年龄超过 120 岁,因此我们将移除这些异常值。
-
接下来,我们来看一下收入分布:
data["Income"].plot.hist(figsize=(12,6))
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_7.jpg
图 8.7:收入数据
同样,我们可以看到大多数收入低于 20,000,因此我们将限制消费水平。
-
接下来,我们通过设置
年龄
的上限来剔除异常值,以避免数据不真实,并将收入限定为包含 99%的案例:prev_len = len(data)
data = data[(data["Age"]<99)]
data = data[(data["Income"]<150000)]
new_len = prev_len - len(data)
print('Removed outliers:',new_len)
上述代码将输出以下内容:
>>> Removed outliers: 11
-
现在,我们可以回过头来查看
年龄
和消费
数据分布,以更好地了解我们的客户。我们首先通过创建年龄
特征的直方图来开始:data["Age"].plot.hist(figsize=(12,6))
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_8.jpg
图 8.8:无异常值的年龄分布
年龄集中在 50 岁左右,右偏,意味着我们的客户的平均年龄超过 45 岁。
data["Income"].plot.hist(figsize=(12,6))
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_9.jpg
图 8.9:无异常值的收入分布
查看消费分布,它呈正态分布,集中在 4,000 左右,略微向左偏斜。
-
接下来,我们将创建一个 Seaborn 成对图,以根据父母身份的颜色标签显示不同变量之间的关系:
sns.pairplot(data[["Income", "Recency", "Customer_For", "Age", "Spent", "Is_Parent"]], hue= "Is_Parent",palette= (["red","blue"]))
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_10.jpg
图 8.10:关系图
这些图形使我们能够快速观察不同变量之间的关系以及它们的分布。最明显的关系之一是消费与收入之间的关系,我们可以看到收入越高,支出越高,同时还观察到单亲家庭的支出大于非单亲家庭。我们还可以看到,最近一次消费行为较频繁的是父母,而单身消费者的消费频率较低。接下来,让我们来看一下特征之间的相关性(此时排除类别属性)。
-
我们将使用
corr
方法创建相关矩阵,并通过numpy
掩码仅显示数据的下三角部分。最后,我们将使用 Seaborn 方法来显示这些值:df_corr = data.corr()
mask = np.triu(np.ones_like(df_corr, dtype=bool))
df_corr = df_corr.mask(mask).round(3)
fig, ax = plt.subplots(figsize=(16,16))
cmap = colors.ListedColormap(["#682F2F", "#9E726F", "#D6B2B1", "#B9C0C9", "#9F8A78", "#F3AB60"])
sns.heatmap(df_corr, cmap=cmap,annot=True,ax=ax)
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_11.jpg
图 8.11:变量相关性
这些相关性使我们能够更详细地探索变量之间的关系。我们可以看到,在平均值上,子女数量与消费支出呈负相关,而子女数量与最近消费行为呈正相关。这些相关性有助于我们更好地理解消费模式。
在接下来的章节中,我们将使用聚类概念将客户分成具有共同特征的群体。
创建客户细分
市场营销人员可以通过细分目标受众,更好地针对不同的受众子群体进行营销。产品开发和传播可能是这些努力的一部分。细分为企业带来如下好处:
-
为每个客户或用户群体在合适的沟通渠道上创建有针对性的营销传播
-
对正确的客户应用正确的定价选项
-
集中精力在最有利可图的客户身上
-
提供更好的客户服务
-
推广和交叉推广其他商品和服务
在本节中,我们将对数据进行预处理,以便应用聚类方法进行客户细分。我们将在这里列出用于预处理数据的步骤:
-
使用标签编码器对分类变量进行编码,将它们转换为数值列
-
使用标准缩放器对特征进行缩放,以规范化值
-
应用主成分分析(PCA)进行降维
所以,让我们按照这里的步骤进行操作:
-
首先,我们需要列出分类变量。在这里,我们将使用列名并检查列的
dtype
,以仅获取对象列:object_cols = [c for c in data.columns if data[c].dtypes == 'object']
print("Categorical variables in the dataset:", object_cols)
-
接下来,我们将使用
sklearn
的LabelEncoder
函数对dtypes
对象进行编码:LE = LabelEncoder()
for i in object_cols:
data[i]=data[[i]].apply(LE.fit_transform)
-
我们对数据进行子集选择,并通过删除“已接受交易”和“促销”特征,对数值变量进行缩放:
scaled_ds = data.copy()
cols_del = ['AcceptedCmp3', 'AcceptedCmp4', 'AcceptedCmp5', 'AcceptedCmp1','AcceptedCmp2', 'Complain', 'Response']
scaled_ds = scaled_ds.drop(cols_del, axis=1)
-
最后,我们可以应用缩放:
scaler = StandardScaler()
scaler.fit(scaled_ds)
scaled_ds = pd.DataFrame(scaler.transform(
scaled_ds),columns= scaled_ds.columns )
这个数据集中有许多属性描述了数据。特征越多,在商业环境中正确分析它们就越困难。这些特征中的许多是冗余的,因为它们是相互关联的。因此,在将特征输入分类器之前,我们将对选定的特征进行降维处理。
降维是减少考虑的随机变量数量的过程。为了降低大型数据集的维度,通常使用一种名为 PCA 的技术。PCA 通过将大量变量压缩成一个较小的集合,同时保留较大集合中的大部分数据来工作。
随着数据集变量的减少,准确性自然会下降,但降维的答案是牺牲一些准确性换取简单性,因为机器学习算法可以通过更小的数据集更快速、更容易地分析数据,因为需要处理的无关因素更少。总之,PCA 的基本原则是尽可能保留信息,同时减少所收集数据中的变量数量。
本节中我们将应用的步骤如下:
-
使用 PCA 进行降维
-
在三维图中绘制降维后的 DataFrame
-
使用 PCA 进行降维,再一次
这将使我们能够以三维的方式可视化投影的细分。在理想的设置中,我们将使用每个成分的权重来理解每个成分所代表的意义,并更好地理解我们正在可视化的信息。为了简化起见,我们将专注于成分的可视化。以下是步骤:
-
首先,我们将启动 PCA,将维度或特征减少到三个,以简化复杂性:
pca = PCA(n_components=3)
PCA_ds = pca.fit_transform(scaled_ds)
PCA_ds = pd.DataFrame(PCA_ds, columns=([
"component_one","component_two", "component_three"]))
-
数据集中每个主成分(特征向量)所解释的变异度被统计为“解释的方差”。这只是指数据集中每个唯一主成分可以解释的数据变异度。
print(pca.explained_variance_ratio_)
>>>>[0.35092717 0.12336458 0.06470715]
对于这个项目,我们将把维度减少到三维,这能够解释观察变量的 54%的总方差:
print('Total explained variance',sum(pca.explained_variance_ratio_))
>>>> Total explained variance 0.5389989029179605
-
现在我们可以将数据投影到 3D 图中,以查看点的分布:
x,y,z=PCA_ds["component_one"],PCA_ds[
"component_two"],PCA_ds["component_three"]
fig = plt.figure(figsize=(10,8))
ax = fig.add_subplot(111, projection="3d")
ax.scatter(x,y,z, c="maroon", marker="o" )
ax.set_title("A 3D Projection Of Data In The Reduced Dimension")
plt.show()
上述代码将展示我们在三维中投影的维度:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_12.jpg
图 8.12:PCA 变量的三维表现
由于现在的属性仅有三维,聚合聚类将用于执行聚类。凝聚聚类是一种层次聚类技术。在达到适当数量的簇之前,样本将会被合并。
聚类的过程涉及将数据点分组,使得每个组内的数据点比其他组中的数据点更相似。简而言之,目标是将具有相似特征的任何组分成簇。聚类的目标是在数据集中找到独特的组,或称为“簇”。该工具使用机器学习算法来构建组,组内的成员通常会共享相似的特征。
机器学习中使用的两种模式识别方法是分类和聚类。虽然这两个过程之间有一些相似之处,但聚类发现事物之间的相似性,并根据那些将它们与其他对象组区分开来的特征将其分组,而分类则使用预定的类别将对象分配到这些类别中。“簇”是这些集合的名称。
聚类涉及的步骤如下:
-
肘部法用于确定要形成的簇的数量
-
通过凝聚聚类进行聚类
-
通过散点图检查形成的簇
在 K 均值聚类中,理想的簇数是通过肘部法来确定的。通过肘部法绘制不同成本函数值下形成的簇的数量或 K:
-
肘部法是一种启发式方法,用于聚类分析,以估计数据集中存在的簇的数量。将解释的变异度作为簇的数量的函数进行绘图,该过程包括选择曲线的肘部作为适当的簇数,如以下代码片段所示:
fig = plt.figure(figsize=(12,8))
elbow = KElbowVisualizer(KMeans(), k=(2,12), metric='distortion') # distortion: mean sum of squared distances to centers
elbow.fit(PCA_ds)
elbow.show()
这段代码将绘制肘部图,这是对所需簇数的一个良好估算:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_13.jpg
图 8.13:肘部法
-
根据前面的单元,四个簇将是这个数据集的最佳选择。为了获得最终的簇,我们将拟合凝聚聚类模型,如下所示:
AC = AgglomerativeClustering(n_clusters=4)
# fit model and predict clusters
yhat_AC = AC.fit_predict(PCA_ds)
PCA_ds["Clusters"] = yhat_AC
-
最后,我们将向原始 DataFrame 中添加一个
Clusters
特征,以便进行可视化:data["Clusters"]= yhat_AC
-
现在,我们可以使用每个集群的颜色代码,在三维空间中可视化集群:
classes = [0,1,2,3]
values = PCA_ds["Clusters"]
colors = ListedColormap(['red','blue','green','orange'])
fig = plt.figure(figsize=(10,8))
ax = plt.subplot(projection='3d')
scatter = ax.scatter(x, y,z, c=values, cmap=colors)
plt.legend(handles=scatter.legend_elements()[0], labels=classes)
上述代码将显示一个三维可视化的 PCA 组件,颜色根据集群进行区分:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_14.jpg
图 8.14:PCA 变量与集群标注
从中我们可以看到,每个集群在可视化中占据了一个特定的空间。现在我们将深入描述每个集群,以便更好地理解这些群体。
将集群理解为客户群体
为了严格评估获得的输出结果,我们需要评估所描述的集群。这是因为集群分析是一种无监督的方法,提取的模式应该始终反映现实,否则,我们可能只是在分析噪声。
消费者群体之间的共同特征可以帮助企业选择哪些商品或服务应该向哪些群体广告宣传,以及如何向每个群体进行市场营销。
为了做到这一点,我们将使用探索性数据分析(EDA)在集群的上下文中查看数据并做出判断。以下是步骤:
-
让我们首先检查集群组分布:
cluster_count = PCA_ds["Clusters"].value_counts().reset_index()
cluster_count.columns = ['cluster','count']
f, ax = plt.subplots(figsize=(10, 6))
fig = sns.barplot(x="cluster", y="count", palette=['red','blue','green','orange'],data=cluster_count)
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_15.jpg
图 8.15:集群数量
各集群分布相对均衡,集群 0 占主导地位。可以清晰地看到,集群 1 是我们最大的客户群体,其次是集群 0。
-
我们可以使用以下代码来探索每个集群在目标营销策略上的消费:
f, ax = plt.subplots(figsize=(12, 8))
pl = sns.scatterplot(data = data,x=data["Spent"], y=data["Income"],hue=data["Clusters"], palette= colors)
pl.set_title("Cluster vs Income And Spending")
plt.legend()
plt.show()
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_16.jpg
图 8.16:收入与消费关系
在收入与消费关系图中,我们可以看到下一个集群的模式:
-
集群 0 是高消费和中等收入的群体
-
集群 1 是高消费和高收入的群体
-
集群 2 是低消费和低收入的群体
-
集群 3 是高消费和低收入的群体
-
接下来,我们将查看数据中每个产品的消费集群的详细分布。即,我们将探索消费模式。以下是代码:
f, ax = plt.subplots(figsize=(12,6))
sample = data.sample(750)
pl = sns.swarmplot(x=sample["Clusters"], y=sample["Spent"], color= "red", alpha=0.8 ,size=3)
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_17.jpg
图 8.17:每个集群的消费分布
从图 8.17中可以看到,集群 0 的消费分布较为均匀,集群 1 的消费集中在高消费上,而集群 2 和 3 则集中在低消费上。
-
接下来,我们将使用 Seaborn 创建集群的 Boxen 图,以找出每个集群的消费分布:
f, ax = plt.subplots(figsize=(12, 6))
pl = sns.boxenplot(x=data["Clusters"], y=data["Spent"], palette=['red','blue','green','orange'])
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_18.jpg
图 8.18:每个集群的消费分布(Boxen 图)
我们可以使用不同的方式来可视化这些模式,使用 Boxen 图。
-
接下来,我们将创建一个特征来获取接受的促销活动总和,以便我们能够建模它们与不同集群之间的关系:
data["TotalProm"] = data["AcceptedCmp1"]+ data["AcceptedCmp2"]+ data["AcceptedCmp3"]+ data["AcceptedCmp4"]+ data["AcceptedCmp5"]
-
现在,我们将绘制接受的总营销活动数量与集群之间的关系图:
f, ax = plt.subplots(figsize=(10, 6))
pl = sns.countplot(x=data["TotalProm "],hue=data["Clusters"], palette= ['red','blue','green','orange'])
pl.set_title("Total Promotions vs Cluster")
pl.set_xlabel("Cluster")
pl.set_ylabel("Count")
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_19.jpg
图 8.19:每个群体应用的促销活动
我们可以看到,尽管每个群体的促销活动没有显著的特征模式,但我们可以看到群体 0 和群体 2 是应用促销活动数量最多的两个群体。
-
现在,我们可以将每种群体类型的购买交易数量可视化:
f, ax = plt.subplots(figsize=(12, 6))
pl = sns.boxenplot(y=data["NumDealsPurchases"],x=data["Clusters"], palette= ['red','blue','green','orange'])
pl.set_title("Purchased Deals")
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_20.jpg
图 8.20:每个群体购买的交易数量
促销活动未能广泛传播,但交易却非常成功。群体 0 和群体 2 的结果是最好的。群体 1 是我们的顶级客户之一,但他们对促销活动不感兴趣。没有什么能强烈吸引群体 1。
现在,群体已经创建并且其购买模式已经被分析,我们来看看这些群体中的每个成员。为了确定谁是我们的明星客户,谁需要零售店的营销团队进一步关注,我们将对已开发的群体进行分析。
考虑到群体特征,我们将绘制一些代表客户个人特征的元素。我们将根据结果得出结论。
-
我们将使用 Seaborn 联合图来可视化不同变量之间的关系和分布:
sns.jointplot(x=data['Education'], y=data["Spent"], hue =data["Clusters"], kind="kde", palette=['red','blue','green','orange'],height=10)
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_21.jpg
图 8.21:每个群体的消费与教育水平分布
群体 0 主要集中在中等教育水平,但在高学历方面也有一个峰值。群体 2 在教育方面是最低的。
-
接下来,我们将看看家庭规模:
sns.jointplot(x=data['Family_Size'], y=data["Spent"], hue =data["Clusters"], kind="kde", palette=['red','blue','green','orange'],height=10)
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_22.jpg
图 8.22:每个群体的消费与家庭规模分布
群体 1 代表小家庭规模,群体 0 代表夫妻和家庭。群体 2 和群体 3 的分布较为均匀。
-
现在我们来看看消费与客户群体的关系:
sns.jointplot(x=data['Customer_For'], y=data["Spent"], hue =data["Clusters"], kind="kde", palette=['red','blue','green','orange'],height=10)
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_23.jpg
图 8.23:每个群体的消费与客户分布
群体 3 是由年长客户组成的群体。尽管群体 0 的消费最高,但值得注意的是,它在“自客户成为用户以来的天数”这一维度上有一个向左偏移的趋势。
sns.jointplot(x=data['Age'], y=data["Spent"], hue =data["Clusters"], kind="kde", palette=['red','blue','green','orange'],height=10)
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/art-dtdvn-biz/img/B19026_08_24.jpg
图 8.24:每个群体的消费与年龄分布
群体 0 是拥有较年长客户的群体,而拥有最年轻客户的是群体 2。
总结
本章我们进行了无监督聚类。经过降维后,使用了凝聚层次聚类方法。为了更好地基于客户的家庭结构、收入和消费习惯对群体进行分析,我们将用户分为四个群体。这一方法可用于制定更有效的营销计划。
在接下来的章节中,我们将深入研究使用时间序列数据预测销售,以便能够根据一组历史销售确定收入预期,并理解它们与其他变量的关系。