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

Matplotlib三元图KDE密度缩放异常问题排查

三元KDE密度缩放异常问题排查与解决

问题概述

使用Matplotlib + SciPy的gaussian_kde绘制三元核密度估计(KDE)图时,出现密度缩放不符合预期的情况:稀疏的X组数据对应的KDE未呈现暗淡效果,反而和数据量更多的Y、Z组密度强度相当,但散点图已正确展示三组数据的分布差异。


疑问解答与问题修复

1. 为什么X的KDE密度呈现结构化且未如预期般暗淡?

核心原因是每组KDE的密度是独立归一化的,且你手动设置的密度截断逻辑进一步抹平了组间的密度差异:

  • gaussian_kde输出的是概率密度,会自动归一化到整个分布的积分=1,数据量少的组单个核的贡献会被放大,最终和数据多的组的密度数值范围接近
  • 你对每组都用自身的max_density做截断,相当于把每组的密度都拉伸到了相同的相对范围,导致三组的颜色刻度没有统一基准,视觉上看起来密度强度一致

修复方案:

  • 先计算三组KDE的全局最大密度,统一设置颜色刻度的vmin和vmax参数,固定所有contourf的颜色范围
  • 移除或调整手动截断逻辑,保留原始密度的相对差异

2. 带宽选择(bw_method=0.25;'scott')是否导致了不必要的平滑效果?

是的,固定值bw_method=0.25对于稀疏的X组来说带宽过大,会过度平滑数据,导致原本稀疏的点被扩散成结构化的分布:

  • 带宽控制KDE的平滑程度,带宽越大,平滑效果越强,稀疏数据会被拉成更连续的分布
  • 对于小样本数据,固定带宽会比自适应带宽(如'scott'或'silverman')产生更严重的过度平滑

修复方案:

  • 改用自适应带宽方法,比如bw_method='scott'或bw_method='silverman',这类方法会根据样本量自动调整带宽,小样本对应更小的带宽,减少过度平滑
  • 如果需要手动调整,可针对不同组设置不同带宽,比如给X组设置更小的bw_method值(如0.1)

3. 我是否在数据归一化或转换过程中出现错误?

数据归一化和笛卡尔坐标转换逻辑是正确的,细节上可以做一点优化:

  • 归一化时data = data[valid_rows] / sum_values[valid_rows]确保了三元组的和为1,符合 barycentric 坐标要求
  • 笛卡尔转换使用的等边三角形顶点是标准设置,没有问题
  • 噪声添加逻辑(cartesian_points += np.random.normal(0, 1e-6, ...))合理,避免了协方差矩阵秩不足的问题

优化点:

  • 可以在归一化后添加校验:assert np.allclose(np.sum(data, axis=1), 1),确保归一化正确

修复后的核心代码示例

以下是修改后的关键代码片段,解决上述问题:

# 先预计算所有组的KDE密度,获取全局最大密度
all_kde_values = []
ternary_data = []
for data_columns, norm_filter in ternary_combinations:
    filtered_df = df[df['NN'] == norm_filter]
    data = filtered_df[data_columns].dropna().to_numpy()
    if data.shape[0] == 0:
        ternary_data.append(None)
        continue
    sum_values = np.sum(data, axis=1, keepdims=True)
    valid_rows = sum_values[:, 0] > 0
    data = data[valid_rows] / sum_values[valid_rows]
    cartesian_points = barycentric_to_cartesian(data)
    if np.linalg.matrix_rank(np.cov(cartesian_points.T)) < 2:
        cartesian_points += np.random.normal(0, 1e-6, cartesian_points.shape)
    kde = gaussian_kde(cartesian_points.T, bw_method='scott')  # 改用自适应带宽
    # 计算网格点密度
    x = np.linspace(0, 1, 200)
    y = np.linspace(0, np.sqrt(3)/2, 200)
    X, Y = np.meshgrid(x, y)
    grid_points = np.vstack([X.ravel(), Y.ravel()]).T
    triangle_vertices = np.array([[0,0],[1,0],[0.5, np.sqrt(3)/2]])
    mask = is_inside_triangle(grid_points, triangle_vertices)
    Z = np.full(grid_points.shape[0], np.nan)
    Z[mask] = kde(grid_points[mask].T)
    Z = Z.reshape(X.shape)
    all_kde_values.append(Z)
    ternary_data.append((cartesian_points, X, Y, Z))

# 获取全局最大密度,用于统一颜色刻度
global_max = np.nanmax([np.nanmax(z) for z in all_kde_values if z is not None])

# 绘图函数修改为使用全局颜色刻度
def plot_ternary(ax, data_columns, normative_filter, cartesian_points, X, Y, Z, global_max):
    # 散点图绘制
    ax.scatter(cartesian_points[:, 0], cartesian_points[:, 1], s=20, alpha=0.5, 
               facecolors='none', edgecolors='black', linewidth=0.6, zorder=1)
    
    # 使用全局统一的颜色范围绘制KDE
    contour = ax.contourf(X, Y, Z, levels=12, cmap='viridis', alpha=0.85, zorder=2,
                          vmin=0, vmax=global_max)
    
    # 绘制三角形边界、调整轴样式等逻辑不变...
    ax.add_patch(Polygon(np.array([[0,0],[1,0],[0.5, np.sqrt(3)/2]]), closed=True, fill=False, edgecolor='black', 
                         linewidth=2.5, linestyle='dashed', zorder=3))
    ax.set_xticks([])
    ax.set_yticks([])
    ax.set_frame_on(False)
    ax.set_aspect('equal')
    
    cbar = plt.colorbar(contour, ax=ax, orientation='horizontal', fraction=0.05, pad=0.08)
    cbar.set_label('Density', fontsize=12)

# 遍历绘图
for ax, (ternary_set, norm_filter), data in zip(axes, ternary_combinations, ternary_data):
    if data is None:
        continue
    cartesian_points, X, Y, Z = data
    plot_ternary(ax, ternary_set, norm_filter, cartesian_points, X, Y, Z, global_max)

内容的提问来源于stack exchange,提问作者Shubhadeep Roy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 09:41:01