Seaborn:带有频率的计数图()
Posted
技术标签:
【中文标题】Seaborn:带有频率的计数图()【英文标题】:Seaborn: countplot() with frequencies 【发布时间】:2016-01-15 17:12:10 【问题描述】:我有一个 Pandas DataFrame,其中有一列名为“AXLES”,它可以取 3-12 之间的整数值。我正在尝试使用 Seaborn 的 countplot() 选项来实现以下情节:
-
左 y 轴显示这些值在数据中出现的频率。轴延伸为 [0%-100%],每 10% 处有刻度线。
右 y 轴显示实际计数,值对应于左 y 轴确定的刻度线(每 10% 标记一次。)
x 轴显示条形图的类别 [3、4、5、6、7、8、9、10、11、12]。
条形顶部的注释显示该类别的实际百分比。
以下代码为我提供了下面的图,其中包含实际计数,但我找不到将它们转换为频率的方法。我可以使用 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))
编辑:
使用 Pandas 的条形图,抛弃 Seaborn,我更接近于我需要的代码。感觉就像我使用了很多解决方法,并且必须有一种更简单的方法来做到这一点。这种方法的问题:
Pandas 的条形图函数中没有order
关键字,因为 Seaborn 的 countplot() 有,所以我不能像在 countplot() 中那样绘制 3-12 的所有类别。即使该类别中没有数据,我也需要显示它们。
辅助 y 轴由于某种原因弄乱了条形和注释(请参阅在文本和条形上绘制的白色网格线)。
plt.figure(figsize=(12,8))
plt.title('Distribution of Truck Configurations')
plt.xlabel('Number of Axles')
plt.ylabel('Frequency [%]')
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))
【问题讨论】:
为什么不将刻度标签除以总数来获得频率? 我尝试使用vals = ax.get_yticks()
和ax.set_yticks(vals/len(df))
。但是,一旦我这样做了,由于绘图的实际 y 比例,所有标签最终都位于原点附近的最底部。显然我的方法是错误的。你会怎么做?
你救了我的命 :D :D :D
【参考方案1】:
您可以通过为频率创建一个twinx
轴来做到这一点。您可以切换两个 y 轴,使频率保持在左侧,计数保持在右侧,但无需重新计算计数轴(这里我们使用 tick_left()
和 tick_right()
移动刻度,set_label_position
移动到移动轴标签
然后您可以使用matplotlib.ticker
模块设置刻度,特别是ticker.MultipleLocator
和ticker.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')
【讨论】:
谢谢!避免“扁平化”直方图的一种可能改进:# Fix the frequency range to 0-100 without changing axes zoom:
ax2.set_ylim(0,100*ax.get_ylim()[1]/ncount)
【参考方案2】:
我使用核心 matplotlib
的条形图让它工作。我显然没有您的数据,但将其适应您的数据应该是直截了当的。
方法
我使用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' )
【讨论】:
【参考方案3】:我觉得你可以先手动设置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))
【讨论】:
以上是关于Seaborn:带有频率的计数图()的主要内容,如果未能解决你的问题,请参考以下文章