如何根据KDE图y值调整子图高度及修复四分位线显示问题
解决方案:调整子图高度与修复四分位线显示问题
针对你提出的两个核心需求,我修改了代码并逐一解决,以下是完整的修正代码和关键改动说明:
完整修正代码
import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from matplotlib.ticker import NullFormatter import numpy as np import random def plotverweildauernwithuG(df): def rand_float_range(start, end): return random.random() * (end - start) + start def find_nearest(array, value): array = np.asarray(array) idx = (np.abs(array - value)).argmin() return idx df = df[df['method']=='data'] df['years'] = df['years'].astype('float64') sns.set() max_cs = df['cs'].max() # 先创建子图,暂不设置尺寸 fig, ax = plt.subplots(nrows=max_cs, sharex=True) nrows = len(ax) ax[-1].set_xlabel('Verweildauer in Jahren') s = [(df["cs"] == t).sum() for t in range(1, max_cs+1)] s.insert(0, 0) # 第一步:先计算所有子图中KDE曲线的最大y值 max_kde_y = 0 kde_data = [] # 存储每个子图的KDE数据点,后续绘制四分位线用 for i in range(1, max_cs+1): verweildauer_i = df[df['cs'] == i]['years'] # 先绘制KDE获取最大y值,不立即调整图尺寸 sns.kdeplot(verweildauer_i, ax=ax[i-1], shade=True, cumulative=False) data_x, data_y = ax[i-1].lines[0].get_data() kde_data.append((data_x, data_y)) current_max = data_y.max() if current_max > max_kde_y: max_kde_y = current_max # 第二步:根据最大KDE y值调整整个图的高度 # 每个子图需要的高度要兼顾KDE的最大y值和现有ylim范围(-0.5到1) # 这里设置基础高度+KDE峰值对应的额外高度,确保所有子图的KDE都完整显示 base_height_per_subplot = 1.8 # 基础高度,适配散点和标签 total_height = base_height_per_subplot * nrows + max_kde_y * 0.8 # 系数可根据实际效果调整 fig.set_size_inches(10, total_height) plt.subplots_adjust(hspace=0) # 第三步:重新绘制所有元素并修复四分位线 for i in range(1, max_cs+1): # 获取当前子图的KDE数据 data_x, data_y = kde_data[i-1] # 重新获取数据并绘制散点 verweildauer_i_nugw = df[(df['cs'] == i) & (df['uGW']==False)]['years'] cs_bandwith_i_nugw = df[(df['cs'] == i) & (df['uGW']==False)]['cs_bandwith'] verweildauer_i_ugw = df[(df['cs'] == i) & (df['uGW']==True)]['years'] cs_bandwith_i_ugw = df[(df['cs'] == i) & (df['uGW']==True)]['cs_bandwith'] ax[i-1].scatter(verweildauer_i_nugw, cs_bandwith_i_nugw - i - 0.5, c="blue", label='Verweildauer') ax[i-1].scatter(verweildauer_i_ugw, cs_bandwith_i_ugw - i - 0.5, c="red", label='Unterer Grenzwert Verweildauer') # 计算四分位数值 quatrilesx = [ df[df['cs']==i]["years"].describe()['25%'], df[df['cs']==i]["years"].describe()['50%'], df[df['cs']==i]["years"].describe()['75%'] ] # 修复四分位线:基于子图y轴范围计算比例 y_min, y_max = ax[i-1].get_ylim() # 获取当前子图的y轴范围(-0.5, 1) y_range = y_max - y_min # 范围长度为1.5 for k in range(len(quatrilesx)): q_val = quatrilesx[k] # 找到KDE曲线上对应四分位x值的y值 idx = find_nearest(data_x, q_val) kde_y_at_q = data_y[idx] # 将KDE的y值转换为子图y轴的比例(0-1) y_start = 0.05 # 四分位线从底部稍上方开始 y_end = y_start + (kde_y_at_q / y_range) * 0.9 # 占子图高度的90%区间,避免超出 ax[i-1].axvline(q_val, ymin=y_start, ymax=y_end, linestyle='dotted', color='black', linewidth=0.6, alpha=0.8) # 设置子图属性 ax[i-1].set_ylim(-0.5, 1 + max_kde_y * 0.1) # 稍微扩展y轴上限,确保KDE完整 ax[i-1].set_ylabel('ZK ' + str(i)) ax[i-1].set_yticks([]) ax[i-1].axhline(0, linestyle='--', color='blue', linewidth=0.5, alpha=0.35) # 修复图例:避免重复创建 handles, labels = ax[i-1].get_legend_handles_labels() # 去重(散点和KDE可能重复标签) unique_handles = [] unique_labels = [] for h, l in zip(handles, labels): if l not in unique_labels: unique_labels.append(l) unique_handles.append(h) ax[i-1].legend(unique_handles, unique_labels) # 添加右侧计数标签 ax2 = ax[i-1].twinx() ax2.grid(False) ax2.set_ylabel("n = {}".format(s[i]), rotation=0, labelpad=25) ax2.set_yticks([]) plt.show()
关键改动说明
1. 子图高度自适应KDE最大y值
- 问题根源:原代码在循环中逐次调整图尺寸,且仅用单个KDE的y最大值作为高度,没有考虑子图数量和每个子图的基础空间需求,导致最后一个子图的KDE可能被截断。
- 修复方式:
- 先遍历所有子图,计算所有KDE曲线的最大y值
max_kde_y。 - 基于子图数量和最大KDE值计算总高度,确保每个子图有足够空间显示KDE曲线。
- 扩展子图的y轴上限(
1 + max_kde_y * 0.1),避免KDE曲线被ylim截断。
- 先遍历所有子图,计算所有KDE曲线的最大y值
2. 修复四分位线不可见问题
- 问题根源:原代码中
ymin和ymax的计算逻辑错误(使用height/width的比例),导致四分位线的位置超出子图可视范围,或者长度为0。 - 修复方式:
- 基于子图实际的y轴范围(
y_min到y_max)计算比例,将KDE的y值转换为0-1之间的相对位置。 - 调整四分位线的起始位置(
y_start=0.05),确保从子图底部可见,结束位置根据KDE的y值动态计算,保证线条长度与KDE高度匹配。 - 优化图例去重,避免重复添加导致的显示混乱。
- 基于子图实际的y轴范围(
内容的提问来源于stack exchange,提问作者ghxk
相关产品推荐
相关产品推荐

