上一章节( https://juejin.cn/editor/drafts/7268594193933254719 )介绍了对于一个特定的数据集,如何从总体上通过统计分析的方法对其进行分析,以便了解数据集的规模,数值特点以及数据的分布情况。本章在了解数据集的基础上,介绍如何对数据集进行条件筛选,选出想要的数据。同时对于一些复杂的场景,现有的函数无法支持时,如何将自定义函数加入到pandas的数据分析中。
为了继续说明问题,本章继续引用上一章节的测试数据集,即两个班级5名学生的成绩单数据:
import pandas as pd
data = {
'NAME': ['Arial Johnson', 'Derek Davis', 'Latoya Mitchell', 'Tanisha Harris', 'Devin Price'],
'CLASS': ['A', 'A', 'A', 'B', 'B'],
'COURSE1': [99, 88, 81, 61, 97],
'COURSE2': [67, 76, 98, 58, 77],
'COURSE3': [93, 97, 91, 94, 51],
'COURSE4': [95, 65, 80, 53, 65]
}
df = pd.DataFrame(data)
6.条件筛选
6.1 条件判断方法
常用的条件运算符:
>
/>=
:大于/大于等于<
/<=
:小于/小于等于==
/!=
:等于/不等于
常用的逻辑运算符
&
:逻辑与|
:逻辑或~
:逻辑非
通过使用条件运算符和逻辑运算符的组合,我们可以创建复杂的布尔条件来进行数据集的筛选和选择。除此之外,由于df的一列相当于Series,可以用Series中的条件判断函数来实现。
isin
/notin
:用于判断Series中的值是否在给定的列表或集合中;isnull
/notnull
:用于判断Series中的值是否为NaN;
对于Series中的字符类型,可以用pandas.core.strings.StringMethods
中的关于字符型的条件判断方法。
str.contains()
:检查字符串中是否包含指定的子串str.isalnum()
:检查字符串是否由字母和数字组成str.isalpha()
:检查字符串是否由字母组成str.isdigit()
:检查字符串是否由数字组成str.isdecimal()
:检查字符串是否由十进制数字组成str.islower()
:检查字符串是否全小写str.isupper()
:检查字符串是否全大写str.istitle()
:检查字符串是否以大写字母开头str.match()
:检查字符串是否与指定的正则表达式匹配str.startswith()
:检查字符串是否以指定的前缀开头
在Pandas中,可以使用条件筛选来过滤数据集中符合特定条件的行,Pandas 提供了多种方法来进行条件筛选,包括使用布尔索引、使用 query()
函数和使用 loc
和 iloc
索引器。下面逐一介绍这些方法的用法。
6.2 布尔索引
在 Pandas 中,布尔索引是一种使用布尔条件(True/False)来选择数据集中行的方法。通过使用布尔条件,我们可以筛选出符合特定条件的行,并进行数据集的条件筛选和子集选择。布尔索引也叫布尔数组,数组的长度与数据的行数相同。
我们将各门课程考试分数大于等于80分作为优秀,并以此作为条件判断,可生成一个布尔数组:
>>> df['COURSE1'] > 80
0 True
1 True
2 True
3 False
4 True
Name: COURSE1, dtype: bool
# 通过布尔数组,筛选出课程1考试优秀的记录:
>>> df[df['COURSE1'] >= 80]
NAME CLASS COURSE1 COURSE2 COURSE3 COURSE4
0 Arial Johnson A 99 67 93 95
1 Derek Davis A 88 76 97 65
2 Latoya Mitchell A 81 98 91 80
4 Devin Price B 97 77 51 65
# 还可以用到上面说的条件表达式,筛选出所有课程考试都优秀的考生
# 注意,当用到多个条件时,要用括号括起来
>>> df[(df['COURSE1'] >= 80) & (df['COURSE2'] >= 80) & (df['COURSE3'] >= 80) & (df['COURSE4'] >= 80)]
NAME CLASS COURSE1 COURSE2 COURSE3 COURSE4
2 Latoya Mitchell A 81 98 91 80
6.2 query
函数
Pandas中的query
函数是一种特殊的条件表达式,可以用于在DataFrame中执行复杂的筛选、排序和聚合操作。它允许使用Python中的所有标准运算符和函数,以及自定义函数,来构建复杂的查询语句。query
函数使用起来可读性要大于用布尔索引,但布尔索引在大数据集的情况下,比前者效率更高。上面的两个条件表达式查询,可以表示为:
# 求课程1考试优秀的学生
>>> df.query('COURSE1 > 80')
NAME CLASS COURSE1 COURSE2 COURSE3 COURSE4
0 Arial Johnson A 99 67 93 95
1 Derek Davis A 88 76 97 65
2 Latoya Mitchell A 81 98 91 80
4 Devin Price B 97 77 51 65
# 求每一门科都优秀的学生
>>> df.query('COURSE1 >= 80 and COURSE2 >= 80 and COURSE3 >= 80 and COURSE4 >= 80')
NAME CLASS COURSE1 COURSE2 COURSE3 COURSE4
2 Latoya Mitchell A 81 98 91 80
6.3 使用行和列的标签或整数位置进行筛选
关于loc和iloc两个方法我们在前面的第二章节中已经做了介绍,在此不再赘述,用这两个函数来做条件筛选,依然要依赖于布尔数组:
# 使用行标签
>>> df.loc[df['COURSE1'] > 80]
>>> df.loc[(df['COURSE1'] >= 80) & (df['COURSE2'] >= 80) & (df['COURSE3'] >= 80) & (df['COURSE4'] >= 80)]
# 结果略,与上面的结果是相同的
# 使用整数位置
>>> df.iloc[(df['COURSE1'] > 80).values]
>>> df.iloc[(df['COURSE1'] >= 80) & (df['COURSE2'] >= 80) & (df['COURSE3'] >= 80) & (df['COURSE4'] >= 80).values]
7.自定义函数
7.1 Lambda表达式与自定义函数
Lambda表达式是Python中定义匿名函数的一种方法。所谓匿名函数,即没有名字的函数,使用lambda关键字定义,可以在需要函数对象的任何位置使用。Lambda函数常用于函数引用传递参数等情况下,可用于一行代码实现一些简单的功能。
一个lambda表达式的例子:
>>> f = lambda x: x**2
>>> f(5)
25
Lambda表达式可以让代码变得很简洁,对于一些简单逻辑,提高代码的可读性,但对于复杂逻辑,很难在一个表达式中进行拆解,这种情况下则不建议使用lambda表达式。在Pandas中,Lambda表达式通常用于DataFrame数据的处理,可以实现一些非常灵活的数据处理方法。
7.2 常用函数
Pandas提供的丰富的统计分析相关的函数,可以满足我们大多数的数据分析要求,但是对于一些特殊的逻辑,要求逻辑更加灵活且更贴近于业务,这就不得不要求我们利用pandas的一些函数嵌套自定义函数进行分析,常用的函数有:apply,map,applymap,agg和transform接下来逐一介绍它们的用法。
7.2.1 apply
apply()函数用于对DataFrame或Series中的每个元素应用一个自定义的函数,生成一个新的DataFrame或Series。apply()函数的参数可以是一个函数或一个lambda表达式,用于将原始值映射为新的值。apply()函数可以用于对每个元素进行逐个处理,也可以用于对每个列或每个行进行处理。
基本语法:
DataFrame.apply(func, axis=0, raw=False, result_type=None, args=(), **kwds)
Series.apply(func, convert_dtype=True, args=(), **kwds)
参数说明:
func:自定义函数或lambda表达式,用于将原始值映射为新的值。
axis:指定应用函数的轴,0表示列,1表示行,默认为0。
raw:是否将每行或每列作为一个Series对象传递给函数,默认为False,即将每个元素作为一个标量值传递给函数。
result_type:指定返回值的类型,可以是None、‘expand’或’reduce’。如果为None,则返回一个Series或DataFrame对象;如果为’expand’,则返回一个DataFrame对象;如果为’reduce’,则返回一个Series对象。
args:传递给函数的其他参数,以元组的形式传递。
convert_dtype:是否将Series对象的数据类型转换为函数返回值的数据类型,默认为True。
对每一列执行apply函数的场景如下:
# 判断科目1是否及格
>>> df.apply(lambda x:x['COURSE2'] > 60, axis=1)
0 True
1 True
2 True
3 False
4 True
dtype: bool
对每一行执行apply函数的场景如下:
# 判断所有的科目是否及格
>>> df[['COURSE1', 'COURSE2', 'COURSE3', 'COURSE4']].apply(lambda x:(x > 60), axis=1)
COURSE1 COURSE2 COURSE3 COURSE4
0 True True True True
1 True True True True
2 True True True True
3 True False True False
4 True True False True
当然,除了lambda表达式,还可以支持更加复杂的计算逻辑,为了不显示分数,可以以分数等级来划分成绩,这个时候就可以用自定义函数。
分数等级 | 分数范围 |
---|---|
A | 90-100 |
B | 80-89 |
C | 70-79 |
D | 60-69 |
F | <60 |
对应的自定义函数如下: |
def score_level(score):
if score < 60:
return 'F'
elif score < 70:
return 'D'
elif score < 80:
return 'C'
elif score < 90:
return 'B'
else:
return 'A'
此时可以用apply+自定义函数的形式求得科目1的分数等级:
>>> df.apply(lambda x: score_level(x['COURSE1']), axis=1)
0 A
1 B
2 B
3 D
4 A
dtype: object
7.2.2 map
map()函数用于对Series中的每个元素应用一个自定义的函数,生成一个新的Series。与apply()函数类似,但是只能用于Series,不能用于DataFrame。
用map函数,上一节的功能也可以写成:
>>> df['COURSE1'].map(lambda x: score_level(x))
0 A
1 B
2 B
3 D
4 A
Name: COURSE1, dtype: object
7.2.3 applymap
applymap()函数用于对DataFrame中的每个元素应用一个自定义的函数,生成一个新的DataFrame。与apply()函数类似,但是可以用于DataFrame中的每个元素,而不是每个列或每个行。
用applymap函数,可以求得每门课的分数等级。
>>> df[['COURSE1', 'COURSE2', 'COURSE3', 'COURSE4']].applymap(lambda x: score_level(x))
COURSE1 COURSE2 COURSE3 COURSE4
0 A D A A
1 B C A D
2 B A A B
3 D F A F
4 A C F D
7.2.4 agg
agg() 函数agg 函数是 aggregate 函数的简写。它的作用是对一个 DataFrame 或 Series 中的数据进行聚合计算,可以用于一次性计算多个统计量,并将结果返回为一个 DataFrame 或 Series。agg 函数常用的参数是一个字典,字典的键是用于聚合的列名,值是要进行聚合的统计量。
agg()函数主要用于聚合,相比对apply函数有更多的限制,函数仅适用于聚合函数。但使用agg()函数的好处是可以用不同的方法对不同的列进行聚合:
# 同时求每一门的最大值,最小值和平均值
>>> df[['COURSE1', 'COURSE2', 'COURSE3']].agg(['max', 'min', 'mean'])
COURSE1 COURSE2 COURSE3
max 99.0 98.0 97.0
min 61.0 58.0 51.0
mean 85.2 75.2 85.2
# 在一个agg式子中,同时求科目1的最大值,科目2的最小值和科目三的平均值
>>> df[['COURSE1', 'COURSE2', 'COURSE3']].agg({'COURSE1':'max', 'COURSE2':'min', 'COURSE3': 'mean'})
COURSE1 99.0
COURSE2 58.0
COURSE3 85.2
dtype: float64
7.2.5 transform
transform()函数用于对DataFrame中的每个列应用一个自定义的函数,生成一个新的DataFrame。与agg()函数类似,但是可以保留原始的行和列索引。与apply
函数不同,transform
函数在处理后将数据返回为与原始数据相同的形状,这在某些情况下非常有用。
使用transform函数可以用于标准化数据:
>>> df[['COURSE1', 'COURSE2', 'COURSE3', 'COURSE4']].transform(lambda x: (x - x.min()) / (x.max() - x.min()))
COURSE1 COURSE2 COURSE3 COURSE4
0 1.000000 0.225 0.913043 1.000000
1 0.710526 0.450 1.000000 0.285714
2 0.526316 1.000 0.869565 0.642857
3 0.000000 0.000 0.934783 0.000000
4 0.947368 0.475 0.000000 0.285714
计算排名:
>>> df[['COURSE1', 'COURSE2', 'COURSE3', 'COURSE4']].transform('rank', method='dense', ascending=False)
COURSE1 COURSE2 COURSE3 COURSE4
0 1.0 4.0 3.0 1.0
1 3.0 3.0 1.0 3.0
2 4.0 1.0 4.0 2.0
3 5.0 5.0 2.0 4.0
4 2.0 2.0 5.0 3.0
7.2.6 总结
以上函数中,apply是最灵活的,但是对于分组数据的聚合,用agg是最方便的,对于分组转换和展开,transform是最方便的,各有所长。