如何用Seaborn基于密度函数复刻KDE图样式(含曲线下填充)
问题描述
我非常喜欢Seaborn的KDE图样式,现在我有一组由密度函数生成的数据集:x是样本的x值,y是对应的密度函数值,μσ是用于分组的分类变量(用来区分不同的密度分布,示例里是正态分布的均值和标准差)。我能用Seaborn的lineplot绘制曲线,但没法直接实现KDE图那样的曲线下填充效果。目前我是用lineplot加手动填充的方式实现了类似效果,但这只是临时方案,想知道怎么用Seaborn原生的方法更自然地复刻这种KDE图样式(包含曲线下区域填充)。
现有实现代码:
import numpy as np import scipy as sp import pandas as pd from scipy.stats import norm import matplotlib.pyplot as plt import seaborn as sns num_grid_pts = 1000 val_μ = [0, -1, 1, 0] val_σ = [1, 2, 3, 4] num_var = len(val_μ) # variations x = np.linspace(-10, 10, num_grid_pts) P = np.zeros((num_grid_pts, num_var)) # PDF μσ = [f'μ = {μ}, σ = {σ}' for μ, σ in zip(val_μ, val_σ)] for ii, (μ, σ) in enumerate(zip(val_μ, val_σ)): randVar = norm(μ, σ) P[:, ii] = randVar.pdf(x) df_P = pd.DataFrame(data = {'x': np.tile(x, num_var), 'PDF': P.flatten('F'), 'μσ': np.repeat(μσ, len(x))}) f, ax = plt.subplots(figsize=(15, 10)) sns.lineplot(data=df_P, x='x', y='PDF', hue='μσ', ax=ax) plot_lines = ax.get_lines() for ii in range(num_var): ax.fill_between(x=plot_lines[ii].get_xdata(), y1=plot_lines[ii].get_ydata(), alpha=0.25, color=plot_lines[ii].get_color()) ax.set_title(f'Normal Distribution') ax.set_xlabel(f'Value') ax.set_ylabel(f'Probability') plt.show()
解决方案
Seaborn本身没有原生的带填充分组线图API,但可以通过两种更贴合Seaborn设计逻辑的方式替代手动遍历线条的临时方案:
方法1:利用FacetGrid批量绘制填充曲线
借助relplot创建FacetGrid,通过分组映射直接关联颜色,避免手动提取线条:
import numpy as np import pandas as pd from scipy.stats import norm import matplotlib.pyplot as plt import seaborn as sns num_grid_pts = 1000 val_μ = [0, -1, 1, 0] val_σ = [1, 2, 3, 4] μσ = [f'μ = {μ}, σ = {σ}' for μ, σ in zip(val_μ, val_σ)] x = np.linspace(-10, 10, num_grid_pts) P = np.zeros((num_grid_pts, len(val_μ))) for ii, (μ, σ) in enumerate(zip(val_μ, val_σ)): P[:, ii] = norm(μ, σ).pdf(x) df_P = pd.DataFrame({ 'x': np.tile(x, len(val_μ)), 'PDF': P.flatten('F'), 'μσ': np.repeat(μσ, len(x)) }) # 创建带hue分组的线图FacetGrid g = sns.relplot(data=df_P, x='x', y='PDF', hue='μσ', kind='line', figsize=(15,10), legend='full') # 遍历分组,用Seaborn自动分配的颜色填充 for name, group in df_P.groupby('μσ'): color = g._hue_map[name] g.ax.fill_between(group['x'], group['PDF'], alpha=0.25, color=color) g.ax.set_title('Normal Distribution') g.ax.set_xlabel('Value') g.ax.set_ylabel('Probability') plt.show()
方法2:直接使用Seaborn调色板映射颜色
通过color_palette获取Seaborn的分组配色,直接按分组填充,逻辑更直观:
import numpy as np import pandas as pd from scipy.stats import norm import matplotlib.pyplot as plt import seaborn as sns num_grid_pts = 1000 val_μ = [0, -1, 1, 0] val_σ = [1, 2, 3, 4] μσ = [f'μ = {μ}, σ = {σ}' for μ, σ in zip(val_μ, val_σ)] x = np.linspace(-10, 10, num_grid_pts) P = np.zeros((num_grid_pts, len(val_μ))) for ii, (μ, σ) in enumerate(zip(val_μ, val_σ)): P[:, ii] = norm(μ, σ).pdf(x) df_P = pd.DataFrame({ 'x': np.tile(x, len(val_μ)), 'PDF': P.flatten('F'), 'μσ': np.repeat(μσ, len(x)) }) f, ax = plt.subplots(figsize=(15,10)) # 绘制线图,获取对应分组数的调色板 sns.lineplot(data=df_P, x='x', y='PDF', hue='μσ', ax=ax) palette = sns.color_palette(n_colors=len(μσ)) # 按分组填充颜色 for idx, (name, group) in enumerate(df_P.groupby('μσ')): ax.fill_between(group['x'], group['PDF'], alpha=0.25, color=palette[idx]) ax.set_title('Normal Distribution') ax.set_xlabel('Value') ax.set_ylabel('Probability') plt.show()
关键说明
- 两种方法都避免了直接操作Axes对象的线条,完全利用Seaborn的分组和配色系统,保持图例与填充颜色的一致性
- 方法1的
_hue_map是FacetGrid内部维护的分组-颜色映射,能完美匹配Seaborn的图例配色 - 方法2直接调用Seaborn的调色板生成函数,逻辑更清晰,适合快速实现
内容的提问来源于stack exchange,提问作者Mark
相关产品推荐
相关产品推荐

