Python Seaborn:countplot()与频率

Python Seaborn:countplot()与频率,python,pandas,matplotlib,data-visualization,seaborn,Python,Pandas,Matplotlib,Data Visualization,Seaborn,我有一个名为“axes”的数据框,它可以取3-12之间的整数值。我正在尝试使用Seaborn的countplot()选项实现以下绘图: 左y轴显示这些值在数据中出现的频率。轴延伸为[0%-100%],每10%打勾一次 右y轴显示实际计数,数值对应于左y轴确定的刻度线(每10%标记一次) x轴显示条形图的类别[3,4,5,6,7,8,9,10,11,12] 条形图顶部的注释显示该类别的实际百分比 下面的代码给出了下面的图,带有实际的计数,但我找不到将它们转换成频率的方法。我可以使用df.axes.

我有一个名为“axes”的数据框,它可以取3-12之间的整数值。我正在尝试使用Seaborn的countplot()选项实现以下绘图:

  • 左y轴显示这些值在数据中出现的频率。轴延伸为[0%-100%],每10%打勾一次
  • 右y轴显示实际计数,数值对应于左y轴确定的刻度线(每10%标记一次)
  • x轴显示条形图的类别[3,4,5,6,7,8,9,10,11,12]
  • 条形图顶部的注释显示该类别的实际百分比
  • 下面的代码给出了下面的图,带有实际的计数,但我找不到将它们转换成频率的方法。我可以使用
    df.axes.value\u counts()/len(df.index)
    获得频率,但我不确定如何将此信息插入Seaborn的
    countplot()

    我还找到了注释的解决方法,但我不确定这是否是最好的实现

    任何帮助都将不胜感激

    谢谢

    plt.figure(figsize=(12,8))
    ax = sns.countplot(x="AXLES", data=dfWIM, order=[3,4,5,6,7,8,9,10,11,12])
    plt.title('Distribution of Truck Configurations')
    plt.xlabel('Number of Axles')
    plt.ylabel('Frequency [%]')
    
    for p in ax.patches:
            ax.annotate('%{:.1f}'.format(p.get_height()), (p.get_x()+0.1, p.get_height()+50))
    

    编辑: 我用下面的代码,使用熊猫的条形图,挖沟Seaborn,更接近我所需要的。感觉好像我使用了很多变通方法,必须有一种更简单的方法。这种方法的问题是:

    • Pandas的条形图函数中没有Seaborn的countplot()所具有的
      order
      关键字,因此我无法像在countplot()中那样绘制3-12之间的所有类别。我需要有他们显示,即使没有在该类别的数据
    • 由于某种原因,次y轴会弄乱条形图和注释(请参见在文本和条形图上绘制的白色网格线)


    我使用core
    matplotlib
    的条形图使其工作。我显然没有你的数据,但根据你的数据进行调整应该是很简单的。

    方法 我使用了
    matplotlib
    的双轴,并将数据绘制为第二个
    轴上的条形图。剩下的只是一些摆弄,以获得正确的记号并进行注释

    希望这有帮助

    代码
    我认为您可以先手动设置y主刻度,然后修改每个标签

    dfWIM = pd.DataFrame({'AXLES': np.random.randint(3, 10, 1000)})
    total = len(dfWIM)*1.
    plt.figure(figsize=(12,8))
    ax = sns.countplot(x="AXLES", data=dfWIM, order=[3,4,5,6,7,8,9,10,11,12])
    plt.title('Distribution of Truck Configurations')
    plt.xlabel('Number of Axles')
    plt.ylabel('Frequency [%]')
    
    for p in ax.patches:
            ax.annotate('{:.1f}%'.format(100*p.get_height()/total), (p.get_x()+0.1, p.get_height()+5))
    
    #put 11 ticks (therefore 10 steps), from 0 to the total number of rows in the dataframe
    ax.yaxis.set_ticks(np.linspace(0, total, 11))
    
    #adjust the ticklabel to the desired format, without changing the position of the ticks. 
    _ = ax.set_yticklabels(map('{:.1f}%'.format, 100*ax.yaxis.get_majorticklocs()/total))
    

    您可以通过为频率制作轴来实现这一点。您可以切换两个y轴,使频率保持在左侧,计数保持在右侧,但无需重新计算计数轴(此处我们使用和来移动刻度和移动轴标签)

    然后,您可以使用模块设置刻度,特别是和

    对于注释,您可以使用
    patch.get_bbox()

    最后,需要关闭孪生轴的栅格,以防止栅格线显示在条的顶部()

    下面是一个工作脚本:

    import pandas as pd
    import matplotlib.pyplot as plt
    import numpy as np
    import seaborn as sns
    import matplotlib.ticker as ticker
    
    # Some random data
    dfWIM = pd.DataFrame({'AXLES': np.random.normal(8, 2, 5000).astype(int)})
    ncount = len(dfWIM)
    
    plt.figure(figsize=(12,8))
    ax = sns.countplot(x="AXLES", data=dfWIM, order=[3,4,5,6,7,8,9,10,11,12])
    plt.title('Distribution of Truck Configurations')
    plt.xlabel('Number of Axles')
    
    # Make twin axis
    ax2=ax.twinx()
    
    # Switch so count axis is on right, frequency on left
    ax2.yaxis.tick_left()
    ax.yaxis.tick_right()
    
    # Also switch the labels over
    ax.yaxis.set_label_position('right')
    ax2.yaxis.set_label_position('left')
    
    ax2.set_ylabel('Frequency [%]')
    
    for p in ax.patches:
        x=p.get_bbox().get_points()[:,0]
        y=p.get_bbox().get_points()[1,1]
        ax.annotate('{:.1f}%'.format(100.*y/ncount), (x.mean(), y), 
                ha='center', va='bottom') # set the alignment of the text
    
    # Use a LinearLocator to ensure the correct number of ticks
    ax.yaxis.set_major_locator(ticker.LinearLocator(11))
    
    # Fix the frequency range to 0-100
    ax2.set_ylim(0,100)
    ax.set_ylim(0,ncount)
    
    # And use a MultipleLocator to ensure a tick spacing of 10
    ax2.yaxis.set_major_locator(ticker.MultipleLocator(10))
    
    # Need to turn the grid on ax2 off, otherwise the gridlines end up on top of the bars
    ax2.grid(None)
    
    plt.savefig('snscounter.pdf')
    

    为什么不将滴答声标签除以总计数以获得频率?我使用
    vals=ax.get\u-yticks()和
    ax.set\u-yticks(vals/len(df))进行了尝试
    。然而,一旦我这样做,所有标签都会在原点附近的最底部结束,这是由于绘图的实际y比例。显然我的方法是错误的。你会怎么做?你救了我的命:D:D:DThnx!一个可能的改进,以避免“展平”直方图:
    #在不改变轴缩放的情况下将频率范围固定到0-100:
    ax2.set_ylim(0100*ax.get_ylim()[1]/ncount)
    dfWIM = pd.DataFrame({'AXLES': np.random.randint(3, 10, 1000)})
    total = len(dfWIM)*1.
    plt.figure(figsize=(12,8))
    ax = sns.countplot(x="AXLES", data=dfWIM, order=[3,4,5,6,7,8,9,10,11,12])
    plt.title('Distribution of Truck Configurations')
    plt.xlabel('Number of Axles')
    plt.ylabel('Frequency [%]')
    
    for p in ax.patches:
            ax.annotate('{:.1f}%'.format(100*p.get_height()/total), (p.get_x()+0.1, p.get_height()+5))
    
    #put 11 ticks (therefore 10 steps), from 0 to the total number of rows in the dataframe
    ax.yaxis.set_ticks(np.linspace(0, total, 11))
    
    #adjust the ticklabel to the desired format, without changing the position of the ticks. 
    _ = ax.set_yticklabels(map('{:.1f}%'.format, 100*ax.yaxis.get_majorticklocs()/total))
    
    import pandas as pd
    import matplotlib.pyplot as plt
    import numpy as np
    import seaborn as sns
    import matplotlib.ticker as ticker
    
    # Some random data
    dfWIM = pd.DataFrame({'AXLES': np.random.normal(8, 2, 5000).astype(int)})
    ncount = len(dfWIM)
    
    plt.figure(figsize=(12,8))
    ax = sns.countplot(x="AXLES", data=dfWIM, order=[3,4,5,6,7,8,9,10,11,12])
    plt.title('Distribution of Truck Configurations')
    plt.xlabel('Number of Axles')
    
    # Make twin axis
    ax2=ax.twinx()
    
    # Switch so count axis is on right, frequency on left
    ax2.yaxis.tick_left()
    ax.yaxis.tick_right()
    
    # Also switch the labels over
    ax.yaxis.set_label_position('right')
    ax2.yaxis.set_label_position('left')
    
    ax2.set_ylabel('Frequency [%]')
    
    for p in ax.patches:
        x=p.get_bbox().get_points()[:,0]
        y=p.get_bbox().get_points()[1,1]
        ax.annotate('{:.1f}%'.format(100.*y/ncount), (x.mean(), y), 
                ha='center', va='bottom') # set the alignment of the text
    
    # Use a LinearLocator to ensure the correct number of ticks
    ax.yaxis.set_major_locator(ticker.LinearLocator(11))
    
    # Fix the frequency range to 0-100
    ax2.set_ylim(0,100)
    ax.set_ylim(0,ncount)
    
    # And use a MultipleLocator to ensure a tick spacing of 10
    ax2.yaxis.set_major_locator(ticker.MultipleLocator(10))
    
    # Need to turn the grid on ax2 off, otherwise the gridlines end up on top of the bars
    ax2.grid(None)
    
    plt.savefig('snscounter.pdf')