pandas DataFrame.plot如何将x轴UNIX时间戳转为本地时区日期时间
问题描述
使用pandas的DataFrame.plot绘制UNIX时间戳(自1970年UTC纪元起算的秒数)格式的时间序列时,x轴默认始终显示UTC时间,刻度标签、刻度位置均无法自动适配本地时区,手动调整容易出现时区偏移问题。
初始测试代码
import time from dateutil.tz import tzlocal from pandas import DataFrame from numpy import datetime64 from pandas import DatetimeIndex from matplotlib.figure import Figure figure = Figure(figsize=(7, 4)) figure.add_subplot() ax = figure.axes[0] now = time.time() dt64s = [datetime64(int(when), 's') for when in (now - 3600, now - 1800, now)] dti = DatetimeIndex(dt64s, tz=tzlocal()) data = {'a': [1, 2, 3], 'b': [4, 5, 6]} df = DataFrame(data, index=dti) df.plot(ax=ax) figure.savefig('plot1.png')
运行上述代码生成的图表刻度始终为UTC时间,哪怕直接传入不带tz参数的原始datetime64值结果也一致。这是因为numpy datetime64本质是基于UNIX纪元的时间戳,matplotlib默认按UTC时区解析渲染,此前尝试用DateLocator调整未生效。
阶段性调试结果
经过调试,通过三处调整实现了刻度标签的本地时区显示,更新后代码如下:
import time from dateutil.tz import tzlocal from pandas import DataFrame from numpy import datetime64 from pandas import DatetimeIndex from matplotlib.dates import DateFormatter from matplotlib.figure import Figure figure = Figure(figsize=(7, 9)) figure.add_subplot() ax = figure.axes[0] now = time.time() dt64s = [ datetime64(int(when), 's') for when in (now + i * 1800 for i in range(-90, 1)) ] dti = DatetimeIndex(dt64s) print(dti) data = { 'a': [i for i, _ in enumerate(dt64s)], 'b': [i + 2 for i, _ in enumerate(dt64s)] } df = DataFrame(data, index=dti) dfmt = DateFormatter("%Y-%m-%d %H:%M:%S %z %Z", tz=tzlocal()) ax.xaxis.set_major_formatter(dfmt) ax.set_title(f"Data from {dti[0]}..{dti[-1]}") df.plot(ax=ax) figure.savefig('plot1.png')
三处核心改动及验证效果:
- 扩大时间范围覆盖数天数据:更直观展示时区差异,方便和本地时间对比验证准确性
- 构造
DatetimeIndex时移除tz参数:DatetimeIndex构造函数传入tz的逻辑是将传入的无时区时间识别为该时区的本地时间,再转换为内部UTC存储。由于传入的dt64本身是UTC基准的UNIX时间戳,直接传tz会把UTC时间错当成本地时间做一次转换,出现时区偏移(比如GMT+10时区下生成的时间会比正确值快10小时),移除tz参数后偏移消失,能得到和输入时间戳匹配的正确UTC基准时间 - 添加带tz参数的
DateFormatter:可以将索引存储的UTC时间转换为本地时区格式显示在刻度标签上,解决了标签显示错误的问题
剩余问题解决方法
当前刻度位置仍然对齐UTC午夜,核心原因是仅给DateFormatter传入了本地时区参数,没有给刻度定位器(Locator)配置相同时区,Locator默认仍按UTC计算刻度位置,自然会对齐UTC午夜。
操作步骤
- 导入对应时间粒度的时间刻度定位器,和Formatter一样传入
tz=tzlocal()参数,即可让刻度按本地时区规则计算位置。以按天为刻度间隔、对齐本地午夜的场景为例,代码修改如下:
# 新增导入DayLocator from matplotlib.dates import DateFormatter, DayLocator # 数据构造部分保持不变 # ... # 同时配置Locator和Formatter的时区参数 ax.xaxis.set_major_locator(DayLocator(tz=tzlocal())) dfmt = DateFormatter("%Y-%m-%d %H:%M:%S", tz=tzlocal()) ax.xaxis.set_major_formatter(dfmt) df.plot(ax=ax) figure.savefig('plot_local_tz.png')
如果需要其他时间粒度的刻度,比如按小时、按周,替换成对应的HourLocator/WeekdayLocator即可,matplotlib所有内置时间刻度Locator都支持tz参数,传入本地时区后会自动对齐本地午夜、本地整点等时间节点,适配太阳能发电这类和本地日照时间强绑定的时序数据展示需求。
- 更简洁的实现方式是从索引层面就正确转换为本地时区,不需要额外配置matplotlib的Formatter和Locator:
# 从UNIX时间戳生成正确的本地时区DatetimeIndex dti = DatetimeIndex(dt64s).tz_localize('UTC').tz_convert(tzlocal()) df = DataFrame(data, index=dti) # 直接绘图即可,pandas会自动按索引的时区渲染刻度位置和标签 df.plot(ax=ax)
这种写法先将原始UNIX时间戳对应的无时区datetime标记为UTC时区,再转换为本地时区,不会出现之前直接传tz参数导致的偏移问题,pandas绘图时会自动识别索引的时区信息,无需额外配置matplotlib组件。
内容的提问来源于stack exchange,提问作者Cameron Simpson

