【问题标题】:Why do stat_density (R; ggplot2) and gaussian_kde (Python; scipy) differ?为什么 stat_density (R; ggplot2) 和 gaussian_kde (Python; scipy) 不同?
【发布时间】:2019-08-17 08:52:47
【问题描述】:

我正在尝试对可能不是正态分布的一系列分布生成基于 KDE 的 PDF 估计值。

我喜欢 ggplot 在 R 中的 stat_density 似乎可以识别频率的每一个增量,但无法通过 Python 的 scipy-stats-gaussian_kde 方法复制这一点,这似乎过于平滑。

我的 R 代码设置如下:

ggplot(test, aes(x=Val, color = as.factor(Class), group=as.factor(Class))) +
             stat_density(geom='line',kernel='gaussian',bw='nrd0' 
                                                            #nrd0='Silverman'
                                                            ,size=1,position='identity')

而我的python代码是:

kde = stats.gaussian_kde(data.ravel())
kde.set_bandwidth(bw_method='silverman')

统计文档显示here nrd0 是用于 bw 调整的 silverman 方法。

基于上面的代码,我使用相同的内核(高斯)和带宽方法(Silverman)。

谁能解释为什么结果如此不同?

【问题讨论】:

    标签: python r ggplot2 scipy kde


    【解决方案1】:

    对于什么是西尔弗曼规则似乎存在分歧。 TL;DR - scipy 使用了更糟糕的规则版本,该规则仅适用于正态分布的单峰数据。 R 使用了一个更好的版本,它是“两全其美”并且“适用于各种密度”。

    scipy docs 说西尔弗曼的规则是implemented as

    def silverman_factor(self):
        return power(self.neff*(self.d+2.0)/4.0, -1./(self.d+4))
    

    其中d 是维度数(在您的情况下为 1),neff 是有效样本量(点数,假设没有权重)。所以 scipy 带宽是(n * 3 / 4) ^ (-1 / 5)(乘以标准差,用不同的方法计算)。

    相比之下,R 的stats package docs 将 Silverman 的方法描述为“标准偏差和四分位距最小值的 0.9 倍除以样本量的 1.34 倍的负五分之一幂”,这在 R 中也可以验证代码,在控制台中输入bw.nrd0 给出:

    function (x) 
    {
        if (length(x) < 2L) 
            stop("need at least 2 data points")
        hi <- sd(x)
        if (!(lo <- min(hi, IQR(x)/1.34))) 
            (lo <- hi) || (lo <- abs(x[1L])) || (lo <- 1)
        0.9 * lo * length(x)^(-0.2)
    }
    

    另一方面,Wikipedia 将“Silverman 的经验法则”作为估算器的许多可能名称之一:

    1.06 * sigma * n ^ (-1 / 5)
    

    wikipedia 版本相当于 scipy 版本。

    所有三个来源(scipy docs、Wikipedia 和 R docs)都引用了相同的原始参考:Silverman、B.W. (1986 年)。 用于统计和数据分析的密度估计。伦敦:查普曼和霍尔/CRC。页。 48. 国际标准书号 978-0-412-24620-3。 Wikipedia 和 R 特别引用了第 48 页,而 scipy 的文档没有提到页码。 (我已向 Wikipedia 提交了修改以将其页面引用更新到第 45 页,见下文。)

    阅读 Silverman 论文,第 45 页,等式 3.28 是在 Wikipedia 文章中使用的:(4 / 3) ^ (1 / 5) * sigma * n ^ (-1 / 5) ~= 1.06 * sigma * n ^ (-1 / 5)。 Scipy 使用相同的方法,将(4 / 3) ^ (1 / 5) 重写为等效的(3 / 4) ^ (-1 / 5)。 Silverman 描述了这种方法:

    虽然如果总体确实是正态分布,则 (3.28) 会很好地工作,但如果总体是多峰的,它可能会有些过平滑...随着混合物变得更强烈双峰,公式 (3.28) 将越来越过平滑,相对于平滑参数的最优选择。

    scipy 文档reference this weakness,声明:

    它包括自动带宽确定。该估计最适合单峰分布;双峰或多峰分布往往被过度平滑。

    但是,Silverman 的文章继续,改进了 scipy 使用的方法,以获取 R 和 Stata 使用的方法。在第 48 页,我们得到方程 3.31:

    h = 0.9 * A * n ^ (-1 / 5)
    # A defined on previous page, eqn 3.30
    A = min(standard deviation, interquartile range / 1.34)
    

    Silverman 将这种方法描述为:

    两全其美...总之,平滑参数的选择 ([eqn] 3.31) 将适用于广泛的密度范围,并且易于评估。对于许多用途,它肯定是一个适当的窗口宽度选择,而对于其他用途,这将是后续微调的良好起点。

    因此,似乎 Wikipedia 和 Scipy 使用了 Silverman 提出的具有已知弱点的估算器的简单版本。 R 和 Stata 使用更好的版本。

    【讨论】:

    • 哇!感谢您及时详细的回复。我当然更喜欢 eq 3.31 到 3.28,尤其是考虑到数据中存在多模态的可能性。有趣的是,它们的用途有这样的二分法。
    • 是的,这篇论文是一个有趣的略读,让我非常感谢这些默认设置做了多少工作。
    • 关于如何在 python 中使用 gaussian_kde() 有什么想法吗?我已经成功地在 python 中复制了正确的 nrd0 h 值,但是当通过 bw_method 应用时,它会产生意想不到的结果。方程。 3.31 数字(nrd0 产生的)似乎在 0.05 左右,而在 bw_method 中输入接近 0.5 的值似乎复制了 gaussian_kde 中的结果。 h 的 nrd0 (eq. 3.31) 值会产生锯齿状曲线,但平滑度太低。
    • 对不起,我只是一个 Python 爱好者。我建议就此提出一个新问题(并参考这个问题的上下文)。
    • 所以...不确定这是否正确,但将 gaussian_kde 中的带宽方法设置为 3.31 h 值除以 std 似乎可以解决问题。我猜他们在设置方法后将它乘以 std 。很奇怪。
    猜你喜欢
    • 2015-05-04
    • 2014-10-11
    • 1970-01-01
    • 1970-01-01
    • 2018-04-28
    • 2021-03-14
    • 2014-01-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多