Python修复Y轴范围过大图表:解决峰值掩盖有效数据问题
解决Matplotlib图表峰值掩盖有效数据的问题
我完全懂你这种困扰——那些突然飙升的峰值一下子就把你真正要分析的有效数据给淹没了,而且每个子图的Y轴尺度还不一样,硬设Y轴范围确实不是个通用的好办法。这里有两个实用的方案,你可以根据自己的需求来选:
方案一:过滤极端异常值(简单直接)
如果这些峰值属于噪声或者你不需要展示它们,你可以基于百分位数来过滤掉超出合理范围的数值,把它们替换成阈值或者NaN(Matplotlib绘图时会自动跳过NaN)。
示例代码:
import numpy as np # 定义过滤函数,默认保留99%以内的数据,可根据需求调整percentile参数 def filter_extreme_values(data, percentile=99): # 计算指定百分位数作为阈值 upper_threshold = np.percentile(data, percentile) # 把超过阈值的数值替换为阈值(也可以换成np.nan,这样绘图时会跳过这些点) filtered_data = np.where(data > upper_threshold, upper_threshold, data) return filtered_data # 批量处理你的plotdata列表 filtered_plotdata = [filter_extreme_values(dataset) for dataset in plotdata] # 后续绘图代码不变,只需要把plotdata换成filtered_plotdata fig, (ax1,ax2,ax3,ax4) = plt.subplots(4, sharex = False, figsize =(15,15)) ax1.plot(Epoch[0], filtered_plotdata[0]) ax1.set_ylabel(label, fontsize = 20) ax1.tick_params(axis = 'both',which = 'major', labelsize = 18) ax1.get_yaxis().set_tick_params(direction='out',width=2) ax1.set_xlim([start1, stop1]) nbins = 5 ax1.yaxis.set_major_locator(MaxNLocator(nbins=nbins,prune='upper')) # ax2、ax3、ax4的代码同理替换plotdata为filtered_plotdata即可
方案二:使用断裂Y轴(保留所有数据)
如果你不想丢弃峰值数据,只是希望同时看清正常范围的细节,那可以给Y轴做断裂处理——把峰值区域单独放在一个小的子图里,下方子图展示正常尺度的数据,这样既能保留完整数据,又不会让峰值掩盖有效信息。
示例代码:
import numpy as np from matplotlib.ticker import MaxNLocator # 创建4组上下子图,每组对应一个原图表 fig, ((ax1_top, ax2_top), (ax1_bottom, ax2_bottom), (ax3_top, ax4_top), (ax3_bottom, ax4_bottom)) = plt.subplots(4, 2, figsize=(15,15)) # 处理第一个子图ax1 threshold_1 = np.percentile(plotdata[0], 99) # 自定义阈值,比如99%分位数 # 底部子图:显示正常范围数据 ax1_bottom.plot(Epoch[0], plotdata[0]) ax1_bottom.set_ylabel(label, fontsize=20) ax1_bottom.set_ylim(bottom=None, top=threshold_1) ax1_bottom.set_xlim([start1, stop1]) ax1_bottom.tick_params(axis='both', which='major', labelsize=18) ax1_bottom.get_yaxis().set_tick_params(direction='out', width=2) ax1_bottom.yaxis.set_major_locator(MaxNLocator(nbins=5, prune='upper')) # 顶部子图:显示峰值区域 ax1_top.plot(Epoch[0], plotdata[0]) ax1_top.set_ylim(bottom=threshold_1, top=None) ax1_top.set_xlim([start1, stop1]) ax1_top.tick_params(axis='both', which='major', labelsize=18) ax1_top.get_yaxis().set_tick_params(direction='out', width=2) ax1_top.yaxis.set_major_locator(MaxNLocator(nbins=2, prune='lower')) ax1_top.spines.bottom.set_visible(False) # 隐藏底部边框 ax1_bottom.spines.top.set_visible(False) # 隐藏顶部边框 ax1_top.tick_params(labelbottom=False) # 隐藏底部刻度标签 # 添加Y轴断裂的斜线标记 d = 0.015 # 斜线的大小,可调整 kwargs = dict(transform=ax1_top.transAxes, color='black', clip_on=False) ax1_top.plot((-d, +d), (-d, +d), **kwargs) ax1_top.plot((1 - d, 1 + d), (-d, +d), **kwargs) kwargs.update(transform=ax1_bottom.transAxes) ax1_bottom.plot((-d, +d), (1 - d, 1 + d), **kwargs) ax1_bottom.plot((1 - d, 1 + d), (1 - d, 1 + d), **kwargs) # 重复上述逻辑处理ax2、ax3、ax4,注意每个子图的阈值要单独计算(因为数据不同) # 比如处理ax2: threshold_2 = np.percentile(plotdata[1], 99) ax2_bottom.plot(Epoch[1], plotdata[1]) ax2_bottom.set_ylabel(label, fontsize=20) ax2_bottom.set_ylim(bottom=None, top=threshold_2) ax2_bottom.set_xlim([start2, stop2]) # ... 后续设置和ax1类似,不再重复
两种方案对比:
- 方案一适合峰值是噪声或无需展示的场景,代码简单,图表整洁;
- 方案二适合需要保留峰值数据完整性的场景,能让读者同时看到整体数据和细节,但代码稍复杂。
内容的提问来源于stack exchange,提问作者alex anderson
相关产品推荐
相关产品推荐

