本文承接上一篇《Pandas 数据选取》,聚焦业务里最常用的条件过滤、多条件组合、模糊匹配、范围查询、去重过滤、空值过滤、query、mask、where,区分过滤返回新数据、标记筛选、条件替换,大量实战示例,统一教程风格。
前置导入与示例数据
import pandas as pd
import numpy as np
df = pd.DataFrame({
"name": ["张三", "李四", "王五", "赵六", "孙七", "周八"],
"age": [22, 25, 30, 28, 35, None],
"city": ["杭州", "上海", "杭州", "北京", "上海", "杭州"],
"salary": [8000, 12000, 15000, 11000, 18000, 9500],
"level": ["初级", "中级", "高级", "中级", "高级", "初级"]
})
print(df)
一、基础布尔条件过滤
布尔表达式会生成一个同长度的 True/False Series,True 的行会被保留。
# 单条件:薪资大于10000
cond = df["salary"] > 10000
res = df[cond]
# 等价 loc 写法(推荐,支持同时指定输出列)
res = df.loc[df["salary"] > 10000]
# 只返回指定列
res = df.loc[df["salary"] > 10000, ["name", "salary"]]
二、多条件组合(& | ~)
重点规则:
- 每个独立条件必须用括号包裹
&= 并且;|= 或者;~= 取反- 不要用
and / or,它们不支持向量化运算
# 并且:年龄>25 并且 城市是杭州
cond = (df["age"] > 25) & (df["city"] == "杭州")
res = df.loc[cond]
# 或者:薪资>15000 或者 级别是初级
cond = (df["salary"] > 15000) | (df["level"] == "初级")
res = df.loc[cond]
# 取反:不是上海的员工
cond = ~(df["city"] == "上海")
res = df.loc[cond]
三、包含匹配:isin()
适合枚举值、多选筛选,替代多个 == | ==
# 城市在【杭州、上海】
res = df.loc[df["city"].isin(["杭州", "上海"])]
# 取反:不在列表内
res = df.loc[~df["city"].isin(["北京"])]
支持Series、列表、数组传入,适合下拉维度字段过滤。
四、模糊文本匹配:str 系列
前提:字段为 string / object 类型;category 类型需要先转 string
# 包含"五"
res = df.loc[df["name"].str.contains("五", na=False)]
# 以"张"开头
res = df.loc[df["name"].str.startswith("张")]
# 以"七"结尾
res = df.loc[df["name"].str.endswith("七")]
# 正则匹配,忽略大小写
res = df.loc[df["name"].str.contains(r"[王赵]", case=False, na=False)]
参数 na=False:空值直接视为False,避免匹配报错。
五、范围查询:between
数值、日期范围非常好用,闭区间,包含两端
# 薪资 10000 ~ 16000
res = df.loc[df["salary"].between(10000, 16000)]
# 等价写法
res = df.loc[(df["salary"] >= 10000) & (df["salary"] <= 16000)]
六、空值过滤(isna / notna)
# 年龄为空的行
res = df.loc[df["age"].isna()]
# 年龄不为空的行(常用)
res = df.loc[df["age"].notna()]
七、query() 表达式查询(简洁写法)
适合条件多、可读性优先的场景,支持 and / or / not,变量用 @变量名 引用
# 基础写法
res = df.query("salary > 10000 and city == '上海'")
# 外部变量传入
min_sal = 12000
res = df.query("salary >= @min_sal")
# in 语法
res = df.query("city in ['杭州','北京']")
# 取反 not in
res = df.query("city not in ['北京']")
缺点:列名有空格、特殊字符时写起来麻烦;性能略低于原生布尔索引。
八、where / mask:条件保留、条件替换(和筛选不一样)
普通过滤是删除不满足条件的行;where/mask 行数不变,不满足条件的值填充NaN
where(cond):满足条件保留原值,不满足 → NaNmask(cond):满足条件 → NaN,不满足保留原值(和where相反)
# 薪资大于10000保留,其余变NaN
df["salary_where"] = df["salary"].where(df["salary"] > 10000)
# 薪资大于10000置空,其余保留
df["salary_mask"] = df["salary"].mask(df["salary"] > 10000)
# 自定义填充值
df["salary_fill"] = df["salary"].where(df["salary"] > 10000, other=10000)
九、过滤重复行:duplicated + drop_duplicates
# 标记重复行(第一次出现不算重复)
df["is_dup"] = df.duplicated(subset=["city", "level"])
# 只保留不重复的行
res = df.drop_duplicates(subset=["city", "level"], keep="first")
# keep参数:first保留第一个,last保留最后一个,False全部删除
十、过滤后直接修改(避坑)
优先使用 loc 条件赋值,避免 SettingWithCopyWarning
# 杭州员工薪资上浮500
df.loc[(df["city"] == "杭州") & df["salary"].notna(), "salary"] += 500
十一、条件过滤函数:filter()
⚠️ 注意:df.filter() 不是行过滤!是按列名/索引名筛选,很多人踩坑
# 列名包含sal
res = df.filter(regex="sal")
# 行索引匹配
res = df.filter(items=[0,2], axis=0)
行条件过滤不要用 filter,用 loc / query。
十二、过滤选型对照表
| 方式 | 用途 | 特点 |
|---|---|---|
| 布尔索引 + loc | 绝大多数业务过滤 | 性能最好,支持赋值,推荐首选 |
| isin | 枚举多选 | 多值匹配,代码简洁 |
| between | 区间范围 | 数值、日期范围专用 |
| str.contains | 文本模糊 | 正则、前缀后缀匹配 |
| query | 复杂多条件 | 可读性强,支持@变量 |
| where / mask | 按条件置空/替换 | 行数不变,用于打标记、分桶 |
| drop_duplicates | 去重过滤 | 删除重复行 |
| filter() | 列/索引名称筛选 | 不做行条件过滤,容易混淆 |
十三、高频坑清单
- 多条件忘记括号,
&优先级高于比较运算符,直接报错 - 混用
and / or代替& / |,报真值模糊错误 - str.contains 遇到NaN直接报错,必须加
na=False - 混淆
df.filter()(列筛选)和条件行过滤 - query 里面字符串引号冲突:外层单引号内层双引号,或者反过来
- 过滤后直接链式赋值,触发 SettingWithCopyWarning
- between 是闭区间,不要当成左闭右开
十四、综合实战模板
def filter_employee(df, min_salary, city_list):
"""
综合过滤:薪资≥min_salary,城市在city_list,年龄不为空
"""
cond = (
(df["salary"] >= min_salary)
& (df["city"].isin(city_list))
& df["age"].notna()
)
return df.loc[cond, ["name", "age", "city", "salary", "level"]]
res = filter_employee(df, 10000, ["杭州", "上海"])
print(res)
0 条笔记