pythonpandasseaborn

Spurious zero printed in seaborn barplot while plotting pandas dataframe


The following is the minimal code. A spurious zero is printed between second and third bars that I am unable to get rid of in the plot. Please help me fix the code. A minimal working example is below:

Spurious zero between 2nd and 3rd bars

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

data = {
    'Temp': [5, 10, 25, 50, 100, 5, 10, 25, 50, 100, 5, 10, 25, 50, 100, 5, 10, 25, 50, 100],
    'Measurement': ['mean', 'mean', 'mean', 'mean', 'mean', 'std', 'std', 'std', 'std', 'std', 'min', 'min', 'min', 'min', 'min', 'max', 'max', 'max', 'max', 'max'],
    'Value': [-0.03, -0.05, -0.07, -0.09, -0.09, 0.24, 0.44, 0.69, 1.11, 1.45, -2.36, -4.56, -5.86, -11.68, -14.68, 1.30, 3.50, 5.26, 9.28, 11.14]
}

df_melted = pd.DataFrame(data)
print(df_melted)

barplot = sns.barplot(x='Temp', y='Value', hue='Measurement', data=df_melted, palette='viridis')

for p in barplot.patches:
    height = p.get_height()
    x = p.get_x() + p.get_width() / 2.
    if height >= 0:
        barplot.text(x, height + 0.1, f'{height:.1f}', ha='center', va='bottom', fontsize=10, color='black', rotation=90)
    else:
        barplot.text(x, height - 0.1, f'{height:.1f}', ha='center', va='top', fontsize=10, color='black', rotation=90)

plt.ylim([-25,25])   
plt.show()

Verions: Python 3.12.4, matplotlib 3.9.1 and Seaborn 0.13.2

PS: Edited to include maplotlib version.


Solution

  • The additional 0.0 is from the legend patches (in fact four 0.0s on top of each other). You can iterate over the bar containers instead:

    for c in barplot.containers:
        for p in c:
            height = p.get_height()
            x = p.get_x() + p.get_width() / 2.
            if height >= 0:
                barplot.text(x, height + 0.1, f'{height:.1f}', ha='center', va='bottom', fontsize=10, color='black', rotation=90)
            else:
                barplot.text(x, height - 0.1, f'{height:.1f}', ha='center', va='top', fontsize=10, color='black', rotation=90)
    

    enter image description here

    An easier way to label the bars is, howver, using bar_label instead of re-inventing the wheel.

    for c in barplot.containers:
        barplot.bar_label(c, fmt='%.1f', rotation=90, padding=2)