matplotlib循环分月绘制气象时间序列首月数据不显示及子图保存异常
核心问题定位与修复
你的判断方向完全正确,所有问题根源都是matplotlib figure对象的引用被循环逻辑反复覆盖,不存在底层机制的理解门槛问题,具体错误和修复如下:
错误1:1月子图无法渲染的根因
你的原始代码在循环内的执行顺序完全打乱了figure的生命周期:
- 当循环进入第一次迭代(处理1月数据)时,你已经把1月的气象数据画到了初始的空figure上
- 但循环末尾你写了
fig = plt.figure(figsize=(20, 5)),这行代码会生成一个全新的空白figure,直接把之前存放1月绘图内容的figure的引用覆盖掉 - 后续从2月到12月的所有绘图,都画在这个被反复覆盖的新figure上,而存放1月内容的figure因为没有有效引用,直接被matplotlib的内存管理机制回收,最终渲染时自然看不到1月的子图。
你第二次调整的代码这个问题更严重:循环外刚生成了带6行2列共12个空axes的正确figure,结果循环第一行就写fig = plt.figure(figsize=(200, 50)),每处理一个月就重新生成一个空白figure,完全把之前初始化的axes所属的figure引用丢弃了。
错误2:无法正确导出整幅figure的根因
导出失败的原因同样是引用覆盖:
- 你循环结束后,又额外执行了
fig = plt.figure()生成了一个完全空白的新figure - 此时你调用
fig.savefig(),保存的就是这个完全没有任何绘图内容的空白对象,之前画在原6行2列布局上的12个月份子图,因为fig引用被反复覆盖,根本不在当前保存的对象上。
具体修复规则
- 所有figure、axes对象的初始化必须放在循环外部,整个绘图流程中绝对不能重新给fig变量赋值、不能随意新建figure
- 自定义Meteogram类的方法不要自行创建子图,必须使用外部传入的已经初始化好的axes对象
- 类内部不要依赖循环内的全局变量(比如starttime、endtime),所有动态状态要通过参数传入,避免跨循环的变量值串扰
savefig操作必须在循环结束后、直接引用最开始初始化的那个figure对象,绝不能在savefig之前重新创建新的figure
修正后的核心可运行代码
from calendar import monthrange import datetime as dt import matplotlib as mpl import matplotlib.pyplot as plt import pandas as pd import numpy as np # 循环外统一初始化figure和所有子图,整个流程绝不覆盖fig引用 fig, axes = plt.subplots(nrows=6, ncols=2, figsize=(20, 60)) probe_id = 'SOCA' year = 2019 class Meteogram: def __init__(self, dates, start_time, end_time, probeid): # 去掉对全局变量的依赖,所有状态通过参数传入 self.start = start_time self.end = end_time self.dates = mpl.dates.date2num(dates) self.probeid = probeid def plot_winds(self, ws, wd, wsmax, ax, plot_range=None): # 直接使用传入的ax,不自行创建子图 self.ax1 = ax ln1 = self.ax1.plot(self.dates, ws, label='Wind Speed') self.ax1.fill_between(self.dates, ws, 0) self.ax1.set_xlim(self.start, self.end) if not plot_range: plot_range = [0, 30, 5] self.ax1.set_ylabel(f'Wind Speed (knots)\n{self.start.strftime("%Y-%m")}') self.ax1.set_ylim(plot_range[0], plot_range[1], plot_range[2]) self.ax1.grid(b=True, which='major', axis='y', color='k', linestyle='-', linewidth=0.5) ln2 = self.ax1.plot(self.dates, wsmax, '.r', label='Gust') ax7 = self.ax1.twinx() ln3 = ax7.plot(self.dates, wd, '.k', linewidth=0.1, label='Wind Direction') ax7.set_ylabel('Wind\nDirection\n(degrees)', multialignment='center') ax7.set_ylim(0, 360) ax7.set_yticks(np.arange(45, 405, 90), ['NE', 'SE', 'SW', 'NW']) lines = ln1 + ln2 + ln3 labs = [line.get_label() for line in lines] ax7.xaxis.set_major_formatter(mpl.dates.DateFormatter('%m/%d')) ax7.legend(lines, labs, loc='center', bbox_to_anchor=(.9, -0.25), ncol=3, prop={'size': 10}) # 循环内仅做数据处理、绘图,不创建新figure for months in range(1, 13): # 计算当月的时间范围 _, month_day_count = monthrange(year, months) endtime = dt.datetime(year, months, month_day_count, 0, 0) starttime = dt.datetime(year, months, 1, 0, 0) # 过滤当月数据 df_new = df[(df['ICAO'] == probe_id) & (df['month'] == months)].copy() # 数据类型转换 for col in ['wind_speed', 'wind_dir', 'wind_gust', 'temp', 'dewpt', 'press']: df_new[col] = pd.to_numeric(df_new[col], errors='coerce') df_new['wind_gust'] = df_new['wind_gust'].fillna(0) # 获取对应位置的ax(6行2列布局,顺序为1-2月第一行,3-4月第二行...) row_idx = (months - 1) // 2 col_idx = (months - 1) % 2 target_ax = axes[row_idx, col_idx] # 实例化meteogram并绘图 meteogram = Meteogram( dates=df_new['datetime'], start_time=starttime, end_time=endtime, probeid=probe_id ) meteogram.plot_winds( ws=df_new['wind_speed'], wd=df_new['wind_dir'], wsmax=df_new['wind_gust'], ax=target_ax ) # 循环结束后统一调整布局、保存图片,绝不新建figure fig.tight_layout() fig.subplots_adjust(hspace=0.5, wspace=0.3) fig.savefig('monthly_meteogram_2019.png', dpi=150, bbox_inches='tight')
内容的提问来源于stack exchange,提问作者Christopher Hall
相关产品推荐
相关产品推荐

