简介:本文系统梳理 Python 表格数据处理中的逻辑筛选方法,从布尔索引的基本原理讲起,覆盖中括号切片、.loc 与 .iloc 三种筛选入口,讲解等值、区间、集合、缺失值与多条件组合等高频场景,并总结常见误区与性能建议,帮助读者写出准确、高效、可维护的筛选代码。
在数据分析与处理的日常工作中,“从大表中挑出满足条件的行”是出现频率最高的操作之一:取数、清洗、校验、抽样,几乎每个环节都离不开条件筛选。本文围绕 Python 生态中通用的表格数据结构(DataFrame),系统梳理逻辑筛选的原理、常用写法与典型误区,帮助读者建立一套准确、高效的筛选方法论。
为便于演示,先约定示例数据:一张包含 100 名成员的成绩表 df,含 name(姓名)、team(团队)与 Q1~Q4(四个季度成绩)共 6 列。
无论写法多么复杂,逻辑筛选的本质只有两步:
示意代码:
mask = df['Q1'] > 60 # 第一步:整列比较,生成布尔向量result = df[mask] # 第二步:用掩码筛选出满足条件的行
理解两个关键点:
df[df['Q1'] == 8] # Q1 等于 8df[df['Q1'] != 8] # Q1 不等于 8(等价于 df[~(df['Q1'] == 8)])df[df['Q1'] >= 60] # Q1 大于等于 60df[df['name'] == 'Ben'] # 姓名为 Bendf[df['Q1'] > df['Q2']] # 列间比较:一季度成绩高于二季度
最后一个示例常被忽视:比较的右侧不必是常量,也可以是另一列,“环比、同比”类需求可以一行代码直接表达。
多条件组合必须使用位运算符 &(且)、|(或)、~(非):
df[(df['Q1'] > 80) & (df['Q2'] < 15)] # 两个条件同时成立df[(df['Q1'] > 90) | (df['Q2'] < 90)] # 任意一个条件成立df[~(df['Q1'] == 8)] # 取反:不等于 8
两条铁律:
df[df['Q1'].between(60, 90)] # 区间筛选:60 ≤ Q1 ≤ 90(默认含两端)df[df['team'].isin(['A', 'C'])] # 集合筛选:team 属于 A 或 Cdf[df['Q1'].isna()] # Q1 为缺失值的行df[df['Q1'].notna()] # Q1 非缺失的行df[df['name'].str.startswith('B')] # 文本筛选:姓名以 B 开头
两点提醒:
主流实现普遍提供表达式风格的筛选接口,用一段字符串描述条件,更接近自然语言:
df.query('Q1 > 80 and Q2 < 15') # 表达式内可直接使用 and/ordf.query('Q1 > Q2') # 同样支持列间比较
这种写法省去了反复书写 df[‘…’] 的成本,条件较多时可读性更好,可按团队习惯选用。
掩码生成后,交给哪种索引器也有讲究,三者能力不同。
df[df['Q1'] == 8]
.loc[行选择器, 列选择器],两个维度都可接收布尔条件:
df.loc[df['Q1'] == 8] # 行条件筛选,返回整行df.loc[df['Q1'] > 90, 'Q1':] # 行:Q1>90;列:从 Q1 到最后一列df.loc[df['Q1'] > 90, 'Q1'] # 只保留 Q1 一列(去掉冒号)df.loc[(df['Q1'] > 80) & (df['Q2'] < 15)] # 且关系df.loc[(df['Q1'] > 90) | (df['Q2'] < 90)] # 或关系df.loc[df['name'] == 'Ben', ['name', 'Q1']] # 行条件 + 指定列
注意 ‘Q1’: 与 ‘Q1’ 的区别:前者是标签切片,表示“Q1 及其后的所有列”;后者只取 Q1 单列。初学者常把两者混淆,导致取出的列比预期多或少。
.loc 的最大优势是“筛选、选列、赋值”一条语句完成:
df.loc[df['Q1'] < 60, 'level'] = '待提升' # 对满足条件的行批量写入新列
.iloc 只认整数位置,适合与数据内容无关的取数,例如查看前几行、导出固定列区间:
df.iloc[0:5, 0:3] # 前 5 行、前 3 列(位置切片右端不包含)df.iloc[-5:, :] # 最后 5 行
条件筛选优先使用 .loc;若确需在 .iloc 中使用布尔条件,稳妥做法是先把布尔向量转换为等长的布尔数组再传入。日常实践中二者分工明确:.loc 管“按条件与标签”,.iloc 管“按位置序号”。
误区一:用 and/or 连接条件,会直接报错。口诀是“向量条件用 & | ~,每个条件加括号”。
误区二:对筛选结果直接赋值。
df[df['Q1'] > 90]['level'] = '优秀' # 错误:作用于临时副本,原表不变df.loc[df['Q1'] > 90, 'level'] = '优秀' # 正确:筛选与赋值一步完成
第一种属于“链式索引”:先筛行得到中间结果,再对中间结果选列赋值,修改落在临时对象上,通常还会触发“在副本上设置值”的警告。原则:凡涉及“筛选 + 修改”,一律用 .loc[条件, 列] = 值 单步完成。
误区三:用 == 判断缺失。df[df[‘Q1’] == None] 几乎必然得到空结果,正确写法是 isna()。
误区四:忽略副本与视图的歧义。筛选结果如果后续还要加工,建议显式复制:
sub = df[df['team'] == 'A'].copy() # 明确拿到独立副本,避免歧义
误区五:掩码与表行数不一致。布尔向量必须与行数严格等长且顺序对应,否则会直接报索引错误;从外部拼装布尔条件时,务必先校验长度与对齐关系。
mask_q1 = df['Q1'] > 80mask_q2 = df['Q2'] < 15result = df[mask_q1 & mask_q2]
def high_performers(data, q1=80, q2=15):"""返回 Q1 超过 q1 且 Q2 低于 q2 的成员"""return data[(data['Q1'] > q1) & (data['Q2'] < q2)]
逻辑筛选可以浓缩为一个公式:生成布尔掩码,交给索引器取行。三句话带走本文要点:
熟练掌握这些常用操作及其背后的布尔索引原理后,绝大多数“按条件取数”的需求都能快速、准确地落地,也为后续的分组统计、透视分析与可视化打下扎实基础。