Pandas 数据选择与定位 (Selection & Indexing) 速查表
适用版本: Pandas 1.x / 2.x
核心概念:
- Selection (选择): 基于标签或布尔条件筛选数据。
- Indexing (索引): 基于位置或标签快速访问数据。
- View vs Copy: 链式赋值(Chained Assignment)可能导致
SettingWithCopyWarning,推荐使用.loc/.iloc。
1. 读取数据与基础查看
目的: 加载数据并了解其结构,为选择做准备。
import pandas as pd
import numpy as np
# --- 创建示例 DataFrame ---
data = {
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eva'],
'age': [25, 30, 35, 40, 30],
'city': ['NYC', 'LA', 'NYC', 'SF', 'LA'],
'salary': [70000, 80000, 120000, 90000, 75000]
}
df = pd.DataFrame(data)
df.set_index('name', inplace=True) # 将 name 设为索引
# --- 基础查看 ---
df.head() # 查看前5行
df.tail(3) # 查看后3行
df.shape # (行数, 列数)
df.columns # 列名列表
df.index # 索引对象
df.info() # 数据类型和内存占用
df.describe() # 数值列统计摘要
2. 选择列 (Selecting Columns)
目的: 提取单个或多个列。
| 操作 | 代码示例 | 返回类型 | 说明 |
|---|---|---|---|
| 单列 | df['age'] |
pd.Series |
最常用方式 |
| 单列 (属性式) | df.age |
pd.Series |
仅在列名无空格/特殊字符时可用 |
| 多列 | df[['age', 'salary']] |
pd.DataFrame |
传入列名列表 |
| 列名过滤 | df.filter(like='a') |
pd.DataFrame |
选择列名包含 'a' 的列 |
| 按正则选列 | df.filter(regex='^s') |
pd.DataFrame |
选择以 's' 开头的列 |
3. 基于标签选择 (.loc)
目的: 通过行标签 (Index Label) 和 列名 进行选择。推荐用法。
# 语法: df.loc[row_label(s), column_label(s)]
# --- 行选择 ---
df.loc['Alice'] # 选择单行 (Series)
df.loc[['Alice', 'Bob']] # 选择多行 (DataFrame)
# --- 行列同时选择 ---
df.loc['Alice', 'age'] # 标量值 (Scalar)
df.loc['Alice', ['age', 'city']] # 单行多列 (Series)
# --- 切片 (包含末端) ---
df.loc['Alice':'David', 'age':'city']
# --- 布尔索引 (Boolean Indexing) ---
df.loc[df['age'] > 30] # 年龄大于30的所有行
df.loc[df['city'] == 'NYC', 'salary'] # NYC员工的薪水
df.loc[(df['age'] > 30) & (df['salary'] < 100000)] # 多条件 (&, |, ~)
4. 基于位置选择 (.iloc)
目的: 通过整数位置 (Integer Position) 进行选择(从 0 开始)。
# 语法: df.iloc[row_position(s), column_position(s)]
# --- 行选择 ---
df.iloc[0] # 第一行 (Series)
df.iloc[[0, 2]] # 第1和第3行 (DataFrame)
# --- 行列同时选择 ---
df.iloc[0, 1] # 第1行第2列的值
df.iloc[0:2, 1:3] # 行切片(不含2)和列切片(不含3)
# --- 使用列表/数组 ---
df.iloc[np.arange(3), [0, 2]]
# --- 负索引 ---
df.iloc[-1] # 最后一行
5. .loc 与 .iloc 对比 (Cheat Sheet)
| 特性 | .loc |
.iloc |
|---|---|---|
| 访问方式 | 标签 (Label-based) | 位置 (Position-based) |
| 切片行为 |
包含末端 ('A':'C') |
不包含末端 (0:2) |
| 索引示例 | df.loc['Alice', 'age'] |
df.iloc[0, 1] |
| 布尔数组 | 支持 (df.loc[bool_series]) |
不支持 |
| 推荐度 | ⭐⭐⭐⭐⭐ (首选,更安全) | ⭐⭐⭐⭐ (处理位置数据时用) |
6. 条件筛选与布尔索引 (Boolean Mask)
目的: 根据复杂逻辑过滤数据。
# --- 基本比较 ---
df[df['age'] == 30]
df[df['salary'].between(70000, 90000)]
# --- isin() 成员检查 ---
df[df['city'].isin(['NYC', 'LA'])]
# --- 字符串操作 (str accessor) ---
df[df['city'].str.startswith('N')]
df[df['city'].str.contains('Y', na=False)] # na=False 处理 NaN
# --- 缺失值筛选 ---
df[df['salary'].isna()] # 选择 salary 为空的行
df[df['salary'].notna()] # 选择 salary 非空的行
# --- 组合条件 ---
# 注意:每个条件必须用括号括起来,并用 & (AND), | (OR), ~ (NOT)
condition = (df['age'] >= 30) & (df['city'] != 'SF')
df.loc[condition]
7. 赋值与修改数据 (Assignment)
目的: 安全地修改 DataFrame 中的值。
# --- 直接赋值 (广播机制) ---
df['bonus'] = 1000 # 新增一列,所有值为1000
df['total_comp'] = df['salary'] + df['bonus']
# --- 使用 .loc 赋值 (推荐) ---
df.loc['Alice', 'salary'] = 75000 # 修改单个值
df.loc[df['age'] > 35, 'bonus'] = 5000 # 条件更新
# --- 避免链式赋值 (Chained Assignment) ---
# ❌ 不推荐 (可能产生 Warning 或不生效):
df[df['age'] > 30]['bonus'] = 3000
# ✅ 推荐:
df.loc[df['age'] > 30, 'bonus'] = 3000
8. 其他高级选择技巧
# --- at / iat (快速标量访问) ---
df.at['Alice', 'age'] # 等同于 df.loc['Alice', 'age'],但速度更快
df.iat[0, 1] # 等同于 df.iloc[0, 1],但速度更快
# --- query() 方法 (类 SQL 语法) ---
df.query("age > 30 and city == 'NYC'")
df.query("age in [30, 35]")
# --- where() 方法 (保留原结构) ---
# 不满足条件的值会被替换为 NaN (或指定值)
df.where(df['age'] > 30, other=0)
# --- lookup() (已弃用,建议用 .loc) ---
# df.lookup(row_labels, col_labels) -> 使用 df.loc[row_labels, col_labels]
9. 常见错误与解决方案 (Troubleshooting)
| 问题现象 | 原因分析 | 解决方案 |
|---|---|---|
| SettingWithCopyWarning | 使用了链式索引 (e.g., df[df.A>0]['B']=1) |
改用 .loc: df.loc[df.A>0, 'B']=1
|
| KeyError: 'column_name' | 列名不存在或拼写错误 | 检查 df.columns.tolist()
|
| 切片结果不符合预期 | 混淆了 .loc (含尾) 和 .iloc (不含尾) |
明确使用场景,优先用 .loc
|
| 条件过滤失效 | 忘记给多条件加括号 ( )
|
(cond1) & (cond2) |
| 修改了原始数据 | Pandas 默认是 View 还是 Copy 不明确 | 如需副本,显式使用 df.copy()
|
💡 最佳实践总结 (Best Practices)
-
首选
.loc: 除非明确需要按位置索引,否则一律使用.loc进行读写,避免歧义。 -
警惕链式赋值: 永远不要在一个语句中同时使用
[]两次进行修改。 -
使用副本: 如果需要修改数据的子集而不影响原 DataFrame,请先
sub_df = df.loc[...].copy()。 -
性能: 对于循环内的单个元素访问,使用
at/iat代替loc/iloc。