选择数据是 pandas 使用频率最高的操作。本篇文章系统讲解 pandas 的索引体系:列选择、行选择、loc 标签索引、iloc 位置索引、布尔索引、at/iat 快速取值,以及如何安全地修改数据。
目录
1. 选择数据的方式总览
2. 列选择
3. 行选择
4. loc:按标签选择
5. iloc:按位置选择
6. 布尔索引(条件筛选)
7. at / iat:快速取单个值
8. 综合案例:复杂查询
9. 修改数据的正确姿势
10. 常见坑与注意事项
11. 本章小结与练习
1. 选择数据的方式总览
| 方式 | 用途 | 示例 |
|------|------|------|
| df["列名"] | 取一列(返回 Series) | df["年龄"] |
| df[["列名"]] | 取多列(返回 DataFrame) | df[["年龄","城市"]] |
| df[条件] | 布尔筛选行 | df[df["年龄"]>30] |
| df.loc[行, 列] | 按标签选行列 | df.loc[0:3, "姓名"] |
| df.iloc[行, 列] | 按位置选行列 | df.iloc[0:3, 0:2] |
| df.at[行, 列] | 按标签取单个值 | df.at[0, "姓名"] |
| df.iat[行, 列] | 按位置取单个值 | df.iat[0, 1] |
先准备示例数据:
import pandas as pd
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五", "赵六"],
"年龄": [25, 30, 28, 35],
"城市": ["北京", "上海", "广州", "深圳"],
}, index=["a", "b", "c", "d"])
print(df)
# 姓名 年龄 城市
# a 张三 25 北京
# b 李四 30 上海
# c 王五 28 广州
# d 赵六 35 深圳
2. 列选择
# 取一列 -> Series
age = df["年龄"]
print(type(age)) # <class 'pandas.core.series.Series'>
# 取多列 -> DataFrame(注意双中括号)
sub = df[["姓名", "城市"]]
print(type(sub)) # <class 'pandas.core.frame.DataFrame'>
# 取不存在的列 -> KeyError
# df["不存在"] # KeyError: '不存在'
重点:df["年龄"]返回 Series,df[["年龄"]]返回 DataFrame,两者形态不同、后续操作不同,务必分清。
3. 行选择
3.1 用 [] 直接切片行(按位置)
# 前 2 行
print(df[0:2])
# 姓名 年龄 城市
# a 张三 25 北京
# b 李四 30 上海
3.2 用 loc / iloc 取行
# loc:按标签取单行 -> Series
print(df.loc["a"])
# 姓名 张三
# 年龄 25
# 城市 北京
# Name: a, dtype: object
# iloc:按位置取单行
print(df.iloc[0])
规则:loc的切片包含终点(闭区间),iloc的切片不包含终点(开区间)。这是最容易被坑的地方。
4. loc:按标签选择
df.loc[行选择, 列选择],行和列都可以省略(省略表示全部)。
# 4.1 取单个标签行
df.loc["b"]
# 4.2 取标签切片(闭区间!包含 c)
df.loc["a":"c"]
# 姓名 年龄 城市
# a 张三 25 北京
# b 李四 30 上海
# c 王五 28 广州
# 4.3 取多个标签(列表)
df.loc[["a", "c", "d"]]
# 4.4 取指定行 + 指定列
df.loc["a", "年龄"] # 25
df.loc[["a", "b"], ["姓名", "城市"]]
# 4.5 取所有行、指定列
df.loc[:, "年龄"] # 整列(Series)
df.loc[:, ["姓名", "年龄"]] # 多列
# 4.6 取所有列、指定行
df.loc["a", :]
注意:使用默认整数索引时,loc[0:2] 会包含 0, 1, 2 三行(因为按标签闭区间)。
df2 = df.reset_index(drop=True) # 重置为 0,1,2,3
print(df2.loc[0:2]) # 3 行!0,1,2
5. iloc:按位置选择
df.iloc[行位置, 列位置],位置从 0 开始,切片不包含终点。
# 5.1 取单个位置行
df.iloc[0] # 第一行(Series)
# 5.2 位置切片(开区间!不含 3)
df.iloc[0:3] # 0,1,2 三行
# 5.3 取指定位置行
df.iloc[[0, 2]]
# 5.4 行 + 列位置
df.iloc[0, 1] # 第 1 行第 2 列 = 25
df.iloc[0:2, 0:2] # 左上 2x2
df.iloc[:, 0] # 第一列(Series)
df.iloc[:, [0, 2]] # 第 0、2 列
# 5.5 负索引(从尾部数)
df.iloc[-1] # 最后一行
df.iloc[-2:] # 最后两行
loc vs iloc 速查表
| 对比项 | loc | iloc |
|--------|-----|------|
| 依据 | 标签(索引名) | 位置(整数序号) |
| 切片终点 | 包含 | 不包含 |
| 负索引 | 不支持 | 支持 |
| 典型写法 | df.loc["a":"c"] | df.iloc[0:3] |
| 报错时机 | 标签不存在报 KeyError | 位置越界报 IndexError |
坑:如果索引恰好是 0,1,2... 的整数,loc和iloc看起来一样,但切片结果不同(闭区间 vs 开区间)。务必用非默认索引实验一遍。
6. 布尔索引(条件筛选)
这是 pandas 最强大的筛选方式,返回满足条件的行。
6.1 单一条件
# 年龄大于 28 的人
print(df[df["年龄"] > 28])
# 姓名 年龄 城市
# b 李四 30 上海
# d 赵六 35 深圳
6.2 多条件组合
# 与:年龄>28 且 城市是上海/深圳
print(df[(df["年龄"] > 28) & (df["城市"].isin(["上海", "深圳"]))])
# 或:年龄<26 或 城市=广州
print(df[(df["年龄"] < 26) | (df["城市"] == "广州")])
# 非:不是北京的人
print(df[~(df["城市"] == "北京")])
必须用括号:&、|、~的优先级低于比较运算符,所以每个条件都要加括号。
6.3 isin 与字符串方法
# 城市在列表中
print(df[df["城市"].isin(["北京", "广州"])])
# 姓"张"的人
print(df[df["姓名"].str.startswith("张")])
# 城市包含"海"字
print(df[df["城市"].str.contains("海")])
6.4 query 方法(SQL 风格)
# 用字符串写条件,代码更简洁
print(df.query("年龄 > 28"))
print(df.query("年龄 > 28 and 城市 == '上海'"))
print(df.query("城市 in ['北京', '广州']"))
# 用外部变量:@ 前缀
min_age = 30
print(df.query("年龄 >= @min_age"))
6.5 布尔索引配合列选择
# 筛选行 + 只取指定列
print(df.loc[df["年龄"] > 28, ["姓名", "城市"]])
6.6 条件定位(where / mask)
# where:不满足条件的位置填 NaN
print(df["年龄"].where(df["年龄"] > 28))
# a NaN
# b 30.0
# c NaN
# d 35.0
# mask:满足条件的位置填 NaN(where 的取反)
print(df["年龄"].mask(df["年龄"] > 28))
7. at / iat:快速取单个值
取单个单元格时,at/iat 比 loc/iloc 快得多(无需复制整行/整列)。
# at:按标签
print(df.at["b", "年龄"]) # 30
df.at["b", "年龄"] = 31 # 也可直接修改
# iat:按位置
print(df.iat[1, 1]) # 30
df.iat[1, 1] = 31
8. 综合案例:复杂查询
# 需求:年龄在 25~35 之间、且城市为北上广深,返回姓名和年龄
result = df.loc[
(df["年龄"] >= 25) & (df["年龄"] <= 35) & (df["城市"].isin(["北京", "上海", "广州", "深圳"])),
["姓名", "年龄"],
]
print(result)
# 等价 query 写法
result2 = df.query("25 <= 年龄 <= 35 and 城市 in ['北京','上海','广州','深圳']")[["姓名", "年龄"]]
9. 修改数据的正确姿势
9.1 修改单个值
df.at["a", "年龄"] = 26
df.loc["a", "城市"] = "杭州"
9.2 按条件批量修改
# 30 岁以上标记为"资深"
df.loc[df["年龄"] > 30, "等级"] = "资深"
df.loc[df["年龄"] <= 30, "等级"] = "普通"
# 修改整列
df["年龄"] = df["年龄"] + 1
9.3 为什么不要用链式赋值
# 错误示范:可能产生 SettingWithCopyWarning,修改不生效
# df[df["年龄"] > 28]["城市"] = "上海"
# 正确示范:一次性用 loc
df.loc[df["年龄"] > 28, "城市"] = "上海"
SettingWithCopyWarning 是 pandas 最常见的警告之一。它的本质是"你正在修改一个副本,原表不会变"。根治办法:所有"条件修改"一律用 `df.loc[条件, 列] = 值`。10. 常见坑与注意事项
| 坑 | 现象 | 解决办法 |
|----|------|----------|
| df["年龄"] vs df[["年龄"]] | Series 与 DataFrame 混淆 | 牢记单列是 Series、双中括号是 DataFrame |
| loc 切片含终点 | 比预期多一行 | 记住 loc 闭区间、iloc 开区间 |
| 条件忘了加括号 | 报 ValueError | 每个比较条件加括号 |
| 用 and/or | 报歧义错误 | 用 &/\| |
| 链式赋值 | 警告 + 修改失效 | 统一用 .loc |
| 取不存在的列/标签 | KeyError | 先 df.columns 或 df.index 确认 |
11. 本章小结与练习
小结
- 列:
df["列"]、df[["列1","列2"]]; - 行:
df[0:2](位置切片); - loc:按标签,闭区间;iloc:按位置,开区间;
- 布尔索引:
df[(条件1) & (条件2)],配合isin、str.contains、query; - 单值:
at(标签)、iat(位置),速度最快; - 修改:一律用
df.loc[条件, 列] = 值,避免链式赋值。
练习题
1. 用 loc 取出索引 "b" 到 "d" 的所有行(含 d)。
2. 用 iloc 取出前 2 行的第 1、3 列。
3. 筛选出年龄在 25~30 之间且不是北京的用户。
4. 用 query 重写第 3 题。
5. 将年龄大于 30 的人的城市统一改为"上海",并验证是否生效。
下一篇预告:第 7 章 缺失值与重复值处理 —— 数据清洗的重头戏:缺失值检测、删除、填充与插值。
文章回复
0 条公开回复