Melisa Acar
Melisa Acar

Reputation: 45

How to plot bars against percent and annotate with the value

I couldn't expand bars height to their percentage how can I do that? I'm getting confused...

I don't want to see the percentages of the bars, I want the values (40,40,40,40,40,33,17).

I want 7 bars with height of their percentage, but containers on bars should have their value name. For example n=40 in my problem; First bar has the value of 40. So this bars top should at %100 level, but container must wrote as 40.

Also another example; last (Tiktok bar) bar has the value of 17 so container should say 17 but the bar should be at the height of %42.5

Sample DataFrame and Imports

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib.ticker as mtick

plt.figure(figsize=(18,11),dpi=100)
sns.set_theme (style="whitegrid", palette="tab20b")

x_ekseni= ['İlgili Sosyal Medya Hesapları']
Website= [40]
Twitter= [40]
Facebook= [40]
Instagram= [40]
Youtube= [40]
Linkedin= [33]
Tiktok= [17]

index=x_ekseni

df = pd.DataFrame({'Website': Website,
               'Twitter': Twitter,
               'Facebook': Facebook,
               'Instagram': Instagram,
               'Youtube': Youtube,
               'Linkedin': Linkedin,
               'Tiktok': Tiktok}, index=index)

df

                               Website  Twitter  Facebook  Instagram  Youtube  Linkedin  Tiktok
İlgili Sosyal Medya Hesapları       40       40        40         40       40        33      17

Plot

ax = df.plot.bar(stacked=False,edgecolor='white',width = 2.5,linewidth=0.5)

# iterate through the containers
for c in ax.containers:

    # get the current segment label (a string); corresponds to column / legend
    col = c.get_label()

    labels = df[col].replace(0, '')

    # add the annotation
    ax.bar_label(c, labels=labels, label_type='center', fontweight='bold', color='white',fontsize=10)

plt.title("İstanbul'daki Belediyelerin Sosyal Medya Kullanımı", fontsize=11, fontweight=0,        color="black", loc="center")

ax.set_ylabel("İldeki Tüm Belediyeler İçinde Yüzde (n=40)", fontsize= 10)

ax.yaxis.set_major_formatter(mtick.PercentFormatter())

plt.xticks(rotation=0,fontsize=8)
plt.yticks(rotation=0,fontsize=8)
plt.ylim(0,100)

ax.legend(frameon=True, fontsize=8)

enter image description here

Upvotes: 2

Views: 1263

Answers (2)

Trenton McKinney
Trenton McKinney

Reputation: 62523

  • Tested in python 3.11.2, pandas 2.0.0, matplotlib 3.7.1, seaborn 0.12.2
  • Note the plot in the OP is a single group of bars, which is why there's only one tick.
  • In order to have the bars be the height of their respective percent value, calculate and add a new column with the percent value, plot the bars with the new column, and pass the actual values to the labels= parameter in .bar_label.

Imports and DataFrame

import pandas as pd
import matplotlib.ticker as tkr
import matplotlib.pyplot as plt
import seaborn as sns

# test dataframe
data = {'Website': [40], 'Twitter': [40], 'Facebook': [40], 'Instagram': [40], 'Youtube': [40], 'Linkedin': [33], 'Tiktok': [17]}
df = pd.DataFrame(data, index=['İlgili Sosyal Medya Hesapları'])

# transpose df from a wide to a long form
df = df.T

# add a percent column
df['per'] = df.div(df.sum()).mul(100).round(2)

df

           İlgili Sosyal Medya Hesapları   per
Website                               40  16.0
Twitter                               40  16.0
Facebook                              40  16.0
Instagram                             40  16.0
Youtube                               40  16.0
Linkedin                              33  13.2
Tiktok                                17   6.8

Plot using pandas.DataFrame.plot with kind='bar'

# plot percent column 
ax = df.plot(kind='bar', y='per', rot=0, figsize=(10, 7), ylabel='', legend=False)

# add the annotations; there's only 1 container of 7 artists
# iterating through the containers is only necessary for grouped or stacked bars, which have multiple containers
ax.bar_label(ax.containers[0], labels=df['İlgili Sosyal Medya Hesapları'].replace(0, ''), label_type='center', fontweight='bold', color='white', fontsize=10)

ax.set_title("İstanbul'daki Belediyelerin Sosyal Medya Kullanımı", fontsize=11, fontweight=0, color="black", loc="center")

ax.set_ylabel("İldeki Tüm Belediyeler İçinde Yüzde (n=40)", fontsize= 10)
ax.set_xlabel(df.columns[0], fontsize= 10)

ax.yaxis.set_major_formatter(tkr.PercentFormatter())

ax.tick_params(axis='both', which='major', labelsize=8)
_ = ax.set_ylim(0, 100)

enter image description here

Plot using seaborn.barplot

plt.figure(figsize=(10, 7))

# plot df, but the index must be reset
ax = sns.barplot(data=df.reset_index(), x='index', y='per')

ax.bar_label(ax.containers[0], labels=df['İlgili Sosyal Medya Hesapları'].replace(0, ''), label_type='center', fontweight='bold', color='white', fontsize=10)

ax.set_title("İstanbul'daki Belediyelerin Sosyal Medya Kullanımı", fontsize=11, fontweight=0, color="black", loc="center")

ax.set_ylabel("İldeki Tüm Belediyeler İçinde Yüzde (n=40)", fontsize= 10)
ax.set_xlabel(df.columns[0], fontsize= 10)

ax.yaxis.set_major_formatter(tkr.PercentFormatter())

ax.tick_params(axis='both', which='major', labelsize=8)
_ = ax.set_ylim(0, 100)

enter image description here

Note

  • Updated based on additional information added to the OP from a comment, where the percent is calculated against a specific value, n=40, use the following to calculate the 'per' column.
  • Nothing else in the previous code needs to change.
# an arbitrary value
n = 40
df['per'] = df.div(n, axis=0).mul(100).round(2)

# or

# the max value in the column
df['per'] = df.div(df.iloc[:, 0].max(), axis=0).mul(100).round(2)
           İlgili Sosyal Medya Hesapları    per
Website                               40  100.0
Twitter                               40  100.0
Facebook                              40  100.0
Instagram                             40  100.0
Youtube                               40  100.0
Linkedin                              33   82.5
Tiktok                                17   42.5

plotted with pandas.DataFrame.plot

plotted with seaborn.barplot

Upvotes: 2

Julien Sade
Julien Sade

Reputation: 44

To display the percentage values on top of each bar in your stacked bar chart, you can use the bar_label function provided by matplotlib.

for container in ax.containers:
    ax.bar_label(container, labels=[f'{h:.0f}%' if h > 0 else '' for h in container.datavalues], label_type='edge')

Complete code :

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib.ticker as mtick

plt.figure(figsize=(18,11),dpi=100)
sns.set_theme (style="whitegrid", palette="tab20b")

x_ekseni= ['İlgili Sosyal Medya Hesapları']
Website= [40]
Twitter= [40]
Facebook= [40]
Instagram= [40]
Youtube= [40]
Linkedin= [33]
Tiktok= [17]

index=x_ekseni

df = pd.DataFrame({'Website': Website,
               'Twitter': Twitter,
               'Facebook': Facebook,
               'Instagram': Instagram,
               'Youtube': Youtube,
               'Linkedin': Linkedin,
               'Tiktok': Tiktok}, index=index)

ax = df.plot.bar(stacked=False, edgecolor='white', width=2.5, linewidth=0.5)

for container in ax.containers:
    ax.bar_label(container, labels=[f'{h:.0f}%' if h > 0 else '' for h in container.datavalues], label_type='edge')

plt.title("İstanbul'daki Belediyelerin Sosyal Medya Kullanımı", fontsize=11, fontweight=0, color="black", loc="center")

ax.set_ylabel("İldeki Tüm Belediyeler İçinde Yüzde (n=40)", fontsize=10)

ax.yaxis.set_major_formatter(mtick.PercentFormatter())
plt.xticks(rotation=0, fontsize=8)
plt.yticks(rotation=0, fontsize=8)
plt.ylim(0, 100)

ax.legend(frameon=True, fontsize=8)

plt.show()

Upvotes: 0

Related Questions