首页 / Pandas 入门教程 / 索引对象与轴的概念

Pandas 入门教程

索引对象与轴的概念

本教程共 54 篇 · 第 8 篇 · 更新于 2026-08-11 · 约 8 分钟阅读

PandasPandas 入门教程Index 索引axis 轴RangeIndex索引操作

本节目标:搞懂 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") 会把字符串转成整数。

Note

Index 允许重复值,官方把它称作”有序多重集”(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。想清楚”沿哪个方向移动”,就不会搞混。

Note

3.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),你会看到索引共享和不可变在设计上有多默契。