总体思路
基于聚类的精准推荐总体
- 先基于聚类:
- 生成推荐规则:
- 在用户没买过的商品中,同类客户总购买次数(平均购买次数)最多的商品,就是这类客户最喜欢的商品
数据清洗
- 优先删除:
- 缺失率90%以上
- 整个字段只有1个值
- 整个字段有效信息几乎没有
- 需要转码:
数据整合
import numpy as np
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler
from sklearn.cluster import KMeans
plt.style.use('seaborn')
plt.rcParams['font.sans-serif']=['Simhei']
plt.rcParams['axes.unicode_minus']=False
数据清洗
订单表清洗
数据初步探索
order = pd.read_csv(r"...\order.csv",index_col=0)
order.head(1)
|
订单编号 |
买家会员名 |
买家应付货款 |
买家应付邮费 |
买家支付积分 |
总金额 |
返点积分 |
买家实际支付金额 |
买家实际支付积分 |
订单状态 |
... |
是否代付 |
定金排名 |
修改后的sku |
修改后的收货地址 |
异常信息 |
天猫卡券抵扣 |
集分宝抵扣 |
是否是O2O交易 |
退款金额 |
预约门店 |
0 |
21407300627014900 |
1425 |
58.51 |
0.0 |
0 |
58.51 |
0 |
58.51 |
0 |
交易成功 |
... |
否 |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
0.0 |
NaN |
1 rows × 45 columns
order.info()
Int64Index: 3989 entries, 0 to 3988
Data columns (total 45 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 订单编号 3989 non-null int64
1 买家会员名 3989 non-null int64
2 买家应付货款 3989 non-null float64
3 买家应付邮费 3989 non-null float64
4 买家支付积分 3989 non-null int64
5 总金额 3989 non-null float64
6 返点积分 3989 non-null int64
7 买家实际支付金额 3989 non-null float64
8 买家实际支付积分 3989 non-null int64
9 订单状态 3989 non-null object
10 买家留言 384 non-null object
11 收货人姓名 3989 non-null int64
12 收货地址 3989 non-null object
13 运送方式 3989 non-null object
14 联系电话 142 non-null object
15 联系手机 3986 non-null object
16 订单创建时间 3989 non-null object
17 订单付款时间 3989 non-null object
18 宝贝标题 3989 non-null object
19 宝贝种类 3989 non-null int64
20 物流单号 3988 non-null object
21 物流公司 3988 non-null object
22 订单备注 460 non-null object
23 宝贝总数量 3989 non-null int64
24 店铺Id 3989 non-null int64
25 店铺名称 3989 non-null int64
26 订单关闭原因 3989 non-null object
27 卖家服务费 3989 non-null int64
28 买家服务费 3989 non-null object
29 发票抬头 0 non-null float64
30 是否手机订单 3728 non-null object
31 分阶段订单信息 0 non-null float64
32 特权订金订单id 0 non-null float64
33 是否上传合同照片 3989 non-null object
34 是否上传小票 3989 non-null object
35 是否代付 3989 non-null object
36 定金排名 0 non-null float64
37 修改后的sku 0 non-null float64
38 修改后的收货地址 61 non-null object
39 异常信息 0 non-null float64
40 天猫卡券抵扣 0 non-null float64
41 集分宝抵扣 12 non-null float64
42 是否是O2O交易 0 non-null float64
43 退款金额 3989 non-null float64
44 预约门店 0 non-null float64
dtypes: float64(15), int64(11), object(19)
memory usage: 1.6+ MB
删除无用信息
order=order.dropna(axis=1,thresh=order.shape[0]*0.2)
for i in order.columns:
if order[i].nunique()==1:
del order[i]
order=order[["订单编号","买家会员名","买家实际支付金额","收货地址","宝贝标题 ","宝贝种类","宝贝总数量","退款金额"]]
数据编码
order.退款金额=np.where(order.退款金额>0,1,0)
address=order.收货地址.str[:3].str.strip()
address=pd.get_dummies(address,prefix="地址")
kinds=pd.get_dummies(order.宝贝种类,prefix="宝贝种类")
order=order.drop(["收货地址","宝贝种类"],axis=1)
order=pd.concat([order,address,kinds],axis=1)
order.head(1)
|
订单编号 |
买家会员名 |
买家实际支付金额 |
宝贝标题 |
宝贝总数量 |
退款金额 |
地址_上海 |
地址_云南省 |
地址_内蒙古 |
地址_北京 |
... |
宝贝种类_39 |
宝贝种类_40 |
宝贝种类_41 |
宝贝种类_43 |
宝贝种类_45 |
宝贝种类_46 |
宝贝种类_47 |
宝贝种类_48 |
宝贝种类_49 |
宝贝种类_50 |
0 |
21407300627014900 |
1425 |
58.51 |
... |
59 |
0 |
0 |
0 |
0 |
0 |
... |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 rows × 83 columns
订单详情表清洗
数据初步探索
order_detail=pd.read_csv(r"...\Items_order.csv")
order_detail.head(1)
|
订单编号 |
标题 |
价格 |
购买数量 |
外部系统编号 |
商品属性 |
套餐信息 |
备注 |
订单状态 |
商家编码 |
0 |
21407300627014900 |
... |
0.58 |
12 |
WY013-2SZD0426 |
颜色分类:小号 |
NaN |
NaN |
交易成功 |
WY013-2SZD0426 |
order_detail.info()
RangeIndex: 21897 entries, 0 to 21896
Data columns (total 10 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 订单编号 21897 non-null int64
1 标题 21897 non-null object
2 价格 21897 non-null float64
3 购买数量 21897 non-null int64
4 外部系统编号 21897 non-null object
5 商品属性 12636 non-null object
6 套餐信息 0 non-null float64
7 备注 130 non-null object
8 订单状态 21897 non-null object
9 商家编码 21897 non-null object
dtypes: float64(2), int64(2), object(6)
memory usage: 1.7+ MB
删除无用信息
order_detail=order_detail.dropna(axis=1,thresh=order_detail.shape[0]*0.2)
for i in order_detail.columns:
if order_detail[i].nunique()==1:
del order_detail[i]
order_detail=order_detail[["订单编号","标题","价格","购买数量","订单状态"]]
order_detail=order_detail[order_detail.订单状态=="交易成功"]
order_detail=order_detail.reset_index(drop=True).iloc[:,:-1]
order_detail.head(1)
|
订单编号 |
标题 |
价格 |
购买数量 |
0 |
21407300627014900 |
... |
0.58 |
12 |
商品详情表
数据初步探索
items_detail=pd.read_csv(r"...\Items_attribute.csv")
items_detail.head(1)
|
宝贝ID |
标题 |
价格 |
玩具类型 |
适用年龄 |
品牌 |
0 |
537396783238 |
... |
8.9 |
塑胶玩具 |
3岁,4岁,5岁,6岁 |
3 |
items_detail.info()
RangeIndex: 288 entries, 0 to 287
Data columns (total 6 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 宝贝ID 288 non-null int64
1 标题 288 non-null object
2 价格 288 non-null float64
3 玩具类型 252 non-null object
4 适用年龄 284 non-null object
5 品牌 288 non-null int64
dtypes: float64(1), int64(2), object(3)
memory usage: 13.6+ KB
数据编码
items_detail.适用年龄=items_detail.适用年龄.fillna(items_detail.适用年龄.mode()[0])
a=[]
for i in items_detail.适用年龄.value_counts().index:
a.extend(i.split(","))
a=list(set(a))
baby=["3个月","6个月","12个月"]
youer=['18个月','2岁','3岁']
xueqian=['4岁','5岁','6岁']
stu=['7岁','8岁','9岁','10岁','11岁','12岁','13岁','14岁','14岁以上']
def change(x):
a=x.split(",")
st=""
for i in a:
if i in baby:
if st.find("婴儿")!=-1:
continue
st=st+"婴儿|"
elif i in youer:
if st.find("幼儿")!=-1:
continue
st=st+"幼儿|"
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=age.str.get_dummies("|")
age.columns="年龄_"+age.columns
age
|
年龄_婴儿 |
年龄_学前 |
年龄_学生 |
年龄_幼儿 |
0 |
0 |
1 |
0 |
1 |
1 |
0 |
1 |
0 |
1 |
2 |
0 |
1 |
1 |
1 |
3 |
0 |
1 |
1 |
1 |
4 |
0 |
1 |
0 |
1 |
... |
... |
... |
... |
... |
283 |
0 |
1 |
1 |
1 |
284 |
0 |
1 |
1 |
1 |
285 |
1 |
0 |
0 |
1 |
286 |
0 |
1 |
1 |
1 |
287 |
0 |
1 |
1 |
1 |
288 rows × 4 columns
items_detail.品牌.isnull().sum()
brand=pd.get_dummies(items_detail.品牌,prefix="品牌")
items_detail=pd.concat([items_detail.iloc[:,:3],age,brand],axis=1)
表合并
三表合一个表
table_01 = pd.merge(order_detail, items_detail, how="inner", on="标题")
table_02 = pd.merge(table_01, order, how="left", on="订单编号")
先把介意直接求和的客户信息表制作出来
table_02 = table_02.drop_duplicates()
table_03 = table_02.drop(["订单编号", "标题", "宝贝ID", "宝贝标题 ", "价格_x", "价格_y"],
axis=1)
table_04 = table_03.drop(["买家实际支付金额", "宝贝总数量"], axis=1)
order_tag_01 = table_04.groupby("买家会员名").sum()
再把不能直接求和的用order 表求得金额
table_05 = table_02[["订单编号", "买家会员名", "买家实际支付金额", "宝贝总数量"]]
order_tag_02 = order.groupby("买家会员名")[["买家实际支付金额", "宝贝总数量"]].mean()
得到用户购买行为信息表
order_tag_all = pd.merge(order_tag_01, order_tag_02, how="inner", on="买家会员名")
order_tag_all.isnull().sum()[order_tag_all.isnull().sum() != 0]
Series([], dtype: int64)
数据建模
初次建模
mms=MinMaxScaler()
data_norm=mms.fit_transform(order_tag_all.values)
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")
[]
删除无效字段后 再次建模
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)
mms=MinMaxScaler()
data_norm=mms.fit_transform(order_tag_all.values)
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")
[]
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_
pd.Series(clusters).value_counts()
order_tag_all["类别"]=clusters
result=order_tag_all["类别"]
推荐系统
思路:
- 筛选出来所有会员没购买的商品
- 找到对应会员所属的客户类别购买次数最多的商品
用户-商品-购买次数表
user_itmes=pd.merge(order_detail,order,how="left",on="订单编号")
user_itmes=user_itmes[["买家会员名","标题"]]
user_itmes["购买次数"]=1
user_itmes=user_itmes.pivot_table("购买次数","买家会员名","标题",aggfunc=np.sum).fillna(0)
user_itmes=user_itmes.stack().reset_index()
user_itmes.rename(columns={0:"购买次数"},inplace=True)
user_itmes.head(2)
|
买家会员名 |
标题 |
购买次数 |
0 |
0 |
... |
0.0 |
1 |
0 |
... |
0.0 |
用户-未购买商品表
user_item_notbuy=user_itmes[user_itmes.购买次数==0.0]
|
买家会员名 |
标题 |
购买次数 |
0 |
0 |
... |
0.0 |
1 |
0 |
... |
0.0 |
2 |
0 |
.. |
0.0 |
3 |
0 |
.. |
0.0 |
4 |
0 |
... |
0.0 |
user_item_notbuy只记录用户对未购买商品的信息。
用户- 未购买商品-类别表
user_itme_notbuy_clu = pd.merge(user_item_notbuy,result,how='left',on='买家会员名')
del user_itme_notbuy_clu['购买次数']
user_itme_notbuy_clu=user_itme_notbuy_clu.dropna()
user_itme_notbuy_clu.reset_index(drop=True,inplace=True)
cluster_item_num=pd.merge(user_itmes,result,how="left",on="买家会员名").iloc[:,1:]
clusters=pd.DataFrame(result).reset_index()
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()
类别-商品-购买次数表
cluster_item_num=pd.merge(user_itmes,result,how="left",on="买家会员名").iloc[:,1:]
cluster_item_num=cluster_item_num.groupby(["类别","标题"]).sum().reset_index()
最终推荐表
user_notbuy_fre=pd.merge(user_itme_notbuy_clu,cluster_item_num,how="left",on=["类别","标题"])
def sort1(x):
s =x.sort_values('购买次数',ascending=False)[0:5]
s['推荐指数'] = ["❤❤❤❤❤","❤❤❤❤","❤❤❤","❤❤","❤"]
return s
yd = user_notbuy_fre.groupby('买家会员名').apply(sort1).reset_index(drop=True)
yd.head(5)
|
买家会员名 |
标题 |
类别 |
购买次数 |
推荐指数 |
0 |
0 |
.. |
0.0 |
217.0 |
❤❤❤❤❤ |
1 |
0 |
.. |
0.0 |
209.0 |
❤❤❤❤ |
2 |
0 |
.. |
0.0 |
199.0 |
❤❤❤ |
3 |
0 |
... |
0.0 |
187.0 |
❤❤ |
4 |
0 |
... |
0.0 |
141.0 |
❤ |