如何为散点图中每个点设置不同带宽进行核密度估计?
可变带宽核密度估计的实现方法
scipy和sklearn的默认核密度估计(KDE)确实只支持全局固定带宽。如果要给每个样本点设置不同带宽,你需要实现自适应/可变带宽核密度估计,下面是具体的实现思路和代码:
核心原理
可变带宽KDE的公式为:
$\hat{f}(x) = \frac{1}{n} \sum_{i=1}^n \frac{1}{h_i^d} K\left( \frac{x - X_i}{h_i} \right)$
其中:
- $h_i$是第i个样本点的带宽(可手动指定,也可根据数据自适应计算)
- $K$是核函数(这里以常用的高斯核为例)
- $d$是数据的维度
1. 手动实现可变带宽KDE
下面是基于numpy的通用实现,支持手动指定每个样本的带宽:
import numpy as np def variable_bandwidth_kde(x, X, h): """ 计算可变带宽核密度估计值 参数: x: 待预测的点,形状(m, d) X: 样本数据,形状(n, d) h: 每个样本点的带宽,形状(n,)(单维度带宽)或(n, d)(各维度独立带宽) 返回: 每个x点的密度估计值,形状(m,) """ n, d = X.shape m, _ = x.shape # 扩展维度实现广播计算 x_expanded = x[:, np.newaxis, :] # 形状(m, 1, d) X_expanded = X[np.newaxis, :, :] # 形状(1, n, d) # 处理带宽的维度 if h.ndim == 1: h_expanded = h[np.newaxis, :, np.newaxis] # 扩展为(1, n, 1) else: h_expanded = h[np.newaxis, :, :] # 扩展为(1, n, d) # 高斯核计算 kernel = np.exp(-0.5 * np.sum(((x_expanded - X_expanded) / h_expanded)**2, axis=2)) # 高斯核的归一化因子:(2π)^(d/2) * 带宽乘积 norm_factor = (2 * np.pi)**(d/2) * np.prod(h_expanded, axis=2) # 求和并取平均得到密度 density = np.sum(kernel / norm_factor, axis=1) / n return density
2. 自适应计算带宽(常用方案)
如果不想手动指定每个带宽,可以基于全局带宽和样本局部密度自适应生成:
- 先用全局带宽(比如Scott规则)计算每个样本点的密度估计
- 根据局部密度调整带宽:密度低的区域用更大带宽(避免欠平滑),密度高的区域用更小带宽(避免过平滑)
示例代码:
from scipy.stats import gaussian_kde # 生成测试样本 X = np.random.multivariate_normal([0, 0], [[1, 0.5], [0.5, 1]], 1000) # 1. 计算全局带宽(Scott规则) kde_global = gaussian_kde(X.T) h_global = kde_global.factor # 2. 计算每个样本点的全局密度估计 f_hat = kde_global(X.T) # 3. 自适应调整带宽:h_i = h_global * (f_hat / f_mean)^(-alpha) # alpha通常取0.5,平衡平滑程度 alpha = 0.5 f_mean = np.mean(f_hat) h_i = h_global * (f_hat / f_mean)**(-alpha) # 4. 使用可变带宽KDE计算新点的密度 x = np.random.multivariate_normal([0, 0], [[1, 0.5], [0.5, 1]], 100) density = variable_bandwidth_kde(x, X, h_i)
注意事项
- 高维数据下,这种实现的计算量会随样本量增长而显著增加,大样本场景可以考虑用KD树加速最近邻计算
- 手动指定带宽时,要确保所有带宽值为正数,避免出现除零错误
内容的提问来源于stack exchange,提问作者happy
相关产品推荐
相关产品推荐

