【问题标题】:Normalizing a histogram with matplotlib使用 matplotlib 规范化直方图
【发布时间】:2018-09-21 17:50:36
【问题描述】:

我想用 Matplotlib 绘制直方图,但我希望 bin 的值代表总观察值的百分比。 MWE 应该是这样的:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import matplotlib.pyplot as plt
import matplotlib.ticker as tck
import seaborn as sns
import numpy

sns.set(style='dark')

imagen2 = plt.figure(1, figsize=(5, 2))
imagen2.suptitle('StackOverflow Matplotlib histogram demo')

luminance = numpy.random.randn(1000, 1000)
# "Luminance" should range from 0.0...1.0 so we normalize it
luminance = (luminance - luminance.min())/(luminance.max() - luminance.min())

top_left = plt.subplot(121)
top_left.imshow(luminance)
bottom_left = plt.subplot(122)
sns.distplot(luminance.flatten(), kde_kws={"cumulative": True})

# plt.savefig("stackoverflow.pdf", dpi=300)
plt.tight_layout(rect=(0, 0, 1, 0.95))
plt.show()

这里的 CDF 还可以(范围:[0, 1]),但是生成的直方图不符合我的预期:

为什么直方图的结果在 [0, 4] 范围内?有没有什么办法解决这一问题?

【问题讨论】:

  • 直方图实际上是已经归一化的,但就其密度而言。本质上,sum(bin_heights*bin_widths) == 1.0
  • 如果您真的希望 bin 高度总和为 1.0,您也可以使用 numpy.histogram 函数自己计算它们。我在下面的答案中添加了一个示例

标签: python matplotlib histogram seaborn


【解决方案1】:

tel's answer is great! 我只是想提供一种替代方法,用更少的线条为您提供所需的直方图。关键思想是使用 matplotlib hist 函数中的 weights 参数来标准化计数。您可以将您的sns.distplot(luminance.flatten(), kde_kws={"cumulative": True}) 替换为以下三行代码:

lf = luminance.flatten()
sns.kdeplot(lf, cumulative=True)
sns.distplot(lf, kde=False,
             hist_kws={'weights': numpy.full(len(lf), 1/len(lf))})

如果您想在第二个 y 轴上查看直方图(更好的视觉效果),请将 ax=bottom_left.twinx() 添加到 sns.distplot

【讨论】:

  • 这正是我所需要的(直方图/CDF,其值为样本的百分比)。我刚刚扔了yaxis.set_major_formatter(ticker.PercentFormatter(xmax=1)),它已经准备好了。谢谢!
  • 不客气。很高兴您的问题得到解决。
【解决方案2】:

你认为你想要什么

以下是如何绘制直方图以使 bin 总和为 1:

import matplotlib.pyplot as plt
import matplotlib.ticker as tck
import seaborn as sns
import numpy as np

sns.set(style='dark')

imagen2 = plt.figure(1, figsize=(5, 2))
imagen2.suptitle('StackOverflow Matplotlib histogram demo')

luminance = numpy.random.randn(1000, 1000)
# "Luminance" should range from 0.0...1.0 so we normalize it
luminance = (luminance - luminance.min())/(luminance.max() - luminance.min())

# get the histogram values
heights,edges = np.histogram(luminance.flat, bins=30)
binCenters = (edges[:-1] + edges[1:])/2

# norm the heights
heights = heights/heights.sum()

# get the cdf
cdf = heights.cumsum()

left = plt.subplot(121)
left.imshow(luminance)
right = plt.subplot(122)
right.plot(binCenters, cdf, binCenters, heights)

# plt.savefig("stackoverflow.pdf", dpi=300)
plt.tight_layout(rect=(0, 0, 1, 0.95))
plt.show()

# confirm that the hist vals sum to 1
print('heights sum: %.2f' % heights.sum())

输出:

heights sum: 1.00

实际答案

这个其实超级简单。做吧

sns.distplot(luminance.flatten(), kde_kws={"cumulative": True}, norm_hist=True)

这是我在运行您的脚本并进行上述修改时得到的结果:

惊喜转折!

所以事实证明,您的直方图一直是标准化的,根据正式身份:

在简单的(er)英语中,一般做法是根据密度对连续值直方图(即它们的观察值可以表示为浮点数)进行规范。因此,在这种情况下,bin 宽度乘以 bin 高度的总和将为 1.0,正如您通过运行脚本的简化版本所看到的那样:

import matplotlib.pyplot as plt
import matplotlib.ticker as tck
import numpy as np

imagen2 = plt.figure(1, figsize=(4,3))
imagen2.suptitle('StackOverflow Matplotlib histogram demo')

luminance = numpy.random.randn(1000, 1000)
luminance = (luminance - luminance.min())/(luminance.max() - luminance.min())

heights,edges,patches = plt.hist(luminance.ravel(), density=True, bins=30)
widths = edges[1:] - edges[:-1]

totalWeight = (heights*widths).sum()

# plt.savefig("stackoverflow.pdf", dpi=300)
plt.tight_layout(rect=(0, 0, 1, 0.95))
plt.show()
print(totalWeight)

totalWeight 确实会完全等于1.0,会有一点舍入误差。

【讨论】:

    猜你喜欢
    • 2012-08-30
    • 1970-01-01
    • 2014-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-30
    • 2016-06-09
    相关资源
    最近更新 更多