如何修复Matplotlib子图时间轴刻度,仅在数据起始日期后显示标签?
问题:Matplotlib子图时间轴刻度标签仅显示数据起始日期之后的内容
尝试在Matplotlib子图中设置时间轴,让x轴刻度标签仅在对应子图的数据起始日期之后显示,编写了如下代码:
import pandas as pd import numpy as np from datetime import date import matplotlib.pyplot as plt from matplotlib.dates import MonthLocator, num2date from matplotlib.ticker import FuncFormatter xs = [pd.date_range(f'{y}-07-01', '2021-12-31', freq='M') for y in range(2016, 2019)] ys = [np.random.rand(len(x)) for x in xs] fig, axs = plt.subplots(3, 1, figsize=(10, 8)) for ax, x, y in zip(axs, xs, ys): ax.plot(x, y) # Custom formatting function date_min = x[0].date() def custom_date_formatter(x, pos): dt = num2date(x) if dt.date() < date_min: return '' elif dt.month == 1: return dt.strftime('%Y') else: return dt.strftime('%b').upper() ax.xaxis.set_major_locator(MonthLocator((1, 4, 7, 10))) ax.xaxis.set_major_formatter(FuncFormatter(custom_date_formatter)) ax.tick_params(axis='x', labelsize=8) ax.set_xlim(date(2016, 7, 1))
运行后所有子图的x轴刻度标签完全相同,不符合预期。预期每个子图的刻度标签如下:
JUL - OCT - 2017 - APR - JUL - OCT - 2018 ..... ..... JUL - OCT - 2018 - APR - JUL - OCT - 2019 ..... ............ JUL - OCT - 2019 - APR - JUL - OCT - 2020 .....
问题原因
循环中定义custom_date_formatter函数时,存在闭包变量延迟绑定的问题:函数内部的date_min并不会在定义时立即捕获当前循环的变量值,而是在函数实际调用时才会去查找date_min的最终值——也就是循环最后一次迭代的date_min(2018-07-01),导致所有子图都用这个值来判断刻度是否显示。
解决方案
使用functools.partial将当前子图的date_min绑定到格式化函数中,确保每个子图的格式化函数都持有各自的起始日期。修改后的代码如下:
import pandas as pd import numpy as np from datetime import date from functools import partial # 导入partial import matplotlib.pyplot as plt from matplotlib.dates import MonthLocator, num2date from matplotlib.ticker import FuncFormatter xs = [pd.date_range(f'{y}-07-01', '2021-12-31', freq='M') for y in range(2016, 2019)] ys = [np.random.rand(len(x)) for x in xs] fig, axs = plt.subplots(3, 1, figsize=(10, 8)) # 将格式化函数定义在循环外部,接收date_min参数 def custom_date_formatter(date_min, x, pos): dt = num2date(x) if dt.date() < date_min: return '' elif dt.month == 1: return dt.strftime('%Y') else: return dt.strftime('%b').upper() for ax, x, y in zip(axs, xs, ys): ax.plot(x, y) date_min = x[0].date() # 使用partial绑定当前子图的date_min formatter = FuncFormatter(partial(custom_date_formatter, date_min)) ax.xaxis.set_major_locator(MonthLocator((1, 4, 7, 10))) ax.xaxis.set_major_formatter(formatter) ax.tick_params(axis='x', labelsize=8) # 调整每个子图的x轴范围,只显示当前数据的起始日期及之后 ax.set_xlim(date_min)
关键修改点
- 将格式化函数移到循环外:避免每次循环定义新函数导致的闭包问题,同时让函数接收
date_min作为参数。 - 用
partial绑定参数:为每个子图的格式化函数绑定对应的date_min,确保每个子图使用自己的起始日期判断刻度是否显示。 - 调整x轴范围:把固定的起始日期改为当前子图的
date_min,让每个子图的x轴从自身数据的起始日期开始,避免显示多余的空白区域。
内容的提问来源于stack exchange,提问作者steven
相关产品推荐
相关产品推荐

