pandas 0.8.0 版本深度解析:datetime64 时间序列架构革命与迁移指南
【免费下载链接】pandasFlexible and powerful data analysis / manipulation library for Python, providing labeled data structures similar to R data.frame objects, statistical functions, and much more项目地址: https://gitcode.com/gh_mirrors/pa/pandas
导读
pandas 0.8.0(2012 年 6 月 29 日发布)是该库发展史上的一个里程碑式版本,它把时间序列数据从 Python 内建datetime.datetime对象数组全面迁移到 NumPy 的datetime64dtype,并据此重建了整套时间序列处理与加工基础设施,同时引入了DatetimeIndex、PeriodIndex、Timestamp、灵活的resample重采样、时区处理等核心 API。本文以发布说明 doc/source/whatsnew/v0.8.0.rst 为主体,结合当前仓库中对应源码实现,系统梳理该版本的架构变化、新功能清单、绘图能力升级,以及面向 0.7.3 及更早版本用户的完整移植指南,帮助你理解 pandas 时间序列 API 的演化脉络与底层设计逻辑。
版本概览:一次“大版本”跃迁
0.8.0 是从 0.7.3 升级而来的 major release,发布说明明确指出本次发布包含:
- 超过 700 个 commit,来自 20 余位作者;
- 时间序列处理与加工基础设施被大规模重写;
- 全库范围内新增了大量功能。
升级兼容性方面,发布说明给出了谨慎的预期:大多数 0.7.3 及更早版本的用户不应遇到升级问题,但由于数据表示迁移到了 NumPy 的datetime64dtype,仍可能潜伏着一些 bug 和不兼容点。官方承诺这些遗留问题会在 0.8.1 中尽快修复;同时,面向仍在使用旧版 NumPy(< 1.6)的用户,0.7.x 系列会继续提供 bug 修复,但不再进行任何新功能开发。完整的问题清单可参见仓库中的 doc/source/whatsnew/index.rst。
值得注意的是,文档中还预告了 0.8.0 章节末尾附有一份面向 0.7 及更早用户“移植指南”(Porting Guide),这是本次升级最重要的实操文档,下文会专门展开。
两大架构级变更
支持非唯一索引(Non-unique Indexes)
0.8.0 起,所有对象都可以使用非唯一索引。数据对齐(alignment)与 join 操作遵循 SQL join 语义,在多对多(many-to-many)join 时允许索引重复。
这一特性在今天的仓库中体现为索引基础属性is_unique,其底层实现在 pandas/_libs/index.pyx,对应的类型声明见 pandas/_libs/index.pyi。正如发布说明在移植指南中所提醒的:如果旧代码曾用try...except包裹“索引不唯一时报错”的逻辑,升级后这类异常在多数情况下不会再触发(append等个别方法仍会校验唯一性,除非显式禁用)。此时应检查index.is_unique,在返回False时显式抛异常或转入不同代码分支。
NumPy datetime64 dtype 与 NumPy 1.6 依赖
0.8.0 是 pandas 数据表示的分水岭:
- 时间序列数据改用NumPy 的
datetime64dtype表示; - pandas 0.8.0要求至少 NumPy 1.6;
- 同时已针对 NumPy 1.7+(当时的开发版本)做了测试验证,该版本包含一些显著的 API 变更;
- 文档明确提示:NumPy 1.6 在纳秒分辨率数据上存在若干 bug,建议尽量避开其
datetime64API 函数(虽然功能有限),只通过 pandas 提供的接口与该类型数据交互。
该迁移带来了三个直接收益:显著加速 join 操作与数据对齐、降低内存占用、大幅提升序列化/反序列化性能(对比旧的datetime.datetime表示)。
时间序列:功能与底层实现全面升级
高性能、灵活的 resample 重采样
0.8.0 新增了高灵活度的resample方法,支持从高频到低频、从低频到高频的双向转换,并具备:
- 插值(interpolation)支持;
- 用户自定义聚合函数;
- 对区间划分方式(interval)与结果标签(result labeling)的显式控制;
- 一套高性能的 Cython/C 重采样函数,包含 Open-High-Low-Close(OHLC)。
在今天的仓库中,重采样核心由 pandas/core/resample.py 承载:Resampler基类(pandas/core/resample.py)之上派生DatetimeIndexResampler(L2114)与PeriodIndexResampler(L2237),分别针对DatetimeIndex与PeriodIndex索引;aggregate(L346)、interpolate(L851)、ohlc(L1743)等方法正是发布说明所述能力的当代实现。
频率别名重构与频率快捷方式
frequencies模块被重构,并支持频率快捷方式,例如'15min'、'1h30min'这类组合写法。当代实现中频率推断与解析的核心位于 pandas/tseries/frequencies.py:infer_freq_str(L120)与公开 APIinfer_freq(L176)负责从索引推断频率,DatetimeIndex上也提供inferred_freq属性,并支持在构造DatetimeIndex时选择是否自动推断频率。频率别名文档可参见 doc/source/user_guide/timeseries.rst 中的 offset aliases 一节。
新 DatetimeIndex 类取代 DateRange
新增的DatetimeIndex类同时支持固定频率与不规则时间序列,取代了现已弃用的DateRange类。当前仓库中DatetimeIndex定义于 pandas/core/indexes/datetimes.py,继承了DatetimeTimedeltaMixin的完整 datetime 能力。
新 PeriodIndex 与 Period:时间跨度与日历逻辑
PeriodIndex与Period类用于表示时间跨度(time spans)并执行日历逻辑(calendar logic);- 支持12 个财政季度频率(fiscal quarterly frequencies);
- 该实现是 scikits.timeseries 代码库元素的部分移植并做了实质性增强;
- 支持
PeriodIndex与DatetimeIndex之间的相互转换。
当代实现:PeriodIndex位于 pandas/core/indexes/period.py,底层存储PeriodArray定义于 pandas/core/arrays/period.py,dtype 体系见 pandas/core/dtypes/dtypes.py 的PeriodDtype。
新 Timestamp:datetime.datetime 的子类
Timestamp数据类型继承自datetime.datetime,提供与后者一致的接口,同时支持纳秒分辨率数据,并内置时区转换能力。发布说明指出:Timestamp是datetime.datetime的子类,其nanosecond字段保存 0–999 之间的纳秒余数,可直接替换任何原本使用datetime.datetime值的代码。其当代实现位于 pandas/_libs/tslibs/timestamps.pyx。
时区支持增强:tz_convert 与 tz_localize
0.8.0 大幅增强了时区处理:
- 为 TimeSeries(Series)与 DataFrame 新增
tz_convert与tz_localize方法; - 所有时间戳统一按 UTC 存储;带时区的
DatetimeIndex中的时间戳在取出时会本地化为本地时区; - 因此时区转换几乎零成本;
- 用户几乎不需要了解 pytz 库的细节,只需提供时区名字符串;
- 时区感知(time zone-aware)时间戳当且仅当其 UTC 时间戳相等时才相等;
- 不同时区的时区感知序列做运算,结果是UTC 索引的时间序列。
当代实现可追溯至:Series/DataFrame的tz_convert(pandas/core/generic.py)与tz_localize(L11166);DatetimeIndex上的tz_convert(pandas/core/indexes/datetimes.py)与tz_localize(L595);底层Timestamp.tz_localize/tz_convert位于 pandas/_libs/tslibs/timestamps.pyx 与 pandas/_libs/tslibs/timestamps.pyx。时区文档见 doc/source/user_guide/timeseries.rst。
字符串索引快捷方式
时间序列支持字符串索引的便捷写法:可以按年、年+月切片,也可以用字符串直接索引取值。
时间序列绘图增强
绘图能力被增强,直接吸收了scikits.timeseries 基于 matplotlib 的绘图代码。0.8.0 还引入了下文将详述的secondary_y与kde等新绘图能力。
新的工厂函数:date_range、bdate_range、period_range
新增三个索引工厂函数:
date_range:按日历年/月/日等生成DatetimeIndex;bdate_range:生成**工作日(business day)**索引;period_range:生成PeriodIndex。
当代实现分别位于 pandas/core/indexes/datetimes.py(date_range)、pandas/core/indexes/datetimes.py(bdate_range)、pandas/core/indexes/period.py(period_range)。
频率推断:infer_freq 与 inferred_freq
新增健壮的频率推断函数infer_freq,以及DatetimeIndex的inferred_freq属性;构造DatetimeIndex时可选择自动推断频率。
to_datetime:批量解析字符串数组
to_datetime函数可高效地将字符串数组解析为DatetimeIndex;DatetimeIndex本身也可直接解析字符串数组/列表为datetime64。当代实现位于 pandas/core/tools/datetimes.py 的多重签名to_datetime。
其他时间序列改进
- Series/DataFrame 列中对 datetime64 dtype 数据的优化支持;
- 新增NaT(Not-a-Time)类型表示时间戳数组中的 NA 值,其当代实现见 pandas/_libs/tslibs/nattype.pyx 与类型声明 pandas/_libs/tslibs/nattype.pyi;
- 优化
Series.asof,用于按时间戳数组查找“截至某时刻(as of)的值”,当代实现见 pandas/core/generic.py; - 新增Milli(毫秒)、Micro(微秒)、Nano(纳秒)的 DateOffset 对象;
- 可用
datetime.time对象索引时间序列,选取特定时刻(TimeSeries.at_time,见 pandas/core/generic.py)或两个时刻之间的数据(TimeSeries.between_time,见 L9116); - 新增
tshift方法:按照索引的频率(如有)进行前移/后移(lead/lag),区别于朴素的shift。
其他新特性一览
0.8.0 在时间序列之外也引入了一批影响深远的通用功能:
cut与qcut(类似 R 语言的cut):把连续变量按基于数值的区间(cut)或基于分位数的区间(qcut)分箱,得到类别型变量。当代实现在 pandas/core/reshape/tile.py(cut)与 pandas/core/reshape/tile.py(qcut),文档见 doc/source/user_guide/reshaping.rst;Factor更名为Categorical,并增加多项易用性功能;fillna/reindex新增limit参数(见 doc/source/user_guide/missing_data.rst),当代fillna实现(含limit参数)可见 pandas/core/arrays/_mixins.py;- GroupBy 支持更灵活的多函数应用,可传入
(name, function)元组列表,按给定名称与顺序得到结果; - 新增灵活的
replace方法,用于高效替换值(见 doc/source/user_guide/missing_data.rst); - 增强
read_csv/read_table读取时间序列数据,可将多列转换为日期(见 doc/source/user_guide/io.rst 的 parse_dates 说明); - 解析函数(
read_csv等)新增comments、dayfirst(解析国际 DD/MM/YYYY 日期)、dialect(控制 CSV 引号等行为)、thousands(千位分隔符,改进整数解析)选项; - 支持一次性 unstack 多个层级,并缓解了
pivot_table的 bug(不再引入空列); - 索引哈希表迁移至klib 实现,性能更好、内存占用低于 Python dict;
- GroupBy 新增
first、last、min、max、prod优化函数; - 新增
ordered_merge函数(见 doc/source/user_guide/merging.rst); - DataFrame、Series 新增灵活的比较实例方法
eq、ne、lt、gt等(见 doc/source/user_guide/basics.rst 的 binop 说明); - 改进
scatter_matrix绘图函数,对角线可显示直方图或核密度估计(见 doc/source/user_guide/visualization.rst); - 新增
'kde'密度图选项(同上); - 支持通过rpy2 将 DataFrame 转换为 R 的 data.frame;
- 改进 Series 与 DataFrame 中复数的支持;
- 所有数据结构新增
pct_change方法; - 新增
max_colwidth配置选项,控制 DataFrame 控制台输出列宽; - 支持按索引值插值 Series(
interpolate,见 doc/source/user_guide/missing_data.rst); - 支持从 GroupBy 中选择多列;
- Series/DataFrame 新增
update方法,用于原地更新值(见 doc/source/user_guide/merging.rst); - DataFrame 新增
any与all方法。
新绘图方法:secondary_y 与 kde
发布说明给出了两个可直接运行的示例。
secondary_y双轴绘图(把法兰西法郎与意大利里拉画在双 y 轴上):
import pandas as pd import matplotlib.pyplot as plt fx = pd.read_pickle("data/fx_prices") plt.figure() fx["FR"].plot(style="g") fx["IT"].plot(style="k--", secondary_y=True)'kde'核密度图(由 2012 年 GSOC 参与者 Vytautas Jancauskas 贡献的多种新图型之一):
import numpy as np s = pd.Series( np.concatenate((np.random.randn(1000), np.random.randn(1000) * 0.5 + 3)) ) plt.figure() s.hist(density=True, alpha=0.2) s.plot(kind="kde")更全面的绘图说明见 doc/source/user_guide/visualization.rst 的绘图章节。
其他 API 变更
时间序列函数中的offset、time_rule、timeRule参数名被弃用。从 0.8.0 起调用这些旧参数会打印警告,弃用警告将持续到 pandas 0.9 或 1.0。
面向 pandas ≤ 0.7.3 用户的移植指南
发布说明的结尾部分专门为旧用户整理了移植要点,这是升级过程中最容易踩坑的地方。
最大变化:索引的底层数据类型
0.8.0 的最大变化是时间序列索引使用 NumPy 的datetime64dtype,而不再是dtype=object的 Pythondatetime.datetime对象数组。DateRange已被DatetimeIndex取代(行为上保持一致)。
如果你有代码把DateRange或曾经存放datetime.datetime的Index对象转换为纯 NumPy 数组,可能潜伏着标量处理相关的 bug,因为此时控制权交还给了 NumPy:
>>> import datetime >>> rng = pd.date_range("1/1/2000", periods=10) >>> rng[5] Timestamp('2000-01-06 00:00:00') >>> isinstance(rng[5], datetime.datetime) True >>> rng_asarray = np.asarray(rng) >>> scalar_val = rng_asarray[5] >>> type(scalar_val) <class 'numpy.datetime64'>如你所见,通过DatetimeIndex取出的标量是Timestamp(仍可视为datetime.datetime子类),而np.asarray后取出的则是numpy.datetime64。发布说明明确建议:不要把DatetimeIndex强制转换为普通 NumPy 数组。
需要 datetime.datetime 数组时的两种解法
如果你的代码确实需要datetime.datetime对象数组,有两种方案:
方案一:astype(object)产生Timestamp对象数组:
>>> stamp_array = rng.astype(object) >>> stamp_array Index([2000-01-01 00:00:00, 2000-01-02 00:00:00, 2000-01-03 00:00:00, 2000-01-04 00:00:00, 2000-01-05 00:00:00, 2000-01-06 00:00:00, 2000-01-07 00:00:00, 2000-01-08 00:00:00, 2000-01-09 00:00:00, 2000-01-10 00:00:00], dtype='object') >>> stamp_array[5] Timestamp('2000-01-06 00:00:00')方案二:to_pydatetime()得到真正的datetime.datetime对象数组:
>>> dt_array = rng.to_pydatetime() >>> dt_array array([datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2000, 1, 2, 0, 0), datetime.datetime(2000, 1, 3, 0, 0), datetime.datetime(2000, 1, 4, 0, 0), datetime.datetime(2000, 1, 5, 0, 0), datetime.datetime(2000, 1, 6, 0, 0), datetime.datetime(2000, 1, 7, 0, 0), datetime.datetime(2000, 1, 8, 0, 0), datetime.datetime(2000, 1, 9, 0, 0), datetime.datetime(2000, 1, 10, 0, 0)], dtype=object) >>> dt_array[5] datetime.datetime(2000, 1, 6, 0, 0)matplotlib 与 Timestamp 的兼容
matplotlib 认识datetime.datetime但不认识Timestamp。官方推荐用TimeSeries.plot直接绘图;若必须手绘,可改用to_pydatetime()转换,或为Timestamp类型注册 converter。
关于 NumPy 1.6 的纳秒 bug(重要警告)
发布说明给出明确警告:NumPy 1.6 的 datetime64 用户可见 API 存在 bug——特别是数组的字符串显示会出现乱码(garbage values),且到dtype=object的转换同样有问题:
>>> rng = pd.date_range("1/1/2000", periods=10) >>> rng DatetimeIndex(['2000-01-01', '2000-01-02', '2000-01-03', '2000-01-04', '2000-01-05', '2000-01-06', '2000-01-07', '2000-01-08', '2000-01-09', '2000-01-10'], dtype='datetime64[us]', freq='D') >>> np.asarray(rng) array(['2000-01-01T00:00:00.000000', '2000-01-02T00:00:00.000000', '2000-01-03T00:00:00.000000', '2000-01-04T00:00:00.000000', '2000-01-05T00:00:00.000000', '2000-01-06T00:00:00.000000', '2000-01-07T00:00:00.000000', '2000-01-08T00:00:00.000000', '2000-01-09T00:00:00.000000', '2000-01-10T00:00:00.000000'], dtype='datetime64[us]') >>> converted = np.asarray(rng, dtype=object) >>> converted[5] Timestamp('2000-01-06 00:00:00')发布说明的结论非常直白——“相信我,别慌”:只要你在 NumPy 1.6 下把datetime64的交互限制在 pandas 的 API 内就完全没问题。该 dtype 本质上只是内部 64 位整数,所有重要的数据处理都发生在 pandas 层且经过充分测试;官方强烈建议不要在 NumPy 1.6 中直接操作 datetime64 数组,只使用 pandas API。
非唯一索引的兼容处理
移植指南最后回到非唯一索引:如果你的代码在try...catch中捕获了“索引不唯一”导致的失败,升级后大多数场景下不再触发(append等方法仍会校验唯一性,除非显式禁用)。处理方式是显式检查:
if not index.is_unique: raise ValueError("index must be unique")或根据index.is_unique的结果走不同的代码分支。is_unique的底层实现位于 pandas/_libs/index.pyx。
小结
pandas 0.8.0 的意义远超一个普通大版本:它以 NumPydatetime64为地基重构了时间序列数据模型,确立了此后十余年 pandas 时间序列 API 的基本形态——DatetimeIndex、PeriodIndex、Timestamp、resample、date_range、infer_freq、tz_convert/tz_localize这些今天仍在使用的核心构件都在本版本成型或定型。配合cut/qcut、GroupBy 增强、解析器选项与绘图新能力,0.8.0 既是对 scikits.timeseries 遗产的继承与超越,也是 pandas 走向成熟数据科学基础设施的关键一跃。对仍在维护 0.7.x 时代代码的开发者而言,上述移植指南中的Timestamp子类关系、astype(object)/to_pydatetime()转换路径,以及非唯一索引的显式检查,是平滑过渡到新模型的三把钥匙。
【免费下载链接】pandasFlexible and powerful data analysis / manipulation library for Python, providing labeled data structures similar to R data.frame objects, statistical functions, and much more项目地址: https://gitcode.com/gh_mirrors/pa/pandas
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考