一百框、項(xiàng)目背景
Instacart Market Basket Analysis是一個(gè)經(jīng)典的顧客行為預(yù)測案例沟蔑。Instacart的數(shù)據(jù)團(tuán)隊(duì)開源了大約3,000,000條訂單數(shù)據(jù)慌随,我們將通過這些數(shù)據(jù)分析一下用戶的購物行為;
二唬血、獲取數(shù)據(jù)
??項(xiàng)目提供了大約200,000用戶產(chǎn)生的約3,000,000條訂單數(shù)據(jù)堕阔,每一位用戶提供了4到100條帶有產(chǎn)品序列的訂單數(shù)據(jù),這些數(shù)據(jù)包含了訂單產(chǎn)生的時(shí)間信息以及其他重要的信息(將在第三個(gè)session進(jìn)行詳細(xì)的研究)末誓。這些數(shù)據(jù)被分成了多個(gè)csv文件扯俱,可以一鍵全部下載。
數(shù)據(jù)下載的時(shí)候可以繼續(xù)向下滑動(dòng)喇澡,瀏覽一下這些csv文件更詳細(xì)的一些信息迅栅,初步了解一下各個(gè)文件的數(shù)據(jù)。
三晴玖、探索數(shù)據(jù)
??完成對數(shù)據(jù)的基本理解读存,我們試著探索一下數(shù)據(jù)为流,看看能分析出哪些關(guān)于用戶購買行為的信息
??首先,依次查看每一個(gè)表(csv文件)都包含哪些字段(列)以及這些字段都記錄哪些信息让簿。
# 導(dǎo)入相關(guān)的庫
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
# 讀取數(shù)據(jù)
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這兩個(gè)文件。
order_products_train_df.head()
order_products_prior_df.head()
這兩個(gè)文件的字段信息都是相同的尔当,文件命名也只是prior和train的區(qū)別莲祸,那么問題來了,這兩個(gè)文件是有何其他區(qū)別呢椭迎?
欲知這兩個(gè)文件是有何其他區(qū)別虫给,需要配合看一下orders.csv文件,這個(gè)文件記錄了所有用戶的所有訂單信息侠碧,其中有一個(gè)字段是eval_set,這個(gè)字段記錄了這條訂單信息被劃分到了哪一個(gè)數(shù)據(jù)集缠黍,一共有prior弄兜、train、test三個(gè)數(shù)據(jù)集瓷式。舉個(gè)栗子替饿,比如用戶1有5個(gè)訂單,用戶2有10個(gè)訂單贸典,然后用戶1最后一個(gè)訂單會(huì)被劃分到train视卢,剩下的第1到第4個(gè)訂單會(huì)被劃分到prior,用戶2最后一個(gè)訂單會(huì)被劃分到test廊驼,剩下的第1到第9個(gè)訂單會(huì)被劃分到prior据过。為什么要這樣做呢?正如我們前面所言妒挎,因?yàn)槲覀冃枰A(yù)測用戶下一次會(huì)復(fù)購哪些產(chǎn)品绳锅,所以需要把the last order提取出來并分成訓(xùn)練集和測試集兩組數(shù)據(jù)集,最終產(chǎn)生了order_products_prior.csv和order_products_train.csv兩個(gè)文件酝掩。
現(xiàn)在看一下order_products_*的字段鳞芙,一共有4列
order_id => 訂單ID
product_id => 產(chǎn)品ID
add_to_cart_order =>加入購物車的順序
reordered => 是否復(fù)購,0為False期虾,1為True
??在這份文件中我們發(fā)現(xiàn)每一位user將產(chǎn)品“加入購物車的順序”這一信息被采集了原朝,在這里我們產(chǎn)生疑問,無事不登三寶殿镶苞,那它和reordered之間是不是有什么關(guān)系呢喳坠?
沒法直接用散點(diǎn)圖查看他們之間的相關(guān)性,所以需要把數(shù)據(jù)處理一下宾尚,產(chǎn)品被加入購物車的順序是按照1丙笋,2谢澈,3,...排列的御板,而每一個(gè)序號對應(yīng)一個(gè)位置锥忿,但是對應(yīng)的reordered有兩個(gè)狀態(tài),所以這里可以用復(fù)購率來描述產(chǎn)品加入購物車的先后順序是如何影響復(fù)購的怠肋。
# 購買超過70件產(chǎn)品的用戶很少敬鬓,為了避免偶然性過大,所以70以后的加到一起計(jì)算笙各。為了方便處理數(shù)據(jù)钉答,需要添加新列
order_products_prior_df["add_to_cart_order_mod"] = order_products_prior_df["add_to_cart_order"].copy()
# 添加購物車順序超過70的統(tǒng)一標(biāo)記為70,為分組統(tǒng)計(jì)做好預(yù)處理
order_products_prior_df["add_to_cart_order_mod"].ix[order_products_prior_df["add_to_cart_order_mod"]>70] = 70
# 分組杈抢,因?yàn)閞eordered是布爾值数尿,所以我們對分組后的“reordered”列進(jìn)行求平均運(yùn)算后的值就等于復(fù)購率
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()
??通過點(diǎn)線圖,可以清晰的看到產(chǎn)品添加至購物車的順序越靠前惶楼,復(fù)購的可能性越大右蹦。
?? 2、再繼續(xù)看一下orders.csv能提供哪些信息:
orders_df.head(10)
各個(gè)字段代表的信息:
order_id => 訂單ID
user_id => 用戶ID
eval_set => 訂單劃分去向
order_number => 訂單序號
order_dow => 官方說明是day of week,也是就訂單在星期幾產(chǎn)生的
order_hour_of_day => 訂單在一天中哪個(gè)時(shí)間產(chǎn)生的
days_since_prior_order => 復(fù)購時(shí)距離上次購買過了多長時(shí)間
??我們發(fā)現(xiàn)orders.csv中每一個(gè)use_id的order_number都是按順序排列的歼捐,所以我們只要取出每一位user的訂單序號最大值就能得到他的訂單數(shù)量何陆,我們現(xiàn)在看一下用戶訂單數(shù)量的分布情況。
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()
聯(lián)想到前面的order_products_prior.csv文件, 所以看一下每筆訂單購買產(chǎn)品數(shù)量的分布
# 查看每筆訂單購買產(chǎn)品數(shù)量的分布情況
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()
基本類似的分布規(guī)律豹储。
接下來看一下用戶的購物習(xí)慣與reordered之間的關(guān)系贷盲。
首先了解一下用戶的購物習(xí)慣。
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()
購物習(xí)慣基本和我們的作息習(xí)慣是一樣的,主要是在白天呻疹。
現(xiàn)在用熱力圖描述一下order_dow和order_hour_of_day的分布關(guān)系吃引。
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")
和分開觀察的結(jié)果是一樣的,圖中越亮的地方訂單數(shù)量閱讀刽锤,很明顯是在周末8:00-18:00的時(shí)間段镊尺。
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()
比較明顯的可以看出用戶復(fù)購的兩次小高峰是一個(gè)星期后和一個(gè)月后,此外并思,兩個(gè)星期后也出現(xiàn)了小高峰庐氮。
所有的數(shù)據(jù)集從量級來看,是按照一個(gè)user產(chǎn)生多個(gè)orders宋彼,一個(gè)order里面會(huì)購買多個(gè)products∨常現(xiàn)在user和orders層面的數(shù)據(jù)我們已經(jīng)大體的做了了解仙畦,現(xiàn)在探索一下products層面的信息。
??3音婶、先瀏覽一下其他的幾個(gè)*.csv表慨畸。
可以看出products.csv、aisles.csv衣式、departments.csv這三個(gè)表只是存儲(chǔ)了一些分類信息寸士,單獨(dú)分析它們并不能產(chǎn)生更多有價(jià)值的信息,需要通過不同的*_id關(guān)聯(lián)到order_products_prior.csv在進(jìn)行分析碴卧。
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
現(xiàn)在order_products_prior.csv表的信息就很全面了弱卡。
先從最簡單的開始,我們先看一下哪種產(chǎn)品賣的最多吧住册。
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)
水果蔬菜好像是消費(fèi)頻率最高的婶博,繪制單個(gè)產(chǎn)品的直方圖在這里意義并不大,我們按aisle分類繪制直方圖看一下荧飞,取最大的前20個(gè)凡蜻。
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兑巾、瀏覽完基本的信息条获,接下來我們看一下各個(gè)字段與reordered之間的關(guān)系,還是要用到復(fù)購率這一指標(biāo)蒋歌。
就接著看一下各個(gè)departments產(chǎn)品的復(fù)購率大小帅掘,預(yù)測的時(shí)候如果一個(gè)產(chǎn)品如果屬于購率高的departments,那它被reordered概率自然也是大的堂油。
如何求復(fù)購率修档,前文已經(jīng)講過,不在贅述府框。
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()
各個(gè)department產(chǎn)品復(fù)購率的高低一清二楚吱窝。
繼續(xù)查看按aisle劃分的產(chǎn)品復(fù)購率:
# 不同的部門在負(fù)責(zé)不同的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)
# 給每一個(gè)點(diǎn)添加標(biāo)簽
for i, txt in enumerate(grouped_df.aisle.values):
# annotate(標(biāo)簽文本, xy坐標(biāo)點(diǎn), 其他參數(shù))
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這兩個(gè)基于時(shí)間的特征與復(fù)購率有何關(guān)系迫靖。
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()
四院峡、結(jié)論與建議
1、水果蔬菜類的產(chǎn)品消費(fèi)頻率最高系宜,賣的最多的三款產(chǎn)品是Banana照激、Bag of Organic Bananas、Orangic Strawberries盹牧。
2俩垃、產(chǎn)品加入購物車的平均位置越靠前励幼,被復(fù)購的可能性越大;精準(zhǔn)營銷時(shí)先加入購物車的產(chǎn)品應(yīng)該優(yōu)先被推薦給用戶口柳。
3苹粟、用戶每筆訂單的產(chǎn)品數(shù)量做多的區(qū)間是[5, 8]件。如果這是大部分用戶最愿意接受的每次購物量啄清,每次可以向用戶推薦[6, 10]件產(chǎn)品六水,在不影響用戶體驗(yàn)的前提下向用戶展示盡可能多的產(chǎn)品。
4辣卒、用戶通常在周六周日進(jìn)行購物掷贾,周二周三用戶最不活躍,每天的客流量主要集中在白天8:00-18:00荣茫,總的來看用戶在周日的6:00-9:00達(dá)到最高峰想帅,新品促銷廣告展示可以優(yōu)先選擇這幾個(gè)時(shí)間段。
5啡莉、同一產(chǎn)品用戶復(fù)購的兩次小高峰購買的一個(gè)星期后和一個(gè)月后港准,此外,兩個(gè)星期后也出現(xiàn)了小高峰咧欣。在這三個(gè)時(shí)間點(diǎn)應(yīng)該向用戶優(yōu)先推薦用戶購買過的產(chǎn)品浅缸。
6、personal care品類的產(chǎn)品復(fù)購率最低魄咕,需要配合更多特征分析原因衩椒。