(第十二周作业一)案例:电商用户对商品喜好的预测分析

目录

一、任务背景

1.1 任务目标:

1.2 问题分析

二 、 数据清洗

三、特征工程

3.1 为什么需要特征工程

3.2 特征工程的作用

3.3 特征分析

3.4 商品特征计算

3.5 店铺特征计算

3.6 计算每个店铺的商品数

3.7 商品-店铺特征关联

四、划分训练集与测试集

五、模型训练

5.1 线性回归模型的介绍

5.2 建立线性回归模型

六、模型的预测和分析

七、完整代码


一、任务背景

通过上一次你对营销短信的人群与时段的出色分析,阿普闪购的推广活动圆满达成了目标。当日订单量不断刷新新高,但与此同时,另一个问题也暴露出来:部分供应商对此次活动准备不足,导致生产跟不上了,很多商品发货周期变长,用户的整体满意度也受到了影响。

接下来,即将迎来下一次的大促节日,这次节日同以往不同,用户在开始活动的前三天内只能浏览商品、收藏或者添加购物车,不能下单,等三天过后才开始秒杀下单。为了不再重蹈之前准备不足的覆辙。你作为阿普闪购的数据分析师再一次临危受命。

1.1 任务目标:

根据用户前三天预热期的行为记录,预测出每款商品的销量,这样可以提前知会供应商按照预测销量进行准备,避免之前准备不足的问题。

1.2 问题分析

从任务说明中可以发现,我们的核心任务是从其他行为(点击、添加收藏、添加购物车)预测商品的销量。这和我们上一次的分析维度不同,这次主要是从商品维度进行分析。

我们有三个数据源,VIP 会员表和用户信息表基本都是用户的信息。而用户行为表中则包含了商品的信息,所以这次我们重点从用户行为表入手。但面临的挑战是:用户行为表是面向行为的,每一行代表一个用户的一次行为,可能是点击、可能是加入购物车,等等。我们首先需要从这个表中抽象出一个面向商品的表,然后再尝试建立预测销量的模型

二 、 数据清洗

导入数据表

# 导包读入数据
import pandas as pd
import numpy as np
df_user_log = pd.read_csv('../data/user_behavior_time_resampled.csv')
df_vip_user = pd.read_csv('../data/vip_user.csv')

 查看数据缺失情况

# 查看数据缺失情况
df_user_log.isnull().sum()

brand_id 有 18132 个缺失值,但我们这次的任务并不需要考虑 brand_id,所以不需要处理。

# 查看action_type的取值
df_user_log['action_type'].value_counts()

 

可以看到,action_type 的取值没有异常值,并且点击行为都远高于其他三种行为,其中加到购物车的数量最少。

三、特征工程

3.1 为什么需要特征工程

(  在绝大多数需要建立模型的数据分析任务里,特征工程都是必不可少的环节。顾名思义,特征工程就是指准备特征、筛选特征的工作,只是这项工作具有一定的重要性和复杂度,所以也会叫作工程。

以线性回归模型为例,通过上一节的学习,我们都知道线性回归模型本质上是从找出自变量和因变量的关系。现实问题中,因变量是很好找的,因为往往因变量就是我们要预测的任务,比如在这个例子中,因变量就是我们的预测目标:商品的销量

另一方面,自变量,也称为模型的特征,往往没那么明显。一个原因是因变量往往会受非常多的因素所影响,有直接的,也有间接的。另一个原因是我们拿到的数据集上往往没有现成的特征给我们。这需要我们从原始数据集中经过分析、变换之后才能得到我们想要的特征。 )

3.2 特征工程的作用

在机器学习中,构建特征工程是非常重要的一步。 这是因为原始数据通常是不适合直接用于模型训练的,而需要进行特定的处理和转换,以提取出最有用的信息,并消除不必要的噪音和冗余信息。构建特征工程可以帮助我们:

  1. 提高模型的预测准确度:良好的特征工程能够更好地表达样本数据之间的关联性和差异性,使得模型更容易对数据集的关系进行有效建模。

  2. 减少过拟合的风险:通过特征选择,可以去除那些不重要或无用的特征,从而减小模型的复杂度,避免过多地学习数据集的噪声和异常点。

  3. 加速模型的训练速度:去除噪声、减少特征数目等操作都可以加速模型的训练速度。

总之,好的特征工程能为原始数据的处理提供更丰富的信息,帮助模型更好地理解数据的本质,进而提升模型的预测精度。

3.3 特征分析

特征工程的第一步,是根据我们已有的数据(哪怕不是直接可用的数据)以及我们的因变量,来预设出一组特征。第二步,就是从原始数据中计算出这些特征。第三步则是用这些特征对模型进行训练,如何训练出的模型有问题,则需要重新回到第一步,尝试重新尝试其他的特征。

我们这次的因变量是商品的销量,能够作为参考的数据是用户在预热期的三天的行为。用户在浏览商品时,不能下单的话,主要的行为就是:点击商品、把商品添加到购物车,以及收藏商品。那对于商品来说,商品的点击数、加购物车的次数,以及收藏的次数是否可以作为销量的特征呢?

判断特征是否有效可以从特征的值改变是否会影响因变量的值这个标准出发。这三个特征,不管是点击数,还是收藏数,还是加购物车的次数越多,就说明商品越受欢迎,则说明越可能有用户来购买,反之亦然。所以这三个特征都可以作为我们这次任务的特征。

除了商品维度的特征,商品所在店铺的特征是否对商品的销量有帮助呢?比如在一些商品数比较多的店铺,往往用户搜索进入的概率就大,那商品被用户看到的概率也就变大了。另一方面,店铺的 VIP 用户多寡似乎也能说明店铺是否优质,优质的店铺的商品理论上来说也更好卖一些。

综上所述,我们初步总结了五个可能对商品销量有影响的特征。

商品维度:点击数添加到购物车的次数;收藏数。商品所在店铺的维度:店铺的商品数

店铺所拥有的 VIP 用户数

3.4 商品特征计算

首先我们尝试计算商品的特征,从三张表的描述可以得知,在用户行为表中,有用户对商品的行为记录,所以我们从用户行为表入手来计算商品的特征。

基本的思路就是把用户行为表按照 item_id 的维度聚合起来,然后点击数、加购物车数、收藏数和购买数都应该是聚合后的表的列。但目前用户行为表中没有这四列,相关的信息都由 action_type 来表示了。所以第一步,我们需要把 action_type 的内容展开成四列来分别表示:点击、加购物车、收藏和下单。

规则就是如果 action_type 是收藏时,则收藏列为 1,其他三列为 0;action_type 为下单时,下单一列为 1,其他三列为 0, 以此类推。

这里就涉及我们在生成新列的时候,不仅是从现有列直接计算生成,而是需要有一定的逻辑判断(判断 action_type )的值,所以我们可以用 Series 的 apply 函数来实现,apply 函数可以把一个函数执行到指定的 Series 上,然后把函数的返回值作为新的 Series 返回。

# 分别插入 click、cart、order、fav 四列,表示点击、加购物车、下单和收藏
# 规则见上面的文字描述
df_user_log['click'] = df_user_log['action_type'].apply(lambda x:1 if x=='click' else 0)
df_user_log['cart'] = df_user_log['action_type'].apply(lambda x:1 if x=='cart' else 0)
df_user_log['order'] = df_user_log['action_type'].apply(lambda x:1 if x=='order' else 0)
df_user_log['fav'] = df_user_log['action_type'].apply(lambda x:1 if x=='fav' else 0)
df_user_log

提取与目标任务相关的字段

df_clean = df_user_log[['item_id', 'click', 'cart', 'order', 'fav']]
df_clean

按商品编号进行分组聚合操作 

df_item = df_clean.groupby(by=['item_id']).sum()
df_item

 小结:可以看到,输出的表中 item_id 已经成为索引。并且结果表只剩下 75w 行记录,核心原因就是我们把 item_id 相同的记录聚合了,所以现在 df_item 已经是一个商品维度的特征表。

3.5 店铺特征计算

店铺特征包含两个,需要分别进行计算。但一致的是,我们都希望得到一个店铺 id,也就是seller_id 作为 index 的表,而特征则是表的列。

表里的 merchant_id 就是商家的 id,但是在用户行为表中,商家的 id 字段为 seller_id。所以为了后续对应方便,我们首先需要将该列重命名为 seller_id。

然后我们要计算每一个商家的 VIP 用户数,该表存储的是用户是否是某个商家的 VIP,label 字段为 1 代表是。这里我们只考虑商家维度,所以我们只需要简单按照 seller_id 聚合该表,让 seller_id 一致的记录对应的 label 字段求和,即可得到每个商家的 VIP 用户数。

# 重命名merchant_id 为 seller_id
df_vip_user = df_vip_user.rename(columns={'merchant_id':'seller_id'})
# 按seller_id进行分组,对label进行聚合,即可得到各商家VIP用户数
df_brand_vip_users = df_vip_user[['seller_id', 'label']].groupby('seller_id').sum()
df_brand_vip_users

聚合完毕后,表的索引就会是 seller_id ,label 列则代表 VIP 用户的数量,比如最后一行记录的含义就是 id 为 4993 的店铺,有 4 个 VIP 用户。这样,我们第一个店铺特征表已经准备完毕,表的名字为:df_brand_vip_user。

3.6 计算每个店铺的商品数

计算店铺的商品数,首先我们可以从用户行为表中筛选出 seller_id 和 item_id。 因为我们只关心店铺的商品数,所以我们可以先按 item_id 去重,这样留下来的记录就是商品的总数。然后我们在这个表的基础上,将 item_id 列都赋值为 1 ,然后再按 seller_id 聚合,让 item_id 做求和的运算,就可以得出每个 seller_id 对应的商品数。

# 筛选出 seller_id 和 item_id
df_seller_item_count = df_user_log[['seller_id', 'item_id']]
df_seller_item_count

 

# 去重处理
df_seller_item_count = df_seller_item_count.drop_duplicates()
df_seller_item_count

 

df_seller_item_count.loc[:,'item_id'] = 1
df_seller_item_count = df_seller_item_count.groupby('seller_id').sum()
df_seller_item_count = df_seller_item_count.rename(columns={'item_id':'item_count'})
df_seller_item_count

 

可以看到,我们第二个店铺特征的表也准备好了,index 为 seller_id, 有一列 item_count 代表店铺的商品总数。 

3.7 商品-店铺特征关联

可以看到,目前商品特征表中只有 item_id,没有 seller_id。 所以没有办法直接关联店铺特征表。所以第一步,我们首先需要把商品特征表增加 seller_id 的字段。

要增加 seller_id,只需要我们用类似之前的方法,从原始的行为表中抽取出 seller_id 和 item_id 的对应关系表,然后再将对应关系表拼接进商品特征表中即可。代码如下

# 从原始行为表中取出 item_id 和seller_id ,构成新表
df_brand_item_map = df_user_log[["item_id", "seller_id"]]
# 按照item_id去重,去重后得到的结果就相当于是 item id 和 seller_id 的映射关系
df_brand_item_map = df_brand_item_map.drop_duplicates("item_id")
# 将 df_brand_item_map 映射进 df_item, 以 item_id 为 key
df_item = df_item.merge(df_brand_item_map, how="left", on="item_id")
# 查看最新的商品特征表
df_item

 可以看到,现在我们商品特征表已经多了 seller_id 的字段,现在我们可以将两个店铺特征表拼接进商品特征表了。 

# 将店铺vip用户特征表拼接到商品特征表中,以 seller_id 为 key
df_item = df_item.merge(df_brand_vip_users,how = "left", on="seller_id")
df_item

 

 从输出的来看,店铺 VIP 会员数已经拼接到表里了,但是出现了空值,这也是符合预期的。毕竟 VIP 用户表里不是每个店铺都有 VIP 用户。为了不影响后续的模型,我们通过之前学习的填补缺失值的方法,给空值填为 0,这也是符合逻辑的。

# 用 0 填充缺失值
df_item["label"] = df_item["label"].fillna(0)
df_item

# 拼接店铺的商品数特征表
df_item = df_item.merge(df_seller_item_count, how = "left", on = "seller_id")
df_item

 现在,我们的商品特征表已经完成了,不仅有商品的点击、加购物车、下单和收藏记录,还有商品所在店铺的 VIP 用户数和店铺的商品数。下一步我们就基于这张表建立模型进行分析。

四、划分训练集与测试集

解释::::::ctrl+c+v

回归分析   激动人心的时刻马上就要到了,现在我们通过特征工程的环节,已经整理好了能够预测商品销量的特征表,现在我们建立线性回归模型来进行分析。

拆分训练、测试集合

在训练之前,我们还需要想一个问题,训练出模型后,我们怎么衡量模型的好坏呢?总不能等到真正要在生产环境用上模型的时候才进行测试。

一般常见的做法是,将现有的数据拆成两份,比如 70% 一份,30% 一份。 70% 的数据用来训练模型,训练完之后用剩下的 30% 的数据进行测试。这样就能够在模型上线之前就能够衡量模型的好坏。sklearn 提供了现成的 train_test_split 函数,可以帮我们实现数据集的分割。

首先第一步,我们要从商品特征表中拆开自变量和因变量。

因变量是很明显的,就是 order 列,代表商品被下单的数量。

自变量就是除了 order 列之外的所有数值特征,也就是从商品特征表删掉 item_id,seller_id 和 order 列,剩下的都可以作为自变量。)

# 导入分割的方法
from sklearn.model_selection import train_test_split
# 自变量的数据表
X = df_item.drop(columns=["order", "seller_id","item_id"])
# 因变量
y = df_item["order"]
# 分别切割出训练集,测试集,测试集的比例是 20%
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.20, random_state=42)
# 查看自变量的测试集合
X_train

五、模型训练

5.1 线性回归模型的介绍

线性回归模型是一种基于最小二乘法的机器学习算法,用于构建一个将自变量与因变量相关联的线性方程。线性回归的目标是找到一条直线或超平面来拟合(或预测)数据中的目标变量。这个模型主要用于探索两种变量之间的关系,其中一种变量被称为自变量,另一种被称为因变量。

一般情况下,我们把线性回归问题分为单变量线性回归和多变量线性回归两类。

在单变量线性回归中,模型只存在一个自变量和一个因变量。此时,我们可以通过绘制散点图来观察自变量和因变量之间的关系,例如,我们想利用一个人的身高预测他的体重。在这种情况下,身高是自变量,体重是因变量。

在多变量线性回归中,模型存在多个自变量和一个因变量。例如,在房价预测任务中,一个房子的价格可能受到很多因素的影响,例如地理位置、房屋面积、周边环境等等。在这种情况下,我们需要使用多个变量来预测目标变量。

推导过程如下:假设我们有样本数据:{(x1, y1), (x2, y2)... (xn, yn)},其中xi是自变量,yi是因变量。我们的模型需要找到一条直线(或超平面)来拟合这些样本数据。由于直线可以表示成 y = kx+b 的形式,我们可以得到一个线性方程:

y = θ0 + θ1*x1 + θ2*x2 + ... + θm*xm

其中,θ代表模型参数,xm是第m个解释变量。我们的目标是通过训练样本,找到最佳的θ值来最小化误差函数,从而得到一个最优模型,使得预测结果更加准确。

在实际应用中,我们可能会遇到包含异常值或者噪声的数据集,此时可以使用岭回归、Lasso回归等算法进行正则化处理,对模型进行优化。

5.2 建立线性回归模型

接下来的步骤就比较简单,我们建立回归模型,然后调用 fit 方法来从 X_train 和 y_train 中训练模型。

# 初始化线性回归器对象
from sklearn.linear_model import LinearRegression
linear_model = LinearRegression()
# 训练模型
linear_model.fit(X_train, y_train)

  

训练结束之后,我们可以用 linear_model 来 predict X_test,然后拿 predict 函数返回的结果和 y_test 比较,就能知道我们模型的误差。

from sklearn.metrics import mean_absolute_error
# predict X_test 对应的 y 值
y_pred = linear_model.predict(X_test)
# 用 scores 方法,查看模型自变量和因变量的相关性
print("Scores:", linear_model.score(X_test, y_test))
# 查看 predict y 和 y test的平均绝对误差
print("MAE:", mean_absolute_error(y_test, y_pred))
# 查看模型的 b 值
print("intercept:", linear_model.intercept_)
# 查看模型的系数
print("coef_:", linear_model.coef_)

模型的相关性分数说 0.44, 对于现实世界中的回归问题来说,这也算是个不错的成绩,说明我们的特征还是很大程度能够影响下单量。MAE 是 0.99,说明对于测试集而言,我们模型预测的结果和实际的真实结果非常接近,说明模型的拟合还是比较好的。

六、模型的预测和分析

从模型系数中可以看到,我们的前三个特征:click、fav、cart 的系数比较大,后两个特征 label 和 item_count 的系数比较小,说明对于商品的下单量而言,前三个特征更加重要,关联性更强,后两者则对结果影响相对较小。

有时候凭我们的主观判断,对于特征的重要性判断可能是不准的,我们可以都一并带上,然后在模型训练的环节,通过拟合算法自动去找到不同特征的重要性。这样就比人的判断靠谱多了。

我们来测试一下我们的模型,假设某个商品在预热期间,一共有 100 次点击,2 次加购物车,6 次收藏,这个商品所在的店铺有 10 个vip 用户,一共有 30 个商品。那根据我们的模型来预测这个商品的下单数:

print(linear_model.predict([[100, 2, 6, 10, 30]]))

得到的结果代表可能会被买 19 次

七、完整代码

#!/usr/bin/env python
# coding: utf-8

# In[50]:


# 导包读入数据
import pandas as pd
import numpy as np
df_user_log = pd.read_csv('../data/user_behavior_time_resampled.csv')
df_vip_user = pd.read_csv('../data/vip_user.csv')


# In[51]:


df_user_log.head()


# In[52]:


df_vip_user.head()


# In[53]:


# 查看数据缺失情况
df_user_log.isnull().sum()


# In[54]:


# 查看action_type的取值
df_user_log['action_type'].value_counts()


# In[55]:


# 分别插入 click、cart、order、fav 四列,表示点击、加购物车、下单和收藏
# 规则见上面的文字描述
df_user_log['click'] = df_user_log['action_type'].apply(lambda x:1 if x=='click' else 0)
df_user_log['cart'] = df_user_log['action_type'].apply(lambda x:1 if x=='cart' else 0)
df_user_log['order'] = df_user_log['action_type'].apply(lambda x:1 if x=='order' else 0)
df_user_log['fav'] = df_user_log['action_type'].apply(lambda x:1 if x=='fav' else 0)
df_user_log


# In[56]:


# 提取与目标任务相关的字段
df_clean = df_user_log[['item_id', 'click', 'cart', 'order', 'fav']]
df_clean


# In[57]:


# 按商品编号进行分组聚合操作
df_item = df_clean.groupby(by=['item_id']).sum()
df_item


# In[58]:


# 重命名merchant_id 为 seller_id
df_vip_user = df_vip_user.rename(columns={'merchant_id':'seller_id'})
# 按seller_id进行分组,对label进行聚合,即可得到各商家VIP用户数
df_brand_vip_users = df_vip_user[['seller_id', 'label']].groupby('seller_id').sum()
df_brand_vip_users


# In[59]:


# 筛选出 seller_id 和 item_id
df_seller_item_count = df_user_log[['seller_id', 'item_id']]
df_seller_item_count


# In[60]:


# 去重处理
df_seller_item_count = df_seller_item_count.drop_duplicates()
df_seller_item_count


# In[61]:


df_seller_item_count.loc[:,'item_id'] = 1
df_seller_item_count = df_seller_item_count.groupby('seller_id').sum()
df_seller_item_count = df_seller_item_count.rename(columns={'item_id':'item_count'})
df_seller_item_count


# In[62]:


# 从原始行为表中取出 item_id 和seller_id ,构成新表
df_brand_item_map = df_user_log[["item_id", "seller_id"]]
# 按照item_id去重,去重后得到的结果就相当于是 item id 和 seller_id 的映射关系
df_brand_item_map = df_brand_item_map.drop_duplicates("item_id")
# 将 df_brand_item_map 映射进 df_item, 以 item_id 为 key
df_item = df_item.merge(df_brand_item_map, how="left", on="item_id")
# 查看最新的商品特征表
df_item


# In[63]:


# 将店铺vip用户特征表拼接到商品特征表中,以 seller_id 为 key
df_item = df_item.merge(df_brand_vip_users,how = "left", on="seller_id")
df_item


# In[64]:


# 用 0 填充缺失值
df_item["label"] = df_item["label"].fillna(0)
df_item


# In[65]:


# 拼接店铺的商品数特征表
df_item = df_item.merge(df_seller_item_count, how = "left", on = "seller_id")
df_item


# In[66]:


# 导入分割的方法
from sklearn.model_selection import train_test_split
# 自变量的数据表
X = df_item.drop(columns=["order", "seller_id","item_id"])
# 因变量
y = df_item["order"]
# 分别切割出训练集,测试集,测试集的比例是 20%
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.20, random_state=42)
# 查看自变量的测试集合
X_train


# In[67]:


# 初始化线性回归器对象
from sklearn.linear_model import LinearRegression
linear_model = LinearRegression()
# 训练模型
linear_model.fit(X_train, y_train)


# In[68]:


from sklearn.metrics import mean_absolute_error
# predict X_test 对应的 y 值
y_pred = linear_model.predict(X_test)
# 用 scores 方法,查看模型自变量和因变量的相关性
print("Scores:", linear_model.score(X_test, y_test))
# 查看 predict y 和 y test的平均绝对误差
print("MAE:", mean_absolute_error(y_test, y_pred))
# 查看模型的 b 值
print("intercept:", linear_model.intercept_)
# 查看模型的系数
print("coef_:", linear_model.coef_)


# In[69]:


print(linear_model.predict([[100, 2, 6, 10, 30]]))


# In[ ]:




猜你喜欢

转载自blog.csdn.net/qq_62238325/article/details/131048958