【问题标题】:Seaborn KDEPlot - not enough variation in data?Seaborn KDEPlot - 数据变化不够?
【发布时间】:2020-08-31 00:16:24
【问题描述】:

我有一个包含约 900 行的数据框;我正在尝试为某些列绘制 KDEplots。在某些列中,大多数值是相同的最小值。当我包含太多最小值时,KDEPlot 会突然停止显示最小值。例如,以下包含 600 个值,其中 450 个是最小值,并且绘图看起来很好:

y = df.sort_values(by='col1', ascending=False)['col1'].values[:600]
sb.kdeplot(y)

但包含 451 个最小值会给出非常不同的输出:

y = df.sort_values(by='col1', ascending=False)['col1'].values[:601]
sb.kdeplot(y)

最终我想绘制不同列的双变量 KDEPlots,但我想先了解这一点。

【问题讨论】:

    标签: python seaborn kernel-density


    【解决方案1】:

    问题是为kde 的“带宽”选择的默认算法。 The default method'scott',当有许多相等的值时,这不是很有帮助。

    带宽是定位在每个采样点上并求和的高斯的宽度。较低的带宽更接近数据,较高的带宽可以平滑所有内容。甜蜜点在中间的某个地方。在这种情况下,bw=0.3 可能是一个不错的选择。为了比较不同的kde,建议每次选择完全相同的带宽。

    这里有一些示例代码来显示bw='scott'bw=0.3 之间的区别。示例数据是来自标准正态分布的 150 个值以及 400、450 或 500 个固定值。

    import matplotlib.pyplot as plt
    import numpy as np
    import seaborn as sns; sns.set()
    
    fig, axs = plt.subplots(nrows=2, ncols=3, figsize=(10,5), gridspec_kw={'hspace':0.3})
    
    for i, bw in enumerate(['scott', 0.3]):
        for j, num_same in enumerate([400, 450, 500]):
            y = np.concatenate([np.random.normal(0, 1, 150), np.repeat(-3, num_same)])
            sns.kdeplot(y, bw=bw, ax=axs[i, j])
            axs[i, j].set_title(f'bw:{bw}; fixed values:{num_same}')
    plt.show()
    

    第三个图给出了一个警告,即无法使用 Scott 建议的带宽绘制 kde。

    PS:正如@mwascom 在 cmets 中所提到的,在这种情况下使用scipy.statsmodels.nonparametric.kde(不是scipy.stats.gaussian_kde)。默认为 "scott" - 1.059 * A * nobs ** (-1/5.), where A is min(std(X),IQR/1.34)min() 阐明了行为的突然变化。 IQR"interquartile range"第 75 和第 25 个百分位数之间的差异

    编辑:由于 Seaborn 0.11,the statsmodel backend has been dropped,所以 kde 仅通过 scipy.stats.gaussian_kde 计算。

    【讨论】:

    • 只是在这里添加一些上下文:我认为对此最令人惊讶的方面(带有额外数据点的突然变化)的解释是 statsmodels 在计算 Scott 的因子时使用 min(iqr, sd) 和这些度量对于具有大量重复观察的数据,分布可能会大不相同。
    • 谢谢,这很有帮助。我已经尝试将“scott”更改为各种 bw 值,虽然它似乎在单变量图中运行良好,但当我转向双变量图时,我尝试的每个 bw 值根本不会显示较小的人口。是否有关于选择适当 bw 值的指南?
    • 一般来说,高斯 KDE 用于平滑连续数据。您可以尝试过滤掉异常值并将它们的计数分别绘制为条形图(或小饼图)。
    • 另一种似乎可行的方法是将少量随机抖动应用于最小值
    【解决方案2】:

    如果样本具有重复值,这意味着基础分布不连续。在您为说明问题而显示的数据中,我们可以在左侧看到狄拉克分布。内核平滑可能适用于此类数据,但要小心。事实上,为了近似这些数据,我们可以使用与狄拉克相关的带宽为零的内核平滑。然而,在大多数 KDE 方法中,所有内核原子只有一个带宽。此外,用于计算带宽的各种规则是基于对分布 PDF 的二阶导数的粗糙度的某种估计。这不能应用于不连续分布。

    但是,我们可以尝试将样本分成两个子样本:

    • 具有重复性的子样本,
    • 具有独特实现的子样本。

    (johanc 已经提到过这个想法)。

    以下是执行此分类的尝试。 np.unique 方法用于计算复制实现的出现次数。重复值与狄拉克相关,混合物中的重量是根据样本中这些重复值的分数估计的。剩下的实现,uniques,然后用 KDE 来估计连续分布。

    以下函数将有助于克服与​​ OpenTURNS 混合的 draw 方法的当前实现的限制。

    def DrawMixtureWithDiracs(distribution):
        """Draw a distributions which has Diracs.
        https://github.com/openturns/openturns/issues/1489"""
        graph = distribution.drawPDF()
        graph.setLegends(["Mixture"])
        for atom in distribution.getDistributionCollection():
            if atom.getName() == "Dirac":
                curve = atom.drawPDF()
                curve.setLegends(["Dirac"])
                graph.add(curve)
        return graph
    

    以下脚本使用包含狄拉克和高斯分布的 Mixture 创建用例。

    import openturns as ot
    import numpy as np
    distribution = ot.Mixture([ot.Dirac(-3.0),
                              ot.Normal()], [0.5, 0.5])
    DrawMixtureWithDiracs(distribution)
    

    这是结果。

    然后我们创建一个样本。

    sample = distribution.getSample(100)
    

    这是您的问题开始的地方。我们计算每个实现的出现次数。

    array = np.array(sample)
    unique, index, count = np.unique(array, axis=0, return_index=True,
                                     return_counts=True)
    

    对于所有实现,复制值都与狄拉克相关联,并且唯一值放在单独的列表中。

    sampleSize = sample.getSize()
    listOfDiracs = []
    listOfWeights = []
    uniqueValues = []
    for i in range(len(unique)):
        if count[i] == 1:
            uniqueValues.append(unique[i][0])
        else:
            atom = ot.Dirac(unique[i])
            listOfDiracs.append(atom)
            w = count[i] / sampleSize
            print("New Dirac =", unique[i], " with weight =", w)
            listOfWeights.append(w)
    

    连续原子的权重是狄拉克的权重之和的补数。这样,权重之和将等于 1。

    complementaryWeight = 1.0 - sum(listOfWeights)
    weights = list(listOfWeights)
    weights.append(complementaryWeight)
    

    简单的部分来了:独特的实现可用于拟合内核平滑。然后将 KDE 添加到原子列表中。

    sampleUniques = ot.Sample(uniqueValues, 1)
    factory = ot.KernelSmoothing()
    kde = factory.build(sampleUniques)
    atoms = list(listOfDiracs)
    atoms.append(kde)
    

    等等:混合物准备好了。

    mixture_estimated = ot.Mixture(atoms, weights)
    

    以下脚本比较初始混合和估计混合。

    graph = DrawMixtureWithDiracs(distribution)
    graph.setColors(["dodgerblue3", "dodgerblue3"])
    curve = DrawMixtureWithDiracs(mixture_estimated)
    curve.setColors(["darkorange1", "darkorange1"])
    curve.setLegends(["Est. Mixture", "Est. Dirac"])
    graph.add(curve)
    graph
    

    这个数字似乎令人满意,因为连续分布是从一个大小仅等于 50 的子样本(即整个样本的一半)估计的。

    【讨论】:

      猜你喜欢
      • 2021-02-04
      • 2016-12-30
      • 1970-01-01
      • 2016-05-25
      • 1970-01-01
      • 2020-12-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多