如何将seaborn kdeplot的高度归一化至1而非积分归一化?
问题
使用seaborn的「重叠密度(山脊图)」FacetGrid示例时,希望将kdeplot的高度归一化至1(峰值为1),而非默认的积分归一化(曲线下面积为1),但仅找到积分归一化的解决方案,如何实现?
原代码如下:
import numpy as np import pandas as pd import seaborn as sns import matplotlib.pyplot as plt sns.set_theme(style="white", rc={"axes.facecolor": (0, 0, 0, 0)}) # Create the data rs = np.random.RandomState(1979) x = rs.randn(500) g = np.tile(list("ABCDEFGHIJ"), 50) df = pd.DataFrame(dict(x=x, g=g)) m = df.g.map(ord) df["x"] += m # Initialize the FacetGrid object pal = sns.cubehelix_palette(10, rot=-.25, light=.7) g = sns.FacetGrid(df, row="g", hue="g", aspect=15, height=.5, palette=pal) # Draw the densities in a few steps g.map(sns.kdeplot, "x", bw_adjust=.5, clip_on=False, fill=True, alpha=1, linewidth=1.5) g.map(sns.kdeplot, "x", clip_on=False, color="w", lw=2, bw_adjust=.5) # passing color=None to refline() uses the hue mapping g.refline(y=0, linewidth=2, linestyle="-", color=None, clip_on=False) # Define and use a simple function to label the plot in axes coordinates def label(x, color, label): ax = plt.gca() ax.text(0, .2, label, fontweight="bold", color=color, ha="left", va="center", transform=ax.transAxes) g.map(label, "x") # Set the subplots to overlap g.figure.subplots_adjust(hspace=-.25) # Remove axes details that don't play well with overlap g.set_titles("") g.set(yticks=[], ylabel="") g.despine(bottom=True, left=True)
解决方案
Seaborn的kdeplot没有直接设置峰值归一化的参数,需要自定义函数实现:
- 使用
scipy.stats.gaussian_kde计算每个分组的核密度估计 - 将计算得到的密度值除以其最大值,使峰值归一化到1
- 在FacetGrid中调用自定义函数替代原
kdeplot
修改后的完整代码:
import numpy as np import pandas as pd import seaborn as sns import matplotlib.pyplot as plt from scipy.stats import gaussian_kde sns.set_theme(style="white", rc={"axes.facecolor": (0, 0, 0, 0)}) # Create the data rs = np.random.RandomState(1979) x = rs.randn(500) g = np.tile(list("ABCDEFGHIJ"), 50) df = pd.DataFrame(dict(x=x, g=g)) m = df.g.map(ord) df["x"] += m # Initialize the FacetGrid object pal = sns.cubehelix_palette(10, rot=-.25, light=.7) g = sns.FacetGrid(df, row="g", hue="g", aspect=15, height=.5, palette=pal) # 自定义归一化到峰值为1的KDE绘图函数 def norm_kdeplot(x, color, label, bw_adjust=.5): ax = plt.gca() # 计算KDE kde = gaussian_kde(x, bw_method=bw_adjust) # 生成x轴采样点 x_vals = np.linspace(x.min() - 1, x.max() + 1, 1000) # 计算密度值并归一化到峰值1 y_vals = kde(x_vals) y_vals /= y_vals.max() # 绘制填充曲线 ax.fill_between(x_vals, y_vals, color=color, alpha=1) # 绘制白色边框线 ax.plot(x_vals, y_vals, color="w", lw=2) # 绘制黑色KDE线 ax.plot(x_vals, y_vals, color=color, lw=1.5) # 替换原kdeplot调用 g.map(norm_kdeplot, "x", bw_adjust=.5) # passing color=None to refline() uses the hue mapping g.refline(y=0, linewidth=2, linestyle="-", color=None, clip_on=False) # Define and use a simple function to label the plot in axes coordinates def label(x, color, label): ax = plt.gca() ax.text(0, .2, label, fontweight="bold", color=color, ha="left", va="center", transform=ax.transAxes) g.map(label, "x") # Set the subplots to overlap g.figure.subplots_adjust(hspace=-.25) # Remove axes details that don't play well with overlap g.set_titles("") g.set(yticks=[], ylabel="") g.despine(bottom=True, left=True) plt.show()
说明
- 自定义函数
norm_kdeplot中,通过y_vals /= y_vals.max()将密度曲线的峰值强制归一到1 - 保留了原代码中填充、白色边框线、黑色曲线的绘制逻辑
bw_adjust参数和原代码保持一致,确保核密度的平滑度相同
内容的提问来源于stack exchange,提问作者MBlessing
相关产品推荐
相关产品推荐

