如何计算Seaborn distplot中多条KDE曲线各自的面积(common_norm=False场景)
如何计算Seaborn distplot中多条KDE曲线各自的面积(common_norm=False场景)
嗨,我来帮你把这个问题掰扯清楚!首先得纠正你一个可能的误解——你对common_norm=False的归一化逻辑搞反啦,这正是你疑惑的根源~
先搞懂common_norm的真实行为
你提到“设置common_norm=False时,所有曲线的面积之和应该为1”,这其实是**common_norm=True(默认值)**的规则!两个参数的真实区别是:
common_norm=True:所有KDE曲线一起归一化,总面积加起来是1,每条曲线的面积等于该组样本量占总样本量的比例(比如样本量300的组,面积就是300/总样本数)common_norm=False:每条KDE曲线单独归一化,每条曲线自身的面积都是1,如果有3条曲线,总面积就是3,所以你积分出来每条面积一样是完全正确的!你的直觉可能混淆了“单独归一化”和“按样本量加权归一化”的逻辑~
正确计算每条KDE曲线面积的两种方法
方法1:从Seaborn绘图对象中提取曲线数据直接积分
当你用sns.distplot绘图后,可以直接从返回的Axes对象里提取每条KDE曲线的x、y数据,再用simps积分:
import seaborn as sns import numpy as np from scipy.integrate import simps import matplotlib.pyplot as plt np.random.seed(0) # 生成模拟数据 low_peak_data = np.random.normal(loc=5, scale=0.5, size=100) high_peak_data = np.random.normal(loc=7, scale=0.5, size=300) # 补全双峰数据 bimodal_data = np.concatenate([np.random.normal(loc=3, scale=0.5, size=150), np.random.normal(loc=9, scale=0.5, size=150)]) # 绘制distplot,设置common_norm=False sns.set_style("whitegrid") fig, ax = plt.subplots() sns.distplot(low_peak_data, kde=True, hist=False, label="Low Peak", ax=ax, common_norm=False) sns.distplot(high_peak_data, kde=True, hist=False, label="High Peak", ax=ax, common_norm=False) sns.distplot(bimodal_data, kde=True, hist=False, label="Bimodal", ax=ax, common_norm=False) ax.legend() # 提取每条KDE曲线的数据 kde_lines = [line for line in ax.get_lines() if line.get_label() != '_nolegend_'] # 计算每条曲线的面积 for line in kde_lines: x = line.get_xdata() y = line.get_ydata() area = simps(y, x) print(f"曲线「{line.get_label()}」的面积:{area:.4f}")
运行后你会发现每条曲线的面积都接近1,这完全符合common_norm=False的归一化规则。
方法2:手动计算KDE再积分
如果你不想依赖绘图对象,也可以用scipy.stats.gaussian_kde手动计算每组数据的KDE,再积分:
from scipy.stats import gaussian_kde # 为每组数据创建KDE模型 low_kde = gaussian_kde(low_peak_data) high_kde = gaussian_kde(high_peak_data) bimodal_kde = gaussian_kde(bimodal_data) # 生成覆盖所有数据的x轴范围,确保积分范围足够宽 x_min = min(low_peak_data.min(), high_peak_data.min(), bimodal_data.min()) x_max = max(low_peak_data.max(), high_peak_data.max(), bimodal_data.max()) x = np.linspace(x_min - 1, x_max + 1, 1000) # 计算每个x对应的密度值 low_y = low_kde(x) high_y = high_kde(x) bimodal_y = bimodal_kde(x) # 积分计算面积 low_area = simps(low_y, x) high_area = simps(high_y, x) bimodal_area = simps(bimodal_y, x) print(f"Low Peak 面积:{low_area:.4f}") print(f"High Peak 面积:{high_area:.4f}") print(f"Bimodal 面积:{bimodal_area:.4f}")
这个方法的结果和方法1一致,因为gaussian_kde默认也是将单组数据的KDE归一化到面积1的。
如果你想要面积和样本量成正比
如果你的真实需求是让每条曲线的面积和样本量成正比(总面积为1),那只需要把common_norm设置为True(或者不设置,因为这是默认值),这时候积分出来的面积就是该组样本量占总样本量的比例,比如300样本的组面积就是300/(100+300+300)≈0.4286。
备注:内容来源于stack exchange,提问作者user13096842




