【问题标题】:Plot an histogram with y-axis as percentage (using FuncFormatter?)绘制 y 轴为百分比的直方图(使用 FuncFormatter?)
【发布时间】:2018-07-23 07:54:08
【问题描述】:

我有一个数据列表,其中的数字在 1000 到 20 000 之间。

data = [1000, 1000, 5000, 3000, 4000, 16000, 2000]

当我使用hist() 函数绘制直方图时,y 轴表示值在 bin 中出现的次数。而不是出现的次数,我想知道出现的百分比。

上图的代码:

f, ax = plt.subplots(1, 1, figsize=(10,5))
ax.hist(data, bins = len(list(set(data))))

我一直在看这个post,它描述了一个使用FuncFormatter 的例子,但我不知道如何让它适应我的问题。欢迎提供一些帮助和指导:)

编辑: FuncFormatter 使用的 to_percent(y, position) 函数的主要问题。我猜 y 对应于 y 轴上的一个给定值。我需要将此值除以显然无法传递给函数的元素总数...

编辑 2:我不喜欢当前解决方案,因为使用了全局变量:

def to_percent(y, position):
    # Ignore the passed in position. This has the effect of scaling the default
    # tick locations.
    global n

    s = str(round(100 * y / n, 3))
    print (y)

    # The percent symbol needs escaping in latex
    if matplotlib.rcParams['text.usetex'] is True:
        return s + r'$\%$'
    else:
        return s + '%'

def plotting_hist(folder, output):
    global n

    data = list()
    # Do stuff to create data from folder

    n = len(data)
    f, ax = plt.subplots(1, 1, figsize=(10,5))
    ax.hist(data, bins = len(list(set(data))), rwidth = 1)

    formatter = FuncFormatter(to_percent)
    plt.gca().yaxis.set_major_formatter(formatter)

    plt.savefig("{}.png".format(output), dpi=500)

编辑 3: 使用 density = True 的方法

实际期望的输出(带有全局变量的方法):

【问题讨论】:

    标签: python matplotlib


    【解决方案1】:

    其他答案似乎非常复杂。通过使用1/n 对数据进行加权可以轻松生成显示比例而非绝对数量的直方图,其中n 是数据点的数量。

    然后可以使用PercentFormatter 将比例(例如0.45)显示为百分比(45%)。

    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.ticker import PercentFormatter
    
    data = [1000, 1000, 5000, 3000, 4000, 16000, 2000]
    
    plt.hist(data, weights=np.ones(len(data)) / len(data))
    
    plt.gca().yaxis.set_major_formatter(PercentFormatter(1))
    plt.show()
    

    这里我们看到 7 个值中的三个在第一个 bin 中,即 3/7=43%。

    【讨论】:

    • 嗨,这看起来不错。但是,条形图并未完全在 x 轴刻度上完成,但它们每次都会向右移动一点。我怎样才能使这些对齐?
    • @PoeteMaudit 您没有对齐直方图的条形。它们恰好位于垃圾箱边缘。如果要更改 bin 边缘,请使用直方图的 bins 参数。
    • 感谢您的回复,但从视觉上看,bin 边缘未与 x 轴的刻度线对齐。因此,甚至很难解释与每个 bin 相关的值是什么。
    • 您可以通过选择 bin 边缘来解决此问题,这样它们的数字就很好,并将刻度设置为这些数字,而不是相反。
    • 要消除对numpy的依赖,可以将weights=np.ones(len(data)) / len(data)替换为weights = [1/len(data)] * len(data)
    【解决方案2】:

    您可以自己计算百分比,然后将它们绘制成条形图。这需要您使用numpy.histogram(matplotlib 无论如何都使用“幕后”)。然后您可以调整 y 刻度标签:

    import matplotlib.pyplot as plt
    import numpy as np
    
    f, ax = plt.subplots(1, 1, figsize=(10,5))
    data = [1000, 1000, 5000, 3000, 4000, 16000, 2000]
    
    heights, bins = np.histogram(data, bins = len(list(set(data))))
    
    percent = [i/sum(heights)*100 for i in heights]
    
    ax.bar(bins[:-1], percent, width=2500, align="edge")
    vals = ax.get_yticks()
    ax.set_yticklabels(['%1.2f%%' %i for i in vals])
    
    plt.show()
    

    【讨论】:

      【解决方案3】:

      我认为最简单的方法是使用 seaborn,它是 matplotlib 上的一个层。请注意,您仍然可以使用 plt.subplots()figsize()axfig 来自定义您的绘图。

      import seaborn as sns
      

      并使用以下代码:

      sns.displot(data, stat='probability'))
      

      另外,sns.displot 有很多参数,可以很容易地制作非常复杂和信息丰富的图表。他们可以在这里找到:displot Documentation

      【讨论】:

        【解决方案4】:

        只需将密度设置为 true,权重就会被隐式归一化。

        import numpy as np
        import matplotlib.pyplot as plt
        from matplotlib.ticker import PercentFormatter
        
        data = [1000, 1000, 5000, 3000, 4000, 16000, 2000]
        
        plt.hist(data, density=True)
        
        plt.gca().yaxis.set_major_formatter(PercentFormatter(1))
        plt.show()
        

        【讨论】:

        • 最佳答案 IMO。
        • density=True 没有按照 OP 的要求给出“出现的百分比”。大多数用户不会寻找density=True(谷歌看看为什么)。
        【解决方案5】:

        您可以使用functools.partial 来避免在您的示例中使用globals。

        只需在函数参数中添加n

        def to_percent(y, position, n):
            s = str(round(100 * y / n, 3))
        
            if matplotlib.rcParams['text.usetex']:
                return s + r'$\%$'
        
            return s + '%'
        

        然后创建一个包含两个参数的偏函数,您可以将其传递给FuncFormatter

        percent_formatter = partial(to_percent,
                                    n=len(data))
        formatter = FuncFormatter(percent_formatter)
        

        完整代码:

        from functools import partial
        
        import matplotlib.pyplot as plt
        from matplotlib.ticker import FuncFormatter
        
        data = [1000, 1000, 5000, 3000, 4000, 16000, 2000]
        
        
        def to_percent(y, position, n):
            s = str(round(100 * y / n, 3))
        
            if matplotlib.rcParams['text.usetex']:
                return s + r'$\%$'
        
            return s + '%'
        
        
        def plotting_hist(data):    
            f, ax = plt.subplots(figsize=(10, 5))
            ax.hist(data, 
                    bins=len(set(data)), 
                    rwidth=1)
        
            percent_formatter = partial(to_percent,
                                        n=len(data))
            formatter = FuncFormatter(percent_formatter)
            plt.gca().yaxis.set_major_formatter(formatter)
        
            plt.show()
        
        
        plotting_hist(data)
        

        给出:

        【讨论】:

        • @ImportanceOfBeingErnest 你能解释一下为什么这个输出不正确,而来自 DavidG 的输出是正确的吗?我真的看不出有什么区别。他们在第一个垃圾箱中也没有 43%。
        • 对不起,这似乎是正确的。但我认为在轴上使用任意复杂的数字(例如 42.857 而不是 40)是没有用的。
        • 你的两个都是正确的,但是来自@ImportanceOfBeingErnest 的那个更简单。
        猜你喜欢
        • 2020-03-19
        • 2021-12-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-01
        • 1970-01-01
        • 1970-01-01
        • 2013-12-22
        相关资源
        最近更新 更多