Seaborn KDEPlot - 数据变化不够大?

Seaborn KDEPlot - not enough variation in data?

我有一个包含约 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 相互对比,但我想先了解这一点。

问题是为 kde. The default method is '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在评论中提到的,在这种情况下scipy.statsmodels.nonparametric.kde is used (not scipy.stats.gaussian_kde). There the default is "scott" - 1.059 * A * nobs ** (-1/5.), where A is min(std(X),IQR/1.34). The min() clarifies the abrupt change in behavior. IQR is the "interquartile range"75th和25th百分位数之间的差异.

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

计算

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

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

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

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

下面是执行此分类的尝试。 np.unique 方法用于计算复制实现的出现次数。复制值与 Diracs 相关联,混合物中的权重是根据样本中这些复制值的分数估算的。其余的实现,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

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

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)

对于所有实现,复制值与 Diracs 相关联,唯一值放在单独的列表中。

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)

Et voilà:混合物已准备就绪。

mixture_estimated = ot.Mixture(atoms, weights)

以下脚本比较了初始 Mixture 和估计的 Mixture。

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 的子样本估计的,即全样本的一半。