Chapter 05

数据选择与过滤

loc/iloc 语义精讲、布尔索引、query 方法与 Copy-on-Write 安全操作

Pandas 索引系统精讲

Pandas 的数据选择是整个库中最容易让初学者困惑的部分,因为有多种方式可以完成"同一件事",但背后的语义和性能差异显著。

df[col] 或 df[[cols]]
按列名选择,df['A'] 返回 Series,df[['A', 'B']] 返回 DataFrame。注意不能用于行选择。
df.loc[行标签, 列标签]
基于标签的索引。行和列都用标签(名称)来指定。切片时两端都是闭合区间:df.loc[0:3] 包含第0、1、2、3行(如果索引是整数)。
df.iloc[行位置, 列位置]
基于位置的索引(integer location)。始终使用整数位置,不管索引是什么类型。切片遵循 Python 惯例:df.iloc[0:3] 包含第0、1、2行(不含第3行)。
import pandas as pd

df = pd.DataFrame({
    'name': ['Alice', 'Bob', 'Carol', 'Dave'],
    'age': [25, 30, 27, 35],
    'salary': [50000, 60000, 55000, 70000],
    'dept': ['Dev', 'Dev', 'HR', 'Dev']
}, index=['a', 'b', 'c', 'd'])  # 注意:使用字符串索引

# loc:按标签
df.loc['a']              # 选择索引标签为 'a' 的行
df.loc['a':'c']         # 选择 a 到 c 的行(含 c!)
df.loc[:, 'age':'salary']  # 选择 age 到 salary 的列
df.loc['a', 'name']     # 单个元素:第 'a' 行,'name' 列

# iloc:按整数位置
df.iloc[0]               # 第 0 行(位置,不是标签)
df.iloc[0:3]            # 第 0、1、2 行(不含第 3 行!)
df.iloc[:, 1:3]         # 第 1、2 列('age' 和 'salary')
df.iloc[-1]             # 最后一行

布尔索引

# 单条件
df[df['age'] > 27]                     # 年龄大于 27 的行
df[df['dept'] == 'Dev']               # 部门为 Dev 的行

# 多条件:必须用 & (and) | (or) ~ (not),不能用 and/or/not
df[(df['dept'] == 'Dev') & (df['salary'] > 55000)]
df[(df['age'] < 28) | (df['dept'] == 'HR')]

# isin:检查是否在指定集合中
df[df['dept'].isin(['Dev', 'QA'])]

# between:数值范围过滤(含两端)
df[df['salary'].between(50000, 65000)]

query 方法:更简洁的过滤语法

# query() 接受字符串表达式,更接近 SQL 风格
df.query("dept == 'Dev' and salary > 55000")

# 引用 Python 变量:用 @ 前缀
min_salary = 55000
dept_filter = 'Dev'
df.query("dept == @dept_filter and salary > @min_salary")

# 列名含空格时用反引号
df.query("`employee name` == 'Alice'")

Copy-on-Write 与链式操作安全

Pandas 2.0 引入了 Copy-on-Write(CoW) 语义,默认在 2.2 开启。这改变了链式赋值的行为:

# ❌ Pandas 1.x 中可能有效,但语义不清晰
df[df['age'] > 25]['salary'] = 0  # CoW 下不会修改原 df!

# ✅ 正确方式一:用 loc 定位后赋值
df.loc[df['age'] > 25, 'salary'] = 0

# ✅ 正确方式二:用 where/mask
df['salary'] = df['salary'].where(df['age'] <= 25, other=0)

# ✅ 正确方式三:用 assign 返回新 DataFrame(函数式风格,推荐)
df_new = df.assign(salary=df['salary'].where(df['age'] <= 25, 0))
CoW 的设计哲学

Copy-on-Write 使链式操作的语义变得清晰和可预测:任何返回新 DataFrame 的操作都创建独立副本,不会意外修改原数据。这消除了 SettingWithCopyWarning 警告,代码行为更安全。代价是某些就地修改需要用 .loc 重写。

本章小结