【问题标题】:Scaling and fitting to a log-normal distribution using a logarithmic axis in python在 python 中使用对数轴缩放和拟合对数正态分布
【发布时间】:2016-05-02 07:03:24
【问题描述】:

我有一组对数正态分布的样本。我可以使用具有线性或对数 x 轴的直方图来可视化样本。我可以对直方图进行拟合以获取 PDF,然后使用线性 x 轴将其缩放到图中的直方图,另请参见 this previously posted question

但是,我无法正确地将 PDF 绘制到带有对数 x 轴的图中。

不幸的是,这不仅是 PDF 区域缩放到直方图的问题,而且 PDF 也会向左移动,如下图所示。

我现在的问题是,我在这里做错了什么?使用 CDF 绘制预期的直方图 as suggested in this answer 是可行的。我只是想知道我在这段代码中做错了什么,因为据我所知它也应该有效。

这是python代码(对不起,它比较长,但我想发布一个“完整的单机版”):

import numpy as np
import matplotlib.pyplot as plt
import scipy.stats

# generate log-normal distributed set of samples
np.random.seed(42)
samples   = np.random.lognormal( mean=1, sigma=.4, size=10000 )

# make a fit to the samples
shape, loc, scale = scipy.stats.lognorm.fit( samples, floc=0 )
x_fit       = np.linspace( samples.min(), samples.max(), 100 )
samples_fit = scipy.stats.lognorm.pdf( x_fit, shape, loc=loc, scale=scale )

# plot a histrogram with linear x-axis
plt.subplot( 1, 2, 1 )
N_bins = 50
counts, bin_edges, ignored = plt.hist( samples, N_bins, histtype='stepfilled', label='histogram' )
# calculate area of histogram (area under PDF should be 1)
area_hist = .0
for ii in range( counts.size):
    area_hist += (bin_edges[ii+1]-bin_edges[ii]) * counts[ii]
# oplot fit into histogram
plt.plot( x_fit, samples_fit*area_hist, label='fitted and area-scaled PDF', linewidth=2)
plt.legend()

# make a histrogram with a log10 x-axis
plt.subplot( 1, 2, 2 )
# equally sized bins (in log10-scale)
bins_log10 = np.logspace( np.log10( samples.min()  ), np.log10( samples.max() ), N_bins )
counts, bin_edges, ignored = plt.hist( samples, bins_log10, histtype='stepfilled', label='histogram' )
# calculate area of histogram
area_hist_log = .0
for ii in range( counts.size):
    area_hist_log += (bin_edges[ii+1]-bin_edges[ii]) * counts[ii]
# get pdf-values for log10 - spaced intervals
x_fit_log       = np.logspace( np.log10( samples.min()), np.log10( samples.max()), 100 )
samples_fit_log = scipy.stats.lognorm.pdf( x_fit_log, shape, loc=loc, scale=scale )
# oplot fit into histogram
plt.plot( x_fit_log, samples_fit_log*area_hist_log, label='fitted and area-scaled PDF', linewidth=2 )

plt.xscale( 'log' )
plt.xlim( bin_edges.min(), bin_edges.max() )
plt.legend()
plt.show()

更新 1

我忘了提及我正在使用的版本:

python      2.7.6
numpy       1.8.2
matplotlib  1.3.1
scipy       0.13.3

更新 2

正如@Christoph 和@zaxliu 所指出的(感谢两者),问题在于PDF 的缩放。当我使用与直方图相同的 bin 时,它可以工作,就像在@zaxliu 的解决方案中一样,但是在为 PDF 使用更高分辨率时我仍然遇到一些问题(如我上面的示例所示)。如下图所示:

右侧图的代码是(我省略了导入和数据样本生成的东西,你可以在上面的例子中找到它们):

# equally sized bins in log10-scale
bins_log10 = np.logspace( np.log10( samples.min()  ), np.log10( samples.max() ), N_bins )
counts, bin_edges, ignored = plt.hist( samples, bins_log10, histtype='stepfilled', label='histogram' )

# calculate length of each bin (required for scaling PDF to histogram)
bins_log_len = np.zeros( bins_log10.size )
for ii in range( counts.size):
    bins_log_len[ii] = bin_edges[ii+1]-bin_edges[ii]

# get pdf-values for same intervals as histogram
samples_fit_log = scipy.stats.lognorm.pdf( bins_log10, shape, loc=loc, scale=scale )

# oplot fitted and scaled PDF into histogram
plt.plot( bins_log10, np.multiply(samples_fit_log,bins_log_len)*sum(counts), label='PDF using histogram bins', linewidth=2 )

# make another pdf with a finer resolution
x_fit_log       = np.logspace( np.log10( samples.min()), np.log10( samples.max()), 100 )
samples_fit_log = scipy.stats.lognorm.pdf( x_fit_log, shape, loc=loc, scale=scale )
# calculate length of each bin (required for scaling PDF to histogram)
# in addition, estimate middle point for more accuracy (should in principle also be done for the other PDF)
bins_log_len       = np.diff( x_fit_log )
samples_log_center = np.zeros( x_fit_log.size-1 )
for ii in range( x_fit_log.size-1 ):
    samples_log_center[ii] = .5*(samples_fit_log[ii] + samples_fit_log[ii+1] )

# scale PDF to histogram
# NOTE: THIS IS NOT WORKING PROPERLY (SEE FIGURE)
pdf_scaled2hist = np.multiply(samples_log_center,bins_log_len)*sum(counts)

# oplot fit into histogram
plt.plot( .5*(x_fit_log[:-1]+x_fit_log[1:]), pdf_scaled2hist, label='PDF using own bins', linewidth=2 )

plt.xscale( 'log' )
plt.xlim( bin_edges.min(), bin_edges.max() )
plt.legend(loc=3)

【问题讨论】:

  • 为什么不使用 CDF 创建预期的直方图,正如我在回答您的其他问题 (stackoverflow.com/questions/34893615/…) 中所建议的那样?
  • 我应该补充一点,当我按照您的建议进行操作时,使用 CDF 绘制预期的直方图,它可以工作。我只想知道我在上面的例子中做错了什么,因为据我所知它也应该起作用......
  • 我在这里可能错了,但看起来您在创建具有可变大小的 bin 的直方图时使用的是通常的 PDF(以便它们在对数图中具有相等的宽度)。没有理由假设 PDF 和直方图应该看起来一样,对吧?

标签: python matplotlib scipy statistics


【解决方案1】:

正如@Christoph 所指出的,问题在于您缩放采样 pdf 的方式。

因为 pdf 是概率密度的密度,如果你想要一个 bin 中的预期频率,你应该先将密度乘以 bin 长度,得到一个样本落入这个 bin 的近似概率,然后你可以乘以这个概率通过样本总数来估计将落入这个 bin 的样本数。

换句话说,每个 bin 应该以对数尺度不均匀地缩放,而您使用“hist under hist”统一缩放它们。作为修复,您可以执行以下操作:

# make a histrogram with a log10 x-axis
plt.subplot( 1, 2, 2 )
# equally sized bins (in log10-scale)
bins_log10 = np.logspace( np.log10( samples.min()  ), np.log10( samples.max() ), N_bins )
counts, bin_edges, ignored = plt.hist( samples, bins_log10, histtype='stepfilled', label='histogram' )
# calculate length of each bin
len_bin_log = np.zeros([bins_log10.size,])
for ii in range( counts.size):
    len_bin_log[ii] = (bin_edges[ii+1]-bin_edges[ii])

# get pdf-values for log10 - spaced intervals
# x_fit_log       = np.logspace( np.log10( samples.min()), np.log10( samples.max()), N_bins )
samples_fit_log = scipy.stats.lognorm.pdf( bins_log10, shape, loc=loc, scale=scale )

# oplot fit into histogram
plt.plot(bins_log10 , np.multiply(samples_fit_log,len_bin_log)*sum(counts), label='fitted and area-scaled PDF', linewidth=2 )
plt.xscale( 'log' )
plt.xlim( bin_edges.min(), bin_edges.max() )
# plt.legend()
plt.show()

此外,您可能还想考虑以类似的方式修改线性比例的缩放方法。实际上,您不需要累积面积,只需按 bin 大小乘以密度乘以样本总数。

更新

我想到,我目前估计垃圾箱概率的方法可能不是最准确的方法。由于 pdf 曲线是凹的,因此在中间点使用样本进行估计可能会更准确。

【讨论】:

  • 感谢@Christoph 和@zaxliu。 @zaxliu 建议的解决方案效果很好,但是当我希望 PDF 的分辨率比直方图的分辨率更高时(就像在我的原始示例中一样),它不再正确缩放:我正在使用 x_fit_log 大小为 @ PDF 的 987654323@(大于 N_bins),计算每个 bin 的长度,将其乘以 PDF 值(使用中点),然后乘以 sum(counts)。当绘制到直方图中时,由于某种原因,缩放的 PDF 值太小了x_fit_log.size/N_bins 的系数...
  • @Alf 你能提供一些关于你如何计算 bin 长度和中点 pdf 样本的代码吗?
  • 我更新了问题以显示您的示例(工作)和我的示例(不工作)
【解决方案2】:

根据我在@Warren Weckesser 的原始答案中的理解,您reffered to“所有你需要做的”是:

cdf(b) - cdf(a) 的近似值写为 cdf(b) - cdf(a) = pdf(m)*(b - a) 其中,m 是区间 [a, b] 的中点

我们可以尝试按照他的建议,根据 bin 的中心点绘制两种获取 pdf 值的方法:

  1. 带PDF功能
  2. 带 CDF 功能:

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats 

# generate log-normal distributed set of samples
np.random.seed(42)
samples = np.random.lognormal(mean=1, sigma=.4, size=10000)
N_bins = 50

# make a fit to the samples
shape, loc, scale = stats.lognorm.fit(samples, floc=0)
x_fit       = np.linspace(samples.min(), samples.max(), 100)
samples_fit = stats.lognorm.pdf(x_fit, shape, loc=loc, scale=scale)

# plot a histrogram with linear x-axis
fig, (ax1, ax2) = plt.subplots(1,2, figsize=(10,5), gridspec_kw={'wspace':0.2})
counts, bin_edges, ignored = ax1.hist(samples, N_bins, histtype='stepfilled', alpha=0.4,
                                      label='histogram')

# calculate area of histogram (area under PDF should be 1)
area_hist = ((bin_edges[1:] - bin_edges[:-1]) * counts).sum()

# plot fit into histogram
ax1.plot(x_fit, samples_fit*area_hist, label='fitted and area-scaled PDF', linewidth=2)
ax1.legend()

# equally sized bins in log10-scale and centers
bins_log10 = np.logspace(np.log10(samples.min()), np.log10(samples.max()), N_bins)
bins_log10_cntr = (bins_log10[1:] + bins_log10[:-1]) / 2

# histogram plot
counts, bin_edges, ignored = ax2.hist(samples, bins_log10, histtype='stepfilled', alpha=0.4,
                                      label='histogram')

# calculate length of each bin and its centers(required for scaling PDF to histogram)
bins_log_len = np.r_[bin_edges[1:] - bin_edges[: -1], 0]
bins_log_cntr = bin_edges[1:] - bin_edges[:-1]

# get pdf-values for same intervals as histogram
samples_fit_log = stats.lognorm.pdf(bins_log10, shape, loc=loc, scale=scale)

# pdf-values for centered scale
samples_fit_log_cntr = stats.lognorm.pdf(bins_log10_cntr, shape, loc=loc, scale=scale)

# pdf-values using cdf 
samples_fit_log_cntr2_ = stats.lognorm.cdf(bins_log10, shape, loc=loc, scale=scale)
samples_fit_log_cntr2 = np.diff(samples_fit_log_cntr2_)

# plot fitted and scaled PDFs into histogram
ax2.plot(bins_log10, 
         samples_fit_log * bins_log_len * counts.sum(), '-', 
         label='PDF with edges',  linewidth=2)

ax2.plot(bins_log10_cntr, 
         samples_fit_log_cntr * bins_log_cntr * counts.sum(), '-', 
         label='PDF with centers', linewidth=2)

ax2.plot(bins_log10_cntr, 
         samples_fit_log_cntr2 * counts.sum(), 'b-.', 
         label='CDF with centers', linewidth=2)


ax2.set_xscale('log')
ax2.set_xlim(bin_edges.min(), bin_edges.max())
ax2.legend(loc=3)
plt.show()

您可以看到,第一种(使用 pdf)和第二种(使用 cdf)方法都给出了几乎相同的结果,并且两者都不完全匹配使用 bin 边缘计算的 pdf。

如果放大,您会清楚地看到差异:

现在人们可以提出的问题是:使用哪一个?我想答案将取决于但如果我们看一下累积概率:

print 'Cumulative probabilities:'
print 'Using edges:         {:>10.5f}'.format((samples_fit_log * bins_log_len).sum())
print 'Using PDF of centers:{:>10.5f}'.format((samples_fit_log_cntr * bins_log_cntr).sum())
print 'Using CDF of centers:{:>10.5f}'.format(samples_fit_log_cntr2.sum())

您可以从输出中看到哪个方法更接近 1.0:

Cumulative probabilities:
Using edges:            1.03263
Using PDF of centers:   0.99957
Using CDF of centers:   0.99991

CDF 似乎给出了最接近的近似值。

这很长,但我希望这是有道理的。

更新:

我已经调整了代码来说明如何平滑 PDF 线条。 注意s 变量,它定义了线条的平滑程度。 我在变量中添加了_s 后缀,以指示需要在哪里进行调整。

# generate log-normal distributed set of samples
np.random.seed(42)
samples = np.random.lognormal(mean=1, sigma=.4, size=10000)
N_bins = 50

# make a fit to the samples
shape, loc, scale = stats.lognorm.fit(samples, floc=0)

# plot a histrogram with linear x-axis
fig, ax2 = plt.subplots()#1,2, figsize=(10,5), gridspec_kw={'wspace':0.2})

# equally sized bins in log10-scale and centers
bins_log10 = np.logspace(np.log10(samples.min()), np.log10(samples.max()), N_bins)
bins_log10_cntr = (bins_log10[1:] + bins_log10[:-1]) / 2

# smoother PDF line
s = 10 # mulpiplier to N_bins - the bigger s is the smoother the line
bins_log10_s = np.logspace(np.log10(samples.min()), np.log10(samples.max()), N_bins * s)
bins_log10_cntr_s = (bins_log10_s[1:] + bins_log10_s[:-1]) / 2

# histogram plot
counts, bin_edges, ignored = ax2.hist(samples, bins_log10, histtype='stepfilled', alpha=0.4,
                                      label='histogram')

# calculate length of each bin and its centers(required for scaling PDF to histogram)
bins_log_len = np.r_[bins_log10_s[1:] - bins_log10_s[: -1], 0]
bins_log_cntr = bins_log10_s[1:] - bins_log10_s[:-1]

# smooth pdf-values for same intervals as histogram
samples_fit_log_s = stats.lognorm.pdf(bins_log10_s, shape, loc=loc, scale=scale)

# pdf-values for centered scale
samples_fit_log_cntr = stats.lognorm.pdf(bins_log10_cntr_s, shape, loc=loc, scale=scale)

# smooth pdf-values using cdf 
samples_fit_log_cntr2_s_ = stats.lognorm.cdf(bins_log10_s, shape, loc=loc, scale=scale)
samples_fit_log_cntr2_s = np.diff(samples_fit_log_cntr2_s_)

# plot fitted and scaled PDFs into histogram
ax2.plot(bins_log10_cntr_s, 
         samples_fit_log_cntr * bins_log_cntr * counts.sum() * s, '-', 
         label='Smooth PDF with centers', linewidth=2)

ax2.plot(bins_log10_cntr_s, 
         samples_fit_log_cntr2_s * counts.sum() * s, 'k-.', 
         label='Smooth CDF with centers', linewidth=2)

ax2.set_xscale('log')
ax2.set_xlim(bin_edges.min(), bin_edges.max())
ax2.legend(loc=3)
plt.show)

这会产生这个情节:

如果您放大平滑版本与非平滑版本,您会看到:

希望这会有所帮助。

【讨论】:

  • 非常感谢您的透彻分析!我玩弄了你的缩放方式,但是,当我想为我的 PDF 设置比直方图更高的分辨率(如果我想让它更平滑)时,即samples_fit_log.size 大于bins_log10.size,我无法正确缩放 PDF...
  • 我添加了代码来为基于 PDF 和基于 CDF 的方法生成平滑的线条。
  • 再次感谢您的努力,但我认为您在此处犯了一个“错误”,并不是真正的错误,但没有必要再制作一个直方图(bin_edges_s 与 @987654341 相同@)。另外,我仍然不明白为什么您必须将 PDF 与您的示例中的因子 s 或我的示例中的 x_fit_log.size/N_bins 相乘(如对@zaxliu 回复的评论中所写),因为您使用的是新创建的 bin 和 PDF-samples 和 count.sum() 当然应该不会改变。
  • 感谢您发现“错误” - 已更正。关于s:如果不需要您的pdf 行来匹配(或勾勒)您的直方图,那么您无需担心s - 只需将其删除即可。但是,如果您希望 pdf 线适合直方图(即从sample.size 绘制的每个 N_bins 的概率)出现大约接近您的 pdf 线(即 N_bins * s 来自同一个sample.size)你显然需要缩放pdf概率(即* s)。因为出现在N_bins 中的概率(对于sample 中的元素)将始终高于出现在N_bins * s 中。
  • 嗯,我的天真 (?) 假设是,如果我更改直方图的分辨率(在我的示例中为 bins_log10),sum(counts) 不会改变,PDF 下的区域也应该如此与垃圾箱的大小无关(在我的示例中为x_fit_log)。因此,我只需要通过将 PDF 的面积(应该是 1,对吗?)与直方图的面积相乘,将(更高分辨率的)PDF 重新缩放为直方图。不知何故,在我的示例中似乎并非如此......
【解决方案3】:

由于我遇到了同样的问题并想通了,我想解释一下发生了什么,并为原始问题提供不同的解决方案。

当您使用对数 bin 绘制直方图时,这相当于更改变量 ,其中 x 是您的原始样本(或您用来绘制它们的网格),t 是相对于箱是线性间隔的。因此,实际对应直方图的PDF是

我们仍在使用 x 变量作为 PDF 的输入,所以这变成了

您需要将 PDF 乘以 x!

这固定了 PDF 的形状,但我们仍然需要缩放 PDF 以使曲线下的面积等于直方图。事实上,PDF 下的面积等于 1,因为我们是在 x 上积分,并且

因为我们正在处理对数正态变量。因为根据scipy documentation,分布参数对应shape = sigmascale = exp(mu),我们可以很容易地计算出你代码中的右边为scale * np.exp(shape**2/2.)

事实上,一行代码修复了您的原始脚本,将计算出的 PDF 值乘以 x 并除以上面计算的面积:

samples_fit_log *= x_fit_log / (scale * np.exp(shape**2/2.))

导致以下情节:

或者,您可以通过在日志空间中集成直方图来更改直方图“区域”的定义。请记住,在对数空间(t 变量)中,PDF 的面积为 1。因此您可以跳过缩放因子,并将上面的行替换为:

area_hist_log = np.dot(np.diff(np.log(bin_edges)), counts)
samples_fit_log *= x_fit_log

后一种解决方案可能更可取,因为它不依赖于任何有关手头分布的信息。它适用于任何分布,而不仅仅是对数正态分布。

作为参考,这是添加了我的行的原始脚本:

import numpy as np
import matplotlib.pyplot as plt
import scipy.stats

# generate log-normal distributed set of samples
np.random.seed(42)
samples   = np.random.lognormal( mean=1, sigma=.4, size=10000 )

# make a fit to the samples
shape, loc, scale = scipy.stats.lognorm.fit( samples, floc=0 )
x_fit       = np.linspace( samples.min(), samples.max(), 100 )
samples_fit = scipy.stats.lognorm.pdf( x_fit, shape, loc=loc, scale=scale )

# plot a histrogram with linear x-axis
plt.subplot( 1, 2, 1 )
N_bins = 50
counts, bin_edges, ignored = plt.hist( samples, N_bins, histtype='stepfilled', label='histogram' )
# calculate area of histogram (area under PDF should be 1)
area_hist = .0
for ii in range( counts.size):
    area_hist += (bin_edges[ii+1]-bin_edges[ii]) * counts[ii]
# oplot fit into histogram
plt.plot( x_fit, samples_fit*area_hist, label='fitted and area-scaled PDF', linewidth=2)
plt.legend()

# make a histrogram with a log10 x-axis
plt.subplot( 1, 2, 2 )
# equally sized bins (in log10-scale)
bins_log10 = np.logspace( np.log10( samples.min()  ), np.log10( samples.max() ), N_bins )
counts, bin_edges, ignored = plt.hist( samples, bins_log10, histtype='stepfilled', label='histogram' )
# calculate area of histogram
area_hist_log = .0
for ii in range( counts.size):
    area_hist_log += (bin_edges[ii+1]-bin_edges[ii]) * counts[ii]
# get pdf-values for log10 - spaced intervals
x_fit_log       = np.logspace( np.log10( samples.min()), np.log10( samples.max()), 100 )
samples_fit_log = scipy.stats.lognorm.pdf( x_fit_log, shape, loc=loc, scale=scale )
# scale pdf output:
samples_fit_log *= x_fit_log / (scale * np.exp(shape**2/2.))
# alternatively you could do:
#area_hist_log = np.dot(np.diff(np.log(bin_edges)), counts)
#samples_fit_log *= x_fit_log

# oplot fit into histogram
plt.plot( x_fit_log, samples_fit_log*area_hist_log, label='fitted and area-scaled PDF', linewidth=2 )

plt.xscale( 'log' )
plt.xlim( bin_edges.min(), bin_edges.max() )
plt.legend()
plt.show()

【讨论】:

    猜你喜欢
    • 2017-08-31
    • 2016-04-25
    • 1970-01-01
    • 2016-04-17
    • 2015-12-07
    • 1970-01-01
    • 2017-11-16
    • 2013-03-15
    • 1970-01-01
    相关资源
    最近更新 更多