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()