How to generate labelled barplots using seaborn?

Viewed 53

I am a bit new to Python. And I am playing with a dummy dataset to get some Python data manipulation practice. Below is the code for generating the dummy data:

d = {
    'SeniorCitizen': [0,1,0,0,0,0,0,1,0,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0] , 
    'CollegeDegree': [0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1] , 
    'Married': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1] , 
    'FulltimeJob': [1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,0,0,1,1,0,0,0,1] , 
    'DistancefromBranch': [7,9,14,20,21,12,22,25,9,9,9,12,13,14,16,25,27,4,14,14,20,19,15,23,2] , 
    'ReversedPayment': [0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,1,0,1,0] }
CarWash = pd.DataFrame(data = d)


categoricals = ['SeniorCitizen','CollegeDegree','Married','FulltimeJob','ReversedPayment']
        numerical = ['DistancefromBranch']
CarWash[categoricals] = CarWash[categoricals].astype('category')

I am basically struggling with a couple of things:

#1. A stacked barplot with absolute values (like the excel example below)

#2. A stacked barplot with percentage values (like the excel example below)

Below are my target visualizations for # 1 and # 2 using countplot().

#1 enter image description here #2 enter image description here

For # 1, instead of a stacked barplot, with countplot() I am able to make a clustered barplot, like below, and also the annotation snippet feels more like a workaround rather than being Python elegant.

# Looping through each categorical column and viewing target variable distribution (ReversedPayment) by value
figure, axes = plt.subplots(2,2,figsize = (10,10))

for i,ax in zip(categoricals[:-1],axes.flatten()):
    sns.countplot(x= i, hue = 'ReversedPayment', data = CarWash, ax = ax)
    for p in ax.patches:
        height = np.nan_to_num(p.get_height()) # gets the height of each patch/bar
        adjust = np.nan_to_num(p.get_width())/2 # a calculation for adusting the data label later
        
        label_xy = (np.nan_to_num(p.get_x()) + adjust,np.nan_to_num(p.get_height()) + adjust)  #x,y coordinates where we want to put our data label
        ax.annotate(height,label_xy) # final annotation

enter image description here

For # 2, I tried creating a new data frame housing % values but that felt tedious and error-prone.

I feel an option like stacked = True, proportion = True, axis = 1, annotate = True could have been so useful for countplot() to have.

Are there any other libraries that would be straight-froward and less code-intensive? Any comments or suggestions are welcome.

1 Answers

In this case, I think plotly.express may be more intuitive for you.

import plotly.express as px

df_temp = CarWash.groupby(['SeniorCitizen', 'ReversedPayment'])['DistancefromBranch'].count().reset_index().rename({'DistancefromBranch':'count'}, axis=1)

fig = px.bar(df_temp, x="SeniorCitizen", y="count", color="ReversedPayment", title="SeniorCitizen", text='count')
fig.update_traces(textposition='inside')
fig.show()

enter image description here

Basically, if you want to have more flexibility to adjust your charts, it is hard to avoid writing lots of codes.

I also try using matplotlib and pandas to create a stacked bar chart for percentages. If you are interested in it, you can try it.

sns.set()
fig, ax = plt.subplots(nrows=2, ncols=2, figsize=[12,8], dpi=100)

# Conver the axes matrix to a 1-d array
axes = ax.flatten()


for i, col in enumerate(['SeniorCitizen', 'CollegeDegree', 'Married', 'FulltimeJob']):
    
    # Calculate the number of plots
    df_temp = (CarWash.groupby(col)['ReversedPayment']
               .value_counts()
               .unstack(1).fillna(0)
               .rename({0:f'No', 1:f'Yes'})
               .rename({0:'No', 1:'Yes'}, axis=1))
    df_temp = df_temp / df_temp.sum(axis=0)
    df_temp.plot.bar(stacked=True, ax=axes[i])

    axes[i].set_title(col, y=1.03, fontsize=16)
    
    rects = axes[i].patches
    labels = df_temp.values.flatten()

    for rect, label in zip(rects, labels):
        if label == 0: continue
        axes[i].text(rect.get_x() + rect.get_width() / 2, rect.get_y() + rect.get_height() / 3, '{:.2%}'.format(label),
                ha='center', va='bottom', color='white', fontsize=12)

    axes[i].legend(title='Reversed\nPayment', bbox_to_anchor=(1.05, 1), loc='upper left', title_fontsize = 10, fontsize=10)
    axes[i].tick_params(rotation=0)

plt.tight_layout()
plt.show()

enter image description here

Related