总体思路
基于聚类的精准推荐总体
先基于聚类:把用户分群,对每一个客户都标记上标签值。1号和3号都属于标签A的客户。
推荐规则生成:
- 推荐个客户的商品,都是ta没买过的
- 在ta没有购买过的商品中,哪些是和ta同类客户最喜欢的?
– 同类客户总购买次数(平均购买次数)最多的商品,就是这类客户最喜欢的商品
– 同类客户总购买数量(平均购买数量)最多的商品,就是这类客户最喜欢的商品
数据整理和数据清洗的总体思路:
- 最终要生成推荐列表,前提是有一张表记录所有用户的购买行为(一行记录一个用户的所有购买行为信息)
- 用户购买行为记录表中所有的字段可以从什么地方提取。
- 从3张表中提取用户的购买行为数据,这3张表有什么关联(每张表中每一行记录有什么关系,通过什么条件来将不同表中的记录连接在一起)
– 3张表哪些字段需要保留–和用户购买行为相关的字段尽可能保留(对聚类算法有用的尽可能保留)
– 有些需要优先删除:1, 缺失率80%以上 2,一整个字段只有1个值 3,整个字段有效信息几乎没有
– 需要转码:
— 分类型变量: 如果是回归类模型:转哑编码
— 分类型、基于距离的模型:主要独热编码
– 每一张表都完成上面的处理,3张表最后合并到一起---->“用户购买行为记录表”
— 最终的合并表,再作最后一次数据清洗
数据清洗和探索
订单表的清洗
import numpy as np,pandas as pd
order=pd.read_csv(r"D:\电商玩具店营销案例\data\order.csv",index_col=0) - 导入订单表
order.head()```
order.info() - 查看订单表信息
删除无关字段
- 删除缺失率高达80%以上的字段
```python
order=order.dropna(axis=1,thresh=order.shape[0]*0.2)
order.head()
删除整个字段只有一类的元素
```python
for i in order.columns:
if order[i].nunique()==1:
del order[i]
order.shape
(3989, 17)
经过上面的操作,我们可以对剩下的字段进行人工筛选,哪些对作聚类有用的就留下来:
```python
order.columns
Index(['订单编号', '买家会员名', '买家应付货款', '买家应付邮费', '总金额', '买家实际支付金额', '收货人姓名', '收货地址',
'联系手机', '订单创建时间', '订单付款时间 ', '宝贝标题 ', '宝贝种类', '物流单号 ', '物流公司', '宝贝总数量',
'退款金额'],
dtype='object')
order=order[["订单编号","买家会员名","买家实际支付金额","收货地址","宝贝标题 ","宝贝种类","宝贝总数量","退款金额"]]
order.shape
(3989, 8)
分类型变量编码
退款金额0-1编码(由于’退款金额’字段严重偏态,需做简单的分箱)
order.退款金额=np.where(order.退款金额>0,1,0)
收货地址独热编码
address=order.收货地址.str[:3].str.strip()
address
address=pd.get_dummies(address,prefix="地址")
address.head()
宝贝种类独热编码
kinds=pd.get_dummies(order.宝贝种类,prefix="宝贝种类")
kinds.head()
合并订单表
删除原表中已经编码完成的字段
order=order.drop(["收货地址","宝贝种类"],axis=1)
order
把编码成功之后生成表(address,kinds)和order合并:
order=pd.concat([order,address,kinds],axis=1)
order.head()
订单详情表
导入数据
order_detail=pd.read_csv(r"D电商玩具店营销案例\data\Items_order.csv")
order_detail.head()
删除无关字段
order_detail=order_detail.dropna(axis=1,thresh=order_detail.shape[0]*0.2)
order_detail.head()
for i in order_detail.columns:
if order_detail[i].nunique()==1:
del order_detail[i]
order_detail.shape
选择对表有用的字段,且与用户购买行为有关的字段
order_detail=order_detail[["订单编号","标题","价格","购买数量","订单状态"]]
order_detail.head()
筛选交易成功的记录
order_detail=order_detail[order_detail.订单状态=="交易成功"]
order_detail.head()
order_detail=order_detail.reset_index(drop=True).iloc[:,:-1]
order_detail.head()
商品详情表
导入数据
items_detail=pd.read_csv(r"D:\电商玩具店营销案例\data\Items_attribute.csv")
items_detail.head()
items_detail.isnull().sum() - 查看缺失值
items_detail.玩具类型.value_counts() - 发现“玩具类型”字段有价值的信息极少,不能体现用户购买行为的特征,字段删除
items_detail.适用年龄.value_counts().index - 年龄字段过于混杂,需要将字段拆分处理
年龄字段处理
items_detail.适用年龄
0 3岁,4岁,5岁,6岁
1 3岁,4岁,5岁,6岁
2 3岁,4岁,5岁,6岁,7岁,8岁,9岁,10岁,11岁,12岁
3 3岁,4岁,5岁,6岁,7岁,8岁,9岁,10岁,11岁,12岁,13岁,14岁
4 3岁,4岁
items_detail.适用年龄=items_detail.适用年龄.fillna(items_detail.适用年龄.mode()[0]) - 填充缺失值
对出现的所有年龄小标签进行人工分组
“婴儿”:一岁以内:3个月,6个月,12个月
“幼儿”:1岁到3岁:18个月,2岁,3岁
“学前”:3岁到7岁:4岁,5岁,6岁
“学生”:7岁以上:7岁,8岁,9岁,10岁,11岁,12岁,13岁,14岁,14岁以上
封装函数:
def change(x):
a=x.split(",") # 将字符串切割“3岁,4岁,5岁,6岁”--->['3岁','4岁','5岁','6岁']
st="" # 空字符串用来装载新的年龄标签,比如“幼儿”、“学前”...
for i in a: # 把a看成['3岁','4岁','5岁','6岁']
if i in baby: # 每次遍历出来一个旧年龄标签,都判断一下这个年龄标签属于哪个新类别,下面for循环代码体就是不断地判断旧年龄标签
if st.find("婴儿")!=-1:
continue # 跳过for循环该次遍历
st=st+"婴儿|"
elif i in youer:
if st.find("幼儿")!=-1: # 第一次遍历"3岁",此时st还是空列表,因此第一次循环不会执行continue
continue # 执行continue的前提是此次for遍历,str字符串中已经"幼儿"两个字
st=st+"幼儿|" # 第一次遍历"3岁",str从空字符串变成"幼儿|"
elif i in xueqian:
if st.find("学前")!=-1:
continue
st=st+"学前|"
else:
if st.find("学生")!=-1:
continue
st=st+"学生|"
return st
age=items_detail.适用年龄.apply(change)
age
0 幼儿|学前|
1 幼儿|学前|
2 幼儿|学前|学生|
3 幼儿|学前|学生|
4 幼儿|学前|
...
283 幼儿|学前|学生|
284 幼儿|学前|学生|
285 婴儿|幼儿|
品牌字段独热编码处理
brand=pd.get_dummies(items_detail.品牌,prefix="品牌")
brand.head()
商品详情表合并
items_detail=pd.concat([items_detail.iloc[:,:3],age,brand],axis=1)
items_detail.head()
表合并
第一次表合并:尽可能的保留客户的购买行为信息
- table_01是由item_detail表和order_detail合并的
- table_01表中记录了用户在所用订单中购买的所有商品的详细信息(19877个记录)
- order表记录了用户所有订单的订单信息(3989)
table_01=pd.merge(order_detail,items_detail,how="inner",on="标题")
table_01.head()
第二次表合并:
table_02=pd.merge(table_01,order,how="left",on="订单编号")
table_02.head()
构建用户行为表
- 把table_02转化为一行记录一个用户所有购买行为的信息表。–>要经历按照用户名来groupby的过程。
- 由于table_02每一行记录是代表某一笔订单中某一类商品的购买信息,大多数字段根据将table_02按照买家会员名进行分组求和,
- 但是某些字段不能直接求和,比如“买家实际支付金额”在同一订单中不同类型的商品信息会重复出现,直接按照买家会员名进行分组求和会重复计算
table_02=table_02.drop_duplicates()
table_02[table_02.订单编号==24043728806509300][["订单编号","宝贝总数量","宝贝ID","标题"]]
剔除合并表中与用户购买行为无关的字段
table_03=table_02.drop(["订单编号","标题","宝贝ID","宝贝标题 ","价格_x","价格_y"],axis=1)
table_03.head()
对以上的字段以外的分组求和
table_04=table_03.drop(["买家实际支付金额","宝贝总数量"],axis=1)
table_04.head()
order_tag_01=table_04.groupby("买家会员名").sum()
order_tag_01.head()
- 买家实际支付金额:来源order表,记录一笔订单实际支付金额
- 宝贝总数量:来源order表,记录一笔订单宝贝总数量
- 以上需要分组求均值。
table_05=table_02[["订单编号","买家会员名","买家实际支付金额","宝贝总数量"]]
table_05.head()
- 如何使用“买家实际支付金额”体现买家每一笔订单的平均支付能力?—>不要再使用table_02表了,因为:
- table_02中的一行记录的是不同订单中不同款产品数据
- 而table_02中的“买家实际支付金额”,“宝贝总数量”字段却是记录的是每一个订单的总金额和总数量
- 使用原表order来统计不同买家每笔订单的平均实际支付金额与平均购买宝贝数量
- 只要生成的字段能够体现每个买家的购买力和购买数即可
order_tag_02=order.groupby("买家会员名")[["买家实际支付金额","宝贝总数量"]].mean()
order_tag_02
order_tag_all=pd.merge(order_tag_01,order_tag_02,how="inner",on="买家会员名")
order_tag_all=order_tag_all.set_index("买家会员名")
order_tag_all.head()
order_tag_all.isnull().sum()[order_tag_all.isnull().sum()!=0] - 观察由于缺失值
归一化
from sklearn.preprocessing import MinMaxScaler
mms=MinMaxScaler()
data_norm=mms.fit_transform(order_tag_all.values)
data_norm
K-Means聚类
确定k值方法:
- 业务经验;
- 手肘法+轮廓系数
手肘法
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
sse=[]
for k in range(1,25):
km=KMeans(n_clusters=k)
km.fit(data_norm)
sse.append(km.inertia_)
sse
plt.plot(range(1,25),sse,marker="o") - 轮廓系数1400,过大
删除一些不是特别重要的特征:‘宝贝种类’,‘地址’,‘品牌’
a=[] # 空列表用来存储可能对聚类没太大作用特征
for i in order_tag_all.columns:
if i.find("宝贝种类")!=-1 or i.find("地址")!=-1 or i.find("品牌")!=-1 :
a.append(i)
order_tag_all.drop(a,axis=1,inplace=True)
重新归一化,重新拟合模型
# 重新再做归一化
from sklearn.preprocessing import MinMaxScaler
mms=MinMaxScaler()
data_norm=mms.fit_transform(order_tag_all.values)
# 重新拟合模型
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
sse=[]
for k in range(1,25):
km=KMeans(n_clusters=k)
km.fit(data_norm)
sse.append(km.inertia_)
plt.plot(range(1,25),sse,marker="o")
轮廓系数图
from sklearn.metrics import silhouette_score
score=[]
for k in range(2,25):
km=KMeans(n_clusters=k)
res_km=km.fit(data_norm)
score.append(silhouette_score(data_norm,res_km.labels_))
plt.plot(range(2,25),score,marker="o")
发现k=5是误差平均和极小值点,轮廓系数在极大值位置
重新建模
km=KMeans(n_clusters=5)
km.fit(data_norm)
clusters=km.labels_
clusters
order_tag_all["类别"]=clusters
order_tag_all.head()
result=order_tag_all["类别"]
result
构建推荐系统
基于聚类的精准推荐总体
- 先基于聚类:把用户分群,对每一个客户都标记上标签值。
- 推荐规则生成:
– 推荐个客户的商品,都是ta没买过的
– 在ta没有购买过的商品中,哪些是和ta同类客户最喜欢的?
---- 同类客户总购买次数(平均购买次数)最多的商品,就是这类客户最喜欢的商品
---- 同类客户总购买数量(平均购买数量)最多的商品,就是这类客户最喜欢的商品
用户-商品-购买次数表
- 想要提取所有用户对所有商品的购买次数信息,需要从order表和order_detail中提取对应的记录:
user_itmes=pd.merge(order_detail,order,how="left",on="订单编号")
user_itmes.head()
user_itmes=user_itmes[["买家会员名","标题"]]
user_itmes
user_itmes["购买次数"]=1
user_itmes
- 我们需要知道所有会员对所有商品的购买总次数,所以被透视的字段“购买次数”:
user_itmes=user_itmes.pivot_table("购买次数","买家会员名","标题",aggfunc=np.sum).fillna(0)
user_itmes.head()
user_itmes=user_itmes.stack().reset_index()
user_itmes.rename(columns={0:"购买次数"},inplace=True)
user_itmes
aa=pd.DataFrame([[1,2,3],[1,2,3]],index=["第一行","第二行"],columns=["第一列","第二列","第三列"])
aa
pd.DataFrame(aa.stack())
用户-未购买商品表
user_item_notbuy=user_itmes[user_itmes.购买次数==0.0]
user_item_notbuy.head()
用户-未购买商品-类别表
clusters=pd.DataFrame(result).reset_index()
clusters
user_itme_notbuy_clu=pd.merge(user_item_notbuy,clusters,how="left",on="买家会员名")
user_itme_notbuy_clu.drop("购买次数",axis=1,inplace=True)
user_itme_notbuy_clu=user_itme_notbuy_clu.dropna()
user_itme_notbuy_clu
类别-商品-购买次数表
cluster_item_num=pd.merge(user_itmes,clusters,how="left",on="买家会员名").iloc[:,1:]
cluster_item_num.head()
cluster_item_num=cluster_item_num.groupby(["类别","标题"]).sum().reset_index()
cluster_item_num.head()
最终推荐表
user_notbuy_fre=pd.merge(user_itme_notbuy_clu,cluster_item_num,how="left",on=["类别","标题"])
user_notbuy_fre.head()
def change(x): # x看做每个小子表,小子表是DataFrame对象
recom=x.sort_values("购买次数",ascending=False)[:5] # 将每一个小子表按照“购买次数”字段降序,然后返回前5行记录
return recom
recom=user_notbuy_fre.groupby("买家会员名").apply(change).reset_index(drop=True)
recom.head()