Instacart Market Basket Analysis
数据理解
比赛使用的数据仅包含交易数据,不包含浏览数据。主要由以下几张表构成:
-
订单表 orders(订单ID,用户ID,所属数据集,该用户的订单序号,订单下单在星期几,订单下单所在小时,距离上一次下单过去的天数):数据粒度为一个订单事实。其中,所属数据集包含三类:a) 先验集:所有用户在历史一段时间内产生的所有订单;b) 训练集:从所有用户中抽出一部分训练用户,在考察周期内产生的所有订单;c) 测试集:除去训练用户外剩下的用户,在考察周期内产生的所有订单。先验集中,每个用户可能包含多个订单。而对于训练集和测试集,两者的用户无交集,且每个用户在各自集合内也只会有一个订单。
-
商品表 products(商品ID,商品名称,通道ID,分类ID):数据粒度为一件商品。
-
通道表 aisles(通道ID,通道名称):数据粒度为一个通道。这里的通道,就是超市里的通道/走道,每一个通道两侧的商品,通常是一个类别的,而走道上方,往往会有一个标识牌。
-
分类表 departments(分类ID,分类名称):数据粒度为一个分类。是比通道更大的分类概念,但二者相互没有确定的包含关系。
-
先验集订单商品表 order_products_prior(订单ID,商品ID,加入购物车的次序,是否复购):注意先验集包含所有的用户,这里的数据粒度是历史一段时期内,所有用户购买的所有商品记录。
-
训练集订单商品表 order_products_train(订单ID,商品ID,加入购物车次序,是否复购):这里是训练集用户,在考察期内,购买的所有商品记录。上面提过,这个数据集里的每个用户,只会有一个订单,其中包含若干个商品,可能包含也可能不包含复购的商品。
如果不使用NLP的方法对名称类字段进行处理的话,商品名称、通道名称、分类名称这几个字段是没有用的。在实际项目中,也没有进行相关处理,所以后面这几个字段将略过不谈。
最终产出的数据,是订单表的测试集中,每个订单所包含的复购商品,也即仅包含复购的测试集订单商品表。由于上面提到了,训练集和测试集实际上是按照用户划分的,所以最终提交的数据,也是测试用户在考察期间内,复购的所有商品。
数据加载
prducts_df = pd.read_csv("./data/products.csv")
aisles_df = pd.read_csv("data/aisles.csv")
departments = pd.read_csv("data/departments.csv")
order_products_prior_df = pd.read_csv('data/order_products__prior.csv')
order_products_train_df = pd.read_csv("data/order_products__train.csv")
orders_df = pd.read_csv("data/orders.csv")
统计不同加购订单顺序下的复购率情况
order_products_prior_df["add_to_cart_order_mod"] = order_products_prior_df.add_to_cart_order
# 设置加购件数到达70件以上,则表示为70件
order_products_prior_df.add_to_cart_order_mod[order_products_prior_df.add_to_cart_order_mod>70]=70
### 对加购顺序进行groupy并,计算复购率
groupby_df = order_products_prior_df.groupby(by="add_to_cart_order_mod")["reordered"]
groupby_df = groupby_df.aggregate("mean")
# 可视化
plt.figure(figsize=(12, 6))
groupby_df.plot(c="r", linestyle="dashdot")
plt.ylabel("reordered ratio", fontsize=11)
plt.xlabel("add to cart order", fontsize=11)
plt.title("The recorder ratio of in different order numbers")
plt.grid()
plt.show()
结论:首先放入加购的商品,再一次购买的可能较大(复购率高),越是后面加购的商品,其再一次购买的可能较少。
用户不同大小订单数量分布趋势
# 在订单表中单个用户拥有不止一个订单
orders_df.head()
order_id | user_id | eval_set | order_number | order_dow | order_hour_of_day | days_since_prior_order | |
---|---|---|---|---|---|---|---|
0 | 2539329 | 1 | prior | 1 | 2 | 8 | NaN |
1 | 2398795 | 1 | prior | 2 | 3 | 7 | 15.0 |
2 | 473747 | 1 | prior | 3 | 3 | 12 | 21.0 |
3 | 2254736 | 1 | prior | 4 | 4 | 7 | 29.0 |
4 | 431534 | 1 | prior | 5 | 4 | 15 | 28.0 |
groupby_df = orders_df.groupby(by="user_id")["order_number"].aggregate(np.max)
# 统计不同订单次数下的购买次数
cnt_srs = cnt_srs.order_number.value_counts()
plt.figure(figsize=(18, 6))
sns.barplot(cnt_srs.index, cnt_srs.values
, color='red'
, alpha=0.6
)
plt.xticks(rotation="90")
plt.ylabel("counts", fontsize=12)
plt.xlabel("order numbers", fontsize=12)
plt.title("The counts of different order numbers", fontsize=14)
plt.show()
结论:大多数用户趋向较少的订单,集中处于区间[4, 8]
分析每一笔订单购买商品数据量的分布情况
# 此张表中每一个用户只有一个订单,但订单内可以有很多种商品
order_products_train_df.head()
# 统计一个订单中的商品数量
group_df = order_products_train_df.groupby("order_id")["add_to_cart_order"].aggregate("max").reset_index()
cnt_srs = group_df.add_to_cart_order.value_counts()
plt.figure(figsize=(20, 10))
sns.barplot(cnt_srs.index, cnt_srs.values, color="green", alpha=0.5)
plt.xlabel("Number of products in given order", fontsize=12)
plt.ylabel("Frequency", fontsize= 12)
plt.show()
结论:从图中可以看出大多数用户的订单内的商品都集中在区间[4, 7], 且集中以一个订单5个商品居多.
统计用户在不同时间点(一周内)的购物习惯
plt.figure(figsize=(12, 6))
sns.countplot(x="order_dow", data=orders_df)
plt.xlabel("Day of week")
plt.xticks([i for i in range(7)], ["Sat.", "Sun.", "Mons", "Tues.", "wed.", "Thur.", "Fri."])
plt.title("The frequency in differen day of week")
plt.show()
结论:用户一般习惯集中在周末进行购物,而在周三出现购物的低谷。
研究用户在不同时间点(一天内)的购物习惯
plt.figure(figsize=(12, 6))
sns.countplot(x="order_hour_of_day", data=orders_df, color="blue", alpha=0.6)
plt.xlabel("hour of day")
plt.title("The frequency of order in differen hour of day")
plt.show()
结论:大多数用户偏向与在中午和下午进行购物,也即主要是在白天进行购物
统计用户相隔多长时间加购订单的情况
plt.figure(figsize=(18, 6))
sns.countplot(x="days_since_prior_order", data=orders_df, color="red", alpha=0.5)
plt.ylabel("Count", fontsize=12)
plt.xlabel("day", fontsize=12)
plt.show()
结论:大多数用户在上一次订单之后会在一周之后明显的增加浮动,而在一个月以后出现一个订单回购的小高峰.
不同商品的购买情况
# 商品表
prducts_df.head()
product_id | product_name | aisle_id | department_id | |
---|---|---|---|---|
0 | 1 | Chocolate Sandwich Cookies | 61 | 19 |
1 | 2 | All-Seasons Salt | 104 | 13 |
2 | 3 | Robust Golden Unsweetened Oolong Tea | 94 | 7 |
3 | 4 | Smart Ones Classic Favorites Mini Rigatoni Wit... | 38 | 1 |
4 | 5 | Green Chile Anytime Sauce | 5 | 13 |
# 通道表
aisles_df.head()
aisle_id | aisle | |
---|---|---|
0 | 1 | prepared soups salads |
1 | 2 | specialty cheeses |
2 | 3 | energy granola bars |
3 | 4 | instant foods |
4 | 5 | marinades meat preparation |
# 分类表
departments.head()
department_id | department | |
---|---|---|
0 | 1 | frozen |
1 | 2 | other |
2 | 3 | bakery |
3 | 4 | produce |
4 | 5 | alcohol |
# 订单商品表
order_products_prior_df.head()
order_id | product_id | add_to_cart_order | reordered | add_to_cart_order_mod | |
---|---|---|---|---|---|
0 | 2 | 33120 | 1 | 1 | 1 |
1 | 2 | 28985 | 2 | 1 | 2 |
2 | 2 | 9327 | 3 | 0 | 3 |
3 | 2 | 45918 | 4 | 1 | 4 |
4 | 2 | 30035 | 5 | 0 | 5 |
order_products_prior_df = pd.merge(order_products_prior_df
, prducts_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
, on="department_id"
, how = "left"
)
查看不同的商品对于的销售量
cnt_srs = order_products_prior_df.groupby(by="product_name").count()["add_to_cart_order_mod"].reset_index()
cnt_srs.sort_values(by="add_to_cart_order_mod", ascending=False).head(20)
product_name | add_to_cart_order_mod | |
---|---|---|
3676 | Banana | 472565 |
3471 | Bag of Organic Bananas | 379450 |
31920 | Organic Strawberries | 264683 |
28840 | Organic Baby Spinach | 241921 |
30297 | Organic Hass Avocado | 213584 |
28804 | Organic Avocado | 176815 |
22413 | Large Lemon | 152657 |
42904 | Strawberries | 142951 |
23420 | Limes | 140627 |
32478 | Organic Whole Milk | 137905 |
31363 | Organic Raspberries | 137057 |
32565 | Organic Yellow Onion | 113426 |
30000 | Organic Garlic | 109778 |
32605 | Organic Zucchini | 104823 |
29008 | Organic Blueberries | 100060 |
11630 | Cucumber Kirby | 97315 |
29980 | Organic Fuji Apple | 89632 |
30577 | Organic Lemon | 87746 |
2628 | Apple Honeycrisp Organic | 85020 |
30139 | Organic Grape Tomatoes | 84255 |
# 绘制不同通道下的品类的情况
cnt_srs = order_products_prior_df.aisle.value_counts()
plt.figure(figsize=(12, 6))
sns.barplot(cnt_srs.head(20).index, cnt_srs.head(20).values
, alpha=0.5
, color="blue"
)
plt.xticks(rotation="90")
plt.ylabel("Count")
plt.xlabel("Aisle name")
plt.show()
结论:从图中可以看出购买新鲜蔬菜和水果的订单量较大
绘制不同产品类别之间的所占分数
temp = order_products_prior_df.department.value_counts()
temp.head()
produce 9479291
dairy eggs 5414016
snacks 2887550
beverages 2690129
frozen 2236432
Name: department, dtype: int64
plt.figure(figsize=(8, 8))
plt.pie(temp
, labels=temp.index
, autopct="%1.1f%%"
, startangle=200
)
plt.show()
结论:通过观察可以看出产品和奶制品和鸡蛋类的所占比重较大,可以看出用户偏向与这类商品。
查看不同的大类别下的复购率情况
group_df = order_products_prior_df.groupby("department")["reordered"].mean()
group_df = group_df.reset_index()
group_df.head()
department | reordered | |
---|---|---|
0 | alcohol | 0.569924 |
1 | babies | 0.578971 |
2 | bakery | 0.628141 |
3 | beverages | 0.653460 |
4 | breakfast | 0.560922 |
plt.figure(figsize=(12, 5))
sns.barplot(group_df.department
, group_df.reordered
, alpha=0.8
, color="blue")
plt.ylabel("reordered ratio", fontsize=12)
plt.title("The reordered ratio in differfent departments", fontsize=13)
plt.xlabel("department", fontsize=12)
plt.xticks(rotation=90)
plt.show()
不同部门下不同通道的复购率情况
group_df = order_products_prior_df.groupby(["department_id", "aisle"])["reordered"].mean()
group_df = group_df.reset_index()
group_df
department_id | aisle | reordered | |
---|---|---|---|
0 | 1 | frozen appetizers sides | 0.525557 |
1 | 1 | frozen breads doughs | 0.539992 |
2 | 1 | frozen breakfast | 0.626221 |
3 | 1 | frozen dessert | 0.420777 |
4 | 1 | frozen juice | 0.450855 |
... | ... | ... | ... |
129 | 20 | lunch meat | 0.606517 |
130 | 20 | prepared meals | 0.619759 |
131 | 20 | prepared soups salads | 0.596597 |
132 | 20 | tofu meat alternatives | 0.607775 |
133 | 21 | missing | 0.395849 |
134 rows × 3 columns
fig, ax = plt.subplots(figsize=(14, 14))
ax.scatter(group_df.reordered, group_df.department_id, alpha=0.8, color="red")
plt.plot([0.6]*23, np.arange(0, 23), color="red", linestyle="--")
for i, txt in enumerate(group_df.aisle.values):
ax.annotate(txt, (group_df.reordered[i], group_df.department_id[i])
, rotation=45
, color="green"
)
plt.xlabel("reordered ratio", fontsize=14)
plt.ylabel("department", fontsize=14)
plt.title("Reorder ration of different aisles", fontsize=20)
plt.yticks(np.arange(1, 22), departments.department)
plt.ylim([0, 22])
plt.xlim([0, 0.9])
plt.grid()
plt.show()
结论:通过设置最低的复购率准线,可以查看到每一个部门下的下分类别的复购率情况
用户在不同时间(周内)的复购率情况
order_products_prior_df = pd.merge(order_products_prior_df, orders_df, on="order_id", how="left")
group_df = order_products_prior_df.groupby(by="order_dow")["reordered"].mean()
sns.barplot(group_df.index, group_df.values, color="b", alpha=0.5)
plt.xticks([i for i in range(7)], ["Sat.", "Sun.", "Mons", "Tues.", "wed.", "Thur.", "Fri."])
plt.ylabel("reordered ratio")
plt.xlabel("week")
plt.title("The reordered ratio of different week")
plt.ylim([0.5, 0.7])
plt.show()
用户在不同时间(周内)的复购率情况
group_df = order_products_prior_df.groupby(by="order_hour_of_day")["reordered"].mean()
plt.figure(figsize=(12, 6))
sns.barplot(group_df.index, group_df.values, color="b", alpha=0.8)
plt.ylabel("reordered ratio")
plt.xlabel("hour")
plt.title("The reordered ratio of different hour")
plt.ylim([0.5, 0.7])
plt.show()
group_df= order_products_prior_df.groupby(["order_dow", "order_hour_of_day"])["reordered"].mean().reset_index()
group_df = group_df.pivot("order_dow", "order_hour_of_day", "reordered")
plt.figure(figsize=(12, 6))
sns.heatmap(group_df)
plt.show()