自学教程 学编程从这里开始

Pandas 过滤与条件查询

本文承接上一篇《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"]]

二、多条件组合(& | ~)

重点规则:

  1. 每个独立条件必须用括号包裹
  2. & = 并且;| = 或者;~ = 取反
  3. 不要用 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):满足条件保留原值,不满足 → NaN
  • mask(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()列/索引名称筛选不做行条件过滤,容易混淆

十三、高频坑清单

  1. 多条件忘记括号,& 优先级高于比较运算符,直接报错
  2. 混用 and / or 代替 & / |,报真值模糊错误
  3. str.contains 遇到NaN直接报错,必须加 na=False
  4. 混淆 df.filter()(列筛选)和条件行过滤
  5. query 里面字符串引号冲突:外层单引号内层双引号,或者反过来
  6. 过滤后直接链式赋值,触发 SettingWithCopyWarning
  7. 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 条笔记