☰
Python-100-Days 深入浅出 pandas(六):一文吃透 Index 索引体系与时间索引实战
2026/9/30 6:58:41 网站建设 项目流程
  • 文档
  • 教程

【免费下载链接】Python-100-Days

Python - 100天从新手到大师

项目地址:https://gitcode.com/GitHub_Trending/py/Python-100-Days
点击查看免费下载

导读

Index是 pandas 中为Series与DataFrame提供索引服务的核心类型,它支撑起数据排序(sort_index)、数据对齐(运算与合并时的关键机制)以及对数据的快速检索(索引运算)。本篇文章基于《Python-100 天从新手到大师》项目中 深入浅出pandas-6 的完整内容,系统讲解RangeIndex(范围索引)、CategoricalIndex(分类索引)、MultiIndex(多级索引)、IntervalIndex(间隔索引)与DatetimeIndex(日期时间索引)五大子类型的创建、运算与实战用法,并结合仓库内的 notebook 与数据文件给出可直接运行的示例。读完本文,你将掌握用索引驱动分组聚合、时间重采样、时区转换等高级数据分析能力。

本文是 pandas 系列教程的第六篇,前序内容分别覆盖了 Series/DataFrame 基础、分组聚合与数据透视、透视与描述性统计 以及 同比环比与窗口计算,建议按顺序学习。

认识 Index:pandas 数据结构的“坐标轴”

Index类型为Series和DataFrame提供索引服务。由于DataFrame表示的是二维数据,它的行和列分别拥有自己的索引,即index和columns。有了索引,我们就可以:

  • 排序数据:通过sort_index方法按索引排序;
  • 对齐数据:在运算和合并数据时,pandas 会依据索引将不同对象对齐,这是非常关键的行为;
  • 快速检索:基于索引的运算(如loc定位、分组)能高效定位目标数据。

创建Index对象很简单,通常只需要三个参数:

参数含义
data作为索引的数据
dtype索引的数据类型
name索引的名称

由于Index本身也是一维的数据,索引它的方法和属性与Series非常类似。你可以创建一个Index对象,然后尝试把之前学过的属性和方法在Index类型上验证一遍,很快就能摸清它的行为规律。

范围索引:RangeIndex

范围索引是由具有单调性的整数构成的索引,它本质上就是整数范围的一种高效表示(底层不会把每个整数都显式存储)。创建方式有两种:使用RangeIndex构造器,或使用其类方法from_range。

下面的代码为 12 个月的销售数据创建了从1到12的范围索引:

sales_data = np.random.randint(400, 1000, 12) index = pd.RangeIndex(1, 13, name='月份') ser = pd.Series(data=sales_data, index=index) ser

输出:

月份 1 703 2 705 3 557 4 943 5 961 6 615 7 788 8 985 9 921 10 951 11 874 12 609 dtype: int64

使用要点:RangeIndex的优势在于内存效率极高——它只需记录起点、终点与步长,而不需要保存全部元素。当你用pd.read_excel/pd.read_csv读取数据且没有指定索引列时,pandas 默认就会生成一个0 ~ N-1的RangeIndex,这在仓库配套的 day06.ipynb 的 DataFrame 输出中可以看到(例如RangeIndex: 244 entries, 0 to 243)。

分类索引:CategoricalIndex

分类索引由定类尺度(有限且可枚举的类别)构成。当我们需要按索引将数据分组后再做聚合时,分类索引就能派上用场——它保证分组聚合能够按类别语义进行。

分类索引还有一个独特的reorder_categories方法,可以为索引指定一个顺序,分组聚合的结果将按该指定顺序呈现。

先创建一个按水果类别索引的Series:

sales_data = [6, 6, 7, 6, 8, 6] index = pd.CategoricalIndex( data=['苹果', '香蕉', '苹果', '苹果', '桃子', '香蕉'], categories=['苹果', '香蕉', '桃子'], ordered=True ) ser = pd.Series(data=sales_data, index=index) ser

输出:

苹果 6 香蕉 6 苹果 7 苹果 6 桃子 8 香蕉 6 dtype: int64

基于索引分组数据,然后使用sum进行求和:

ser.groupby(level=0).sum()

输出:

苹果 19 香蕉 12 桃子 8 dtype: int64

重排类别顺序:通过reorder_categories指定索引顺序后,分组聚合结果的展示顺序也随之改变:

ser.index = index.reorder_categories(['香蕉', '桃子', '苹果']) ser.groupby(level=0).sum()

输出:

香蕉 12 桃子 8 苹果 19 dtype: int64

使用要点:categories参数限定了合法类别集合,ordered=True表示类别之间有顺序关系。这在需要按固定业务顺序(而非字母/哈希顺序)呈现分组结果时非常有用。

多级索引:MultiIndex

MultiIndex用来表示层次或多级索引,可以同时从多个维度组织数据。pandas 提供了多个类方法用于创建多级索引:

类方法用途
from_arrays由多个等长数组(每层一个)构建
from_product由多组标签的笛卡尔积构建
from_tuples由元组列表构建

先看from_tuples的用法:

tuples = [(1, 'red'), (1, 'blue'), (2, 'red'), (2, 'blue')] index = pd.MultiIndex.from_tuples(tuples, names=['no', 'color']) index

输出:

MultiIndex([(1, 'red'), (1, 'blue'), (2, 'red'), (2, 'blue')], names=['no', 'color'])

from_arrays的效果与之等价:

arrays = [[1, 1, 2, 2], ['red', 'blue', 'red', 'blue']] index = pd.MultiIndex.from_arrays(arrays, names=['no', 'color']) index

输出:

MultiIndex([(1, 'red'), (1, 'blue'), (2, 'red'), (2, 'blue')], names=['no', 'color'])

基于多级索引创建 Series 与分组聚合:

sales_data = np.random.randint(1, 100, 4) ser = pd.Series(data=sales_data, index=index) ser

输出:

no color 1 red 43 blue 31 2 red 55 blue 75 dtype: int64

按第一级索引(no)分组求和:

ser.groupby('no').sum()

输出:

no 1 74 2 130 dtype: int64

按第二级索引(color)分组求和:

ser.groupby(level=1).sum()

输出:

color blue 106 red 98 dtype: int64

提示:groupby('no')与groupby(level=0)在这里效果一致,前者按索引名称分组,后者按层级位置分组。多级索引下可以按任意一级进行分组聚合,灵活性极高。

实战:学生成绩表。使用from_product构造“学号 × 学期”的层级索引,创建包含 5 名学生期中/期末成绩的DataFrame:

stu_ids = np.arange(1001, 1006) semisters = ['期中', '期末'] index = pd.MultiIndex.from_product((stu_ids, semisters), names=['学号', '学期']) courses = ['语文', '数学', '英语'] scores = np.random.randint(60, 101, (10, 3)) df = pd.DataFrame(data=scores, columns=courses, index=index) df

输出:

语文 数学 英语 学号 学期 1001 期中 93 77 60 期末 93 98 84 1002 期中 64 78 71 期末 70 71 97 1003 期中 72 88 97 期末 99 100 63 1004 期中 80 71 61 期末 91 62 72 1005 期中 82 95 67 期末 84 78 86

进阶聚合:根据第一级索引分组,按“期中成绩占 25%、期末成绩占 75%”加权计算每个学生每门课的最终成绩:

df.groupby(level=0).agg(lambda x: x.values[0] * 0.25 + x.values[1] * 0.75)

输出:

语文 数学 英语 学号 1001 93.00 92.75 78.00 1002 68.50 72.75 90.50 1003 92.25 97.00 71.50 1004 88.25 64.25 69.25 1005 83.50 82.25 81.25

使用要点:多级索引在“宽表转长表、多维度透视、时间×类别双维度数据”等场景中非常常见。值得一提的是,聚合后的DataFrame其列索引同样可能是MultiIndex对象(例如下面时间重采样中agg(['mean', 'std'])的输出),可以通过访问.columns属性来观察其结构。

间隔索引:IntervalIndex

间隔索引使用固定的间隔范围充当索引,通常使用interval_range函数创建。它适用于区间分箱、区间查找等场景。

index = pd.interval_range(start=0, end=5) index

输出:

IntervalIndex([(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]], dtype='interval[int64, right]')

contains 方法:检查范围内是否包含某个元素:

index.contains(1.5)

输出:

array([False, True, False, False, False])

overlaps 方法:检查一个范围跟其他范围是否有重叠:

index.overlaps(pd.Interval(1.5, 3.5))

输出:

array([False, True, True, True, False])

closed 参数控制区间开闭状态。默认是左开右闭(right);如果希望间隔范围是左闭右开,可以通过closed='left'实现;如果希望两边都封闭,则将closed参数的值赋为'both':

index = pd.interval_range(start=0, end=5, closed='left') index

输出:

IntervalIndex([[0, 1), [1, 2), [2, 3), [3, 4), [4, 5)], dtype='interval[int64, left]')

间隔索引同样支持datetime64类型的区间,例如以“天”为粒度的左闭右闭区间:

index = pd.interval_range(start=pd.Timestamp('2022-01-01'), end=pd.Timestamp('2022-01-04'), closed='both') index

输出:

IntervalIndex([[2022-01-01, 2022-01-02], [2022-01-02, 2022-01-03], [2022-01-03, 2022-01-04]], dtype='interval[datetime64[ns], both]')

使用要点:interval_range还支持freq(步长)与periods(区间个数)参数,用于控制区间的粒度和数量;IntervalIndex常与pd.cut/pd.qcut分箱结果配合使用,是处理年龄段、价格区间、时间窗口等数据的利器。

日期时间索引:DatetimeIndex

DatetimeIndex是众多索引中最复杂也最重要的一种,几乎所有时间序列分析都离不开它。我们通常使用date_range()函数来创建日期时间索引,它有五个非常重要的参数:

参数含义
start起始日期时间
end结束日期时间
periods生成周期(个数)
freq采样频率
tz时区

按周期数生成(periods=10表示在起止时间内均匀生成 10 个时间点):

pd.date_range('2021-1-1', '2021-6-30', periods=10)

输出:

DatetimeIndex(['2021-01-01', '2021-01-21', '2021-02-10', '2021-03-02', '2021-03-22', '2021-04-11', '2021-05-01', '2021-05-21', '2021-06-10', '2021-06-30'], dtype='datetime64[ns]', freq=None)

按频率生成(freq='W'表示采样周期为一周,默认以星期日为一周的开始):

pd.date_range('2021-1-1', '2021-6-30', freq='W')

输出:

DatetimeIndex(['2021-01-03', '2021-01-10', '2021-01-17', '2021-01-24', '2021-01-31', '2021-02-07', '2021-02-14', '2021-02-21', '2021-02-28', '2021-03-07', '2021-03-14', '2021-03-21', '2021-03-28', '2021-04-04', '2021-04-11', '2021-04-18', '2021-04-25', '2021-05-02', '2021-05-09', '2021-05-16', '2021-05-23', '2021-05-30', '2021-06-06', '2021-06-13', '2021-06-20', '2021-06-27'], dtype='datetime64[ns]', freq='W-SUN')

说明:freq=W会默认星期日作为一周的开始;如果你希望星期一表示一周的开始,可以将其修改为freq='W-MON'。你也可以尝试把该参数的值修改为12H、M、Q等,看看会发生什么——不难猜到H是小时、M是月末、Q是季度末的含义。

DatetimeIndex 与 DateOffset 的偏移运算

DatetimeIndex可以与DateOffset类型进行运算,设置一个时间差让时间向前或向后偏移。向前偏移 2 天:

index = pd.date_range('2021-1-1', '2021-6-30', freq='W') index - pd.DateOffset(days=2)

输出:

DatetimeIndex(['2021-01-01', '2021-01-08', '2021-01-15', '2021-01-22', '2021-01-29', '2021-02-05', '2021-02-12', '2021-02-19', '2021-02-26', '2021-03-05', '2021-03-12', '2021-03-19', '2021-03-26', '2021-04-02', '2021-04-09', '2021-04-16', '2021-04-23', '2021-04-30', '2021-05-07', '2021-05-14', '2021-05-21', '2021-05-28', '2021-06-04', '2021-06-11', '2021-06-18', '2021-06-25'], dtype='datetime64[ns]', freq=None)

向后偏移 2 小时 10 分钟:

index + pd.DateOffset(hours=2, minutes=10)

输出:

DatetimeIndex(['2021-01-03 02:10:00', '2021-01-10 02:10:00', '2021-01-17 02:10:00', '2021-01-24 02:10:00', '2021-01-31 02:10:00', '2021-02-07 02:10:00', '2021-02-14 02:10:00', '2021-02-21 02:10:00', '2021-02-28 02:10:00', '2021-03-07 02:10:00', '2021-03-14 02:10:00', '2021-03-21 02:10:00', '2021-03-28 02:10:00', '2021-04-04 02:10:00', '2021-04-11 02:10:00', '2021-04-18 02:10:00', '2021-04-25 02:10:00', '2021-05-02 02:10:00', '2021-05-09 02:10:00', '2021-05-16 02:10:00', '2021-05-23 02:10:00', '2021-05-30 02:10:00', '2021-06-06 02:10:00', '2021-06-13 02:10:00', '2021-06-20 02:10:00', '2021-06-27 02:10:00'], dtype='datetime64[ns]', freq=None)

用 asfreq 对时间索引抽样

如果Series或DataFrame使用了DatetimeIndex类型的索引,就可以通过asfreq()方法指定一个时间频率对数据进行抽样。这里沿用前一篇教程(深入浅出pandas-5)使用过的百度股票数据:

baidu_df = pd.read_excel('data/2022年股票数据.xlsx', sheet_name='BIDU', index_col='Date') baidu_df.sort_index(inplace=True) baidu_df.asfreq('5D')

输出:

大家可能注意到了,每 5 天抽取 1 天有可能会抽中非交易日,那么对应的列都变成了空值。为了解决这个问题,使用asfreq方法时可以通过method参数来指定一种填充空值的方法,将相邻的交易日数据填入进来:

baidu_df.asfreq('5D', method='ffill')

输出:

用 resample 对时间数据重采样

当使用DatetimeIndex索引时,也可以通过resample()方法基于时间对数据进行重采样——它相当于根据时间周期对数据进行了分组操作,分组之后还可以进行聚合统计。例如按月('1M')计算均值:

baidu_df.resample('1M').mean()

也可以同时聚合多种统计量:

baidu_df.resample('1M').agg(['mean', 'std'])

提示:注意上面输出的DataFrame的列索引是一个MultiIndex对象——('Close', 'mean')、('Close', 'std')这样的组合正好呼应了前文介绍的多级索引。你可以访问该DataFrame的columns属性确认这一点。

时区本地化与时区转换

如果要实现日期时间的时区转换,可以先用tz_localize()方法将日期时间本地化到某个时区:

baidu_df = baidu_df.tz_localize('Asia/Chongqing') baidu_df

在对时间本地化之后,再使用tz_convert()方法就可以实现转换时区:

baidu_df.tz_convert('America/New_York')

使用要点:tz_localize是为“没有时区信息”的时间数据赋予一个基准时区;tz_convert则是在已有本地化结果的基础上做不同时区之间的换算。注意转换前必须先完成本地化,顺序不能颠倒。

小结:如何选择索引类型

索引类型适用场景常用创建方式关键方法
RangeIndex默认整数位置索引、内存高效的连续整数RangeIndex()/from_range与整数索引切片配合
CategoricalIndex定类尺度、按固定类别顺序分组聚合CategoricalIndex(data, categories, ordered)reorder_categories
MultiIndex多维度层级数据、按任一层级分组聚合from_arrays/from_product/from_tuplesgroupby(level=...)
IntervalIndex区间分箱、区间包含与重叠判断interval_range(start, end, closed, freq)contains/overlaps
DatetimeIndex时间序列、抽样、重采样、时区处理date_range(start, end, periods, freq, tz)asfreq/resample/tz_localize/tz_convert/DateOffset

本文对应的完整教程位于仓库的 深入浅出pandas-6,配套的 notebook 与数据文件(如 day06.ipynb、2022年股票数据.xlsx位于 code/res)均可直接运行复现。如果你的数据使用了DatetimeIndex索引,那么很可能要进行时间序列分析——关于时间序列分析的方法和模型并不是本章节要探讨的内容,我们会在其他专栏中继续分享。

  • 文档
  • 教程

【免费下载链接】Python-100-Days

Python - 100天从新手到大师

项目地址:https://gitcode.com/GitHub_Trending/py/Python-100-Days
点击查看免费下载

相关推荐

上一篇:cult-ui深度解析:50+动画组件如何提升用户体验
下一篇:TVBoxOSC:开源智能电视盒应用开发终极指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询