Kaggle案例:基于Python分析Instacart用户行为

一、项目背景

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品类的产品复购率最低,需要配合更多特征分析原因。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容

  • 2.每个用户有多少个订单[orders] pv浏览量 > uv用户量 3.每个用户【2.平均【1.每个订单是多少...
    进击的小恶魔阅读 1,133评论 0 0
  • 正所谓“纸上得来终觉浅,绝知此事要躬行”,通过对Python进行数据分析的学习掌握,现在需要通过实操总结来检验自己...
    Dylan丶海阅读 3,258评论 3 20
  • 数据来源于CDNOW网站的用户购买记录,包括用户ID,购买日期,购买产品数,购买金额等四项基本数据,以下通过Pyt...
    LucasOoo阅读 4,228评论 5 13
  • 还记得你出生的那天,产房门外家人们焦急的等候,可你非是不出来,直到把妈妈折磨的精疲力竭,才听到你清脆的哭声。因为你...
    王大可爱阅读 252评论 0 0
  • 《伴你同行》一阶第3课作业: 抽出一张土著卡: 1、你看到了什么?看到在一个茅草屋的外边,两个人,后面的那个人手里...
    wang平和阅读 190评论 0 0