【零基础玩转Python】Day41:条件筛选与查询 – query, isin, between,写出更优雅的筛选条件
大家好,我是[知识充电宝的灵感日记]。
前面我们用布尔索引(df[df['列'] > 值])已经能做很多筛选。但当条件变多、变复杂时,这种写法可能变得冗长。今天我们来学习三种更优雅的筛选方法:query()、isin()、between(),让代码更可读、更简洁。
今天的目标:
- ✅ 使用
query()写出类似 SQL 的筛选语句
难度:⭐⭐(语法简单,但 query 需要适应字符串写法)
一、准备工作
import pandas as pd
df = pd.DataFrame({
'姓名': ['张三', '李四', '王五', '赵六', '小明', '小红'],
'年龄': [18, 20, 19, 22, 21, 19],
'成绩': [85, 92, 78, 88, 95, 82],
'城市': ['北京', '上海', '广州', '北京', '上海', '广州'],
'是否通过': [True, True, False, True, True, False]
})
print(df)
姓名 年龄 成绩 城市 是否通过
0 张三 18 85 北京 True
1 李四 20 92 上海 True
2 王五 19 78 广州 False
3 赵六 22 88 北京 True
4 小明 21 95 上海 True
5 小红 19 82 广州 False
二、isin():筛选属于某个集合的行
isin()用于判断某列的值是否在指定的列表/集合中。
# 筛选城市为 '北京' 或 '上海' 的学生
cities = ['北京', '上海']
filtered = df[df['城市'].isin(cities)]
print(filtered)
姓名 年龄 成绩 城市 是否通过
0 张三 18 85 北京 True
1 李四 20 92 上海 True
3 赵六 22 88 北京 True
4 小明 21 95 上海 True
取反:用 ~ 符号,筛选不在集合中的行。
# 不是北京也不是上海
filtered = df[~df['城市'].isin(cities)]
print(filtered) # 王五、小红
姓名 年龄 成绩 城市 是否通过
2 王五 19 78 广州 False
5 小红 19 82 广州 False
三、between():筛选区间内的数值
between(left, right, inclusive='both')用于筛选某列值在 [left, right] 区间内的行(默认包含边界)。
# 筛选成绩在 80 到 90 分之间的学生(包含边界)
filtered = df[df['成绩'].between(80, 90)]
print(filtered)
姓名 年龄 成绩 城市 是否通过
0 张三 18 85 北京 True
3 赵六 22 88 北京 True
5 小红 19 82 广州 False
参数 inclusive='left'或 'right'可控制开闭区间。
四、query():SQL 风格的筛选
query()方法允许你使用字符串表达式进行筛选,语法简洁,尤其适合多条件组合。
基本用法
# 筛选成绩大于 80 的学生
filtered = df.query('成绩 > 80')
print(filtered)
姓名 年龄 成绩 城市 是否通过
0 张三 18 85 北京 True
1 李四 20 92 上海 True
3 赵六 22 88 北京 True
4 小明 21 95 上海 True
5 小红 19 82 广州 False
多条件:与 (& 或 and)、或 (| 或 or)
# 成绩大于 80 且年龄小于 21
filtered = df.query('成绩 > 80 and 年龄 < 21')
# 或者用 & 符号(注意字符串内也要写 &)
filtered2 = df.query('成绩 > 80 & 年龄 < 21')
使用变量:在表达式前加 @ 引用外部变量
min_score = 85
filtered = df.query('成绩 >= @min_score')
print(filtered)
姓名 年龄 成绩 城市 是否通过
0 张三 18 85 北京 True
1 李四 20 92 上海 True
3 赵六 22 88 北京 True
4 小明 21 95 上海 True
字符串列的条件
# 城市为北京或上海
filtered = df.query("城市 in ['北京', '上海']")
# 或使用 @ 引用列表
cities = ['北京', '上海']
filtered = df.query("城市 in @cities")
布尔列直接筛选
# 筛选通过的学生
filtered = df.query('是否通过 == True')
# 或直接写列名(因为本身就是布尔)
filtered = df.query('是否通过')
模糊匹配(使用字符串方法)
# query 中可以使用字符串方法,但需要调用
df = pd.DataFrame({'姓名': ['张三', '张飞', '李四']})
filtered = df.query('姓名.str.startswith("张")')
五、三种方法对比
建议:简单条件用布尔索引;多条件或复杂字符串条件用 query()。
六、实战小案例
案例1:筛选电商订单数据
orders = pd.DataFrame({
'订单号': [101, 102, 103, 104, 105],
'金额': [150, 80, 200, 45, 300],
'状态': ['已完成', '已取消', '已完成', '已完成', '待付款'],
'用户等级': ['A', 'B', 'A', 'C', 'A']
})
# 1. 金额在 100 到 250 之间
filtered = orders.query('金额.between(100, 250)')
# 2. 状态为'已完成' 且 用户等级为'A' 或 'B'
filtered = orders.query("状态 == '已完成' and 用户等级 in ['A', 'B']")
# 3. 订单号不是 103,105
filtered = orders.query('订单号 not in [103, 105]')
print(filtered)
订单号 金额 状态 用户等级
0 101 150 已完成 A
1 102 80 已取消 B
3 104 45 已完成 C
案例2:筛选泰坦尼克数据集的部分乘客
# 加载数据
df = pd.read_csv('https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv')
# 筛选:年龄在 20~40 之间,票价大于 30,且舱位为 1 或 2 等
filtered = df.query('20 <= Age <= 40 and Fare > 30 and Pclass in [1, 2]')
print(filtered.shape)
案例3:结合 query 和变量
min_age = 18
max_age = 30
min_score = 80
filtered = df.query('@min_age <= 年龄 <= @max_age and 成绩 >= @min_score')
print(filtered)
七、今日练习
使用上面的示例 DataFrame df,完成以下筛选:
- b) 成绩在 80 到 90 分之间(包含边界)的学生
- c) 成绩大于 80 且城市不是广州的学生(用 query 实现)
用 isin() 筛选出城市为 '北京' 或 '上海' 的男生(假设有 '性别' 列,可自己添加)。
df = pd.DataFrame({
'产品': ['A', 'B', 'C', 'D'],
'销量': [150, 80, 200, 45],
'库存': [20, 15, 0, 8]
})
用 query 筛选出:销量大于 100 且库存大于 0 的产品。
- (挑战)从以下 URL 加载数据集
df = pd.read_csv('https://raw.githubusercontent.com/mwaskom/seaborn-data/master/tips.csv'),用 query 筛选出:
- 总消费(total_bill)大于 20 且小于 40
并输出符合条件的行数。
八、常见错误与提示
- query 中列名包含空格:需要用反引号括起来,如
df.query('列 名'] > 1")`。 - query 中字符串必须用引号:如 "城市 == '北京'",不能省略。
- isin 括号内是列表:注意多写一层括号,如
df['列'].isin([1,2,3])。 - between 默认包含边界:可通过
inclusive参数调整。 - query 性能:对于大型 DataFrame,query 通常比布尔索引略快(使用 numexpr 引擎)。
- 取反操作:布尔索引用
~,query中用 not或 !=。
九、明日预告
Day42:数据排序与排名 – sort_values, rank
我们将学习如何对 DataFrame 按单列或多列排序,以及如何计算排名。