Search code examples
pythonpython-3.xmatplotlibgraph

Displace pyplot bars along x-axis that has string ticks


So, on this graph: enter image description here I want the "Margem EBITDA" bar to be alongside the other two bars that are on top of each other. I did some research and most answers suggested displacing it with numbers, but since my x-ticks are strings, I'd have to a whole workaround for plotting with numbers and then replacing the ticks, which I really don't want to do. Here's the code:

meses=['Fevereiro/21', 'Março/21', 'Abril/21']
faturamentor=[498899.40, 623122.74, 653200.18]
faturamento=[499, 623, 653]
gasto=[65, 130, 188]
margem=[84.7, 76.6, 69.5]

locale.setlocale(locale.LC_ALL, 'pt_BR.UTF-8')

seaborn.set()
seaborn.set_style("white")

plt.rcParams["figure.figsize"] = (19,10)
plt.rcParams["font.family"] = "Calibri"
plt.rc('font', size=14) 
plt.rc('axes', labelsize=20)
plt.rc('xtick', labelsize=15)
plt.rc('ytick', labelsize=15)
plt.rc('legend', fontsize=15)

ax = plt.subplot(111)
ax.bar(meses, faturamento, width=0.3, color='#434343', align='center', label='Faturamento líquido')
ax.bar(meses, gasto, width=0.3, color='#999999', align='center', label='Gasto em anúncios')
ax.grid(axis='y')
ax.set_ylabel('Valor (milhares de reais)')

for i in range(len(faturamento)):
    plt.annotate(xy=(i, faturamento[i]+10), text=f'R${faturamentor[i]:n}', ha='center', va='center', color='#434343')

plt.legend()
plt.xlabel('Mês')
plt.ylim(0, faturamento[-1]+(0.1*faturamento[-1]))

ax2 = ax.twinx()
ax2.set_ylabel('Margem EBITDA (%)')
ax2.bar(meses, margem, label="Margem EBITDA", color='#cccccc', width=0.1)
ax2.tick_params(axis='y', labelcolor='k')
ax2.set_ylim(20, 92)
ax2.set_yticks([20, 30, 40, 50, 60, 70, 80, 90])

plt.legend()
fig1 = plt.gcf()
plt.show()

Thanks for your help! EDIT: While we're at it, how could I make labels for both axes appear on the same box?


Solution

  • You can use np.linspace to distribute the available space over the bars. With ax.get_legend_handles_labels() you can extract the legend information, and create a new legend with the combined information:

    from matplotlib import pyplot as plt
    import numpy as np
    import seaborn
    
    meses = ['Fevereiro/21', 'Março/21', 'Abril/21']
    faturamentor = [498899.40, 623122.74, 653200.18]
    faturamento = [499, 623, 653]
    gasto = [65, 130, 188]
    margem = [84.7, 76.6, 69.5]
    
    seaborn.set()
    seaborn.set_style("white")
    
    plt.rcParams["figure.figsize"] = (19, 10)
    plt.rcParams["font.family"] = "Calibri"
    plt.rc('font', size=14)
    plt.rc('axes', labelsize=20)
    plt.rc('xtick', labelsize=15)
    plt.rc('ytick', labelsize=15)
    plt.rc('legend', fontsize=15)
    
    num_meses = len(meses)
    num_bars = 3
    tot_width = .8
    dodge = np.linspace(-tot_width / 2, tot_width / 2, num_bars + 1)
    width = dodge[1] - dodge[0]
    
    ax = plt.subplot(111)
    ax.set_xticks(np.arange(num_meses))
    ax.set_xticklabels(meses)
    ax.bar(np.arange(num_meses) + dodge[0], faturamento, width=width,
           color='#434343', align='edge', label='Faturamento líquido')
    ax.bar(np.arange(num_meses) + dodge[1], gasto, width=width,
           color='#999999', align='edge', label='Gasto em anúncios')
    ax.grid(axis='y')
    ax.set_ylabel('Valor (milhares de reais)')
    
    for xpos, faturamento_i in zip(np.arange(num_meses) + dodge[0], faturamento):
        ax.annotate(xy=(xpos+width/2, faturamento_i), text=f'R${faturamento_i:n}\n', ha='center', va='center', color='#434343')
    
    ax.set_xlabel('Mês')
    ax.set_ylim(0, faturamento[-1] + (0.1 * faturamento[-1]))
    
    ax2 = ax.twinx()
    ax2.set_ylabel('Margem EBITDA (%)')
    ax2.bar(np.arange(num_meses) + dodge[2], margem, label="Margem EBITDA", color='#cccccc', width=width, align='edge')
    ax2.tick_params(axis='y', labelcolor='k')
    ax2.set_ylim(20, 92)
    ax2.set_yticks([20, 30, 40, 50, 60, 70, 80, 90])
    
    handles1, labels1 = ax.get_legend_handles_labels()
    handles2, labels2 = ax2.get_legend_handles_labels()
    ax.legend(handles=handles1 + handles2, labels=labels1 + labels2)
    
    plt.show()
    

    combining bars over twin axes