布尔索引与条件筛选
本教程共 54 篇 · 第 20 篇 · 更新于 2026-08-11 · 约 8 分钟阅读
本节目标:学会用比较运算和 &、|、~ 组合条件筛选数据,掌握 isin、between 和字符串条件的用法。
比较运算:先造出”筛子”
筛选的核心是布尔索引:用一个跟 DataFrame 等长的 True/False 序列,把 True 的行留下。
先看比较运算怎么造出这个序列:
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五", "赵六", "钱七"],
"年龄": [25, 30, 28, 35, 22],
"城市": ["北京", "上海", "广州", "北京", "深圳"],
"薪资": [12000, 15000, 11000, 18000, 9000],
})
mask = df["年龄"] > 28
print(mask)
输出是一串 True/False,每个值对应一行。把它放进 df[...] 里,就完成了筛选:
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五", "赵六", "钱七"],
"年龄": [25, 30, 28, 35, 22],
"薪资": [12000, 15000, 11000, 18000, 9000],
})
mask = df["年龄"] > 28
print(df[mask]) # 年龄大于 28 的行
print(df[df["薪资"] >= 12000]) # 直接写也行
等于、不等于、大于、小于都支持:==、!=、>、<、>=、<=。
几个条件的等价写法可以互相替换,看哪种顺眼用哪种:
| 写法 | 含义 | 等价写法 |
|---|---|---|
| isin([“北京”, “上海”]) | 属于集合 | == “北京” | == “上海” |
| between(25, 30) | 闭区间 | >= 25 & <= 30 |
| ~(条件) | 取反 | != 或 反向条件 |
有一个细节要注意:NaN 和任何值比较都是 False,包括它自己。判断缺失要用 isna(),不能写 == np.nan:
import pandas as pd
import numpy as np
s = pd.Series([1, np.nan, 3])
print(s == np.nan) # 全是 False
print(s.isna()) # 这才是判断缺失的正确方式
多条件:& 与 | 与 ~
多个条件组合时,用位运算符:
&:并且|:或者~:取反
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五", "赵六", "钱七"],
"年龄": [25, 30, 28, 35, 22],
"城市": ["北京", "上海", "广州", "北京", "深圳"],
"薪资": [12000, 15000, 11000, 18000, 9000],
})
# 北京 且 薪资大于 10000
print(df[(df["城市"] == "北京") & (df["薪资"] > 10000)])
# 广州 或 薪资大于 15000
print(df[(df["城市"] == "广州") | (df["薪资"] > 15000)])
# 不是北京(也可以写成 df["城市"] != "北京")
print(df[~(df["城市"] == "北京")])
Warning必须用
&和|,不能用 Python 的and、or。原因:and/or 要求两边是单个布尔值,而这里两边是 Series。另外每个条件都要用括号括起来,否则运算优先级会出错。这是 pandas 新手报错率最高的地方。
isin:值在列表里
判断”是否属于某个集合”,用 isin,比一串 == 加 | 清爽得多:
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五", "赵六", "钱七"],
"城市": ["北京", "上海", "广州", "北京", "深圳"],
})
print(df[df["城市"].isin(["北京", "上海"])])
# 取反:不在列表里
print(df[~df["城市"].isin(["北京", "上海"])])
isin 还能同时查多列:先对多列做 isin 得到全是布尔值的 DataFrame,再按行判断”每一列都命中”:
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五", "赵六"],
"城市": ["北京", "上海", "广州", "北京"],
"部门": ["技术", "销售", "技术", "运营"],
})
target = pd.DataFrame({"城市": ["北京", "上海"], "部门": ["技术", "销售"]})
mask = df[["城市", "部门"]].isin(target).all(axis=1)
print(df[mask])
between:数值区间
闭区间判断,两头都包含:
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五", "赵六", "钱七"],
"年龄": [25, 30, 28, 35, 22],
})
print(df[df["年龄"].between(25, 30)])
# 等价写法
print(df[(df["年龄"] >= 25) & (df["年龄"] <= 30)])
字符串条件:.str 系列
对文本列做条件判断,用 .str 访问器:
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五", "赵六", "钱七"],
})
print(df[df["姓名"].str.contains("三")]) # 包含
print(df[df["姓名"].str.startswith("张")]) # 开头是
print(df[df["姓名"].str.endswith("五")]) # 结尾是
str.contains 支持正则表达式,复杂模式也能写:
import pandas as pd
import numpy as np
df = pd.DataFrame({"城市": ["北京市", "上海市", "广州市", "深圳市", "杭州市"]})
print(df[df["城市"].str.contains("^北|^上")]) # 以"北"或"上"开头
.str 系列常用判断方法:
| 方法 | 含义 |
|---|---|
| str.contains(“x”) | 包含子串或正则 |
| str.startswith(“x”) | 以 x 开头 |
| str.endswith(“x”) | 以 x 结尾 |
| str.match(“x”) | 从头匹配正则 |
Tip列里有缺失值时,
str.contains的行为要注意:str类型下缺失值返回 False;object类型下会得到含 NaN 的掩码,直接当筛选条件会报错。稳妥写法是显式加na=False:df["姓名"].str.contains("三", na=False)。
条件 + 列选择:loc 一步到位
筛出符合条件的行,再只取某些列,用 loc 最顺手:
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五", "赵六", "钱七"],
"年龄": [25, 30, 28, 35, 22],
"城市": ["北京", "上海", "广州", "北京", "深圳"],
})
print(df.loc[df["年龄"] > 28, ["姓名", "城市"]])
给符合条件的行赋值也用它:
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五", "赵六", "钱七"],
"年龄": [25, 30, 28, 35, 22],
"薪资": [12000, 15000, 11000, 18000, 9000],
})
# 给年龄大于 28 的人加薪 1000
df.loc[df["年龄"] > 28, "薪资"] += 1000
print(df)
Notepandas 3.0 默认开启写时复制(Copy-on-Write,如 §09 所述),上面的写法才是安全的赋值方式。旧教程里”先筛再改”的链式写法在新版会失效,看到就换成 loc。
筛选结果还能接着用
筛选出来的 DataFrame 可以继续接任何方法,比如筛完再排序、取前三名:
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五", "赵六", "钱七"],
"年龄": [25, 30, 28, 35, 22],
"薪资": [12000, 15000, 11000, 18000, 9000],
"部门": ["技术", "销售", "技术", "运营", "技术"],
})
# 技术或销售部门里薪资最高的 3 人
cond = df["部门"].isin(["技术", "销售"])
print(df[cond].sort_values("薪资", ascending=False).head(3))
复杂条件先存成变量,代码可读性会好很多。想用更接近自然语言的方式写条件,下一节介绍的 query 方法更合适(如 §21 所述)。
再补两个实用判断:any 和 all
布尔 Series 上还有两个常用的收尾操作:
any():有没有任何一个 Trueall():是不是全部 True
import pandas as pd
import numpy as np
mask = pd.Series([True, False, True])
print(mask.any()) # True,至少有一个 True
print(mask.all()) # False,不是全部 True
典型场景是配合 isin 检查多列:
import pandas as pd
import numpy as np
df = pd.DataFrame({"姓名": ["张三", "李四"], "城市": ["北京", "上海"]})
# 每行:姓名含"三" 且 城市是北京
check = df["姓名"].str.contains("三", na=False) & df["城市"].isin(["北京"])
print(check.all()) # 检查所有行是否都满足
筛选完行数会变,索引还是原来的。想重新编号,接 reset_index(drop=True)(如 §23 所述)。
多条件的优先级
多个条件混合时,& 的优先级高于 |,和数学里”乘除先于加减”一个道理。不写括号,A & B | C 会被解释成 (A & B) | C,很可能不是你想要的。所以规则很简单:每个条件一对括号,组合符号放外面。
import pandas as pd
import numpy as np
df = pd.DataFrame({"年龄": [25, 30, 28, 35], "薪资": [12000, 15000, 11000, 18000]})
# 推荐:每个条件都加括号,优先级一目了然
cond = (df["年龄"] > 28) & (df["薪资"] > 10000) | (df["年龄"] < 25)
print(df[cond])
一眼就能看出顺序是:先算两个括号里的,再 &,最后 |。
筛选和索引是分析的基础动作,练熟之后,后面的数据清洗、分组统计都会顺畅很多。
筛选的本质是”按布尔数组取行”,所以 df[mask] 和 df.loc[mask] 等价,结果都是新对象,原 df 不会变。两者差别在于:df.loc[mask] 还能同时选列(df.loc[mask, "薪资"]),df[mask] 只能选行。
新手最常见的三个错误
- 用
and/or连接条件 → 直接报错,换成&/| - 忘了括号 → 结果诡异但不报错,检查每个条件有没有括号
- 列名拼错 → KeyError,用
df.columns核对一遍
错误信息看不懂时,先把条件拆开单独跑:df["年龄"] > 28 单独输出看看是不是一串 True/False,再逐步组合。