Search code examples
pythonpandasmatplotlibplot

How to plot rows containing na's in stacked bar chart python?


I am still an absolute beginner in python and I googled a lot to built code to make a stacked bar chart plot my df. I am using spyder and python 3.12

After some data preparation, I am left with this df:

{'storage': {0: 'a', 2: 'a', 3: 'a', 4: 'b', 5: 'b'},
'time': {0: '4h', 2: '4h', 3: '4h', 4: '4h', 5: '4h'},
'Predicted Class': {0: 'germling',
 2: 'resting',
 3: 'swollen',
 4: 'germling',
 5: 'hyphae'},
'%': {0: 0.22, 2: 99.02, 3: 0.76, 4: 65.41, 5: 1.72}}

I prepare it with pivoting for plotting as a stacked bar chart.

class_order = ['resting', 'swollen', 'germling', 'hyphae', 'mature hyphae']
counts2_reduced_df['Predicted Class'] = pd.Categorical(counts2_reduced_df['Predicted Class'], categories=class_order, ordered=True)

counts2_pivot_df = counts2_reduced_df.pivot_table(index=["storage","time"], columns="Predicted Class", values="%", aggfunc="sum")

This leaves me with this df that now contains to cells with 0 as values that I would like to prevent from being plotted.

When I plot this df now, I get the following plot (it still has other issues like making the values of the small percentages readable, but this is an issue for another post I guess).

1st plot

I tried different approaches like converting 0s to nas and then drop.na of course removes the entire rows, but I would like to only prevent the cells containing 0s from being plotted / shown in the plot.

I tried counts2_pivot_df = counts2_pivot_df.replace(0, "") but it prevents the columns "hyphae" and "mature hyphae" completely from being plotted.

{'resting': {('a', '4h'): 99.02, ('b', '4h'): 2.57, ('c', '4h'): 2.19},
 'swollen': {('a', '4h'): 0.76, ('b', '4h'): 30.19, ('c', '4h'): 25.57},
 'germling': {('a', '4h'): 0.22, ('b', '4h'): 65.41, ('c', '4h'): 70.67},
 'hyphae': {('a', '4h'): '', ('b', '4h'): 1.72, ('c', '4h'): 1.47},
 'mature hyphae': {('a', '4h'): '', ('b', '4h'): 0.11, ('c', '4h'): 0.1}}

second plot after replace 0

Is there a way for solving this issue? I have read and tried many posts but cannot come up with a solution.

EDIT:

This is the code I use to create the plots:

fig, ax = plt.subplots()
# Colors for each class, 
color_mapping = {
    'resting': 'yellow',
    'swollen': 'blue',
    'germling': 'red',
    'hyphae': 'cyan',
    'mature hyphae': 'green',   
}
# Colors for each class using the mapping
colors = [color_mapping[class_name] for class_name in class_order]  # Ensure colors follow the same order

# Plotting the stacked bars
counts2_pivot_df.plot(kind='bar', stacked=True, color=colors, ax=ax)



# Set x-tick labels correctly based on the pivot index
ax.set_xticks(range(len(counts2_pivot_df.index)))
ax.set_xticklabels([f'{i[0]} & {i[1]}' for i in counts2_pivot_df.index], rotation=0)

ax.set_xlabel('Storage Condition and Time after Inoculation')
ax.set_ylabel('Percentage (%)')
plt.xticks(rotation=360)
plt.legend(title='Predicted Class')
ax.legend(loc='center left', bbox_to_anchor=(1, 0.5))

# Annotating percentages
for n, bar_group in enumerate(ax.containers):
    for bar in bar_group:
        height = bar.get_height()
        text_x = bar.get_x() + bar.get_width() / 2
        text_y = bar.get_y() + height / 2
        ha = 'center' if height > 5 else 'left'
        va = 'center'
        text_x_adjusted = text_x if height > 5 else bar.get_x() + bar.get_width()
        ax.annotate(f'{height:.2f}%', xy=(text_x_adjusted, text_y), ha=ha, va=va, color='black', fontsize=8, fontweight='bold')

plt.show()

Solution

  • Since you are plotting the labels manually, an easy option is to add a continue in your loop when the height is null:

        for bar in bar_group:
            height = bar.get_height()
            if height == 0:
                continue
            text_x = bar.get_x() + bar.get_width() / 2
            text_y = bar.get_y() + height / 2
            ha = 'center' if height > 5 else 'left'
            va = 'center'
            text_x_adjusted = text_x if height > 5 else bar.get_x() + bar.get_width()
            ax.annotate(f'{height:.2f}%', xy=(text_x_adjusted, text_y), ha=ha, va=va, color='black', fontsize=8, fontweight='bold')
    

    Output:

    enter image description here