Search code examples
pythonmatplotlib

How to add significance levels on bar graph using Python's Matplotlib?


I have written some code to graph some data in Python's Matplotlib.

The plot currently: How my plot currently looks

The code to produce this plot:

groups=['Control','30min','24hour']
cell_lysate_avg=[11887.42595,   4862.429689,    3414.337554]
cell_lysate_sd=[1956.212855,    494.8437915,    525.8556207]
cell_lysate_avg=[i/1000 for i in cell_lysate_avg]
cell_lysate_sd=[i/1000 for i in cell_lysate_sd]


media_avg=[14763.71106,8597.475539,6374.732852]
media_sd=[240.8983759,  167.005365, 256.1374017]
media_avg=[i/1000 for i in media_avg] #to get ng/ml
media_sd=[i/1000 for i in media_sd]

fig, ax = plt.subplots()
index = numpy.arange(len(groups)) #where to put the bars
bar_width=0.45
opacity = 0.5
error_config = {'ecolor': '0.3'}

cell_lysate_plt=plt.bar(index,cell_lysate_avg,bar_width,alpha=opacity,color='black',yerr=cell_lysate_sd,error_kw=error_config,label='Cell Lysates')
media_plt=plt.bar(index+bar_width,media_avg,bar_width,alpha=opacity,color='green',yerr=media_sd,error_kw=error_config,label='Media')
plt.xlabel('Groups',fontsize=15)
plt.ylabel('ng/ml',fontsize=15)
plt.title('\n'.join(wrap('Average Over Biological Repeats for TIMP1 ELISA (n=3)',45)),fontsize=15)
plt.xticks(index + bar_width, groups)
plt.legend()
ax.tick_params(axis='x', labelsize=14)
ax.tick_params(axis='y', labelsize=14)

I have calculated the various two tailed t tests associated with this data and I want to display using standard scientific journal representation - i.e. a line connecting two bars with a star which represents a significance level of (say) >0.05. Can anybody tell me how to do this?


Solution

  • As far as I know there is no standard scientific journal representation for showing significance. The exact way you draw it is a matter of taste. This is probably the reason why matplotlib has no specific function for significance bars (at least to my knowledge). You could just do it manually. E.g:

    from matplotlib.markers import TICKDOWN
    
    def significance_bar(start,end,height,displaystring,linewidth = 1.2,markersize = 8,boxpad  =0.3,fontsize = 15,color = 'k'):
        # draw a line with downticks at the ends
        plt.plot([start,end],[height]*2,'-',color = color,lw=linewidth,marker = TICKDOWN,markeredgewidth=linewidth,markersize = markersize)
        # draw the text with a bounding box covering up the line
        plt.text(0.5*(start+end),height,displaystring,ha = 'center',va='center',bbox=dict(facecolor='1.', edgecolor='none',boxstyle='Square,pad='+str(boxpad)),size = fontsize)
    
    pvals = [0.001,0.1,0.00001]
    offset  =1
    for i,p in enumerate(pvals):
        if p>=0.05:
            displaystring = r'n.s.'
        elif p<0.0001:
            displaystring = r'***'
        elif p<0.001:
            displaystring = r'**'
        else:
            displaystring = r'*'
    
        height = offset +  max(cell_lysate_avg[i],media_avg[i])
        bar_centers = index[i] + numpy.array([0.5,1.5])*bar_width
        significance_bar(bar_centers[0],bar_centers[1],height,displaystring)
    

    Instead of the stars you could of course also explicitly write p<0.05 or something similar. You can then spend hours fiddling with the parameters until it looks just right.