You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Seaborn kdeplot生成类似R的双峰数据密度轮廓图?

问题描述

我经常需要绘制二元数据的密度轮廓图,有时数据呈**双峰(bimodal)**分布。示例代码及问题如下:

1. 生成双峰分布数据

import numpy as np
x = np.concatenate([np.random.normal(size=1000, scale=.5), 
                    np.random.normal(size=100, loc=10, scale=.1)])
y = np.random.uniform(size=1100)
plt.scatter(x, y)

(散点图展示数据的双峰分布特征)

2. Seaborn kdeplot的过度平滑问题

使用Seaborn绘制二元密度轮廓时,默认参数得到的结果过度平滑,无法体现数据的双峰细节:

import seaborn as sns
import pandas as pd
plt.scatter(x, y)
sns.kdeplot(pd.DataFrame({'x': x, 'y':y}), x='x', y='y', c='orange', levels=[.1,.5])

(轮廓图仅显示过度模糊的密度层级)

同样,一维密度估计也存在类似问题:

plt.hist(x, density=True, bins=50)
sns.kdeplot(x)

(一维密度图过度平滑,双峰特征被掩盖)

3. R中的等效实现(符合预期)

R中使用MASS::kde2d和density()可以得到清晰体现双峰细节的结果,代码如下:

x = c(rnorm(1000, sd=.5), rnorm(100, 10, .1))
y = runif(1100)
plot(x, y)

plot(x, y, cex=.5, xlim=c(-4, 12), ylim=c(-.2, 1.2))
contour(MASS::kde2d(x, y, n=100, lims=c(c(-4, 12), c(-.2, 1.2))), col='red', 
        add=T, levels = c(.1, .5))

hist(x, freq=F, breaks=50)
lines(density(x), col='red')

(结果能精准展示双峰分布的密度轮廓和细节)

需求:如何让Seaborn的kdeplot生成类似R的轮廓图?或Python中有其他更合适的成熟绘图工具?

解决方案

一、调整Seaborn kdeplot的带宽参数

Seaborn默认使用Scott规则计算带宽,而R的density()和kde2d默认使用bw.nrd0规则,两者带宽计算逻辑不同导致平滑程度差异。可以手动指定带宽来贴近R的效果:

1. 一维密度估计

先实现R中bw.nrd0的等效带宽计算,再传入Seaborn:

import scipy.stats as stats
import matplotlib.pyplot as plt

# 实现R的bw.nrd0带宽计算逻辑
def bw_nrd0(x):
    x = np.asarray(x)
    if len(x) < 2:
        return 0
    hi = np.std(x, ddof=1)
    q75, q25 = np.percentile(x, [75, 25])
    lo = (q75 - q25) / 1.34
    return min(hi, lo) * len(x)**(-1/5)

# 计算带宽并绘图
bw = bw_nrd0(x)
plt.hist(x, density=True, bins=50)
# 用bw_adjust适配Seaborn的默认带宽基准
sns.kdeplot(x, bw_adjust=bw / stats.gaussian_kde(x).scotts_factor())
plt.show()

2. 二维密度轮廓图

分别指定x、y方向的R风格带宽:

import seaborn as sns
import pandas as pd

bw_x = bw_nrd0(x)
bw_y = bw_nrd0(y)

plt.scatter(x, y)
sns.kdeplot(
    pd.DataFrame({'x': x, 'y': y}),
    x='x', y='y',
    c='orange',
    levels=[.1, .5],
    bw_method=[bw_x, bw_y]  # 分别设置x、y方向的带宽
)
plt.xlim(-4, 12)
plt.ylim(-0.2, 1.2)
plt.show()

二、使用其他Python工具实现R风格的密度估计

1. 用scipy.stats.gaussian_kde自定义实现

Scipy的高斯核密度估计支持灵活调整带宽,完全复刻R的效果:

from scipy.stats import gaussian_kde
import matplotlib.pyplot as plt

# 构建二维核密度估计模型
kde = gaussian_kde(np.vstack([x, y]), bw_method='silverman')  # 用Silverman规则贴近R的默认逻辑

# 生成绘图网格
xgrid = np.linspace(-4, 12, 100)
ygrid = np.linspace(-0.2, 1.2, 100)
X, Y = np.meshgrid(xgrid, ygrid)
Z = kde(np.vstack([X.ravel(), Y.ravel()])).reshape(X.shape)

# 绘制散点+密度轮廓
plt.scatter(x, y, c='blue', s=10)
plt.contour(X, Y, Z, levels=[0.1, 0.5], colors='orange')
plt.xlim(-4, 12)
plt.ylim(-0.2, 1.2)
plt.show()

2. 用rpy2直接调用R的MASS包

如果想完全复用R的实现逻辑,可以通过rpy2调用R的kde2d:

import rpy2.robjects as robjects
from rpy2.robjects.packages import importr
import matplotlib.pyplot as plt
import numpy as np

MASS = importr('MASS')

# 将Python数据转换为R格式
r_x = robjects.FloatVector(x)
r_y = robjects.FloatVector(y)

# 调用R的kde2d函数
kde_result = MASS.kde2d(r_x, r_y, n=100, lims=robjects.FloatVector([-4,12,-0.2,1.2]))
xgrid = np.array(kde_result[0])
ygrid = np.array(kde_result[1])
Z = np.array(kde_result[2])

# 绘制结果
plt.scatter(x, y, s=10)
plt.contour(xgrid, ygrid, Z, levels=[0.1, 0.5], colors='red')
plt.xlim(-4,12)
plt.ylim(-0.2,1.2)
plt.show()

内容的提问来源于stack exchange,提问作者Devin F

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 16:49:58