Plotly Express – Customizing Size and Axis Label for a Single Facet

I’ve used Plotly Express to develop a faceted horizontal bar chart (attached). There are two tweaks I’d like to make, but I can’t figure out:

  1. Can I make the right facet smaller than the other one?
  2. Can I hide the xaxis label for the right facet?

I’m new to Plotly and trying to avoid using Graph Objects, but I’m starting to think I may need to use GO for this level of customization.

Dummy data and code used to create the plot …

data = [
    {'self': 'Y', 'q1': 'Positive', 'q2': 'Neutral' , 'q3': 'Neutral', 'q4': 'Neutral', 'q5': 'Positive', 'q6': 'Neutral'   },
    {'self': 'N', 'q1': 'Positive', 'q2': 'Neutral' , 'q3': 'Neutral', 'q4': 'Neutral', 'q5': 'Positive', 'q6': 'Neutral'   },
    {'self': 'N', 'q1': 'Positive', 'q2': 'Negative', 'q3': 'Neutral', 'q4': 'Neutral', 'q5': 'Neutral' , 'q6': "Can't Rate"},
    {'self': 'N', 'q1': 'Neutral' , 'q2': 'Negative', 'q3': 'Neutral', 'q4': 'Neutral', 'q5': 'Neutral' , 'q6': 'Negative'  },
    {'self': 'N', 'q1': 'Positive', 'q2': 'Positive', 'q3': 'Neutral', 'q4': 'Neutral', 'q5': 'Neutral' , 'q6': 'Negative'  }
]

total_df = pd.DataFrame(data)

total_df.rename(columns={'self': 'evaluator'}, inplace=True)

total_df['evaluator'] = np.where(total_df.evaluator == 'Y', 'Self', 'Co-Workers')
category_assignment_df = pd.DataFrame([{'question': 'q1', 'category': 'Category A'},
                                       {'question': 'q2', 'category': 'Category A'},
                                       {'question': 'q3', 'category': 'Category B'},
                                       {'question': 'q4', 'category': 'Category B'},
                                       {'question': 'q5', 'category': 'Category C'},
                                       {'question': 'q6', 'category': 'Category C'}])
ratings_df = pd.melt(total_df, id_vars='evaluator', var_name='question', value_name='rating')

ratings_df = pd.merge(ratings_df, category_assignment_df, on='question')

ratings_counts_df = ratings_df.groupby(['evaluator', 'category', 'question', 'rating']).size().to_frame(name='count').reset_index()
fig = px.bar(ratings_counts_df[ratings_counts_df['rating'] != "Can't Rate"], x='count', y='question', barmode='stack', 
             color='rating', orientation='h', facet_col='evaluator', facet_row='category',
             category_orders={'evaluator': ['Co-Workers', 'Self'],
                              'question': ['q1', 'q2', 'q3', 'q4', 'q5', 'q6'],
                              'rating': ['Negative', 'Neutral', 'Positive']},
             color_discrete_map={'Positive': 'DarkGreen', 'Neutral': 'Lime', 'Negative': 'Red'},
             title='Performance Ratings - Plotly Express Bar Plot')

fig.update_yaxes(matches=None, title_text='')
fig.update_xaxes(matches=None)

fig['layout']['xaxis']['title']['text']='Count of Responses'
fig['layout']['xaxis2']['title']['text']=''

fig.for_each_annotation(lambda x: x.update(text=x.text.replace("category=Category ", '')))
fig.for_each_annotation(lambda x: x.update(text=x.text.replace("evaluator=", '')))

fig.update_layout(legend=dict(orientation='h', xanchor='center', yanchor='bottom', x=0.5, y=-0.5, title=''))

fig.show()

I realize this is a Plotly forum, but I don’t need the interactivity that Plotly provides if there’s an easier solution using Matplotlib, Seaborn, or any other library. I was using Plotly because it got me closest to my desired goal.

Hello @DeanVogt,

Welcome to the community!

I’m curious, have you tried using subplots?

With this, you can do a lot more customization.

I was able to eventually produce the plot I wanted and am sharing my solution. I realize this code can be reduced using looping logic, but am sharing this solution as-is for readability. I can’t help but believe there’s a much easier/simpler way to create this plot. If you can think of an easier way, please share!

# Create a dataframe that includes a count of every combination of the three dimensions (evaluator, question, and rating).

evaluators = ['Co-Workers', 'Self']
questions = ['q1', 'q2', 'q3', 'q4', 'q5', 'q6']
ratings = ['Negative', 'Neutral', 'Positive']

from itertools import product
full_df = pd.DataFrame(list(product(evaluators, questions, ratings)), columns=['evaluator', 'question', 'rating'])

def assign_category(x):
    if x in ['q1', 'q2']:
        return 'A'
    elif x in ['q3', 'q4']:
        return 'B'
    else:
        return 'C'

def assign_counts(evaluator, question, rating):
    if evaluator == 'Self':
        if question in ['q1', 'q5'] and rating == 'Positive':
            return 1
        elif question in ['q2', 'q3', 'q4', 'q6'] and rating == 'Neutral':
            return 1
        else:
            return 0
    elif evaluator == 'Co-Workers':
        if question in ['q2', 'q6'] and rating == 'Negative':
            return 2
        elif question in ['q1', 'q2', 'q6'] and rating == 'Neutral':
            return 1
        elif question in ['q3', 'q4'] and rating == 'Neutral':
            return 4
        elif question  in ['q2', 'q5'] and rating == 'Positive':
            return 1
        elif question == 'q1' and rating == 'Positive':
            return 3
        elif question == 'q5' and rating == 'Neutral':
            return 3
        else:
            return 0

full_df['category'] = full_df['question'].apply(assign_category)

full_df['count'] = full_df.apply(lambda x: assign_counts(x['evaluator'], x['question'], x['rating']), axis=1)
# Make a dataframe for each trace

other_a_negative_df = full_df[(full_df.evaluator=='Co-Workers') & (full_df.category=='A') & (full_df.rating=='Negative')] 
other_a_neutral_df  = full_df[(full_df.evaluator=='Co-Workers') & (full_df.category=='A') & (full_df.rating=='Neutral' )]
other_a_positive_df = full_df[(full_df.evaluator=='Co-Workers') & (full_df.category=='A') & (full_df.rating=='Positive')]
other_b_negative_df = full_df[(full_df.evaluator=='Co-Workers') & (full_df.category=='B') & (full_df.rating=='Negative')] 
other_b_neutral_df  = full_df[(full_df.evaluator=='Co-Workers') & (full_df.category=='B') & (full_df.rating=='Neutral' )]
other_b_positive_df = full_df[(full_df.evaluator=='Co-Workers') & (full_df.category=='B') & (full_df.rating=='Positive')]
other_c_negative_df = full_df[(full_df.evaluator=='Co-Workers') & (full_df.category=='C') & (full_df.rating=='Negative')] 
other_c_neutral_df  = full_df[(full_df.evaluator=='Co-Workers') & (full_df.category=='C') & (full_df.rating=='Neutral' )]
other_c_positive_df = full_df[(full_df.evaluator=='Co-Workers') & (full_df.category=='C') & (full_df.rating=='Positive')]

self_a_negative_df  = full_df[(full_df.evaluator=='Self') & (full_df.category=='A') & (full_df.rating=='Negative')] 
self_a_neutral_df   = full_df[(full_df.evaluator=='Self') & (full_df.category=='A') & (full_df.rating=='Neutral' )]
self_a_positive_df  = full_df[(full_df.evaluator=='Self') & (full_df.category=='A') & (full_df.rating=='Positive')]
self_b_negative_df  = full_df[(full_df.evaluator=='Self') & (full_df.category=='B') & (full_df.rating=='Negative')] 
self_b_neutral_df   = full_df[(full_df.evaluator=='Self') & (full_df.category=='B') & (full_df.rating=='Neutral' )]
self_b_positive_df  = full_df[(full_df.evaluator=='Self') & (full_df.category=='B') & (full_df.rating=='Positive')]
self_c_negative_df  = full_df[(full_df.evaluator=='Self') & (full_df.category=='C') & (full_df.rating=='Negative')] 
self_c_neutral_df   = full_df[(full_df.evaluator=='Self') & (full_df.category=='C') & (full_df.rating=='Neutral' )]
self_c_positive_df  = full_df[(full_df.evaluator=='Self') & (full_df.category=='C') & (full_df.rating=='Positive')]
fig = make_subplots(rows=3, cols=2, column_widths=[0.9, 0.1], horizontal_spacing=0.02, vertical_spacing=0.075, 
                    shared_xaxes=True, column_titles=["Co-Workers", "Self"], row_titles=["A", "B", "C"])

fig.add_trace(go.Bar(x=other_a_negative_df['count'], y=other_a_negative_df['question'], orientation='h', name='Negative', marker=dict(color='Red'      ), legendgroup='Negative'), row=1, col=1)
fig.add_trace(go.Bar(x=other_a_neutral_df['count'] , y=other_a_neutral_df['question'] , orientation='h', name='Neutral' , marker=dict(color='Lime'     ), legendgroup='Neutral' ), row=1, col=1)
fig.add_trace(go.Bar(x=other_a_positive_df['count'], y=other_a_positive_df['question'], orientation='h', name='Positive', marker=dict(color='DarkGreen'), legendgroup='Positive'), row=1, col=1)
fig.add_trace(go.Bar(x=other_b_negative_df['count'], y=other_b_negative_df['question'], orientation='h', name='Negative', marker=dict(color='Red'      ), legendgroup='Negative', showlegend=False), row=2, col=1)
fig.add_trace(go.Bar(x=other_b_neutral_df['count'] , y=other_b_neutral_df['question'] , orientation='h', name='Neutral' , marker=dict(color='Lime'     ), legendgroup='Neutral' , showlegend=False), row=2, col=1)
fig.add_trace(go.Bar(x=other_b_positive_df['count'], y=other_b_positive_df['question'], orientation='h', name='Positive', marker=dict(color='DarkGreen'), legendgroup='Positive', showlegend=False), row=2, col=1)
fig.add_trace(go.Bar(x=other_c_negative_df['count'], y=other_c_negative_df['question'], orientation='h', name='Negative', marker=dict(color='Red'      ), legendgroup='Negative', showlegend=False), row=3, col=1)
fig.add_trace(go.Bar(x=other_c_neutral_df['count'] , y=other_c_neutral_df['question'] , orientation='h', name='Neutral' , marker=dict(color='Lime'     ), legendgroup='Neutral' , showlegend=False), row=3, col=1)
fig.add_trace(go.Bar(x=other_c_positive_df['count'], y=other_c_positive_df['question'], orientation='h', name='Positive', marker=dict(color='DarkGreen'), legendgroup='Positive', showlegend=False), row=3, col=1)

fig.add_trace(go.Bar(x=self_a_negative_df['count'], y=self_a_negative_df['question'], orientation='h', name='Negative', marker=dict(color='Red'      ), legendgroup='Negative', showlegend=False), row=1, col=2)
fig.add_trace(go.Bar(x=self_a_neutral_df['count'] , y=self_a_neutral_df['question'] , orientation='h', name='Neutral' , marker=dict(color='Lime'     ), legendgroup='Neutral' , showlegend=False), row=1, col=2)
fig.add_trace(go.Bar(x=self_a_positive_df['count'], y=self_a_positive_df['question'], orientation='h', name='Positive', marker=dict(color='DarkGreen'), legendgroup='Positive', showlegend=False), row=1, col=2)
fig.add_trace(go.Bar(x=self_b_negative_df['count'], y=self_b_negative_df['question'], orientation='h', name='Negative', marker=dict(color='Red'      ), legendgroup='Negative', showlegend=False), row=2, col=2)
fig.add_trace(go.Bar(x=self_b_neutral_df['count'] , y=self_b_neutral_df['question'] , orientation='h', name='Neutral' , marker=dict(color='Lime'     ), legendgroup='Neutral' , showlegend=False), row=2, col=2)
fig.add_trace(go.Bar(x=self_b_positive_df['count'], y=self_b_positive_df['question'], orientation='h', name='Positive', marker=dict(color='DarkGreen'), legendgroup='Positive', showlegend=False), row=2, col=2)
fig.add_trace(go.Bar(x=self_c_negative_df['count'], y=self_c_negative_df['question'], orientation='h', name='Negative', marker=dict(color='Red'      ), legendgroup='Negative', showlegend=False), row=3, col=2)
fig.add_trace(go.Bar(x=self_c_neutral_df['count'] , y=self_c_neutral_df['question'] , orientation='h', name='Neutral' , marker=dict(color='Lime'     ), legendgroup='Neutral' , showlegend=False), row=3, col=2)
fig.add_trace(go.Bar(x=self_c_positive_df['count'], y=self_c_positive_df['question'], orientation='h', name='Positive', marker=dict(color='DarkGreen'), legendgroup='Positive', showlegend=False), row=3, col=2)

fig.update_layout(barmode='stack', title="Performance Ratings - Plotly Graph Objects Bar Plot", hovermode=False,
                  legend=dict(orientation='h', xanchor='center', yanchor='bottom', x=0.5, y=-0.3, title=''),
                  width=900, height=450, autosize=False)
fig.update_yaxes(categoryorder='category descending')
fig.update_xaxes(tick0=0, dtick=1, range=[0,4.125], col=1)
fig.update_xaxes(showticklabels=False, range=[0,1.25], col=2)
fig.update_yaxes(showticklabels=False, col=2)

fig['layout']['xaxis5']['title']['text']='Count of Responses'
fig['layout']['xaxis6']['title']['text']=''

fig.show()

1 Like