RSS
菜单
全部文章快讯开发科技深度热点

第 6 章 数据选择与索引

内容摘要

选择数据是 pandas 使用频率最高的操作。本篇文章系统讲解 pandas 的索引体系:**列选择、行选择、loc 标签索引、iloc 位置索引、布尔索引、at/iat 快速取值**,以及如何安全地修改数据。

选择数据是 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 章 缺失值与重复值处理 —— 数据清洗的重头戏:缺失值检测、删除、填充与插值。
— 全文完 —回到顶部 ↑
下载推广海报

文章推广海报

《第 6 章 数据选择与索引》完整推广海报
DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。