本节介绍通过表达式、条件替换与抽样完成数据选择与过滤的常用工具。


1. query()

query() 使用字符串表达式过滤 DataFrame 行,语法接近自然语言,底层经 NumExpr 加速,性能优秀。

基本语法

df.query('表达式')

常用操作

df = pd.DataFrame({
    '姓名': ['张三', '李四', '王五', '赵六'],
    '年龄': [25, 30, 35, 40],
    '城市': ['北京', '上海', '北京', '广州']
})
 
# 单条件
df.query('年龄 > 30')
df.query('城市 == "北京"')        # 字符串用引号
 
# 多条件(用 and / or / not)
df.query('年龄 > 30 and 城市 == "北京"')
df.query('年龄 > 30 or 城市 == "上海"')
df.query('not 城市 == "北京"')
 
# 比较运算符
df.query('年龄 >= 35')
df.query('年龄 != 25')
 
# in / not in
df.query('城市 in ["北京", "上海"]')
df.query('城市 not in ["北京"]')
 
# 表达式运算
df.query('年龄 * 2 > 60')
df.query('年龄 + 5 < 40')
 
# 列名含空格或非标识符:使用反引号
df2 = df.rename(columns={'城市': '所在 城市'})
df2.query('`所在 城市` == "北京"')

外部变量引用

使用 @ 前缀引用 Python 变量:

min_age = 28
target_cities = ['北京', '上海']
 
df.query('年龄 > @min_age')
df.query('城市 in @target_cities')

query() 与布尔索引对比

方式写法可读性性能
布尔索引df[(df['年龄'] > 30) & (df['城市'] == '北京')]冗长一般
querydf.query('年龄 > 30 and 城市 == "北京"')简洁更快(NumExpr)

query 优点

  • 列名直接在表达式中引用,无需重复写 df['col']
  • 底层使用 NumExpr 加速,大数据集上优势明显
  • 特别适合交互式探索

query 限制

  • 不能直接调用 Python 内置函数(需通过 @ 传入)
  • 普通索引名不能直接引用,MultiIndex 层级名可用
  • 表达式中的字符串必须用引号包裹

2. eval()

eval() 使用字符串表达式对 DataFrame 进行运算和列赋值。

基础用法

df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})
 
# 计算新列(返回新对象)
df.eval('C = A + B')
#    A  B  C
# 0  1  4  5
# 1  2  5  7
# 2  3  6  9
 
# 原地修改
df.eval('C = A + B', inplace=True)
 
# 多表达式(用分号分隔)
df.eval('D = A - B; E = A * B', inplace=True)

表达式语法

# 算术运算
df.eval('C = A ** 2 + B / 2')
 
# 比较运算返回布尔列
df.eval('F = A > B')
 
# 引用外部变量
x = 10
df.eval('G = A + @x')     # 使用 @ 前缀
 
# 调用自定义函数
def my_func(v): return v * 2
df.eval('H = A.apply(@my_func)')

eval() 与 query() 的关系

函数用途
query()过滤行:df.query('A > B')
eval()表达式求值与新列计算:df.eval('C = A + B')

eval 注意

  • eval() 默认返回新对象(除非 inplace=True)
  • 对 MultiIndex 可先 reset_index() 再 eval
  • numexpr 可选安装(pip install numexpr),可进一步提升性能

3. where()

where() 保留满足条件的值,不满足条件的位置替换为 other(默认 NaN)。

Series.where()

s = pd.Series([1, 2, 3, 4, 5])
 
s.where(s > 3)
# 0    NaN
# 1    NaN
# 2    NaN
# 3    4.0
# 4    5.0
 
s.where(s > 3, other=0)   # 替换为 0
# 0    0
# 1    0
# 2    0
# 3    4
# 4    5
 
s.where(s > 3, -1, inplace=True)  # 原地修改

DataFrame.where()

df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})
 
# 条件为 False 的单元格替换为 NaN
df.where(df > 2)
#      A    B
# 0  NaN  4.0
# 1  NaN  5.0
# 2  3.0  6.0
 
# 替换为标量
df.where(df > 2, 0)
 
# 替换为另一个 DataFrame/Series(按位置对齐)
other = pd.DataFrame({'A': [10, 20, 30], 'B': [40, 50, 60]})
df.where(df > 2, other)   # 不满足时取 other 的值

where() 参数详解

参数说明
cond布尔条件(Series、DataFrame 或可调用函数)
other替换值:标量、Series、DataFrame,或可调用函数
axis对齐轴,当 other 为 Series 时使用
levelMultiIndex 对齐层级
inplace是否原地修改

可调用函数

df.where(lambda x: x > 2, other=0)
df.where(df > 2, lambda x: x * 10)

where 与布尔索引的对比

需求推荐方式
筛选行(删除不满足的行)布尔索引 df[cond] / df.query()
保留原形状、替换不合格值where() / mask()
按值替换replace()

4. mask()

mask() 是 where() 的反操作:满足 cond 的位置被替换为 other(默认 NaN),不满足的位置保留。

s = pd.Series([1, 2, 3, 4, 5])
 
s.mask(s > 3)          # 大于 3 的替换为 NaN
# 0    1.0
# 1    2.0
# 2    3.0
# 3    NaN
# 4    NaN
 
# 条件为 True 的位置替换
df.mask(df['A'] > 2, 0)
函数条件 True 时条件 False 时
where()保留原值替换为 other
mask()替换为 other保留原值

逻辑区分

df.where(cond, other) 等价于 df.mask(~cond, other),二者互为逆操作。


5. take()

take() 按整数位置从对象中取行或列(不依赖索引标签)。

df = pd.DataFrame({'A': [10, 20, 30, 40], 'B': [50, 60, 70, 80]})
 
df.take([0, 2])            # 取第 0、2 行
df.take([-1])              # 最后一行
df.take([0, 1], axis=1)    # 取第 0、1 列
df.take([0, 2, 2, 3])      # 允许重复索引

take() 参数

参数说明
indices整数位置列表或数组
axis轴:0 = 行,1 = 列
allow_fill是否允许填充 fill_value(索引含 -1 时)
fill_value替换 -1 的值(allow_fill=True 时)
# 使用 -1 表示缺失位置并填充
df.take([0, -1], axis=0, allow_fill=True, fill_value=-999)

take 与 iloc 的区别

  • take() 不支持切片和布尔数组
  • 主要用于对一维数组(如 np.argsort 结果)按位置取值的场景

6. sample()

sample() 进行随机抽样,支持按行(默认)或按列。

df = pd.DataFrame({'A': range(100), 'B': range(100, 200)})
 
# 随机抽取 5 行
df.sample(n=5)
 
# 按比例抽取
df.sample(frac=0.1)
 
# 设置随机种子(结果可复现)
df.sample(n=5, random_state=42)
 
# 有放回抽样(可重复)
df.sample(n=200, replace=True)
 
# 按权重抽样
weights = [0.1] * 99 + [0.9]   # 最后一行权重更大
df.sample(n=10, weights=weights)
 
# 抽样列
df.sample(n=2, axis=1)
 
# 重置索引
df.sample(frac=0.1, ignore_index=True)

sample() 参数

参数说明
n抽样数量(与 frac 二选一)
frac抽样比例(0~1)
replace是否允许重复抽样(有放回)
weights行权重(数组或列名)
axis抽样轴(0 = 行,1 = 列)
random_state随机种子,保证结果可复现
ignore_index是否重置输出索引

用途

  • 训练/测试集划分(配合 frac)
  • 大数据集快速探索(抽样查看)
  • Bootstrap 重抽样(replace=True)