0

I have been working at producing some boxplots that include an image that shows what the parts of boxplots represent. As shown in the top right here. This is fine.

with legend

But, if I remove the green dashed line, which removes the legend, then the little boxplot image is cropped out when I view the plot in jupyter, but also if I save it as image file.e.g. enter image description here

The solution offered here using 'tight' doesn't work, i.e.:

plt.savefig("test1.jpg", dpi=300,bbox_inches='tight')

Nor does:

plt.tight_layout()

I've also tried using AnnotationBbox but can't find a solution.

Working example code below:

import numpy as np

import pandas as pd
import seaborn as sns
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.offsetbox import (TextArea, DrawingArea, OffsetImage,
                                  AnnotationBbox)
df = pd.DataFrame(np.random.randn(40, 4), columns=list('ABCD'))
df['Class']=list('ADFADAFDADFAFDAADFAFDAFDDFADFAFDADDFDFAD')


assay=df

factor_to_plot='A'
f=factor_to_plot

x_axis_factor='Class'
g=x_axis_factor

pcntls=assay.groupby([g]).describe(percentiles=[0.05,0.1,0.25,0.5,0.75,0.9,0.95])
sumry= pcntls[f].T
#print sumry
ordered=sorted(assay[g].dropna().unique())

#set figure size and scale text
plt.rcParams['figure.figsize']=(15,10)
plt.rcParams['figure.dpi'] = 300
text_scaling=1.9
sns.set(style="whitegrid")
sns.set_context("paper", font_scale=text_scaling) 

#plot boxplot
ax=sns.boxplot(x=assay[g],y=assay[f],width=0.5,order=ordered, whis=[10,90],data=assay, showfliers=False,color='lightblue', 
            showmeans=True,meanprops={"marker":"x","markersize":12,"markerfacecolor":"white", "markeredgecolor":"black"})


#add dashed line at a value
plt.axhline(0.3, color='green',linestyle='dashed', label="S%=0.3")

#this line sets the scale to logarithmic
#ax.set_yscale('log')

#add legend for dashed line
#plt.legend(markerscale=1.5,loc='center left',bbox_to_anchor=(1.0, 0.5))

#plt.title("Assay data")

#add gridlines (use for log plots)
plt.grid(True, which='both')


#plot additional percentiles not included in boxplots
ax.scatter(x=sorted(list(sumry.columns.values)),y=sumry.loc['5%'],s=120,color='white',edgecolor='black') 
ax.scatter(x=sorted(list(sumry.columns.values)),y=sumry.loc['95%'],s=120,color='white',edgecolor='black')




#next line is important, select a column that has no blanks or nans as the total items are counted to produce
#N= annotations to plot. 
assay['value']=assay['B']

vals=assay.groupby([g])['value'].count()
j=vals

ymin, ymax = ax.get_ylim()
xmin, xmax = ax.get_xlim()
#print ymax

#put n= values at top of plot    
x=0
for i in range(len(j)):

    plt.text(x = x , y = ymax, s = "N=\n" +str(int(j[i])),horizontalalignment='center')
    #plt.text(x = x , y = 102.75, s = "n=",horizontalalignment='center')
    x+=1


#add legend image
img = plt.imread("legend4.jpg")
plt.figimage(img, 3900,1800, zorder=1, alpha=1)

'''xy = [1.1, 0.8]
fn = "legend4.jpg"
arr_img = plt.imread(fn, format='jpg')

imagebox = OffsetImage(arr_img, zoom=0.2)
imagebox.image.axes = ax

ab = AnnotationBbox(imagebox, xy,
                        boxcoords="figure fraction",


                        )

ax.add_artist(ab)'''



#plt.tight_layout()

#use the section below to adjust the y axis lable format to avoid default of 10^1 etc for log scale plots.
#ylabels = ['{:.1f}'.format(y) for y in ax.get_yticks()]
#ax.set_yticklabels(ylabels)

plt.savefig("test1.jpg", dpi=300,bbox_inches='tight') 
flashliquid
  • 500
  • 9
  • 23
  • It's not cropped, but rather it's not expanded. The figimage is outside the figure and is not taken into account for the `'tight'` option. I would opt for placing it *inside* the figure boundaries – ImportanceOfBeingErnest May 29 '19 at 14:00
  • @ImportanceOfBeingErnest Yes it is not perceived as part of the plt. How come this: https://matplotlib.org/2.1.1/gallery/text_labels_and_annotations/demo_annotation_box.html is presented? Can I plot the image in a box outside. My attempts have resulted in errors, but it was getting late.... – flashliquid May 30 '19 at 00:57
  • 1
    Yes you can plot it in a box, but it will still not be shown when using the tight option. For a box the issue would be [this](https://github.com/matplotlib/matplotlib/pull/13457). So in any case, you will need to not use the bbox_inches='tight' when saving. Or see [this comment](https://github.com/matplotlib/matplotlib/issues/12699#issuecomment-435196759) for a workaround. – ImportanceOfBeingErnest May 30 '19 at 01:05
  • 1
    @ImportanceOfBeingErnest okay, thanks, I think I've got it.I didn't realise you built matplotlib! – flashliquid May 30 '19 at 08:32

1 Answers1

0

Using guidance from @ImportanceOfBeingErnest I have got what I required but in saved plots only, the jupyter inline displays are still cropped (not showing annotation box) using the code below:

import numpy as np

import pandas as pd
import seaborn as sns
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.offsetbox import (TextArea, DrawingArea, OffsetImage,
                                  AnnotationBbox)
df = pd.DataFrame(np.random.randn(40, 4), columns=list('ABCD'))
df['Class']=list('ADFADAFDADFAFDAADFAFDAFDDFADFAFDADDFDFAD')


assay=df

factor_to_plot='A'
f=factor_to_plot

x_axis_factor='Class'
g=x_axis_factor

pcntls=assay.groupby([g]).describe(percentiles=[0.05,0.1,0.25,0.5,0.75,0.9,0.95])
sumry= pcntls[f].T
#print sumry
ordered=sorted(assay[g].dropna().unique())

#set figure size and scale text
plt.rcParams['figure.figsize']=(15,10)
plt.rcParams['figure.dpi'] = 300
text_scaling=1.9
sns.set(style="whitegrid")
sns.set_context("paper", font_scale=text_scaling) 

#plot boxplot
ax=sns.boxplot(x=assay[g],y=assay[f],width=0.5,order=ordered, whis=[10,90],data=assay, showfliers=False,color='lightblue', 
            showmeans=True,meanprops={"marker":"x","markersize":12,"markerfacecolor":"white", "markeredgecolor":"black"})


#add dashed line at a value
#plt.axhline(0.3, color='green',linestyle='dashed', label="S%=0.3")

#this line sets the scale to logarithmic
#ax.set_yscale('log')

#add legend for dashed line
#plt.legend(markerscale=1.5,loc='center left',bbox_to_anchor=(1.0, 0.5))

#plt.title("Assay data")

#add gridlines (use for log plots)
plt.grid(True, which='both')


#plot additional percentiles not included in boxplots
ax.scatter(x=sorted(list(sumry.columns.values)),y=sumry.loc['5%'],s=120,color='white',edgecolor='black') 
ax.scatter(x=sorted(list(sumry.columns.values)),y=sumry.loc['95%'],s=120,color='white',edgecolor='black')




#next line is important, select a column that has no blanks or nans as the total items are counted to produce
#N= annotations to plot. 
assay['value']=assay['B']

vals=assay.groupby([g])['value'].count()
j=vals

ymin, ymax = ax.get_ylim()
xmin, xmax = ax.get_xlim()
#print ymax

#put n= values at top of plot    
x=0
for i in range(len(j)):

    plt.text(x = x , y = ymax, s = "N=\n" +str(int(j[i])),horizontalalignment='center')

    x+=1


#add legend image

fn = plt.imread("legend4.jpg")
arr_img = fn

imagebox = OffsetImage(arr_img, zoom=0.2)
imagebox.image.axes = ax

ab = AnnotationBbox(imagebox, xy=(1.05, 0.5), xycoords="axes fraction",
                                        boxcoords="offset points",
                    pad=0.5

                    )

ax.add_artist(ab)

plt.savefig('test.png', bbox_extra_artists=[a.offsetbox for a in [ab]])

I found that I had to delete the 'bbox_inches='tight' part from the final save statement or I found that my 'N=...' text annotations were being cropped. Can't believe it is this hard to do!

gives:

plot with image key

flashliquid
  • 500
  • 9
  • 23
  • 2
    To prevent cropping in IPython, you can use `%config InlineBackend.print_figure_kwargs={'bbox_inches':None}` in your jupyter notebook. This will tell the inline backend not to use the `tight` option when creating the png files which are shown in the jupyter output cells. – ImportanceOfBeingErnest May 30 '19 at 13:22
  • It's not obvious but for some reason there is extra white-space on the left of the figure. I don't know why this has appeared. – flashliquid Jun 05 '19 at 09:11
  • If you don't crop the figure, there will be more whitespace. – ImportanceOfBeingErnest Jun 05 '19 at 21:40