在Pandas中,根据条件删除行本质上是先构造一个布尔序列,用True标记需要保留或删除的行,再利用该序列对DataFrame进行筛选。常见做法包括使用布尔索引直接选择符合条件的行,或者先找出不符合条件的行索引,再调用drop方法删除。下面从最基础的布尔索引开始,逐步覆盖各种条件组合和更高效的方法。

布尔索引:最直接的条件筛选方式
布尔索引是Pandas中最常用的条件筛选手段。它的核心思路是:对DataFrame的某一列或整个DataFrame应用比较运算,得到一个与行数相同的布尔Series,然后将这个布尔Series放入方括号中,Pandas会保留布尔值为True的行,False的行被自动过滤掉。
假设有一个员工信息表,包含姓名、年龄、部门和薪资四列。要筛选出年龄大于30岁的员工,可以这样写:
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eva'],
'age': [25, 35, 28, 42, 31],
'department': ['HR', 'IT', 'Finance', 'IT', 'HR'],
'salary': [5000, 8000, 6000, 9000, 5500]
})
# 筛选年龄大于30岁的行
result = df[df['age'] > 30]
print(result)
这段代码中,df['age'] > 30会生成一个布尔Series:False, True, False, True, True。将其传入df[...]后,只保留对应为True的三行。这种方法的好处是语法简洁、可读性强,适合大多数日常筛选需求。
当需要同时满足多个条件时,可以使用&(与)、|(或)和~(非)运算符。注意每个条件必须用圆括号包裹,否则会因运算符优先级问题报错。例如筛选年龄大于30且部门为IT的员工:
# 多条件组合:年龄大于30且部门为IT result = df[(df['age'] > 30) & (df['department'] == 'IT')] print(result)
布尔索引也可以用于选择行而不是删除行。如果需求是“删除年龄小于等于30岁的行”,其实等价于“保留年龄大于30岁的行”,所以直接用df[df['age'] > 30]即可。只有在需要明确表达“删除”语义时,才会用到后面介绍的drop方法。
使用drop方法按条件删除行
如果业务逻辑更倾向于“删除不符合条件的行”,可以先通过条件筛选出需要删除的行的索引,再调用drop方法。这种方式的好处是删除意图更明确,尤其在处理大型DataFrame时,可以先拿到索引列表再统一删除。
例如要删除薪资低于6000的员工,可以先用布尔索引获取这些行的索引,然后调用df.drop(index):
# 获取薪资低于6000的行的索引 drop_index = df[df['salary'] < 6000].index # 删除这些行 df_cleaned = df.drop(drop_index) print(df_cleaned)
这里df['salary'] < 6000返回布尔Series,df[...]筛选出薪资低于6000的行,再取.index得到行标签。如果原DataFrame的索引不是默认的0到N-1,这一步仍然能准确获取需要删除的行标签。
另一种常见的删除场景是基于索引值或行号。比如要删除索引为2和4的行,可以直接传入列表:df.drop([2, 4])。如果要按位置删除,可以组合iloc获取索引后再删除。但按条件删除时,先筛选索引再drop是最直观的。需要留意的是,drop默认返回新对象,不会修改原DataFrame,除非设置inplace=True。但官方建议尽量避免使用inplace,以保持数据操作的链式风格和可预测性。
drop方法还支持axis参数,但删除行时axis=0是默认值,无需额外指定。如果传入的索引不存在,会抛出KeyError;可以通过设置errors='ignore'忽略不存在的索引,避免中断执行。
query方法:用字符串表达式简化条件
对于包含多个复杂条件的场景,Pandas提供了query方法,允许使用类似SQL的字符串表达式进行筛选。它的优势是语法更接近自然语言,可以避免大量圆括号的嵌套,代码读起来更清爽。
例如要筛选部门为HR且薪资大于等于5500的员工,用布尔索引需要写成df[(df['department'] == 'HR') & (df['salary'] >= 5500)],而用query可以写成:
result = df.query("department == 'HR' and salary >= 5500")
print(result)
在query表达式内部,列名可以直接引用,字符串值需要加引号。逻辑运算符可以使用and、or、not,也可以使用&、|、~,但推荐使用英文单词以提升可读性。query方法底层通过numexpr进行优化,对于大型DataFrame往往比普通的布尔索引更快。
query还支持在表达式中引用外部变量,只需在变量名前加@符号。例如将阈值存入变量后进行筛选:
min_salary = 6000
result = df.query("salary >= @min_salary")
print(result)
这种方法非常适用于动态条件场景,比如在函数中根据传入参数过滤数据。不过query的缺点是表达式是字符串,无法利用IDE的自动补全,且调试起来不如原生Python代码直观。如果条件中包含复杂函数调用或非常规列名(如列名包含空格),query可能无法直接处理,此时需要退回布尔索引或使用反引号转义列名。
条件删除中的常见陷阱与性能考量
在实际使用中,有几个容易出错的地方需要特别注意。首先是运算符优先级问题:在布尔索引中,&的优先级高于比较运算符,因此每个条件必须用括号包裹。忘记括号会触发类似The truth value of a Series is ambiguous的报错。例如df[df['age'] > 30 & df['department'] == 'IT']是错误的,必须写成df[(df['age'] > 30) & (df['department'] == 'IT')]。
其次是inplace=True的误用。很多教程会使用df.drop(index, inplace=True)直接修改原DataFrame,但这种方式可能造成难以追踪的副作用,尤其在函数内部修改传入的DataFrame时。更好的做法是显式赋值给新变量,或者使用链式操作。
性能方面,对于中小型数据集(几万行以内),布尔索引和query的差异几乎可以忽略。但当数据量达到百万行级别时,query通常比普通布尔索引更快,因为它会在底层使用numexpr进行并行计算和内存优化。此外,如果只需要删除少量行,先获取索引再drop会比较高效;如果删除比例很高,直接使用布尔索引保留目标行反而更简洁。另一个优化点是避免重复计算相同的布尔条件,可以将布尔Series先存为变量复用。
如果条件涉及字符串匹配,可以使用str访问器,例如df[df['name'].str.startswith('A')]。对于缺失值,可以使用isna()或notna()进行过滤,比如删除薪资为空的行:df[df['salary'].notna()]。这些方法同样遵循布尔索引的机制,只是条件构造方式不同。
最后,针对需要同时查看删除的行和保留的行的场景,可以先构造布尔掩码,然后分别取反:mask = df['age'] > 30,保留行为df[mask],删除行为df[~mask]。这样一次条件计算即可得到两份数据,逻辑清晰且高效。

