Matplotlib循环生成子图时如何兼容单/多轴场景并实现X轴共享?
异常根因定位
- 第一类报错(未设置
squeeze=False,nrows=1时触发):matplotlib默认开启squeeze=True,当子图布局仅1行1列时,会直接返回单个Axes对象,不会封装为可索引的数组结构,此时对ax做下标索引就会触发'AxesSubplot' object is not subscriptable错误。 - 第二类报错(设置
squeeze=False后触发):开启squeeze=False后,无论子图规模多大,都会严格按照(nrows, ncols)的二维形状返回numpy数组存储Axes对象。由于你的场景固定ncols=1,因此ax的形状为(len(gap_list), 1),之前直接用ax[i]取值,拿到的是第i行的长度为1的子数组,而非单个Axes实例,因此调用绘图方法时会触发'numpy.ndarray' object has no attribute 'fill_between'错误。
你最初采用的plt.subplots实现是最优方向,仅需微调两处即可同时兼容动态行数、共享X轴、无索引报错:
- 保留
squeeze=False设置,保证所有规模下ax的数组结构完全一致,不会随行数变化返回不同类型的对象 - 在初始化子图后增加一行数组扁平化处理,无需修改原有的绘图索引逻辑即可适配所有场景
调整后完整代码
# 初始化画布与动态子图 fig, ax = plt.subplots( nrows=len(gap_list), ncols=1, figsize=(12, len(gap_list)), sharex=True, squeeze=False ) # 将二维Axes数组拉平为一维,兼容原有ax[i]的索引习惯 ax = ax.ravel() # 遍历通道绘图 for i, ch in enumerate(gap_list): resample_s = 4 * ch_gap[ch]['rec_rate'] ylabel = f"{ch} ({ch_gap[ch]['board']}) - {ch_gap[ch]['unit']}" data = df[ch].resample(f'{resample_s}s').mean() is_nan = data.isnull() ax[i].fill_between( data.index, 0, is_nan * data.max(), color='r', step='mid', linewidth='0' ) ax[i].plot( data.index, data, color='b', linestyle='-', marker=',', label=ylabel ) ax[i].legend(loc='upper left') plt.tight_layout() plt.show()
注:原代码中
i = gap_list.index(ch)的写法存在潜在风险,若通道列表存在重复列名,会固定取第一个匹配项的索引导致绘图错位,改用enumerate遍历可以从根源避免这类问题,同时性能更优。
方案兼容性验证
- 当
gap_list长度为1(nrows=1)时,扁平化后ax是长度为1的一维数组,ax[0]可正确取到唯一的Axes对象,无索引错误 - 当
gap_list长度为2-10(nrows>1)时,扁平化后ax是长度为nrows的一维数组,完全匹配原有代码的索引逻辑 sharex=True参数会正常生效,所有子图自动实现X轴范围同步、刻度联动,无需额外手动处理
内容的提问来源于stack exchange,提问作者almonde
相关产品推荐
相关产品推荐

