一、项目背景
Instacart Market Basket Analysis是一个经典的顾客行为预测案例。Instacart的数据团队开源了大约3,000,000条订单数据,我们将通过这些数据分析一下用户的购物行为;
二、获取数据
项目提供了大约200,000用户产生的约3,000,000条订单数据,每一位用户提供了4到100条带有产品序列的订单数据,这些数据包含了订单产生的时间信息以及其他重要的信息(将在第三个session进行详细的研究)。这些数据被分成了多个csv文件,可以一键全部下载。
数据下载的时候可以继续向下滑动,浏览一下这些csv文件更详细的一些信息,初步了解一下各个文件的数据。
三、探索数据
完成对数据的基本理解,我们试着探索一下数据,看看能分析出哪些关于用户购买行为的信息
首先,依次查看每一个表(csv文件)都包含哪些字段(列)以及这些字段都记录哪些信息。
# 导入相关的库
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
color = sns.color_palette()
%matplotlib inline
pd.options.mode.chained_assignment = None
# 读取数据
order_products_train_df = pd.read_csv("/projects/instacart-market-basket-analysis/Instacart_datasets/order_products__train.csv")
order_products_prior_df = pd.read_csv("/projects/instacart-market-basket-analysis/Instacart_datasets/order_products__prior.csv")
orders_df = pd.read_csv("/projects/instacart-market-basket-analysis/Instacart_datasets/orders.csv")
products_df = pd.read_csv("/projects/instacart-market-basket-analysis/Instacart_datasets/products.csv")
aisles_df = pd.read_csv("/projects/instacart-market-basket-analysis/Instacart_datasets/aisles.csv")
departments_df = pd.read_csv("/projects/instacart-market-basket-analysis/Instacart_datasets/departments.csv")
1、首先是order_products__train.csv和order_products__prior.csv这两个文件。
order_products_train_df.head()
order_products_prior_df.head()
这两个文件的字段信息都是相同的,文件命名也只是prior和train的区别,那么问题来了,这两个文件是有何其他区别呢?
欲知这两个文件是有何其他区别,需要配合看一下orders.csv文件,这个文件记录了所有用户的所有订单信息,其中有一个字段是eval_set,这个字段记录了这条订单信息被划分到了哪一个数据集,一共有prior、train、test三个数据集。举个栗子,比如用户1有5个订单,用户2有10个订单,然后用户1最后一个订单会被划分到train,剩下的第1到第4个订单会被划分到prior,用户2最后一个订单会被划分到test,剩下的第1到第9个订单会被划分到prior。为什么要这样做呢?正如我们前面所言,因为我们需要预测用户下一次会复购哪些产品,所以需要把the last order提取出来并分成训练集和测试集两组数据集,最终产生了order_products_prior.csv和order_products_train.csv两个文件。
现在看一下order_products_*的字段,一共有4列
order_id => 订单ID
product_id => 产品ID
add_to_cart_order =>加入购物车的顺序
reordered => 是否复购,0为False,1为True
在这份文件中我们发现每一位user将产品“加入购物车的顺序”这一信息被采集了,在这里我们产生疑问,无事不登三宝殿,那它和reordered之间是不是有什么关系呢?
没法直接用散点图查看他们之间的相关性,所以需要把数据处理一下,产品被加入购物车的顺序是按照1,2,3,...排列的,而每一个序号对应一个位置,但是对应的reordered有两个状态,所以这里可以用复购率来描述产品加入购物车的先后顺序是如何影响复购的。
# 购买超过70件产品的用户很少,为了避免偶然性过大,所以70以后的加到一起计算。为了方便处理数据,需要添加新列
order_products_prior_df["add_to_cart_order_mod"] = order_products_prior_df["add_to_cart_order"].copy()
# 添加购物车顺序超过70的统一标记为70,为分组统计做好预处理
order_products_prior_df["add_to_cart_order_mod"].ix[order_products_prior_df["add_to_cart_order_mod"]>70] = 70
# 分组,因为reordered是布尔值,所以我们对分组后的“reordered”列进行求平均运算后的值就等于复购率
grouped_df = order_products_prior_df.groupby(["add_to_cart_order_mod"])["reordered"].aggregate("mean").reset_index()
grouped_df
# 作图
plt.figure(figsize=(12,8))
sns.pointplot(grouped_df['add_to_cart_order_mod'].values, grouped_df['reordered'].values, alpha=0.8, color=color[2])
plt.ylabel('Reorder ratio', fontsize=12)
plt.xlabel('Add to cart order', fontsize=12)
plt.title("Add to cart order - Reorder ratio", fontsize=15)
plt.xticks(rotation='vertical')
plt.show()
通过点线图,可以清晰的看到产品添加至购物车的顺序越靠前,复购的可能性越大。
2、再继续看一下orders.csv能提供哪些信息:
orders_df.head(10)
各个字段代表的信息:
order_id => 订单ID
user_id => 用户ID
eval_set => 订单划分去向
order_number => 订单序号
order_dow => 官方说明是day of week,也是就订单在星期几产生的
order_hour_of_day => 订单在一天中哪个时间产生的
days_since_prior_order => 复购时距离上次购买过了多长时间
我们发现orders.csv中每一个use_id的order_number都是按顺序排列的,所以我们只要取出每一位user的订单序号最大值就能得到他的订单数量,我们现在看一下用户订单数量的分布情况。
cnt_srs = orders_df.groupby("user_id")["order_number"].aggregate(np.max).reset_index()
cnt_srs = cnt_srs.order_number.value_counts()
plt.figure(figsize=(20, 8))
sns.barplot(cnt_srs.index, cnt_srs.values, alpha=0.8, color=color[1])
plt.ylabel("Number of Occurences", fontsize=12)
plt.xlabel("Eval set type", fontsize=12)
plt.title("Count of rows in each dataset", fontsize=15)
plt.xticks(rotation="90")
plt.show()
联想到前面的order_products_prior.csv文件, 所以看一下每笔订单购买产品数量的分布
# 查看每笔订单购买产品数量的分布情况
grouped_df = order_products_train_df.groupby("order_id")["add_to_cart_order"].aggregate("max").reset_index()
cnt_srs = grouped_df.add_to_cart_order.value_counts()
plt.figure(figsize=(12,8))
sns.barplot(cnt_srs.index, cnt_srs.values, alpha=0.8)
plt.ylabel('Number of Occurrences', fontsize=12)
plt.xlabel('Number of products in the given order', fontsize=12)
plt.xticks(rotation='vertical')
plt.show()
基本类似的分布规律。
接下来看一下用户的购物习惯与reordered之间的关系。
首先了解一下用户的购物习惯。
order_dow:
# order_dow:
plt.figure(figsize=(20, 8))
sns.countplot(x="order_dow", data=orders_df, color=color[0])
plt.ylabel("Count", fontsize=12)
plt.xlabel("Day of week", fontsize=12)
plt.xticks([i for i in range(7)], ["Sat.", "Sun.", "Mons", "Tues.", "wed.", "Thur.", "Fri."])
plt.title("Frequency of order by week day", fontsize=15)
plt.show()
明显看出,星期六、星期日(0,1)也就是周末购物的频率最高,星期三最低。
order_hour_of_day:
# order_hour_of_day:
plt.figure(figsize=(20, 8))
sns.countplot(x="order_hour_of_day", data=orders_df, color=color[0])
plt.ylabel("Count", fontsize=12)
plt.xlabel("Hour of day", fontsize=12)
plt.xticks(rotation=90)
plt.title("Frequency of order by hour of day", fontsize=15)
plt.show()
购物习惯基本和我们的作息习惯是一样的,主要是在白天。
现在用热力图描述一下order_dow和order_hour_of_day的分布关系。
group_df =orders_df.groupby(["order_dow", "order_hour_of_day"])["order_number"].count().reset_index()
group_df = group_df.pivot("order_dow", "order_hour_of_day", "order_number")
group_df
绘图:
plt.figure(figsize=(20, 8))
sns.heatmap(group_df)
plt.title("Frequency of Day of week Vs hour of day")
和分开观察的结果是一样的,图中越亮的地方订单数量阅读,很明显是在周末8:00-18:00的时间段。
days_since_prior_order:
# days_since_prior_order:
plt.figure(figsize=(20, 8))
sns.countplot(x="days_since_prior_order", data=orders_df, color=color[0])
plt.ylabel("Count", fontsize=12)
plt.xlabel("Days since prior order", fontsize=12)
plt.xticks(rotation=90)
plt.title("Frequency distribution by days since prior order", fontsize=15)
plt.show()
比较明显的可以看出用户复购的两次小高峰是一个星期后和一个月后,此外,两个星期后也出现了小高峰。
所有的数据集从量级来看,是按照一个user产生多个orders,一个order里面会购买多个products。现在user和orders层面的数据我们已经大体的做了了解,现在探索一下products层面的信息。
3、先浏览一下其他的几个*.csv表。
可以看出products.csv、aisles.csv、departments.csv这三个表只是存储了一些分类信息,单独分析它们并不能产生更多有价值的信息,需要通过不同的*_id关联到order_products_prior.csv在进行分析。
order_products_prior_df = pd.merge(order_products_prior_df, products_df, on='product_id', how='left')
order_products_prior_df = pd.merge(order_products_prior_df, aisles_df, on='aisle_id', how='left')
order_products_prior_df = pd.merge(order_products_prior_df, departments_df, on='department_id', how='left')
order_products_prior_df
现在order_products_prior.csv表的信息就很全面了。
先从最简单的开始,我们先看一下哪种产品卖的最多吧。
cnt_srs = order_products_prior_df.groupby("product_name").count()["add_to_cart_order_mod"].reset_index()
cnt_srs.columns = ["product_name", "counts"]
cnt_srs.sort_values("counts", ascending=False).head(10)
水果蔬菜好像是消费频率最高的,绘制单个产品的直方图在这里意义并不大,我们按aisle分类绘制直方图看一下,取最大的前20个。
cnt_srs = order_products_prior_df["aisle"].value_counts().head(20)
plt.figure(figsize=(20,8))
sns.barplot(cnt_srs.index, cnt_srs.values, alpha=0.8, color=color[1])
plt.ylabel('Counts', fontsize=12)
plt.xlabel('Aisle', fontsize=12)
plt.xticks(rotation='vertical')
plt.show()
再按department分类看一下,departments并不是很多,绘制饼图看一下他们之间的比例。
plt.figure(figsize=(10,10))
temp_series = order_products_prior_df['department'].value_counts()
labels = (np.array(temp_series.index))
sizes = (np.array((temp_series / temp_series.sum())*100))
plt.pie(sizes, labels=labels,
autopct='%1.1f%%', startangle=200)
plt.title("Departments distribution", fontsize=15)
plt.show()
4、浏览完基本的信息,接下来我们看一下各个字段与reordered之间的关系,还是要用到复购率这一指标。
就接着看一下各个departments产品的复购率大小,预测的时候如果一个产品如果属于购率高的departments,那它被reordered概率自然也是大的。
如何求复购率,前文已经讲过,不在赘述。
grouped_df = order_products_prior_df.groupby(["department"])["reordered"].aggregate("mean").reset_index()
plt.figure(figsize=(20,8))
sns.barplot(grouped_df['department'].values, grouped_df['reordered'].values, alpha=0.8, color=color[2])
plt.ylabel('Reorder ratio', fontsize=12)
plt.xlabel('Department', fontsize=12)
plt.title("Department wise reordered ratio", fontsize=15)
plt.xticks(rotation='vertical')
plt.show()
各个department产品复购率的高低一清二楚。
继续查看按aisle划分的产品复购率:
# 不同的部门在负责不同的aisle
grouped_df = order_products_prior_df.groupby(["department_id", "aisle"])["reordered"].aggregate("mean").reset_index()
fig, ax = plt.subplots(figsize=(12,20))
ax.scatter(grouped_df.reordered.values, grouped_df.department_id.values)
# 给每一个点添加标签
for i, txt in enumerate(grouped_df.aisle.values):
# annotate(标签文本, xy坐标点, 其他参数)
ax.annotate(txt, (grouped_df.reordered.values[i], grouped_df.department_id.values[i]), rotation=45, ha='center', va='center', color='green')
plt.xlabel('Reorder Ratio')
plt.ylabel('department_id')
plt.title("Reorder ratio of different aisles", fontsize=15)
plt.show()
分布比较均匀。
最后我们看一下order_dow和order_hour_of_day这两个基于时间的特征与复购率有何关系。
order_dow - reordered Ratio:
order_products_prior_df = pd.merge(order_products_prior_df, orders_df, on="order_id", how="left" )
group_df = order_products_prior_df.groupby("order_dow")["reordered"].mean().reset_index()
plt.figure(figsize=(16, 8))
sns.barplot(group_df.order_dow.values, group_df.reordered.values, alpha=0.8, color=color[1])
plt.ylabel('Reorder ratio', fontsize=12)
plt.xlabel('Day of week', fontsize=12)
plt.title("Reorder ratio across day of week", fontsize=15)
plt.xticks([i for i in range(7)], ["Sat.", "Sun.", "Mons", "Tues.", "wed.", "Thur.", "Fri."])
plt.ylim(0.5, 0.7)
plt.show()
order_hour_of_day - reordered Ratio:
grouped_df = order_products_prior_df.groupby(["order_hour_of_day"])["reordered"].aggregate("mean").reset_index()
plt.figure(figsize=(16,8))
sns.barplot(grouped_df['order_hour_of_day'].values, grouped_df['reordered'].values, alpha=0.8, color=color[4])
plt.ylabel('Reorder ratio', fontsize=12)
plt.xlabel('Hour of day', fontsize=12)
plt.title("Reorder ratio across hour of day", fontsize=15)
plt.xticks(rotation='vertical')
plt.ylim(0.5, 0.7)
plt.show()
order_dow - order_hour_of_day - reordered Ratio:
grouped_df = order_products_prior_df.groupby(["order_dow", "order_hour_of_day"])["reordered"].aggregate("mean").reset_index()
grouped_df = grouped_df.pivot('order_dow', 'order_hour_of_day', 'reordered')
plt.figure(figsize=(16,8))
sns.heatmap(grouped_df)
plt.title("Reorder ratio of Day of week Vs Hour of day")
plt.show()
四、结论与建议
1、水果蔬菜类的产品消费频率最高,卖的最多的三款产品是Banana、Bag of Organic Bananas、Orangic Strawberries。
2、产品加入购物车的平均位置越靠前,被复购的可能性越大;精准营销时先加入购物车的产品应该优先被推荐给用户。
3、用户每笔订单的产品数量做多的区间是[5, 8]件。如果这是大部分用户最愿意接受的每次购物量,每次可以向用户推荐[6, 10]件产品,在不影响用户体验的前提下向用户展示尽可能多的产品。
4、用户通常在周六周日进行购物,周二周三用户最不活跃,每天的客流量主要集中在白天8:00-18:00,总的来看用户在周日的6:00-9:00达到最高峰,新品促销广告展示可以优先选择这几个时间段。
5、同一产品用户复购的两次小高峰购买的一个星期后和一个月后,此外,两个星期后也出现了小高峰。在这三个时间点应该向用户优先推荐用户购买过的产品。
6、personal care品类的产品复购率最低,需要配合更多特征分析原因。