Search code examples
pythonpandasmatplotlibplotlybar-chart

Reformat bidirectional bar chart to match example


I have generated this bar-chart

enter image description here

Using this code:

s = """level,margins_fluid,margins_vp
Volume,0,0
1L*,0.718,0.690
2L,0.501,0.808
5L,0.181,0.920
MAP,0,0
64*,0.434,0.647
58,0.477,0.854
52,0.489,0.904
Exam,0,0
dry,0.668,0.713
euvolemic*,0.475,0.798
wet,0.262,0.893
History,0,0
COPD*,0.506,0.804
Kidney,0.441,0.778
HF,0.450,0.832
Case,0,0
1 (PIV),0.435,0.802
2 (CVC)*,0.497,0.809"""

data = np.array([a.split(',') for a in s.split("\n")])


fluid_vp_1_2 = pd.DataFrame(data[1:], columns=data[0])
fluid_vp_1_2['margins_fluid'] = fluid_vp_1_2['margins_fluid'].apply(float)
fluid_vp_1_2['margins_vp'] = fluid_vp_1_2['margins_vp'].apply(float)
fluid_vp_1_2

variableNames = {'Volume', 'MAP', 'Exam', 'History', 'Case'}

font_color = '#525252'
hfont = {'fontname':'DejaVu Sans'}
facecolor = '#eaeaf2'
index = fluid_vp_1_2.index#['level']
column0 = fluid_vp_1_2['margins_fluid']*100
column1 = fluid_vp_1_2['margins_vp']*100
title0 = 'Fluids'
title1 = 'Vasopressors'

fig, axes = plt.subplots(figsize=(10,5), facecolor=facecolor, ncols=2, sharey=True)
axes[0].barh(index, column0, align='center', color='dimgray', zorder=10)
axes[0].set_title(title0, fontsize=18, pad=15, color='black', **hfont)
axes[1].barh(index, column1, align='center', color='silver', zorder=10)
axes[1].set_title(title1, fontsize=18, pad=15, color='black', **hfont)
# If you have positive numbers and want to invert the x-axis of the left plot
axes[0].invert_xaxis() 
# To show data from highest to lowest
plt.gca().invert_yaxis()

axes[0].set(xlim = [100,0])
axes[1].set(xlim = [0,100])

axes[0].yaxis.tick_right()
axes[0].set_yticks(range(len(fluid_vp_1_2)))
maxWordLength = fluid_vp_1_2['level'].apply(lambda x: len(x)).max()

formattedyticklabels = [r'$\bf{'+f"{t}"+r'}$' 
                        if t in variableNames else t for t in fluid_vp_1_2['level']]
axes[0].set_yticklabels(formattedyticklabels, ha='center', position=(1.12, 0))

axes[0].tick_params(right = False)

axes[1].tick_params(left = False)
    
fig.tight_layout()
plt.savefig("fluid_vp_1_2.jpg")

plt.show()

However, I would like to modify this chart to more closely resemble the below example, where the y-axis labels are on the left-hand side, bi-directional bars are making contact in the center, white background, more vertical in shape (shrunken x-axis), add x-axis label (“adjusted proportion of respondents”), but I would still like to maintain the order of variables and the gaps in bars caused by the bolded header labels like Volume, MAP, etc.

enter image description here

Any tips?


Solution

  • There is a some simplification/factorization you can deal with to make styling your plots easier. But you are basically almost there. Just set the tick labels and remove spaces between plots with fig.subplots_adjust(wspace=0) (you have to remove fig.tight_layout()):

    from io import StringIO
    import matplotlib.pyplot as plt
    import pandas as pd
    
    s = """level,margins_fluid,margins_vp
    Volume,0,0
    1L*,0.718,0.690
    2L,0.501,0.808
    5L,0.181,0.920
    MAP,0,0
    64*,0.434,0.647
    58,0.477,0.854
    52,0.489,0.904
    Exam,0,0
    dry,0.668,0.713
    euvolemic*,0.475,0.798
    wet,0.262,0.893
    History,0,0
    COPD*,0.506,0.804
    Kidney,0.441,0.778
    HF,0.450,0.832
    Case,0,0
    1 (PIV),0.435,0.802
    2 (CVC)*,0.497,0.809"""
    
    # building df directly with pandas
    fluid_vp_1_2 = pd.read_csv(StringIO(s))
    fluid_vp_1_2['margins_fluid'] = fluid_vp_1_2['margins_fluid']*100
    fluid_vp_1_2['margins_vp'] = fluid_vp_1_2['margins_vp']*100
    
    # style parameters for all plots
    title_format = dict(
        fontsize=18,
        pad=15,
        color='black',
        fontname='DejaVu Sans'
    )
    
    plot_params = dict(
        align='center',
        zorder=10,
        legend=None,
        width=0.9
    )
    
    grid_params = dict(
        zorder=0,
        axis='x'
    )
    
    tick_params = dict(
        left=False,
        which='both'
    )
    
    variableNames = {'Volume', 'MAP', 'Exam', 'History', 'Case'}
    
    fig, axes = plt.subplots(figsize=(8,10), ncols=2, sharey=True, facecolor='#eaeaf2')
    # removing spaces between plots
    fig.subplots_adjust(wspace=0)
    
    # plotting Fluids
    fluid_vp_1_2.plot.barh(y='margins_fluid', ax=axes[0], color='dimgray', **plot_params)
    axes[0].grid(**grid_params)
    axes[0].set_title('Fluids', **title_format)
    axes[0].tick_params(**tick_params)
    
    # plotting Vasopressors
    fluid_vp_1_2.plot.barh(y='margins_vp', ax=axes[1], color='silver', **plot_params)
    axes[1].grid(**grid_params)
    axes[1].set_title('Vasopressors', **title_format)
    axes[1].tick_params(**tick_params)
    
    # adjust axes
    axes[0].invert_xaxis()
    plt.gca().invert_yaxis()
    axes[0].set(xlim = [100,0])
    axes[1].set(xlim = [0,100])
    
    # adding y labels
    formattedyticklabels = [rf'$\bf{{{t}}}$' 
                            if t in variableNames else t for t in fluid_vp_1_2['level']]
    axes[0].set_yticklabels(formattedyticklabels)
    
    plt.show()
    

    Edit: you can get a "longer" plot by changing figsize. Output for figsize=(8,10):

    enter image description here