countplot() 带有频率。

44
我有一个名为“AXLES”的 Pandas DataFrame 列,它可以取 3-12 之间的整数值。我正在尝试使用 Seaborn 的 countplot() 选项来实现以下绘图:
  1. 左 y 轴显示数据中出现这些值的频率。轴延伸范围为 [0%-100%],每 10% 标记一次。
  2. 右 y 轴显示实际计数,值对应于由左 y 轴确定的刻度标记(每 10% 标记一次)。
  3. x 轴显示条形图的类别 [3, 4, 5, 6, 7, 8, 9, 10, 11, 12]。
  4. 在条形上方的注释显示该类别的实际百分比。
下面的代码给出了实际计数的绘图,但我找不到将其转换为频率的方法。我可以使用 df.AXLES.value_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))

enter image description here

编辑:

我使用Pandas的条形图代码更接近我所需的内容,放弃了Seaborn。感觉我在使用太多的变通方法,必须有更简单的方法来完成它。这种方法存在的问题:

  • Pandas 的柱形图函数中没有像 Seaborn 的 countplot() 函数那样的 order 关键字,所以我无法像在 countplot() 中那样绘制从 3 到 12 的所有类别。即使该类别中没有数据,我也需要将它们显示出来。
  • 次要 y 轴会出现问题,导致条形图和注释混乱(请参见白色网格线覆盖的文本和条形)。

    plt.figure(figsize=(12,8))
    plt.title('货车配置分布')
    plt.xlabel('轴数')
    plt.ylabel('频率 [%]')
    
    ax = (dfWIM.AXLES.value_counts()/len(df)*100).sort_index().plot(kind="bar", rot=0)
    ax.set_yticks(np.arange(0, 110, 10))
    
    ax2 = ax.twinx()
    ax2.set_yticks(np.arange(0, 110, 10)*len(df)/100)
    
    for p in ax.patches:
        ax.annotate('{:.2f}%'.format(p.get_height()), (p.get_x()+0.15, p.get_height()+1))
    

enter image description here


为什么不通过总数来将刻度标签分成频率呢? - mwaskom
我尝试使用 vals = ax.get_yticks()ax.set_yticks(vals/len(df)) 实现。然而,一旦这么做了,由于图形的实际 y 轴比例,所有标签最终会在接近原点的底部。显然我的方法是错误的。你会怎么做呢? - marillion
你救了我的命 :D :D :D - Ishwor Bhusal
3个回答

53

您可以通过创建一个twinx坐标轴来完成这个操作。您可以交换两个y轴的位置,使频率保持在左侧,计数保持在右侧,但不必重新计算计数轴(这里我们使用tick_left()tick_right()移动刻度线以及set_label_position移动坐标轴标签)。

然后,您可以使用matplotlib.ticker模块来设置刻度线,具体来说是使用ticker.MultipleLocatorticker.LinearLocator

至于注释,您可以使用patch.get_bbox().get_points()获取柱形图所有4个角的x和y位置。这样,配合正确设置水平和垂直对齐方式,就不需要给注释位置添加任何任意偏移量。

最后,您需要关闭双轴上的网格,以防止网格线出现在柱形图之上(ax2.grid(None))。

以下是可行的脚本:

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')

在此输入图像描述


谢谢!一种可能的改进方法是避免“压扁”直方图:#将频率范围固定为0-100,而不更改轴缩放:ax2.set_ylim(0,100*ax.get_ylim()[1]/ncount) - ntg

10

我使用了核心matplotlib的条形图成功地运行了它。显然我没有你的数据,但是适应你的数据应该很简单。

enter image description here

方法

我使用matplotlib的双轴,将数据作为条形图绘制在第二个Axes对象上。其余部分只是一些调整使刻度正确并进行注释。

希望这能有所帮助。

代码

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from mpl_toolkits.mplot3d import Axes3D
import seaborn as sns

tot = np.random.rand( 1 ) * 100
data = np.random.rand( 1, 12 )
data = data / sum(data,1) * tot

df = pd.DataFrame( data )
palette = sns.husl_palette(9, s=0.7 )

### Left Axis
# Plot nothing here, autmatically scales to second axis.

fig, ax1 = plt.subplots()
ax1.set_ylim( [0,100] )

# Remove grid lines.
ax1.grid( False )
# Set ticks and add percentage sign.
ax1.yaxis.set_ticks( np.arange(0,101,10) )
fmt = '%.0f%%'
yticks = matplotlib.ticker.FormatStrFormatter( fmt )
ax1.yaxis.set_major_formatter( yticks )

### Right Axis
# Plot data as bars.
x = np.arange(0,9,1)
ax2 = ax1.twinx()
rects = ax2.bar( x-0.4, np.asarray(df.loc[0,3:]), width=0.8 )

# Set ticks on x-axis and remove grid lines.
ax2.set_xlim( [-0.5,8.5] )
ax2.xaxis.set_ticks( x )
ax2.xaxis.grid( False )

# Set ticks on y-axis in 10% steps.
ax2.set_ylim( [0,tot] )
ax2.yaxis.set_ticks( np.linspace( 0, tot, 11 ) )

# Add labels and change colors.
for i,r in enumerate(rects):
    h = r.get_height()
    r.set_color( palette[ i % len(palette) ] )
    ax2.text( r.get_x() + r.get_width()/2.0, \
              h + 0.01*tot,                  \
              r'%d%%'%int(100*h/tot), ha = 'center' )

9

我认为您可以先手动设置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))

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接