索引对象与轴的概念
本教程共 54 篇 · 第 8 篇 · 更新于 2026-08-11 · 约 8 分钟阅读
本节目标:搞懂 Index 索引对象是什么、为什么不可变,理解 axis=0/1 两个轴和索引对齐机制,学会用交集、并集、差集与排序操作管理索引。
索引:数据的”门牌号”
回想一下 Series 和 DataFrame 的结构:每行、每列都有一组标签,行方向的叫 index,列方向的叫 columns。这组标签在 pandas 里就是索引(Index)。
索引的作用类似数据库的主键,也像 Excel 里的行号。有了它,你可以用”名字”而不是”第几行”来定位数据。比如 df.loc["张三"] 能直接取出张三那一行,靠的就是索引。
索引还负责两件重要的事:自动对齐和快速查找。两个 DataFrame 做运算时,pandas 会按索引自动对上数据;查找某个标签时,索引能帮你快速定位,不用一行行翻。
认识 Index 对象
索引本身也是一个对象,类型是 pd.Index。你可以直接创建它:
import pandas as pd
import numpy as np
idx = pd.Index(["e", "d", "a", "b"])
print(idx)
print("d" in idx) # True,支持 in 判断
输出里有个细节:dtype='str'。这是 pandas 3.0 的变化——字符串索引默认使用新的字符串类型,不再是 object。
创建时还能指定名称和类型:
idx = pd.Index([1, 5, 12], name="编号")
print(idx.name) # 编号
print(idx.dtype) # int64
没有显式指定 dtype 时,pandas 会自动推断。想强制类型也可以,比如 pd.Index(["1", "2"], dtype="int64") 会把字符串转成整数。
NoteIndex 允许重复值,官方把它称作”有序多重集”(ordered multiset)。判断索引是否唯一用
idx.is_unique,大多数操作在唯一索引上表现更好。
RangeIndex:默认的整数索引
平时创建 DataFrame 不指定 index,你会得到一个 RangeIndex——从 0 开始、步长为 1 的整数索引:
df = pd.DataFrame({"姓名": ["张三", "李四"], "年龄": [25, 30]})
print(df.index) # RangeIndex(start=0, stop=2, step=1)
print(type(df.index)) # <class 'pandas.core.indexes.range.RangeIndex'>
RangeIndex 很像 Python 的 range(n):只存起点、终点和步长,不存每个值。所以即使有 1000 万行,它占的内存也几乎可以忽略,是内存效率最高的索引类型。
用 reset_index() 重置索引后,返回的也是 RangeIndex:
df2 = df.set_index("姓名") # 换成姓名做索引
print(type(df2.index)) # Index
df3 = df2.reset_index()
print(type(df3.index)) # RangeIndex,又回来了
Tip只要数据行的顺序对你没意义,就放心用默认的 RangeIndex。它省内存,还免去了维护标签的麻烦。
索引的不可变性
Index 有个重要特性:不可变。创建之后,你不能直接修改里面的元素:
idx = pd.Index([1, 2, 3])
# idx[0] = 99 # 会报 TypeError: Index does not support mutable operations
为什么这么设计?因为索引经常被多个 DataFrame 共享。如果谁都能改,一个对象的修改就可能悄悄影响别的对象。不可变让索引可以安全共享,也方便哈希和快速查找。
不可变不等于完全不能动。你可以:
idx = pd.Index([1, 2, 3])
new = idx.rename("编号") # 返回一个改好名的新索引
print(new.name) # 编号
print(idx.name) # 还是 None,原索引没变
idx.name = "编号" # 唯一例外:name 可以直接赋值
注意 rename 返回的是新对象,原索引不受影响。想改 DataFrame 的索引或列名,也是同样的思路,具体在 §23 讲。
轴 axis:0 还是 1?
很多函数都有 axis 参数,新手常在这里栽跟头。记一条规律就够:
axis=0:沿行方向,即 index 轴axis=1:沿列方向,即 columns 轴
看例子最直观:
df = pd.DataFrame(np.arange(12).reshape(3, 4),
index=["r1", "r2", "r3"],
columns=["A", "B", "C", "D"])
print(df.drop("A", axis=1)) # 删掉 A 列
print(df.drop("r1", axis=0)) # 删掉 r1 行,axis=0 是默认值
axis=0 是绝大多数函数的默认值,所以 df.drop("r1") 删的是行。另一个常见例子是求和:
print(df.sum(axis=0)) # 每列求和,返回 Series
print(df.sum(axis=1)) # 每行求和
sum(axis=0) 把每列的数加起来(沿行方向走),结果是一个按列名索引的 Series。想清楚”沿哪个方向移动”,就不会搞混。
Note3.0 里还可以用名字代替数字:
axis="index"等价于 0,axis="columns"等价于 1。语义更清楚,但数字写法最常见。
索引的对齐魔法
索引不只是”标签”,它还负责自动对齐。两个 Series 做运算时,pandas 会按索引把数据对上,而不是像数组那样按位置硬算:
s1 = pd.Series([1, 2, 3], index=["a", "b", "c"])
s2 = pd.Series([10, 20], index=["b", "a"])
print(s1 + s2)
结果是:
a 21.0
b 12.0
c NaN
dtype: float64
相同标签相加:a 是 1+20=21,b 是 2+10=12。只有一个 Series 里出现的 c,结果只能是 NaN——pandas 不会擅自猜测缺失值。
这正是索引存在的意义之一:两份数据不用先排序、先对齐,pandas 自动按标签配对。缺标签时得到 NaN,你一眼就能看出数据没对上。
索引的集合运算
索引之间可以做集合运算,结果会自动按升序排好:
a = pd.Index(["c", "b", "a"])
b = pd.Index(["c", "e", "d"])
print(a.union(b)) # 并集:c, b, a, e, d
print(a.intersection(b)) # 交集:c
print(a.difference(b)) # 差集:a, b(在 a 不在 b)
print(a.symmetric_difference(b)) # 对称差:a, b, d, e
四个方法对应四种场景:
union():合并两个索引,相当于”并集”intersection():两边都有的标签,“交集”difference():只在一边出现的标签,“差集”symmetric_difference():只在一边出现、不同时存在的标签,“对称差”
不同 dtype 的索引做并集时,会先转换到公共类型。比如整数和浮点数并集,整数会被转成浮点数:
idx1 = pd.Index([0, 1, 2])
idx2 = pd.Index([0.5, 1.5])
print(idx1.union(idx2)) # Index([0.0, 0.5, 1.0, 1.5, 2.0], dtype='float64')
集合运算在实战中很常用:比对两个批次数据的编号是否一致、找出只在一个列表里出现的用户,都靠它。
排序:sort_index
给索引排序有两种入口。先看 Index 自己的方法:
idx = pd.Index([3, 1, 2])
print(idx.sort_values()) # Index([1, 2, 3])
更多时候是对整个 DataFrame 按索引排序:
df = pd.DataFrame({"值": [1, 2, 3]}, index=["c", "a", "b"])
print(df.sort_index()) # 按行索引升序
print(df.sort_index(ascending=False)) # 降序
print(df.sort_index(axis=1)) # 按列名排序
sort_index 返回新对象,原 DataFrame 不变,这是 pandas 一贯的风格。排序后索引变得有序,某些查找操作会更快。
常见的索引子类型
pd.Index 是一族类型的基类,按场景还有专化子类:
- DatetimeIndex:日期时间索引,支持按时间切片(§45)
- MultiIndex:多级索引,多个维度共同定位一行(§22)
- PeriodIndex、TimedeltaIndex、CategoricalIndex 等,各有专属用途
创建 DataFrame 时给 index 传日期范围,会自动得到 DatetimeIndex:
dates = pd.date_range("2026-01-01", periods=3)
df = pd.DataFrame({"值": [1, 2, 3]}, index=dates)
print(type(df.index)) # DatetimeIndex
在合适的场景用专化索引,能获得专属功能和更好的性能。不过这一章只需记住:它们都是 Index 家族成员,前面讲的属性、不可变性、集合运算全都适用。
常用的索引属性和方法
最后过一遍高频用法,都很好记:
idx = pd.Index(["a", "b", "c", "b"])
print(idx.tolist()) # ['a', 'b', 'c', 'b'],转成 Python 列表
print(idx.values) # 底层数组
print(idx.unique()) # 去重后的 Index
print(idx.is_unique) # False,有重复
print(idx.get_loc("b")) # 2,返回标签对应的位置
get_loc("b") 返回的是位置信息;遇到重复标签会返回一个数组,这也是为什么唯一索引更受欢迎。
小结
- Index 是行和列的标签集合,负责定位、对齐和查找
- RangeIndex 是默认索引,只存范围不存值,最省内存
- 索引不可变,
rename返回新对象,只有name能直接改 axis=0沿行方向,axis=1沿列方向- 索引自动对齐:运算按标签配对,缺标签得 NaN
- 交集、并集、差集、对称差四个集合运算,结果自动升序
- 排序用
sort_index(),返回新对象
下一节讲写时复制(Copy-on-Write),你会看到索引共享和不可变在设计上有多默契。