2.1 数据的列名设置
在前面的课程学习中我们知道,想要创建一个 DataFrame 对象,可以将一个字典传给DataFrame()的参数 data。如下方的代码所示:

在这里插入代码片
import pandas as pd

dict_1 = {'年龄': [23, 22, 21], '岗位': ['客服', '运营', '公关'], '年购买量': [10, 15, 8]}
jobs_df = pd.DataFrame(dict_1)
jobs_df

实际上,想要得到一个 DataFrame 对象,传入的数据既可以是字典,也可以是一个嵌套列表:

在这里插入代码片
goods_info = [
    ['巧克力', 77.675, '47箱', '广东佛山仓'], 
    ['牛奶', 58.755, '40箱', '广东揭阳仓'], 
    ['威化饼干', 7.385, '285盒', '广西南宁仓'], 
    ['火腿肠', 34.823, '20件', '广西柳州仓'], 
    ['巧克力', 62.648, '30箱', '广东揭阳仓'], 
    ['牛奶', 45.75, '12箱', '广西柳州仓'], 
    ['威化饼干', 5.235, '148盒', '广东佛山仓'], 
    ['火腿肠', 33.736, '28件', '广东佛山仓']
]

goods_df = pd.DataFrame(goods_info)
goods_df

观察两个数据的内容,可以发现二者的区别。

1)以字典作为数据传入时,字典的键会作为 DataFrame 对象的列名显示,而字典的值会作为对象的列数据显示。

2)以嵌套列表作为数据传入时,列表的元素会作为 DataFrame 对象的行数据显示,且会为数据默认生成从 0 开始的列名。

但是嵌套列表默认生成的列名不能很好地表达出每列数据的含义。

因此,在使用列表创建 DataFrame() 对象时,可以借助 DataFrame() 类的另一个参数 columns 来设置列名。

你可以先看看参数 columns 的使用语法:

在这里插入图片描述
参数 columns 的值可以是一个列表,该列表的长度需与传入 DataFrame() 的列表的元素长度一致。

在这里插入图片描述
现在,我在使用列表 goods_info 创建 DataFrame 对象的同时,分别将列名设置为:名称、单价、库存和地址,代码如下:

newgoods_df = pd.DataFrame(goods_info, columns=['名称', '单价', '库存', '地址'])
newgoods_df

在这里插入图片描述

# 设置列表
students_info = [['小蓝', '语文', '79'], 
                ['小邱', '数学', '83'], 
                ['小李', '英语', '92']]

# 使用列表创建 DataFrame 对象,同时设置列名
students_df = pd.DataFrame(students_info, columns=['姓名', '科目', '分数'])
students_df

那么,对于已创建好的 DataFrame 对象,是否可以修改列名呢?

当然,这个操作可以借助 DataFrame 对象的 columns 属性实现。
在这里插入图片描述

1
# 修改 goods_df 的列名
2
goods_df.columns = ['名称', '单价', '库存', '地址']
3
goods_df

这里同样将需要修改的列名组合成一个列表,再把列表赋给 DataFrame 对象的 columns 属性,对列名进行了修改。

数据的列名设置学习完毕,咱们继续下个知识点:数据的排序。

2.2 数据的排序
在分析数据的过程中,如果想让数据根据某列从小到大的情况进行排序,手动调整显然不是个明智的选择。

比如搞清楚下方图片中,单价从小到大依次是哪些食品。

花点时间手动操作,确实能给数据做一个排序。但在数据量大的时候,显然就不适用了。

好在有 sort_values() 方法,该方法可以对指定列进行排序操作。该方法的语法如下图所示:
在这里插入图片描述

# 依照【单价】列的数据进行排序
sorted_goods = newgoods_df.sort_values(by='单价')
sorted_goods

想对某列进行排序,可以将它的列名传给 sort_values() 的参数 by,如上述中的’单价’,默认情况是升序排列。

如果我们不想对上面的数据集进行排序,只想提取出【单价】列,单独进行排序,代码可以这么写:

# 提取【单价】列的数据进行排序
newgoods_df['单价'].sort_values()

另外,不单是 DataFrame 对象可以调用 sort_values() 方法,Series 对象也能调用该方法。

但要注意的是,使用 DataFrame 对象调用 sort_values() 方法时,可以通过参数 by 来指定某一列。

而 Series 对象调用该函数时,函数没有参数 by。原因是 Series 对象不具备列索引(列名)。这一细节点,在书写代码时需要多加留意。

ok,数据的排序也学习完毕,再看一眼排序后的数据 sorted_goods。

可以看到,虽然数据集依照【单价】列进行了排序,但是排序后的行索引不再是连续的,而是比较混乱。

为了解决索引混乱的问题,我们需要学习一个新的知识点 —— 重置索引。

2.3 重置索引
重置索引可以使用 reset_index() 方法来实现。请你运行下方的代码来体验该方法的功能:

# 重置数据 sorted_goods 的行索引
reset_goods = sorted_goods.reset_index(drop=True)
reset_goods

我们来看一下 reset_index() 的语法,看看它是如何重置索引的吧。

在这里插入图片描述

reset_index() 方法中,参数 drop 的值为布尔值,表示是否去除原有索引。

默认情况下,drop 的值为 False,即保留原索引,像上面的代码中,如果使用默认值 False,它的运行结果将会如下图所示。

当数据集需要使用 reset_index() 重置索引时,且原索引没有参考价值,建议参数 drop 赋值为 True ,去除原索引,避免数据集存在一些无意义的数据。

重置索引的操作已教给你了。趁热打铁,来场练习吧。

# 依照【身高】列的数据进行升序排序
reset_humans = humans_df.sort_values(by='身高')
# 重置索引,且不保留原索引
reset_humans.reset_index(drop=True)

数据的排序以及行索引重置已讲解完毕,下一个知识点 —— 数值的四舍五入。

2.4 数值的四舍五入
我们将使用前面重置过索引的数据 reset_goods 来学习该知识点,请你运行下方的代码看看数据的内容。
有时,你需要调整数值的小数部分。如上方数据中的【单价】列,每个值都有三位小数。
如果想要数据仅保留两位小数,可以使用 round() 函数来实现。请你运行代码体验一下:

reset_goods['单价'] = round(reset_goods['单价'], 2)
reset_goods

可以看到,round() 函数将每个数据的最后一位小数四舍五入到第二位小数。
在这里插入图片描述
使用该函数主要用到两个参数:number 和 ndigits。

1)参数 number 传入的是要进行四舍五入的数据。同时,因为 number 是函数的第一个位置参数,所以我们在使用时无需为它赋值,可以直接写为 round(reset_goods[‘单价’]);

2)参数 ndigits 传入的是四舍五入后要保留的小数位数。默认情况下该参数的值为 0,即不保留小数。

学习了 round() 函数的作用和语法后,你也来练习一下该知识点吧。

2.5 agg() 方法
继续使用前面的数据 reset_goods 来学习该知识点,请你运行下方的代码看看数据的内容。

如果想计算各种食品的总库存量,只需要按照【名称】列分组后,再将【库存】列的库存量相加。

但从数据可以看到,每件商品的库存量后都附有单位,无法直接进行求和操作。
这就需要我们将每个数据的单位切除后,才能进行库存量的求和。

由于每个数据的单位都为一个字符,可以选择使用切片的方式切除。关键是如何对【库存】列整列的数据进行切片操作。

这里就可以使用 agg() 方法对整列的数据进行统一操作。

接下来,我将展示 agg() 方法的使用步骤,请你好好观察吧。
首先,我定义了函数 new_func(),功能是把传入的数据进行切片,切除数据的最后一个字符。

# 定义函数 new_func()
def new_func(data):
    number = data[:-1]
    return int(number)

接着,使用 agg() 方法将【库存】列的每个数据都传入 new_func() 函数进行计算,并取得函数的返回值。

# 使用 agg() 方法对【库存】列的数据进行切片处理
reset_goods['库存'] = reset_goods['库存'].agg(new_func)
reset_goods

从显示的结果可以看到,【库存】列的数据已把单位去除完。为了更好地理解代码,我们来看看 agg() 的使用语法。

在这里插入图片描述

方法 agg() 中的参数 func 可以为自定义的函数名,如我们上方定义的 new_func() 函数。

这里在使用时需要注意,传入自定义的函数时,只需要写上函数名,不需要加上括号以及函数的参数。

我们看回上方使用 agg() 方法对【库存】列数据进行切片的代码。

代码中使用 reset_goods[‘库存’] 指定【库存】列,再通过 agg() 方法将【库存】列的数据依次传给函数 new_func() 的参数 data。

函数 new_func() 会切除传入数据的最后一个字符,再将切除后的字符串转换为整数型,然后返回该数据。

通过 new_func() 函数返回的每个数据,最终会形成一个 Series 对象。将该对象重新赋给 reset_goods[‘库存’],【库存】列即为不带单位的数据。

现在,库存量的单位已经去除,数据类型也转换为整数型。此时计算各食品的总库存量可以通过下方的代码实现:

# 计算各食品的总库存量
reset_goods.groupby('名称')['库存'].sum()

如果想在统计食品总库存量的同时,也能看到各食品在所有地区的平均单价,应该怎么实现呢?

刚刚所学的 agg() 方法也能解决这个难题。略有不同的是,方法内的参数 func 这次要传入字典。

在这里插入图片描述

# 定义字典 dict_2
dict_2 = {
    '单价': 'mean', 
    '库存': 'sum'
}
# 同时计算各食品的平均单价和总库存量
reset_goods.groupby('名称').agg(dict_2)

代码可以分为 3 步进行理解:

1)使用 reset_goods.groupby(‘名称’) 让数据按【名称】列进行分组;

2)定义一个字典 dict_2,字典的键为我们需要操作的列名,如 ‘单价’、‘库存’。字典的值为我们需要操作的函数名,这里我们需要求平均值函数 mean(),求和函数 sum()。(可使用的函数可参照下表:)

在这里插入图片描述
同样需要注意的是,操作的函数只需要函数名,不需要函数名后的括号以及函数的参数。

3)分组后的数据调用 agg() 方法时,为方法的参数 func 传入字典 dict_2,可以同时得到各食品的平均单价和总库存量。

ok,agg() 方法的两种用法已经学习完毕。需要注意的是,两种用法在调用函数时,只需写函数名,不需要函数名后的括号。

可以看到数据中的【数量列】有意义不明的 “-”,需要将其统一替换为 0。

请你补充下面的代码,实现以下功能:
1)将【数量】列的“-”统一替换为 0;
2)处理完异常数据后按【食品名】分组;
3)同时计算【单价】列的最小值和【数量】列的最大值。

# 定义函数,以替换【数量】列的“-”为 0
def replace_info(data):
    if data == '-':
        data = 0
    return data
# 使用 agg() 方法对【数量】列的内容进行替换
foods_df['数量'] = foods_df['数量'].agg(replace_info)

再使用 foods_df.groupby(‘食品名’),让数据按【食品名】列进行分组。

最后,使用分组后的数据调用 agg() 方法,方法中传入列名以及对应函数组成的字典。

其中,【单价】列需要求最小值的函数 min(),【数量】列需要求最大值的函数 max()。

# 同时计算【单价】列的最小值和【数量】列的最大值
foods_df.groupby('食品名').agg({'单价': 'min', '数量': 'max'})
  1. 分析方法
    在讲解关联分析之前,我先给你分享一个业界的经典小故事。

说是20世纪90年代,沃尔玛超市在对顾客的购物记录进行购物篮分析时,发现了一个奇怪的现象: “啤酒”和“尿布”两件看上去毫无关系的商品,经常出现在同一个购物篮中。

随后,他们深入分析,发现这种奇怪的现象大多发生在年轻的父亲身上,原因是父亲在购买婴儿尿片时,通常会捎带几瓶啤酒犒劳自己。

于是,沃尔玛调整了货架,尝试将啤酒和尿布摆在一个区域,方便顾客购买,从而提升了超市的销量。

虽然现实生活中,“啤酒”与“尿布”并不会放在一块售卖,这只是一个有趣的营销故事。

但是购物篮分析却是真实存在的,它是关联分析中的一个重要应用。通过关联分析,确实能发现事物之间一些令人意外的“关系”。

依据这些“关系”,可以引导卖家调整营销策略,把关联程度较高的商品打包在一起,提高销量。或者针对买家已购买的产品,推荐与之相关的其他产品或信息,精准营销。

除了销售行业,关联分析还被广泛应用在生物信息学、医疗诊断、网页挖掘、地球科学等领域。

在这里插入图片描述
下面,我会先给你的普及一些关联分析的基本概念,然后你再来学习怎么通过Apriori 算法实现这一分析方法。

3.1 关联分析及相关概念
关联分析,是一门分析技术,用于发现大量数据中,各组数据之间的联系。

现在,我手里有一份快餐店的用户交易数据:

在这里插入图片描述
基于这份数据,我会向快餐店老板提出建议,推出薯条和奶茶的组合套餐。因为我发现购买奶茶的用户,有很大概率会加购薯条,不过,我是怎么发现的呢?

事情还要从那份只有4条数据的交易数据入手,它有点像购物小票。

交易数据记录了用户的单次消费行为,即交易记录,如1001号订单,意思是某用户一次性购买了两种(非数量)食品:薯条、可乐。

而每条交易记录又可称为一个事务。所以,这份交易数据一共含有4条“事务”。
交易中的不同物品可称为一个项。所以,这4条交易记录,一共含有4个项(商品类目):“薯条”,“可乐”,“奶茶”,“汉堡”。

在这里插入图片描述

0个或多个项的集合,可称为一个项集,一般用{X}的形式表示项集,k 个项组成的项集,叫 k 项集。如{薯条,可乐}(2 项集),{薯条}(1 项集)。

前面的4项商品,可以相互组成15个项集,项集内不存在相同的项,如{奶茶,薯条,薯条}。

在这里插入图片描述
不同“项集”,受顾客的欢迎程度不同。支持度(Support)可以表示项集在事务中出现的概率(频率),你也可以理解成顾客对某一个项集的“支持程度”。

{X}的支持度 = {X}在事务中出现的次数 / 事务总数。

在这里插入图片描述
支持度是我们课程的一个重难点,请你根据它的公式,试着计算一下 {奶茶} 的支持度吧。

在这 4 条事务中,{奶茶} 总共只出现了 1 次,因此,{奶茶} 的支持度 = 1 / 4,也就是 0.25。

每个项集都有支持度,但事实上,也不是每个项集都是有用的。

比如支持度为0的 4 项集:{可乐,汉堡,奶茶,薯条},客人都没买过这样的套餐,能有啥指导价值?该删!

此时,我们需要人为地设定一个支持度,名为最小支持度,用于筛掉那些不符合需求的项集。被留下来的项集(≥ 最小支持度),被称为频繁项集。
在这里插入图片描述
有了频繁项集,就可以生产关联规则了。

等等,什么是关联规则?

前面,我说关联分析是探索数据之间联系的技术,而数据之间的联系,我们用关联规则来表示,表达式为:{X}→{Y}(X 和 Y 之间不存在相同项)。

规则有顺序之分,为了方便描述,我们把规则前面的项集叫前件,把规则后面的项集叫后件。

在这里插入图片描述
假设有频繁项集 {奶茶,薯条},它可以生成2条关联规则:{奶茶}→{薯条}和{薯条}→{奶茶}。前者的意思是,购买“奶茶”的顾客,和购买“薯条”之间,可能存在有某种联系!🤔

但是,怎么知道这种联系是否可靠?如果可靠,到底有多可靠?哪个更可靠?

置信度(Confidence)可用于衡量关联规则的可靠程度,表示在前件出现的情况下,后件出现的概率。一般来说,概率越高,规则的可靠性越强。

关联规则{X}→{Y}的置信度 = {X,Y}的支持度 / {X}的支持度。

在这里插入图片描述
请你根据置信度的计算公式,及 {薯条}→{奶茶} 置信度的计算示例,计算一下 {奶茶}→{薯条} 的置信度:

【提示】
{奶茶,薯条}的支持度:0.25;
{奶茶}的支持度:0.25;
{薯条}的支持度:0.75。

同为关联规则,可靠程度有“强”有“弱”。

在实际业务中,也需要人为地设定置信度,名为最小置信度,用于筛掉一些不符合需求的关联规则。

被留下来的关联规则( ≥ 最小置信度),叫做强关联规则。

在这里插入图片描述

众所周知,在自然界,不同物种之间,不仅有“互惠互利”的合作关系,也有“势不两立”的竞争关系。

关联规则也一样,既有促进关系,也有抑制关系。因而,还需引入提升度(Lift)对它们进行判断。

{X}→{Y}的提升度 = {X}→{Y}的置信度 / {Y}的支持度,意思是评估 X 的出现,对 Y 出现的影响有多大。

在这里插入图片描述
请你根据提升度的计算公式,及 {薯条}→{奶茶} 提升度的计算示例,计算一下 {奶茶}→{薯条} 的提升度:

【提示】
{奶茶}→{薯条}的置信度:1.0;
{薯条}→{奶茶}的置信度:0.33;
{奶茶}的支持度:0.25;
{薯条}的支持度:0.75。
{奶茶}→{薯条} 的提升度 = {奶茶}→{薯条} 的置信度 / {薯条} 的支持度 = 1.0 / 0.75,结果约为 1.33 。

提升度的值小于1,表示前件对后件是抑制的关系;
提升度大于1,表示前件对后件是促进的关系;
特别地,当提升度的值等于1时,表示前件不影响后件,两者之间没有关系。

提升度的值小于1,表示前件对后件是抑制的关系;
提升度大于1,表示前件对后件是促进的关系;
特别地,当提升度的值等于1时,表示前件不影响后件,两者之间没有关系。

在这里插入图片描述
也就是说,对于关联规则{奶茶}→{薯条}(提升度 ≈ 1.33 > 1)来说,购买{奶茶}会促进{薯条}的购买。

好的,以上就是关联分析相关概念的全部内容,不知道你吸收得怎么样,来接受考验吧:

请你选出以下说法正确的选项:

A.
购物篮分析不属于关联分析。

B.
若一个项集满足最小支持度,可称之为强关联规则。

C.
最小支持度和最小置信度,是使用者根据自身需求,自行设置的。

回答正确
购物篮分析属于关联分析,选项 A 错误。满足最小支持度的项集称为频繁项集,满足最小置信度的关联规则称为强关联规则,选项 B 错误。
在这里插入图片描述
大多数的关联分析工作,主要任务就是生成频繁项集和关联规则。有了计算公式和流程,理论上,我们可以手算,不过难度可想而知:

一个3项(k 项)的数据集,能产生7(2^k - 1)个非空频繁项集。
一个3项(k 项)的频繁项集,可产生6(2^k - 2)个关联规则。

在这里插入图片描述
随着“项”的增加,频繁项集和关联规则的计算量必将呈指数增长。

而现实生活中的“项”(商品)成百上千,真实的“事务”(交易)数以万计。摸着稀疏的头发坦白讲,提高计算效率是必须的,发量要紧。

那说到提高计算效率,就要请出Apriori 算法了。

3.2 Apriori 算法
Apriori是用于挖掘出数据背后的关联规则的一种算法,以下简写为 Apr 算法,它的流程可分为两步(简单了解它的运作原理即可,无需深究):

在这里插入图片描述
步骤一:确定最小支持度和最小置信度

第一步,我们要“拍脑袋”确定一个最小支持度和最小置信度。不过,“脑袋”不可以随意“乱拍”,“拍”也是有技巧,有范围的。

最小支持度和最小置信度都是描述事件发生的概率,所以取值范围在 0 和 1 之间。

假如最小支持度设定过高,就会导致一些重要但不频繁的项集被过滤掉;设定过低,一方面,会影响计算性能,另一方面,一些无实际意义的数据也会被保留下来,最小置信度也是同理。

但判断它们是否“合适”的感觉很微妙,没有特定的标准答案,这里仅提供几个方法供你参考,不做延展:

在这里插入图片描述
其实总结起来就一个字:试。

一开始设定的最小支持度和置信度和理想状态有一定偏差也没关系,后续再慢慢调整。

还是原始的快餐店数据,倘若我现在确定的最小支持度为0.2,最小置信度为0.7,一起进入下一步骤。

在这里插入图片描述

步骤二:找出频繁项集和强关联规则

这一步,Apr 的算法主要依赖两个性质:

1)一个项集如若是频繁的,那它的非空子集也一定是频繁的。假如购买{薯条,奶茶}的概率都很高,那购买{薯条}或{奶茶}的概率肯定也很高。

2)一个项集如若是非频繁的,那包含该项集的项集也一定是非频繁的。假设购买{薯条}的次数少,那购买{薯条,奶茶}的次数肯定也少。

在 Apr 算法出现之前,要找出所有的频繁项集,就得先枚举所有的项集,计算它们的支持度,然后和最小支持度(0.2)进行对比,筛选出频繁项集。

在这里插入图片描述
但 Apr 算法不一样,它是一个驾轻熟路的“老司机”,对错误的“道路”进行了提前预判。

一旦找到某个不满足条件的“非频繁项集”,包含该集合的其他项集不需要计算,更不用对比,通通绕开。

比如,在对比2项集的时候,发现{奶茶,汉堡}的支持度为 0,小于最小支持度0.2,属于非频繁项集。

在这里插入图片描述

于是,后面在计算3项集(及4项集)的时候,会直接把包含{奶茶,汉堡}的项集,如{薯条,奶茶,汉堡}去掉,不再计算。
在这里插入图片描述

  1. 分析方法
    运行全部cells
  2. 今天学什么?
    本节课,我们会通过公众号文章的组合投放案例,学习如何用 Apriori 算法进行关联分析。

整个课程结构分成三部分,分别是:基础知识、分析方法、案例实战,我们将按顺序进行学习。

基础知识:抽取案例所需的 Python 基础知识,进行针对性的讲解。

分析方法:主要介绍案例所需的分析方法及其核心知识。

案例实战:结合前面所学的基础知识和数据分析分析方法,去解决一个具体的案例问题。

在接触具体的案例前,你还需要学习一些处理数据的方法,及关联分析的核心知识,以便后续更加专注算法的应用。

  1. 基础知识
    在这个环节,你需要掌握以下5个处理数据的方法:

2.1 数据的列名设置
在前面的课程学习中我们知道,想要创建一个 DataFrame 对象,可以将一个字典传给DataFrame()的参数 data。如下方的代码所示:

只读
案例示范
NO.1
In [ 28 ]
1
import pandas as pd
2

3
dict_1 = {‘年龄’: [23, 22, 21], ‘岗位’: [‘客服’, ‘运营’, ‘公关’], ‘年购买量’: [10, 15, 8]}
4
jobs_df = pd.DataFrame(dict_1)
5
jobs_df
Out [ 28 ]
年龄 岗位 年购买量
0 23 客服 10
1 22 运营 15
2 21 公关 8
实际上,想要得到一个 DataFrame 对象,传入的数据既可以是字典,也可以是一个嵌套列表:

只读
案例示范
NO.2
In [ 29 ]
1
goods_info = [
2
[‘巧克力’, 77.675, ‘47箱’, ‘广东佛山仓’],
3
[‘牛奶’, 58.755, ‘40箱’, ‘广东揭阳仓’],
4
[‘威化饼干’, 7.385, ‘285盒’, ‘广西南宁仓’],
5
[‘火腿肠’, 34.823, ‘20件’, ‘广西柳州仓’],
6
[‘巧克力’, 62.648, ‘30箱’, ‘广东揭阳仓’],
7
[‘牛奶’, 45.75, ‘12箱’, ‘广西柳州仓’],
8
[‘威化饼干’, 5.235, ‘148盒’, ‘广东佛山仓’],
9
[‘火腿肠’, 33.736, ‘28件’, ‘广东佛山仓’]
10
]
11

12
goods_df = pd.DataFrame(goods_info)
13
goods_df
Out [ 29 ]
0 1 2 3
0 巧克力 77.675 47箱 广东佛山仓
1 牛奶 58.755 40箱 广东揭阳仓
2 威化饼干 7.385 285盒 广西南宁仓
3 火腿肠 34.823 20件 广西柳州仓
4 巧克力 62.648 30箱 广东揭阳仓
5 牛奶 45.750 12箱 广西柳州仓
6 威化饼干 5.235 148盒 广东佛山仓
7 火腿肠 33.736 28件 广东佛山仓
观察两个数据的内容,可以发现二者的区别。

1)以字典作为数据传入时,字典的键会作为 DataFrame 对象的列名显示,而字典的值会作为对象的列数据显示。

2)以嵌套列表作为数据传入时,列表的元素会作为 DataFrame 对象的行数据显示,且会为数据默认生成从 0 开始的列名。

但是嵌套列表默认生成的列名不能很好地表达出每列数据的含义。

因此,在使用列表创建 DataFrame() 对象时,可以借助 DataFrame() 类的另一个参数 columns 来设置列名。

你可以先看看参数 columns 的使用语法:

参数 columns 的值可以是一个列表,该列表的长度需与传入 DataFrame() 的列表的元素长度一致。

现在,我在使用列表 goods_info 创建 DataFrame 对象的同时,分别将列名设置为:名称、单价、库存和地址,代码如下:

只读
案例示范
NO.3
In [ 30 ]
1
newgoods_df = pd.DataFrame(goods_info, columns=[‘名称’, ‘单价’, ‘库存’, ‘地址’])
2
newgoods_df
Out [ 30 ]
名称 单价 库存 地址
0 巧克力 77.675 47箱 广东佛山仓
1 牛奶 58.755 40箱 广东揭阳仓
2 威化饼干 7.385 285盒 广西南宁仓
3 火腿肠 34.823 20件 广西柳州仓
4 巧克力 62.648 30箱 广东揭阳仓
5 牛奶 45.750 12箱 广西柳州仓
6 威化饼干 5.235 148盒 广东佛山仓
7 火腿肠 33.736 28件 广东佛山仓
上面的代码,将列名组合成一个列表,再传给参数 columns,成功为每项数据设置上了对应的列名。

下面,我创建了一个新列表,请你补充下方代码的第 7 行,使用该列表创建一个列名为姓名、科目、分数的 DataFrame 对象。

NO.4
In [ 31 ]
1
#已跳过练习,代码默认变成注释
2

设置列表

3
#students_info = [[‘小蓝’, ‘语文’, ‘79’],
4

[‘小邱’, ‘数学’, ‘83’],

5

[‘小李’, ‘英语’, ‘92’]]

6

7

使用列表创建 DataFrame 对象,同时设置列名

8
#students_df =
9
#students_df
Out [ 31 ]
参考下我的答案吧:

只读
案例示范
NO.5
In [ 32 ]
1

设置列表

2
students_info = [[‘小蓝’, ‘语文’, ‘79’],
3
[‘小邱’, ‘数学’, ‘83’],
4
[‘小李’, ‘英语’, ‘92’]]
5

6

使用列表创建 DataFrame 对象,同时设置列名

7
students_df = pd.DataFrame(students_info, columns=[‘姓名’, ‘科目’, ‘分数’])
8
students_df
Out [ 32 ]
姓名 科目 分数
0 小蓝 语文 79
1 小邱 数学 83
2 小李 英语 92
如此,使用列表作为数据创建 DataFrame 对象的同时,设置列名的知识已学习完毕。

那么,对于已创建好的 DataFrame 对象,是否可以修改列名呢?

当然,这个操作可以借助 DataFrame 对象的 columns 属性实现。

拿上面列名为默认值的数据做例子,请你先看看数据的内容:

只读
案例示范
NO.6
In [ 33 ]
1

查看变量 goods_df

2
goods_df
Out [ 33 ]
0 1 2 3
0 巧克力 77.675 47箱 广东佛山仓
1 牛奶 58.755 40箱 广东揭阳仓
2 威化饼干 7.385 285盒 广西南宁仓
3 火腿肠 34.823 20件 广西柳州仓
4 巧克力 62.648 30箱 广东揭阳仓
5 牛奶 45.750 12箱 广西柳州仓
6 威化饼干 5.235 148盒 广东佛山仓
7 火腿肠 33.736 28件 广东佛山仓
如果要修改 goods_df 的列名为名称、单价、库存和地址,可以使用下方的代码实现:

只读
案例示范
NO.7
In [ 34 ]
1

修改 goods_df 的列名

2
goods_df.columns = [‘名称’, ‘单价’, ‘库存’, ‘地址’]
3
goods_df
Out [ 34 ]
名称 单价 库存 地址
0 巧克力 77.675 47箱 广东佛山仓
1 牛奶 58.755 40箱 广东揭阳仓
2 威化饼干 7.385 285盒 广西南宁仓
3 火腿肠 34.823 20件 广西柳州仓
4 巧克力 62.648 30箱 广东揭阳仓
5 牛奶 45.750 12箱 广西柳州仓
6 威化饼干 5.235 148盒 广东佛山仓
7 火腿肠 33.736 28件 广东佛山仓
这里同样将需要修改的列名组合成一个列表,再把列表赋给 DataFrame 对象的 columns 属性,对列名进行了修改。

下面,请你补充下方代码的第 5 行,将数据的列名修改为姓名、年龄、公司。

NO.8
In [ 35 ]
1

设置一个 DataFrame 对象的数据

2
peoples_df = pd.DataFrame({‘name’: [‘南大川’, ‘陈知枫’], ‘age’: [‘27’, ‘25’], ‘company’: [‘闪光科技公司’, ‘闪光科技公司’]})
3

4

将 peoples_df 的列名分别改为"姓名"、“年龄”、“公司”

5

6
peoples_df
Out [ 35 ]
name age company
0 南大川 27 闪光科技公司
1 陈知枫 25 闪光科技公司
你可以看看我写的代码:

只读
案例示范
NO.9
In [ 36 ]
1

将 peoples_df 的列名分别改为"姓名"、“年龄”、“公司”

2
peoples_df.columns = [‘姓名’, ‘年龄’, ‘公司’]
3
peoples_df
Out [ 36 ]
姓名 年龄 公司
0 南大川 27 闪光科技公司
1 陈知枫 25 闪光科技公司
数据的列名设置学习完毕,咱们继续下个知识点:数据的排序。

2.2 数据的排序
在分析数据的过程中,如果想让数据根据某列从小到大的情况进行排序,手动调整显然不是个明智的选择。

比如搞清楚下方图片中,单价从小到大依次是哪些食品。

花点时间手动操作,确实能给数据做一个排序。但在数据量大的时候,显然就不适用了。

好在有 sort_values() 方法,该方法可以对指定列进行排序操作。该方法的语法如下图所示:

让我们使用前面所用到的数据 newgoods_df 作为例子,更直观地体验 sort_values() 方法的效果。请你运行下方的代码看看数据的内容。

只读
案例示范
NO.10
In [ 37 ]
1

查看数据 newgoods_df 的内容

2
newgoods_df
Out [ 37 ]
名称 单价 库存 地址
0 巧克力 77.675 47箱 广东佛山仓
1 牛奶 58.755 40箱 广东揭阳仓
2 威化饼干 7.385 285盒 广西南宁仓
3 火腿肠 34.823 20件 广西柳州仓
4 巧克力 62.648 30箱 广东揭阳仓
5 牛奶 45.750 12箱 广西柳州仓
6 威化饼干 5.235 148盒 广东佛山仓
7 火腿肠 33.736 28件 广东佛山仓
如果想让食品数据依照【单价】列进行升序排序,可以通过下方的代码来实现:

只读
案例示范
NO.11
In [ 38 ]
1

依照【单价】列的数据进行排序

2
sorted_goods = newgoods_df.sort_values(by=‘单价’)
3
sorted_goods
Out [ 38 ]
名称 单价 库存 地址
6 威化饼干 5.235 148盒 广东佛山仓
2 威化饼干 7.385 285盒 广西南宁仓
7 火腿肠 33.736 28件 广东佛山仓
3 火腿肠 34.823 20件 广西柳州仓
5 牛奶 45.750 12箱 广西柳州仓
1 牛奶 58.755 40箱 广东揭阳仓
4 巧克力 62.648 30箱 广东揭阳仓
0 巧克力 77.675 47箱 广东佛山仓
想对某列进行排序,可以将它的列名传给 sort_values() 的参数 by,如上述中的’单价’,默认情况是升序排列。

如果我们不想对上面的数据集进行排序,只想提取出【单价】列,单独进行排序,代码可以这么写:

只读
案例示范
NO.12
In [ 39 ]
1

提取【单价】列的数据进行排序

2
newgoods_df[‘单价’].sort_values()
Out [ 39 ]
6 5.235
2 7.385
7 33.736
3 34.823
5 45.750
1 58.755
4 62.648
0 77.675
Name: 单价, dtype: float64
另外,不单是 DataFrame 对象可以调用 sort_values() 方法,Series 对象也能调用该方法。

但要注意的是,使用 DataFrame 对象调用 sort_values() 方法时,可以通过参数 by 来指定某一列。

而 Series 对象调用该函数时,函数没有参数 by。原因是 Series 对象不具备列索引(列名)。这一细节点,在书写代码时需要多加留意。

ok,数据的排序也学习完毕,再看一眼排序后的数据 sorted_goods。

只读
案例示范
NO.13
In [ 40 ]
1

查看数据 sorted_goods 的内容

2
sorted_goods
Out [ 40 ]
名称 单价 库存 地址
6 威化饼干 5.235 148盒 广东佛山仓
2 威化饼干 7.385 285盒 广西南宁仓
7 火腿肠 33.736 28件 广东佛山仓
3 火腿肠 34.823 20件 广西柳州仓
5 牛奶 45.750 12箱 广西柳州仓
1 牛奶 58.755 40箱 广东揭阳仓
4 巧克力 62.648 30箱 广东揭阳仓
0 巧克力 77.675 47箱 广东佛山仓
可以看到,虽然数据集依照【单价】列进行了排序,但是排序后的行索引不再是连续的,而是比较混乱。

为了解决索引混乱的问题,我们需要学习一个新的知识点 —— 重置索引。

2.3 重置索引
重置索引可以使用 reset_index() 方法来实现。请你运行下方的代码来体验该方法的功能:

只读
案例示范
NO.14
In [ 41 ]
1

重置数据 sorted_goods 的行索引

2
reset_goods = sorted_goods.reset_index(drop=True)
3
reset_goods
Out [ 41 ]
名称 单价 库存 地址
0 威化饼干 5.235 148盒 广东佛山仓
1 威化饼干 7.385 285盒 广西南宁仓
2 火腿肠 33.736 28件 广东佛山仓
3 火腿肠 34.823 20件 广西柳州仓
4 牛奶 45.750 12箱 广西柳州仓
5 牛奶 58.755 40箱 广东揭阳仓
6 巧克力 62.648 30箱 广东揭阳仓
7 巧克力 77.675 47箱 广东佛山仓
从显示的结果可以看到,数据的行索引被重置,索引值从 0 开始。

我们来看一下 reset_index() 的语法,看看它是如何重置索引的吧。

reset_index() 方法中,参数 drop 的值为布尔值,表示是否去除原有索引。

默认情况下,drop 的值为 False,即保留原索引,像上面的代码中,如果使用默认值 False,它的运行结果将会如下图所示。

当数据集需要使用 reset_index() 重置索引时,且原索引没有参考价值,建议参数 drop 赋值为 True ,去除原索引,避免数据集存在一些无意义的数据。

重置索引的操作已教给你了。趁热打铁,来场练习吧。

下面我创建了一个 DataFrame 对象,请运行代码查看数据的内容:

只读
案例示范
NO.15
In [ 42 ]
1

创建一个 DataFrame 对象的数据

2
humans_df = pd.DataFrame({‘姓名’: [‘小猿’, ‘小马’, ‘小鱼’, ‘小猫’, ‘小龙’], ‘身高’: [‘165’, ‘170’, ‘160’, ‘172’, ‘178’], ‘体重’: [56, 45, 60, 40, 70]})
3
humans_df
Out [ 42 ]
姓名 身高 体重
0 小猿 165 56
1 小马 170 45
2 小鱼 160 60
3 小猫 172 40
4 小龙 178 70
请你补充下方代码的第 2、4 行,实现如下的功能:
1)让数据 humans_df 根据【身高】列的数据进行升序排序;
2)重置索引,且不保留原索引。

NO.16
In [ 43 ]
1
#已跳过练习,代码默认变成注释
2

依照【身高】列的数据进行升序排序

3
#reset_humans =
4

重置索引,且不保留原索引

5

Out [ 43 ]
我的答案是这样的:

只读
案例示范
NO.17
In [ 44 ]
1

依照【身高】列的数据进行升序排序

2
reset_humans = humans_df.sort_values(by=‘身高’)
3

重置索引,且不保留原索引

4
reset_humans.reset_index(drop=True)
Out [ 44 ]
姓名 身高 体重
0 小鱼 160 60
1 小猿 165 56
2 小马 170 45
3 小猫 172 40
4 小龙 178 70
数据的排序以及行索引重置已讲解完毕,下一个知识点 —— 数值的四舍五入。

2.4 数值的四舍五入
我们将使用前面重置过索引的数据 reset_goods 来学习该知识点,请你运行下方的代码看看数据的内容。

只读
案例示范
NO.18
In [ 45 ]
1

查看数据 reset_goods 的内容

2
reset_goods
Out [ 45 ]
名称 单价 库存 地址
0 威化饼干 5.235 148盒 广东佛山仓
1 威化饼干 7.385 285盒 广西南宁仓
2 火腿肠 33.736 28件 广东佛山仓
3 火腿肠 34.823 20件 广西柳州仓
4 牛奶 45.750 12箱 广西柳州仓
5 牛奶 58.755 40箱 广东揭阳仓
6 巧克力 62.648 30箱 广东揭阳仓
7 巧克力 77.675 47箱 广东佛山仓
有时,你需要调整数值的小数部分。如上方数据中的【单价】列,每个值都有三位小数。

如果想要数据仅保留两位小数,可以使用 round() 函数来实现。请你运行代码体验一下:

只读
案例示范
NO.19
In [ 46 ]
1
reset_goods[‘单价’] = round(reset_goods[‘单价’], 2)
2
reset_goods
Out [ 46 ]
名称 单价 库存 地址
0 威化饼干 5.24 148盒 广东佛山仓
1 威化饼干 7.38 285盒 广西南宁仓
2 火腿肠 33.74 28件 广东佛山仓
3 火腿肠 34.82 20件 广西柳州仓
4 牛奶 45.75 12箱 广西柳州仓
5 牛奶 58.76 40箱 广东揭阳仓
6 巧克力 62.65 30箱 广东揭阳仓
7 巧克力 77.68 47箱 广东佛山仓
可以看到,round() 函数将每个数据的最后一位小数四舍五入到第二位小数。

下面,让我们看看 round() 函数是如何使用的吧。

使用该函数主要用到两个参数:number 和 ndigits。

1)参数 number 传入的是要进行四舍五入的数据。同时,因为 number 是函数的第一个位置参数,所以我们在使用时无需为它赋值,可以直接写为 round(reset_goods[‘单价’]);

2)参数 ndigits 传入的是四舍五入后要保留的小数位数。默认情况下该参数的值为 0,即不保留小数。

学习了 round() 函数的作用和语法后,你也来练习一下该知识点吧。

我创建了一个 DataFrame 对象的数据,运行下方的代码查看数据的内容:

只读
案例示范
NO.20
In [ 47 ]
1

创建一个 DataFrame 对象的数据

2
humans_df = pd.DataFrame({‘姓名’: [‘小猿’, ‘小马’, ‘小鱼’, ‘小猫’, ‘小龙’], ‘身高’: [‘165’, ‘170’, ‘160’, ‘172’, ‘178’], ‘体重’: [56.25, 45.63, 60.72, 40.34, 70.5]})
3
humans_df
Out [ 47 ]
姓名 身高 体重
0 小猿 165 56.25
1 小马 170 45.63
2 小鱼 160 60.72
3 小猫 172 40.34
4 小龙 178 70.50
请你补充下方代码的第 2 行,将【体重】列的数据保留到一位小数。

NO.21
In [ 48 ]
1

保留【体重】列的数据到一位小数

2

3
humans_df
Out [ 48 ]
姓名 身高 体重
0 小猿 165 56.25
1 小马 170 45.63
2 小鱼 160 60.72
3 小猫 172 40.34
4 小龙 178 70.50
实现代码如下:

只读
案例示范
NO.22
In [ 49 ]
1

保留【体重】列的数据到一位小数

2
humans_df[‘体重’] = round(humans_df[‘体重’], 1)
3
humans_df
Out [ 49 ]
姓名 身高 体重
0 小猿 165 56.2
1 小马 170 45.6
2 小鱼 160 60.7
3 小猫 172 40.3
4 小龙 178 70.5
数值的四舍五入也学习完毕。只剩本环节的重难点知识 —— agg() 方法。

2.5 agg() 方法
继续使用前面的数据 reset_goods 来学习该知识点,请你运行下方的代码看看数据的内容。

只读
案例示范
NO.23
In [ 50 ]
1

查看数据 reset_goods 的内容

2
reset_goods
Out [ 50 ]
名称 单价 库存 地址
0 威化饼干 5.24 148盒 广东佛山仓
1 威化饼干 7.38 285盒 广西南宁仓
2 火腿肠 33.74 28件 广东佛山仓
3 火腿肠 34.82 20件 广西柳州仓
4 牛奶 45.75 12箱 广西柳州仓
5 牛奶 58.76 40箱 广东揭阳仓
6 巧克力 62.65 30箱 广东揭阳仓
7 巧克力 77.68 47箱 广东佛山仓
如果想计算各种食品的总库存量,只需要按照【名称】列分组后,再将【库存】列的库存量相加。

但从数据可以看到,每件商品的库存量后都附有单位,无法直接进行求和操作。

这就需要我们将每个数据的单位切除后,才能进行库存量的求和。

由于每个数据的单位都为一个字符,可以选择使用切片的方式切除。关键是如何对【库存】列整列的数据进行切片操作。

这里就可以使用 agg() 方法对整列的数据进行统一操作。

接下来,我将展示 agg() 方法的使用步骤,请你好好观察吧。

首先,我定义了函数 new_func(),功能是把传入的数据进行切片,切除数据的最后一个字符。

只读
案例示范
NO.24
In [ 51 ]
1

定义函数 new_func()

2
def new_func(data):
3
number = data[:-1]
4
return int(number)
Out [ 51 ]
接着,使用 agg() 方法将【库存】列的每个数据都传入 new_func() 函数进行计算,并取得函数的返回值。

只读
案例示范
NO.25
In [ 52 ]
1

使用 agg() 方法对【库存】列的数据进行切片处理

2
reset_goods[‘库存’] = reset_goods[‘库存’].agg(new_func)
3
reset_goods
Out [ 52 ]
名称 单价 库存 地址
0 威化饼干 5.24 148 广东佛山仓
1 威化饼干 7.38 285 广西南宁仓
2 火腿肠 33.74 28 广东佛山仓
3 火腿肠 34.82 20 广西柳州仓
4 牛奶 45.75 12 广西柳州仓
5 牛奶 58.76 40 广东揭阳仓
6 巧克力 62.65 30 广东揭阳仓
7 巧克力 77.68 47 广东佛山仓
从显示的结果可以看到,【库存】列的数据已把单位去除完。为了更好地理解代码,我们来看看 agg() 的使用语法。

方法 agg() 中的参数 func 可以为自定义的函数名,如我们上方定义的 new_func() 函数。

这里在使用时需要注意,传入自定义的函数时,只需要写上函数名,不需要加上括号以及函数的参数。

我们看回上方使用 agg() 方法对【库存】列数据进行切片的代码。

代码中使用 reset_goods[‘库存’] 指定【库存】列,再通过 agg() 方法将【库存】列的数据依次传给函数 new_func() 的参数 data。

函数 new_func() 会切除传入数据的最后一个字符,再将切除后的字符串转换为整数型,然后返回该数据。

通过 new_func() 函数返回的每个数据,最终会形成一个 Series 对象。将该对象重新赋给 reset_goods[‘库存’],【库存】列即为不带单位的数据。

现在,库存量的单位已经去除,数据类型也转换为整数型。此时计算各食品的总库存量可以通过下方的代码实现:

只读
案例示范
NO.26
In [ 53 ]

计算各食品的总库存量

reset_goods.groupby(‘名称’)[‘库存’].sum()
1

计算各食品的总库存量

2
reset_goods.groupby(‘名称’)[‘库存’].sum()
Out [ 53 ]
名称
威化饼干 433
巧克力 77
火腿肠 48
牛奶 52
Name: 库存, dtype: int64
尽管食物的总库存有了,但每个地区的食品【价格】却不一样。

如果想在统计食品总库存量的同时,也能看到各食品在所有地区的平均单价,应该怎么实现呢?

刚刚所学的 agg() 方法也能解决这个难题。略有不同的是,方法内的参数 func 这次要传入字典。

接着,来看看我的实现代码吧:

只读
案例示范
NO.27
In [ 54 ]

定义字典 dict_2

dict_2 = {
‘单价’: ‘mean’,
‘库存’: ‘sum’
}

同时计算各食品的平均单价和总库存量

reset_goods.groupby(‘名称’).agg(dict_2)
1

定义字典 dict_2

2
dict_2 = {
3
‘单价’: ‘mean’,
4
‘库存’: ‘sum’
5
}
6

同时计算各食品的平均单价和总库存量

7
reset_goods.groupby(‘名称’).agg(dict_2)
Out [ 54 ]
单价 库存
名称
威化饼干 6.310 433
巧克力 70.165 77
火腿肠 34.280 48
牛奶 52.255 52
代码可以分为 3 步进行理解:

1)使用 reset_goods.groupby(‘名称’) 让数据按【名称】列进行分组;

2)定义一个字典 dict_2,字典的键为我们需要操作的列名,如 ‘单价’、‘库存’。字典的值为我们需要操作的函数名,这里我们需要求平均值函数 mean(),求和函数 sum()。(可使用的函数可参照下表:)

同样需要注意的是,操作的函数只需要函数名,不需要函数名后的括号以及函数的参数。

3)分组后的数据调用 agg() 方法时,为方法的参数 func 传入字典 dict_2,可以同时得到各食品的平均单价和总库存量。

ok,agg() 方法的两种用法已经学习完毕。需要注意的是,两种用法在调用函数时,只需写函数名,不需要函数名后的括号。

下面我将创建一个 DataFrame 对象的数据,让你来练练 agg() 方法的两种用法。请运行下方的代码看看数据的内容:

只读
案例示范
NO.28
In [ 55 ]
1

创建一个 DataFrame 对象的数据

2
foods_df = pd.DataFrame({‘食品名’: [‘肚脐饼’, ‘水晶饺’, ‘擂茶’, ‘肚脐饼’, ‘擂茶’], ‘单价’: [2, 10, 12, 4, 20], ‘数量’: [20, ‘-’, 35, 37, ‘-’], ‘城市’: [‘潮州’, ‘汕尾’, ‘梅州’, ‘深圳’, ‘深圳’]})
3
foods_df
Out [ 55 ]
食品名 单价 数量 城市
0 肚脐饼 2 20 潮州
1 水晶饺 10 - 汕尾
2 擂茶 12 35 梅州
3 肚脐饼 4 37 深圳
4 擂茶 20 - 深圳
可以看到数据中的【数量列】有意义不明的 “-”,需要将其统一替换为 0。

请你补充下面的代码,实现以下功能:
1)将【数量】列的“-”统一替换为 0;
2)处理完异常数据后按【食品名】分组;
3)同时计算【单价】列的最小值和【数量】列的最大值。

NO.29
In [ 56 ]
1

定义函数,以替换【数量】列的“-”为 0

2

3

4

使用 agg() 方法对【数量】列的内容进行替换

5

6

同时计算【单价】列的最小值和【数量】列的最大值

7

Out [ 56 ]
答案不唯一,你可以参考我的答案:

首先我定义函数 replace_info(),用来判断传入的值是否为 ”-“,是则替换并返回 0,不是则返回原值。

只读
案例示范
NO.30
In [ 57 ]

定义函数,以替换【数量】列的“-”为 0

def replace_info(data):
if data == ‘-’:
data = 0
return data
1

定义函数,以替换【数量】列的“-”为 0

2
def replace_info(data):
3
if data == ‘-’:
4
data = 0
5
return data
Out [ 57 ]
接着,使用 agg() 方法,将【数量】列的数据依次传给函数 replace_info(),完成内容的替换。

只读
案例示范
NO.31
In [ 58 ]
1

使用 agg() 方法对【数量】列的内容进行替换

2
foods_df[‘数量’] = foods_df[‘数量’].agg(replace_info)
Out [ 58 ]
再使用 foods_df.groupby(‘食品名’),让数据按【食品名】列进行分组。

最后,使用分组后的数据调用 agg() 方法,方法中传入列名以及对应函数组成的字典。

其中,【单价】列需要求最小值的函数 min(),【数量】列需要求最大值的函数 max()。

只读
案例示范
NO.32
In [ 59 ]

同时计算【单价】列的最小值和【数量】列的最大值

foods_df.groupby(‘食品名’).agg({‘单价’: ‘min’, ‘数量’: ‘max’})
1

同时计算【单价】列的最小值和【数量】列的最大值

2
foods_df.groupby(‘食品名’).agg({‘单价’: ‘min’, ‘数量’: ‘max’})
Out [ 59 ]
单价 数量
食品名
擂茶 12 35
水晶饺 10 0
肚脐饼 2 37
至此,本节课的基础知识点就讲完啦,我们通过下方的思维导图来回顾这几个知识点。

接下来,让我们开始学习本案例使用到的分析方法 — 关联分析。

  1. 分析方法
    在讲解关联分析之前,我先给你分享一个业界的经典小故事。

说是20世纪90年代,沃尔玛超市在对顾客的购物记录进行购物篮分析时,发现了一个奇怪的现象: “啤酒”和“尿布”两件看上去毫无关系的商品,经常出现在同一个购物篮中。

随后,他们深入分析,发现这种奇怪的现象大多发生在年轻的父亲身上,原因是父亲在购买婴儿尿片时,通常会捎带几瓶啤酒犒劳自己。

于是,沃尔玛调整了货架,尝试将啤酒和尿布摆在一个区域,方便顾客购买,从而提升了超市的销量。

虽然现实生活中,“啤酒”与“尿布”并不会放在一块售卖,这只是一个有趣的营销故事。

但是购物篮分析却是真实存在的,它是关联分析中的一个重要应用。通过关联分析,确实能发现事物之间一些令人意外的“关系”。

依据这些“关系”,可以引导卖家调整营销策略,把关联程度较高的商品打包在一起,提高销量。或者针对买家已购买的产品,推荐与之相关的其他产品或信息,精准营销。

除了销售行业,关联分析还被广泛应用在生物信息学、医疗诊断、网页挖掘、地球科学等领域。

说了这么多,那关联分析究竟是什么呢?它究竟是怎么发挥作用的?

下面,我会先给你的普及一些关联分析的基本概念,然后你再来学习怎么通过Apriori 算法实现这一分析方法。

3.1 关联分析及相关概念
关联分析,是一门分析技术,用于发现大量数据中,各组数据之间的联系。

现在,我手里有一份快餐店的用户交易数据:

基于这份数据,我会向快餐店老板提出建议,推出薯条和奶茶的组合套餐。因为我发现购买奶茶的用户,有很大概率会加购薯条,不过,我是怎么发现的呢?

事情还要从那份只有4条数据的交易数据入手,它有点像购物小票。

交易数据记录了用户的单次消费行为,即交易记录,如1001号订单,意思是某用户一次性购买了两种(非数量)食品:薯条、可乐。

而每条交易记录又可称为一个事务。所以,这份交易数据一共含有4条“事务”。

交易中的不同物品可称为一个项。所以,这4条交易记录,一共含有4个项(商品类目):“薯条”,“可乐”,“奶茶”,“汉堡”。

0个或多个项的集合,可称为一个项集,一般用{X}的形式表示项集,k 个项组成的项集,叫 k 项集。如{薯条,可乐}(2 项集),{薯条}(1 项集)。

前面的4项商品,可以相互组成15个项集,项集内不存在相同的项,如{奶茶,薯条,薯条}。

不同“项集”,受顾客的欢迎程度不同。支持度(Support)可以表示项集在事务中出现的概率(频率),你也可以理解成顾客对某一个项集的“支持程度”。

{X}的支持度 = {X}在事务中出现的次数 / 事务总数。

支持度是我们课程的一个重难点,请你根据它的公式,试着计算一下 {奶茶} 的支持度吧。

单选题
选出 {奶茶} 的支持度:

A.
0.25

B.
0.75

C.
1

回答正确
在这 4 条事务中,{奶茶} 总共只出现了 1 次,因此,{奶茶} 的支持度 = 1 / 4,也就是 0.25。

每个项集都有支持度,但事实上,也不是每个项集都是有用的。

比如支持度为0的 4 项集:{可乐,汉堡,奶茶,薯条},客人都没买过这样的套餐,能有啥指导价值?该删!

此时,我们需要人为地设定一个支持度,名为最小支持度,用于筛掉那些不符合需求的项集。被留下来的项集(≥ 最小支持度),被称为频繁项集。

有了频繁项集,就可以生产关联规则了。

等等,什么是关联规则?

前面,我说关联分析是探索数据之间联系的技术,而数据之间的联系,我们用关联规则来表示,表达式为:{X}→{Y}(X 和 Y 之间不存在相同项)。

规则有顺序之分,为了方便描述,我们把规则前面的项集叫前件,把规则后面的项集叫后件。

假设有频繁项集 {奶茶,薯条},它可以生成2条关联规则:{奶茶}→{薯条}和{薯条}→{奶茶}。前者的意思是,购买“奶茶”的顾客,和购买“薯条”之间,可能存在有某种联系!🤔

但是,怎么知道这种联系是否可靠?如果可靠,到底有多可靠?哪个更可靠?

置信度(Confidence)可用于衡量关联规则的可靠程度,表示在前件出现的情况下,后件出现的概率。一般来说,概率越高,规则的可靠性越强。

关联规则{X}→{Y}的置信度 = {X,Y}的支持度 / {X}的支持度。

请你根据置信度的计算公式,及 {薯条}→{奶茶} 置信度的计算示例,计算一下 {奶茶}→{薯条} 的置信度:

【提示】
{奶茶,薯条}的支持度:0.25;
{奶茶}的支持度:0.25;
{薯条}的支持度:0.75。

单选题
选出 {奶茶}→{薯条} 的置信度:

A.
0.33

B.
0.75

C.
1

回答错误
答案是 C
{奶茶}→{薯条} 的置信度 = {奶茶,薯条} 的支持度 / {奶茶} 的支持度 = 0.25 / 0.25,结果等于 1 。

同为关联规则,可靠程度有“强”有“弱”。

在实际业务中,也需要人为地设定置信度,名为最小置信度,用于筛掉一些不符合需求的关联规则。

被留下来的关联规则( ≥ 最小置信度),叫做强关联规则。

众所周知,在自然界,不同物种之间,不仅有“互惠互利”的合作关系,也有“势不两立”的竞争关系。

关联规则也一样,既有促进关系,也有抑制关系。因而,还需引入提升度(Lift)对它们进行判断。

{X}→{Y}的提升度 = {X}→{Y}的置信度 / {Y}的支持度,意思是评估 X 的出现,对 Y 出现的影响有多大。

请你根据提升度的计算公式,及 {薯条}→{奶茶} 提升度的计算示例,计算一下 {奶茶}→{薯条} 的提升度:

【提示】
{奶茶}→{薯条}的置信度:1.0;
{薯条}→{奶茶}的置信度:0.33;
{奶茶}的支持度:0.25;
{薯条}的支持度:0.75。

单选题
选出 {奶茶}→{薯条} 的提升度:

A.
1.33

B.
1.75

C.
1

回答正确
{奶茶}→{薯条} 的提升度 = {奶茶}→{薯条} 的置信度 / {薯条} 的支持度 = 1.0 / 0.75,结果约为 1.33 。

提升度的值小于1,表示前件对后件是抑制的关系;
提升度大于1,表示前件对后件是促进的关系;
特别地,当提升度的值等于1时,表示前件不影响后件,两者之间没有关系。

也就是说,对于关联规则{奶茶}→{薯条}(提升度 ≈ 1.33 > 1)来说,购买{奶茶}会促进{薯条}的购买。

好的,以上就是关联分析相关概念的全部内容,不知道你吸收得怎么样,来接受考验吧:

单选题
请你选出以下说法正确的选项:

A.
购物篮分析不属于关联分析。

B.
若一个项集满足最小支持度,可称之为强关联规则。

C.
最小支持度和最小置信度,是使用者根据自身需求,自行设置的。

回答正确
购物篮分析属于关联分析,选项 A 错误。满足最小支持度的项集称为频繁项集,满足最小置信度的关联规则称为强关联规则,选项 B 错误。

最后,总结一下这部分的知识:

大多数的关联分析工作,主要任务就是生成频繁项集和关联规则。有了计算公式和流程,理论上,我们可以手算,不过难度可想而知:

一个3项(k 项)的数据集,能产生7(2^k - 1)个非空频繁项集。
一个3项(k 项)的频繁项集,可产生6(2^k - 2)个关联规则。

随着“项”的增加,频繁项集和关联规则的计算量必将呈指数增长。

而现实生活中的“项”(商品)成百上千,真实的“事务”(交易)数以万计。摸着稀疏的头发坦白讲,提高计算效率是必须的,发量要紧。

那说到提高计算效率,就要请出Apriori 算法了。

3.2 Apriori 算法
Apriori是用于挖掘出数据背后的关联规则的一种算法,以下简写为 Apr 算法,它的流程可分为两步(简单了解它的运作原理即可,无需深究):

步骤一:确定最小支持度和最小置信度

第一步,我们要“拍脑袋”确定一个最小支持度和最小置信度。不过,“脑袋”不可以随意“乱拍”,“拍”也是有技巧,有范围的。

最小支持度和最小置信度都是描述事件发生的概率,所以取值范围在 0 和 1 之间。

假如最小支持度设定过高,就会导致一些重要但不频繁的项集被过滤掉;设定过低,一方面,会影响计算性能,另一方面,一些无实际意义的数据也会被保留下来,最小置信度也是同理。

但判断它们是否“合适”的感觉很微妙,没有特定的标准答案,这里仅提供几个方法供你参考,不做延展:

其实总结起来就一个字:试。

一开始设定的最小支持度和置信度和理想状态有一定偏差也没关系,后续再慢慢调整。

还是原始的快餐店数据,倘若我现在确定的最小支持度为0.2,最小置信度为0.7,一起进入下一步骤。

步骤二:找出频繁项集和强关联规则

这一步,Apr 的算法主要依赖两个性质:

1)一个项集如若是频繁的,那它的非空子集也一定是频繁的。假如购买{薯条,奶茶}的概率都很高,那购买{薯条}或{奶茶}的概率肯定也很高。

2)一个项集如若是非频繁的,那包含该项集的项集也一定是非频繁的。假设购买{薯条}的次数少,那购买{薯条,奶茶}的次数肯定也少。

在 Apr 算法出现之前,要找出所有的频繁项集,就得先枚举所有的项集,计算它们的支持度,然后和最小支持度(0.2)进行对比,筛选出频繁项集。

但 Apr 算法不一样,它是一个驾轻熟路的“老司机”,对错误的“道路”进行了提前预判。

一旦找到某个不满足条件的“非频繁项集”,包含该集合的其他项集不需要计算,更不用对比,通通绕开。

比如,在对比2项集的时候,发现{奶茶,汉堡}的支持度为 0,小于最小支持度0.2,属于非频繁项集。

于是,后面在计算3项集(及4项集)的时候,会直接把包含{奶茶,汉堡}的项集,如{薯条,奶茶,汉堡}去掉,不再计算。

以此类推,直到算到最后一层项集,所有的频繁项集才算筛选完毕。

在这里插入图片描述

凭借新生成的频繁项集,就可以开始“制造”关联规则了。

在这里插入图片描述
值得注意的是,因为频繁 1 项集只有一个项,无法构成具有指导意义的关联关系(≥ 2 项),可直接忽略。

去掉频繁 1 项集后,筛选出来的频繁项集剩下 7 个,我抽取其中一个频繁 3 项集{薯条,可乐,汉堡},进入关联规则的生产环节。

Apr 算法会先产生一系列后件项数为1的关联规则,与最小置信度进行比较,得到一部分强关联规则。

在这里插入图片描述
然后,频繁项集继续生成后件的项数为2的关联规则,再对它们的置信度进行比较,又收获一批强关联规则。

在这里插入图片描述
当无法从剩下的频繁项集中生成新的关联规则时,该过程就结束了。

通过这种方式,其他的频繁项集也加入到关联规则的“生产线”上,最终通过最小置信度筛选,就算“牵手”成功,得到强关联规则如下:

在这里插入图片描述
关联规则的支持度,和构建规则的频繁项集的支持度一致。

若是这些强关联规则正好是你想要的,那就进一步计算它们的提升度。

相反,若是你对筛选出来的强关联规则不满意,那我们就得重新调整最小支持度和最小置信度,再计算一次。

在这里插入图片描述
尽管 Apr 算法已经对原始的关联分析做了优化,但手动计算依然繁琐,特别是当我想要调整最小支持度或者最小置信度的时候。

如果能把 Apr 算法的计算流程抽象成函数,将最小支持度、最小置信度和最小提升度设置成参数,通过调整参数来查看关联规则,想怎么调就怎么调,那该多好。

好吧,实际上,Python 早在暗中实现了这一切。

3.3 Python 调用 apriori 函数
提供这一便利的是apyori模块下的apriori函数。

调用模块下的函数这一知识点,对于现在的你来说已经是小菜一碟,这里就不再练习了。

需要提醒你的是,apyori模块属于 Python 的第三方模块,我在线上已经安装好了,假如你在本地使用它,记得先用命令行安装一下。

言归正传,一起来看一下apriori函数的庐山真面目:

在这里插入图片描述

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐