如何让Seaborn的KDE曲线适配加权直方图数据?
问题:如何绘制加权直方图对应的加权KDE曲线?
我希望绘制两组数据的加权直方图及对应的加权Kernel Density Estimate(KDE)曲线:数据为DNA片段长度length(取值在(0, 1e8)区间的整数),按分类变量regions分为两组。目前使用seaborn的histplot时,设置weights='length'后直方图已变为加权形式,但内置的KDE曲线并未同步更新为加权版本。
附最小可复现代码:
import io import matplotlib import matplotlib.pyplot as plt import numpy as np import pandas as pd import seaborn as sns def plot_restriction_digest(df, out_file_base, weights): # Prevent the python icon from showing in the dock when the script is # running: matplotlib.use('Agg') sns.set_theme(style='ticks') f, ax = plt.subplots(figsize=(7, 5)) sns.despine(f) hist = sns.histplot(data=df, x='length', hue='regions', weights=weights, stat='density', common_norm=False, bins=100, log_scale=True, kde=True, # 此处KDE未应用权重 ) sns.move_legend(hist, 'upper left') plt.savefig(f'{out_file_base}.pdf') return testdata=""" 1 all 1 all 2 all 2 all 2 all 3 all 4 captured 4 captured 5 captured 5 captured 5 captured 8 captured """ # 默认未加权图 df = pd.read_csv(io.StringIO(testdata), sep='\s+', header=None, names='length regions'.split()) plot_restriction_digest(df, 'test_tiny', None) # 加权图(KDE未同步) df = pd.read_csv(io.StringIO(testdata), sep='\s+', header=None, names='length regions'.split()) plot_restriction_digest(df, 'test_tiny_weighted', 'length') print('Done.')
说明:
- 两组数据对应“all”和“captured”两类基因组区域;
- 真实数据含数千万行,需对数变换X轴区分分布;
- 使用环境:Python 3.11.6,matplotlib-base 3.8.2,numpy 1.26.3,pandas 2.2.0,seaborn 0.13.1。
解决方案
seaborn内置的histplot(kde=True)不支持传递权重参数给KDE计算,因此需要手动计算每组的加权KDE并叠加到直方图上。具体步骤如下:
- 绘制加权直方图:去掉
kde=True参数,先完成加权直方图的绘制; - 计算加权KDE:使用
scipy.stats.gaussian_kde(支持权重参数)对每个分组单独计算KDE; - 叠加KDE曲线:在对数轴上生成合适的X值,计算对应的KDE密度,再绘制到直方图的轴上。
修改后的完整代码:
import io import matplotlib import matplotlib.pyplot as plt import numpy as np import pandas as pd import seaborn as sns from scipy.stats import gaussian_kde # 导入加权KDE计算工具 def plot_restriction_digest(df, out_file_base, weights): matplotlib.use('Agg') sns.set_theme(style='ticks') f, ax = plt.subplots(figsize=(7, 5)) sns.despine(f) # 1. 绘制加权直方图,关闭内置KDE hist = sns.histplot(data=df, x='length', hue='regions', weights=weights, stat='density', common_norm=False, bins=100, log_scale=True, kde=False, # 禁用内置KDE ) # 2. 为每个分组计算并绘制加权KDE # 获取分组和对应的颜色(保持与直方图一致) hue_groups = df['regions'].unique() palette = sns.color_palette() color_map = dict(zip(hue_groups, palette[:len(hue_groups)])) # 生成对数空间的X值,覆盖数据范围 x_min = df['length'].min() x_max = df['length'].max() x_vals = np.logspace(np.log10(x_min), np.log10(x_max), 1000) for group in hue_groups: group_data = df[df['regions'] == group] lengths = group_data['length'].values # 获取权重,若无权重则设为1 group_weights = group_data[weights].values if weights else np.ones(len(lengths)) # 计算加权KDE kde = gaussian_kde(lengths, weights=group_weights) # 计算密度值,与histplot的stat='density'逻辑一致 kde_vals = kde(x_vals) # 绘制KDE曲线,颜色与直方图对应 ax.plot(x_vals, kde_vals, color=color_map[group], label=f'{group} KDE') # 调整图例 sns.move_legend(hist, 'upper left') plt.savefig(f'{out_file_base}.pdf') return testdata=""" 1 all 1 all 2 all 2 all 2 all 3 all 4 captured 4 captured 5 captured 5 captured 5 captured 8 captured """ # 默认未加权图 df = pd.read_csv(io.StringIO(testdata), sep='\s+', header=None, names='length regions'.split()) plot_restriction_digest(df, 'test_tiny', None) # 加权图(带加权KDE) df = pd.read_csv(io.StringIO(testdata), sep='\s+', header=None, names='length regions'.split()) plot_restriction_digest(df, 'test_tiny_weighted', 'length') print('Done.')
关键说明:
- 使用
scipy.stats.gaussian_kde直接处理原始数据和权重,确保KDE与加权直方图的统计逻辑一致; - 对数空间生成X值
x_vals,适配log_scale=True的轴设置,保证曲线在对数轴上平滑显示; - 复用seaborn的调色板,让KDE曲线颜色与对应分组的直方图保持一致,提升可视化一致性。
内容的提问来源于stack exchange,提问作者Timur Shtatland
相关产品推荐
相关产品推荐

