How to add percentages on top of bars in seaborn?

2019-01-16 16:39发布

Given the following count plot how do I place percentages on top of the bars?

import seaborn as sns
sns.set(style="darkgrid")
titanic = sns.load_dataset("titanic")
ax = sns.countplot(x="class", hue="who", data=titanic)

enter image description here

For example for "First" I want total First men/total First, total First women/total First, and total First children/total First on top of their respective bars.

Please let me know if my explanation is not clear.

Thanks!

2条回答
三岁会撩人
2楼-- · 2019-01-16 17:30

sns.barplot doesn't explicitly return the barplot values the way matplotlib.pyplot.bar does (see last para), but if you've plotted nothing else you can risk assuming that all the patches in the axes are your values. Then you can use the sub-totals that the barplot function has calculated for you:

from matplotlib.pyplot import show
import seaborn as sns
sns.set(style="darkgrid")
titanic = sns.load_dataset("titanic")
total = float(len(titanic)) # one person per row 
#ax = sns.barplot(x="class", hue="who", data=titanic)
ax = sns.countplot(x="class", hue="who", data=titanic) # for Seaborn version 0.7 and more
for p in ax.patches:
    height = p.get_height()
    ax.text(p.get_x()+p.get_width()/2.,
            height + 3,
            '{:1.2f}'.format(height/total),
            ha="center") 
show()

produces

Countplot

An alternate approach is to do the sub-summing explicitly, e.g. with the excellent pandas, and plot with matplotlib, and also do the styling yourself. (Though you can get quite a lot of styling from sns context even when using matplotlib plotting functions. Try it out -- )

查看更多
倾城 Initia
3楼-- · 2019-01-16 17:32

With the help of cphlewis's solution, I managed to put the correct percentages on top of the chart, so the classes sum up to one.

for index, category in enumerate(categorical):
    plt.subplot(plot_count, 1, index + 1)

    order = sorted(data[category].unique())
    ax = sns.countplot(category, data=data, hue="churn", order=order)
    ax.set_ylabel('')

    bars = ax.patches
    half = int(len(bars)/2)
    left_bars = bars[:half]
    right_bars = bars[half:]

    for left, right in zip(left_bars, right_bars):
        height_l = left.get_height()
        height_r = right.get_height()
        total = height_l + height_r

        ax.text(left.get_x() + left.get_width()/2., height_l + 40, '{0:.0%}'.format(height_l/total), ha="center")
        ax.text(right.get_x() + right.get_width()/2., height_r + 40, '{0:.0%}'.format(height_r/total), ha="center")

enter image description here

However, the solution assumes there are 2 options (man, woman) as opposed to 3 (man, woman, child).

Since Axes.patches are ordered in a weird way (first all the blue bars, then all the green bars, then all red bars), you would have to split them and zip them back together accordingly.

查看更多
登录 后发表回答