Matplotlib datetime X轴全量数据下月首刻度格式化异常问题
问题描述
对2021年逐小时时间序列数据做可视化时,X轴为datetime64[ns]类型的时间值,目标配置为仅在每月1日(1-12月)显示主刻度。实际运行时出现两类表现:
- 加载全量数据点时,X轴刻度标签显示错误年份,刻度总数远超出预期
- 删除大比例数据后,刻度标签可正常显示,但会丢失原始数据
需要在保留全部数据的前提下,实现X轴仅在每月首日显示刻度与对应标签的效果。
复现代码
import matplotlib.dates as mdates import matplotlib.pyplot as plt import numpy as np import pandas as pd times=pd.date_range(start='2021-01-01', end='2021-12-31', freq='1h') adf = pd.DataFrame({ 'time': times, 'A': np.random.rand(times.shape[0]), 'B': np.random.rand(times.shape[0]) * 100 }) # 取消注释删除大比例数据后,刻度可正常格式化 # for col in adf.columns: # adf.loc[adf.sample(frac=0.7).index, col] = np.nan adf.set_index('time', inplace=True) fig, ax = plt.subplots(dpi=300) ax2 = ax.twinx() a = adf.plot(y='A', ax=ax, color='blue', alpha=0.3, legend=False) b = adf.plot(y='B', ax=ax2, color='red', alpha=0.3, legend=False) ax.legend([ a.get_lines()[0], b.get_lines()[0] ], ['A', 'B']) ax.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d-%y')) ax.xaxis.set_major_locator(mdates.MonthLocator(interval=1)) ax.tick_params(axis='x', rotation=30)
异常表现
全量数据场景刻度异常
全量数据加载时,刻度及标签数量异常偏多,执行ax.get_xticklabels()返回结果如下,标签显示为1994年的错误日期:
[Text(447088.0, 0, '02-01-94'), Text(447116.0, 0, '03-01-94'), Text(447147.0, 0, '04-01-94'), Text(447177.0, 0, '05-01-94'), Text(447208.0, 0, '06-01-94'), Text(447238.0, 0, '07-01-94'), Text(447269.0, 0, '08-01-94'), Text(447300.0, 0, '09-01-94'), Text(447330.0, 0, '10-01-94'), Text(447361.0, 0, '11-01-94'), ...]

删减量数据场景刻度正常
删除70%左右数据后,刻度可正常显示对应数量,但会丢失原始数据,效果如下:
问题原因
出现该问题的核心是pandas默认绘图逻辑的冲突:
- 当绘制的时间序列数据无缺失值、点密度较高时,pandas会启用自带的高密度时间刻度优化逻辑,自动生成小时/日级的刻度规则,后续手动设置的matplotlib日期刻度配置不会覆盖这套规则
- 你看到的1994年错误标签,本质是pandas生成的刻度值为距时间起点的小时偏移量,被
DateFormatter误解析为1900年代的日期 - 当数据中存在大量NaN值时,pandas会自动切换回matplotlib原生的日期刻度模式,后续手动设置的locator和formatter就会生效,也就是删数据后刻度正常的原因
解决方案
在调用pandas的plot()方法时传入x_compat=True参数,禁用pandas自带的时间刻度自动优化逻辑,让后续手动配置的matplotlib日期刻度规则正常生效。如果需要严格限定仅每月1日生成刻度,可以给MonthLocator额外传入bymonthday=1参数。
修正后的完整可运行代码如下:
import matplotlib.dates as mdates import matplotlib.pyplot as plt import numpy as np import pandas as pd times=pd.date_range(start='2021-01-01', end='2021-12-31', freq='1h') adf = pd.DataFrame({ 'time': times, 'A': np.random.rand(times.shape[0]), 'B': np.random.rand(times.shape[0]) * 100 }) adf.set_index('time', inplace=True) fig, ax = plt.subplots(dpi=300) ax2 = ax.twinx() # 关键修改:传入x_compat=True禁用pandas自动刻度逻辑 a = adf.plot(y='A', ax=ax, color='blue', alpha=0.3, legend=False, x_compat=True) b = adf.plot(y='B', ax=ax2, color='red', alpha=0.3, legend=False, x_compat=True) ax.legend([ a.get_lines()[0], b.get_lines()[0] ], ['A', 'B']) # 配置仅每月1日显示主刻度 ax.xaxis.set_major_locator(mdates.MonthLocator(interval=1, bymonthday=1)) ax.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d-%y')) ax.tick_params(axis='x', rotation=30) # 自动调整布局避免标签截断 plt.tight_layout() plt.show()
运行后即可在保留全量逐小时数据的前提下,实现X轴仅在每月1日显示正确的2021年日期刻度标签。
内容的提问来源于stack exchange,提问作者K. Shores
相关产品推荐
相关产品推荐

