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 的子样本估计的,即全样本的一半。
我有一个包含约 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 的子样本估计的,即全样本的一半。