Pandas 查询筛选数据
《Pandas 教程》 修订中,可作为 Pandas 入门进阶课程、Pandas 中文手册、用法大全,配有案例讲解和速查手册。提供建议、纠错、催更等加作者微信: sinbam 和关注公众号「盖若」ID: gairuo。查看更新日志。
数据有选择是 Pandas 最基础的使用,我们通过各种条件的组合可以模拟 Excel 的筛选操作。Pandas 也能实现类似列表的切片的操作。
注:本文所使用的 df
和 s
是数据信息一文中的数据。
数据检查
我们一拿到数据需要对数据有一个抽查,一方面是了解数据结构,另一方面随机检查一下数据的质量问题。常用的:
语法 | 操作 | 返回结果 |
---|---|---|
df.head(n) | 查看 DataFrame 对象的前n行 | DataFrame |
df.tail(n) | 查看 DataFrame 对象的最后n行 | DataFrame |
df.sample(n) | 查看 n 个样本,随机 | DataFrame |
以上都是选择整行。
查看头部 df.head()
每次加载数据后一般需要看一下头部数据
df.head()
'''
name team Q1 Q2 Q3 Q4
0 Liver E 89 21 24 64
1 Arry C 36 37 37 57
2 Ack A 57 60 18 84
3 Eorge C 93 96 71 78
4 Oah D 65 49 61 86
'''
# 可指定数量
df.head(15)
查看尾部 df.tail()
查看最后的尾部数据。
df.head()
'''
name team Q1 Q2 Q3 Q4
95 Gabriel C 48 59 87 74
96 Austin7 C 21 31 30 43
97 Lincoln4 C 98 93 1 20
98 Eli E 11 74 58 91
99 Ben E 21 43 41 74
'''
# 可指定数量
df.tail(15)
查看样本 df.sample()
df.sample()
会随机返回一条样本数据。
df.sample()
'''
name team Q1 Q2 Q3 Q4
79 Tyler A 75 16 44 63
'''
# 可指定数量
df.sample(15)
数据截取:
# 去掉索引之前和之后的数据
df.truncate(before=2, after=4) # 只要索引 2-4
s.truncate(before="60", after="66")
df.truncate(before="A", after="B", axis="columns") # 选取列
操作列
以下两种方法都可以代表一列:
df['name'] # 会返回本列的 Series
df.name
df.Q1
# df.1Q 即使列名叫 1Q 也无法使用
# df.my name 有空格也无法调用,可以处理加上下划线
注意,当列名为一个合法的 python 变量时可以直接作为属性去使用。
选择部分行列
有时我们需要按条件选择部分列、部分行,一般常用的有:
操作 | 语法 | 返回结果 |
---|---|---|
选择列 | df[col] | Series |
按索引选择行 | df.loc[label] | Series |
按数字索引选择行 | df.iloc[loc] | Series |
使用切片选择行 | df[5:10] | DataFrame |
用表达式筛选行 | df[bool_vec] | DataFrame |
以上操作称为 Fancy Indexing(花式索引),它来自 Numpy,是指传递一个索引序列,然后一次性得到多个索引元素。Fancy Indexing 和 slicing 不同,它通常将元素拷贝到新的数据对象中。索引中可以有切片 slice,或者省略 ellipsis、新轴 newaxis、布尔数组或者整数数组索引等,可以看做是一个多维切片。
接下来我们将重点介绍一下这些查询的方法。
切片 []
我们可以像列表那样利用切片功能选择部分行的数据,但是不支持索引一条:
df[:2] # 前两行数据
df[4:10]
df[:] # 所有数据,一般没这么用的
df[:10:2] # 按步长取
s[::-1] # 反转顺序
也可以选择列:
df['name'] # 只要一列,Series
df[['Q1', 'Q2']] # 选择两列
df[['name']] # 选择一列,返回 DataFrame,注意和上例区别
按标签 .loc
df.loc()
的格式为 df.loc[<索引表达式>, <列表达式>],表达式支持以下形式:
单个标签:
# 代表索引,如果是字符需要加引号
df.loc[0] # 选择索引为 0 的行
df.loc[8]
单个列表标签:
df.loc[[0,5,10]] # 指定索引 0,5,10 的行
df.loc[['Eli', 'Ben']] # 如果索引是 name
# 真假选择,长度要和索引一样
df.loc[[False, True]*50] # 为真的列显示,隔一个显示一个
带标签的切片,包括起始和停止start:stop
, 可以其中只有一个,返回包括它们的数据:
df.loc[0:5] # 索引切片, 代表0-5行,包括5
df.loc['2010':'2014'] # 如果索引是时间可以用字符查询
df.loc[:] # 所有
# 本方法支持 Series
进行切片操作,索引必须经过排序,意味着索引单调递增或者单调递减,以下代码中其中一个为 True,否则会引发 KeyError
错误。
# 索引单调性
(
df.index.is_monotonic_increasing,
df.index.is_monotonic_decreasing
)
# (True, False)
通过上边的规则可以先对索引排序再执行词义上的查询,如:
# 姓名开头从 Ad 到 Bo 的
df.set_index('name').sort_index().loc['Ad':'Bo']
# 姓名开头从开始到 Bo 的
df.set_index('name').sort_index().loc[:'Bo']
# 团队名称从 C 到 D 的
df.set_index('team').sort_index().loc['C': 'D']
# 姓名开头从 Te 到 X 的
df.sort_values('name').set_index('name').loc['Te': 'X']
列筛选,必须有行元素:
dft.loc[:, ['Q1', 'Q2']] # 所有行,Q1 和 Q2两列
dft.loc[:, ['Q1', 'Q2']] # 所有行,Q1 和 Q2两列
dft.loc[:10, 'Q1':] # 0-10 行,Q1后边的所有列
按位置 .iloc
df.iloc
与 df.loc
相似,但只能用自然索引(行和列的 0 - n 索引),不能用标签。
df.iloc[:3]
df.iloc[:]
df.iloc[:, [1, 2]]
df.iloc[2:20:3]
s.iloc[:3]
如果想筛选多个不连续的行列数据(使用 np.r_
),可以使用以下方法:
# 筛选索引0-4&10&5-29每两行取一个&70-74
df.iloc[np.r_[:5, 10, 15:30:2, 70:75]] # 行
df.iloc[:, np.r_[0, 2:6]] # 列,0列和第2-5列
# 也可以使用追加的方式拼接
df.loc[:5].append(df.loc[10]).append(df.loc[15:30:2])
取具体值 .at
类似于 loc, 但仅取一个具体的值,结构为 at[<索引>,<列名>]:
# 注:索引是字符需要加引号
df.at[4, 'Q1'] # 65
df.at['lily', 'Q1'] # 65 假定索引是 name
df.at[0, 'name'] # 'Liver'
df.loc[0].at['name'] # 'Liver'
# 指定列的值对应其他列的值
df.set_index('name').at['Eorge', 'team'] # 'C'
df.set_index('name').team.at['Eorge'] # 'C'
# 指定列的对应索引的值
df.team.at[3] # 'C'
同样 iat 和 iloc 一样,仅支持数字索引:
df.iat[4, 2] # 65
df.loc[0].iat[1] # 'E'
.get 可以做类似字典的操作,如果无值给返回默认值(例中是0):
df.get('name', 0) # 是 name 列
df.get('nameXXX', 0) # 0, 返回默认值
s.get(3, 0) # 93, Series 传索引返回具体值
df.name.get(99, 0) # 'Ben'
表达式筛选
[]
切片里可以使用表达式进行筛选:
df[df['Q1'] == 8] # Q1 等于8
df[~(df['Q1'] == 8)] # 不等于8
df[df.name == 'Ben'] # 姓名为Ben
df.loc[df['Q1'] > 90, 'Q1':] # Q1 大于90,显示Q1后边的所有列
df.loc[(df.Q1 > 80) & (df.Q2 < 15)] # and 关系
df.loc[(df.Q1 > 90) | (df.Q2 < 90)] # or 关系
df[df.Q1 > df.Q2]
df.loc
里的索引部分可以使用表达式进行数据筛选。
df.loc[df['Q1'] == 8] # 等于8
df.loc[df.Q1 == 8] # 等于8
df.loc[df['Q1'] > 90, 'Q1':] # Q1 大于90,只显示 Q1
# 其他表达式与切片一致
# 通过列位置筛选列
df.loc[:, lambda df: df.columns.str.len()==4] # 真假组成的序列
df.loc[:, lambda df: [i for i in df.columns if 'Q' in i]] # 列名列表
df.iloc[:3, lambda df: df.columns.str.len()==2] # 真假组成的序列
逻辑判断和函数:
df.eq() # 等于相等 ==
df.ne() # 不等于 !=
df.le() # 小于等于 >=
df.lt() # 小于 <
df.ge() # 大于等于 >=
df.gt() # 大于 >
# 都支持 axis{0 or ‘index’, 1 or ‘columns’}, default ‘columns’
df[df.Q1.ne(89)] # Q1 不等于8
df.loc[df.Q1.gt(90) & df.Q2.lt(90)] # and 关系 Q1>90 Q2<90
其他函数:
# isin
df[df.team.isin(['A','B'])] # 包含 AB 两组的
df[df.isin({'team': ['C', 'D'], 'Q1':[36,93]})] # 复杂查询,其他值为 NaN
函数筛选
函数生成具体的标签值或者同长度对应布尔索引,作用于筛选:
df[lambda df: df['Q1'] == 8] # Q1为8的
df.loc[lambda df: df.Q1 == 8, 'Q1':'Q2'] # Q1为8的, 显示 Q1 Q2
# 选择字段时尽量使用字典法,属性法在条件表达式时一些情况可能有 bug
函数不仅能应用在行位上,也能应用在列位上。
where 和 mask
s.where(s > 90) # 不符合条件的为 NaN
s.where(s > 90, 0) # 不符合条件的为 0
# np.where, 大于80是真否则是假
np.where(s>80, True, False)
np.where(df.num>=60, '合格', '不合格')
s.mask(s > 90) # 符合条件的为 NaN
s.mask(s > 90, 0) # 符合条件的为 0
# 例:能被整除的显示,不能的显示相反数
m = df.loc[:,'Q1':'Q4'] % 3 == 0
df.loc[:,'Q1':'Q4'].where(m, -df.loc[:,'Q1':'Q4'])
# 行列相同数量,返回一个 array
df.lookup([1,3,4], ['Q1','Q2','Q3']) # array([36, 96, 61])
df.lookup([1], ['Q1']) # array([36])
mask 和 where 还可以通过数据筛选返回布尔序列:
# 返回布尔序列,符合条件的行为 True
(df.where((df.team=='A') & (df.Q1>60)) == df).Q1
# 返回布尔序列,符合条件的行为 False
(df.mask((df.team=='A') & (df.Q1>60)) == df).Q1
query
df.query('Q1 > Q2 > 90') # 直接写类型 sql where 语句
df.query('Q1 + Q2 > 180')
df.query('Q1 == Q2')
df.query('(Q1<50) & (Q2>40) and (Q3>90)')
df.query('Q1 > Q2 > Q3 > Q4')
df.query('team != "C"')
df.query('team not in ("E","A","B")')
# 对于名称中带有空格的列,可以使用反引号引起来
df.query('B == `team name`')
# 支持传入变量,如:大于平均分40分的
a = df.Q1.mean()
df.query('Q1 > @a+40')
df.query('Q1 > `Q2`+@a')
# df.eval() 用法与 df.query 类似
df[df.eval("Q1 > 90 > Q3 > 10")]
df[df.eval("Q1 > `Q2`+@a")]
filter
使用 filter 可以对行名和列名进行筛选。
df.filter(items=['Q1', 'Q2']) # 选择两列
df.filter(regex='Q', axis=1) # 列名包含Q的
df.filter(regex='e$', axis=1) # 以 e 结尾的
df.filter(regex='1$', axis=0) # 正则, 索引名包含1的
df.filter(like='2', axis=0) # 索引中有2的
# 索引中2开头列名有Q的
df.filter(regex='^2', axis=0).filter(like='Q', axis=1)
索引选择器 pd.IndexSlice
pd.IndexSlice
的使用方法类似于df.loc[]
切片中的方法,常用在多层索引中,以及需要指定应用范围(subset 参数)的函数中,特别是在链式方法中。
df.loc[pd.IndexSlice[:, ['Q1', 'Q2']]]
# 变量化使用
idx = pd.IndexSlice
df.loc[idx[:, ['Q1', 'Q2']]]
df.loc[idx[:, 'Q1':'Q4'], :] # 多索引
复杂的选择:
# 创建复杂条件选择器
selected = df.loc[(df.team=='A') & (df.Q1>90)]
idxs = pd.IndexSlice[selected.index, 'name']
# 应用选择器
df.loc[idxs]
# 选择这部分区域加样式(样式功能见教程后文介绍)
df.style.applymap(style_fun, subset=idxs)
按数据类型
可以只选择或者排除指定类型数据:
df.select_dtypes(include=['float64']) # 选择 float64 型数据
df.select_dtypes(include='bool')
df.select_dtypes(include=['number']) # 只取数字型
df.select_dtypes(exclude=['int']) # 排除 int 类型
df.select_dtypes(exclude=['datetime64'])
any 和 all
any 方法如果至少有一个值为 True 是便为 True,all 需要所有值为 True 才为 True。它们可以传入 axis 为 1,会按行检测。
# Q1 Q2 成绩全为 80 分的
df[(df.loc[:,['Q1','Q2']] > 80).all(1)]
# Q1 Q2 成绩至少有一个 80 分的
df[(df.loc[:,['Q1','Q2']] > 80).any(1)]
理解筛选原理
df[<表达式>] 里边的表达式如果单独拿出来,可以看到:
df.Q1.gt(90)
'''
0 False
1 False
2 False
3 True
4 False
...
Name: Q1, Length: 100, dtype: bool
'''
会有一个由真假值组成的数据,筛选后的结果就是为 True 的内容。