Maintain pixel-size and aspect ratio of elements in matplotlib - python

After a few years of finding solutions to all my coding-problems on this site, this is my first post with (as far as I can tell) a new question!
I want to create several bar-charts from one data-set and save them as individual images. I want the image-size to scale automatically so that any given object (e.g. a 1x1 square) appears the same size on every image.
The following code produces two such images in which each 1x1 element is about 60x60 pixel, so far so good:
import matplotlib.pyplot as plt
def barchart(bars,size,title):
hspace,vspace = (max(size)+1,len(size))
fig = plt.figure(figsize=(hspace,vspace+1))
fig.add_axes([0.2,0.2,0.6,0.6])
plt.title(title)
plt.axis('scaled')
x_pos = xrange(vspace)
plt.xlim(0,hspace)
plt.ylim(-1,vspace)
plt.barh(x_pos, size, height=1, align='center', alpha=0.5)
plt.yticks(x_pos, bars)
plt.savefig(title+'.png',bbox_inches='tight')
plt.clf()
barchart(["1x1","A","B","C"],[1,3,5,2],"many short bars")
barchart(["1x1","A"],[1,17],"few long bars")
But I would like to do this with a different aspect-ratio, so that e.g. each 1x1 element appears as 60x30 pixel on the image. Is there a replacement for .axis('scaled') which does this? I have tried to scale the width in figsize, xlim and both, as well as in .add_axes() and several key-words in .axis(). They all seem to affect the final scale and aspect ratio of the images in different ways.
The exact pixel-size does not matter, whether it is 60x30 or 66x33 or otherwise, as long as it is consistent throughout all images.

Finally figured out the answer with the hints in the comment above and some more trial-and-error:
import matplotlib.pyplot as plt
def barchart(bars,size,title):
hspace,vspace = (max(size)+1,len(size))
AR = 0.5 # x-axis will be scaled to 50%
fig = plt.figure(figsize=(AR*hspace,vspace+1))
fig.add_axes([0.2,0.2,0.6,0.6])
plt.xlim(0,hspace)
plt.ylim(-1,vspace)
plt.title(title)
x_pos = xrange(vspace)
plt.barh(x_pos, size, height=1, align='center', alpha=0.5)
plt.yticks(x_pos, bars)
plt.savefig(title+'.png',bbox_inches='tight')
plt.clf()
barchart(["1x1","A","B","C"],[1,3,5,2],"many short bars")
barchart(["1x1","A"],[1,17],"few long bars")
The solution was to fix both the figure size and the axis limits to the same proportions and to simply leave out the .axis('scaled'). Then scale only the fig-width by the desired factor.

Related

Setting geometrical width/height for axes

I have trouble to produce four same sized plots.
I have four different plots, which are to be shown in a 2x2 matrix in a document. Two of the them have a second y-axis, and one of these have a slightly higher ax title (a greek letter). So, they come out in four differnt sizes of the plot, which does not look good. Additionally i Want to have them in single plots to give them an individual label.
Is there a way to directly set the length of the single axis in inch, so that they have exaclty the same size? And/or an option to define the origin ( in ccordinates) to prevent them from having a differnt adjustment?
Can I force them to be squred and equal using one plot? In this case, i will bite the bullet.
Thanks alot
Bad looking
This is how i plot each of the figures:
pre,ax = plt.subplots(figsize=(3,3))
ax2 = ax.twinx()
ax.plot([1,2],[3,4])
ax2.plot([3,4],[100,1000])
ax.set_box_aspect(1)
ax2.set_box_aspect(1)
plt.show()
To put an axes at exactly a given position in inches is relatively trivial. The following puts the axes exactly 0.5 inches from each side.
import matplotlib.pyplot as plt
w = 4
h = 3
margin = 0.5
fig =plt.figure(figsize=(w, h), facecolor='lightblue')
pos = [margin/w, margin/h, (w-2*margin)/ w, (h-2*margin)/h]
ax = fig.add_axes(pos)
plt.show()
This has been answered before, but many of the other solutions are pretty complex, whereas this is super straightforward.

How to define the aspect ratio of a matplotlib figure?

Python produces an aspect ratio that is suitable for its content e.g., respects the structure of the font of each label, axis title, etc. This is the basic code using Jupyter Notebook:
fig, ax = plt.subplots()
ax.boxplot(dataLipid)
ax.set_title("Lipid contact analysis")
plt.xticks([1,2,3,4,5],["x4 Monomers","x2 Monomers\nDimer","x2 Dimers","Monomer\nTrimer", "x4mer"])
plt.show()
However, I want to save the image as a tiff, with a dpi of 600, and a width of 8.3cm (maximum height is an A4 page, but the nature of my question will make that irrelevant).
I'm using the code:
fig.savefig("bar.tiff", dpi=600, format="tiff", pil_kwards = {"compression":"tiff_lzm"})
This produces the following:
All good so far. Next, the Royal Soc. of Chemistry expect a single column image to be 8.3 cm in width (height, no more than the page).
My question:
Is there any way for Python to calculate the height of the figure given only the wdith, whilst maintaining the correct aspect ratio for the fonts, titles and ticks etc.? If I specify width=height, the image looks terrible:
fig.set_size_inches(3.26,3.26)
fig.savefig("bar.tiff", dpi=600, format="tiff", pil_kwards = {"compression":"tiff_lzm"})
Or is this a case where I define the size of the figure first, then adjust the font sizes as a separate step? I'm looking more for a one-fix solution as I have multiple figures of different size requirements (all being dpi=600 though) to produce.
Here you go:
dataLipid = np.random.uniform(0,1,(100,5)) * 90000
fig, ax = plt.subplots()
ax.boxplot(dataLipid)
ax.set_title("Lipid contact analysis")
plt.xticks([1,2,3,4,5],["x4 Monomers","x2 Monomers\nDimer","x2 Dimers","Monomer\nTrimer", "x4mer"])
fig.set_size_inches(3.26,3.26)
# rotate ticks
plt.xticks(rotation=45)
# set bottom margin
plt.subplots_adjust(left=0.2, bottom=0.3)
fig.savefig("bar.tiff", dpi=600, format="tiff", pil_kwards = {"compression":"tiff_lzm"})
There is no general solution as far as I know. So setting the correct margin depends on your content and your data. Rotating the ticks is always a good option to make them readable in case of close spacing.
You can use the Axes.set_aspect method.
# square plot
ax.set_aspect(1)
Also have a look at the tight_layout method to ensure everything is redrawn to fit in the figure.

Avoid overlapping ticks in matplotlib

I am generating plots like this one:
When using less ticks, the plot fits nicely and the bars are wide enough to see them correctly. Nevertheless, when there are lots of ticks, instead of making the plot larger, it just compress the y axe, resulting in thin bars and overlapping tick text.
This is happening both for plt.show() and plt.save_fig().
Is there any solution so it plots the figure in a scale which guarantees that bars have the specified width, not more (if too few ticks) and not less (too many, overlapping)?
EDIT:
Yes, I'm using barh, and yes, I'm setting height to a fixed value (8):
height = 8
ax.barh(yvalues-width/2, xvalues, height=height, color='blue', align='center')
ax.barh(yvalues+width/2, xvalues, height=height, color='red', align='center')
I don't quite understand your code, it seems you do two plots with the same (only shifted) yvalues, but the image doesn't look so. And are you sure you want to shift by width/2 if you have align=center? Anyways, to changing the image size:
No, I am not sure there is no other way, but I don't see anything in the manual at a glance. To set image size by hand:
fig = plt.figure(figsize=(5, 80))
ax = fig.add_subplot(111)
...your_code
the size is in cm. You can compute it beforehand, try for example
import numpy as np
fig_height = (max(yvalues) - min(yvalues)) / np.diff(yvalue)
this would (approximately) set the minimum distance between ticks to a centimeter, which is too much, but try to adjust it.
I think of two solutions for your case:
If you are trying to plot a histogram, use hist function [1]. This will automatically bin your data. You can even plot multiple overlapping histograms as long as you set alpha value lower than 1. See this post
import matplotlib.pyplot as plt
import numpy as np
x = mu + sigma*np.random.randn(10000)
plt.hist(x, 50, normed=1, facecolor='green',
alpha=0.75, orientation='horizontal')
You can also identify interval of your axis ticks. This will place a tick every 10 items. But I doubt this will solve your problem.
import matplotlib.ticker as ticker
...
ax.yaxis.set_major_locator(ticker.MultipleLocator(10))

matplotlib - change figsize but keep fontsize constant

I want to display several figures with different sizes, making sure that the text has always the same size when the figures are printed. How can I achieve that?
As an example. Let's say I have two figures:
import matplotlib.pylab as plt
import matplotlib as mpl
mpl.rc('font', size=10)
fig1 = plt.figure(figsize = (3,1))
plt.title('This is fig1')
plt.plot(range(0,10),range(0,10))
plt.show()
mpl.rc('font', size=?)
fig2 = plt.figure(figsize = (20,10))
plt.title('This is fig2')
plt.plot(range(0,10),range(0,10))
plt.show()
How can I set the fontsize in such way that when printed the title and axis ticklabels in fig1 will have the same size as those in fig2?
In this case, the font size would be the same (i.e. also 10 points).
However, in Jupyter Notebook the figures may be displayed at a different size if they are too wide, see below:
Note that font size in points has a linear scale, so if you would want the size of the letters to be exactly twice as big, you would need to enter exactly twice the size in points (e.g. 20pt). That way, if you expect to print the second figure at 50% of the original size (length and width, not area), the fonts would be the same size.
But if the only purpose of this script is to make figures to then print, you would do best to set the size as desired (on paper or on screen), and then make the font size equal. You could then paste them in a document at that exact size or ratio and the fonts would indeed be the same size.
As noted by tcaswell, bbox_inches='tight' effectively changes the size of the saved figure, so that the size is different from what you set as figsize. As this might crop more whitespaces from some figures than others, the relative sizes of objects and fonts could end up being different for a given aspect ratio.

Matplotlib: Constrain plot width while allowing flexible height

What I would like to achive are plots with equal scale aspect ratio, and fixed width, but a dynamically chosen height.
To make this more concrete, consider the following plotting example:
import matplotlib as mpl
import matplotlib.pyplot as plt
def example_figure(slope):
# Create a new figure
fig = plt.figure()
ax = fig.add_subplot(111)
# Set axes to equal aspect ratio
ax.set_aspect('equal')
# Plot a line with a given slope,
# starting from the origin
ax.plot([x * slope for x in range(5)])
# Output the result
return fig
This example code will result in figures of different widths, depending on the data:
example_figure(1).show()
example_figure(2).show()
Matplotlib seems to fit the plots into a certain height, and then chooses the width to accomodate the aspect ratio. The ideal outcome for me would be the opposite -- the two plots above would have the same width, but the second plot would be twice as tall as the first.
Bonus — Difficulty level: Gridspec
In the long run, I would like to create a grid in which one of the plots has a fixed aspect ratio, and I would again like to align the graphs exactly.
# Create a 2x1 grid
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(2, 1)
# Create the overall graphic, containing
# the top and bottom figures
fig = plt.figure()
ax1 = fig.add_subplot(gs[0, :], aspect='equal')
ax2 = fig.add_subplot(gs[1, :])
# Plot the lines as before
ax1.plot(range(5))
ax2.plot(range(5))
# Show the figure
fig.show()
The result is this:
So again, my question is: How does one create graphs that vary flexibly in height depending on the data, while having a fixed width?
Two points to avoid potential misunderstandings:
In the above example, both graphs have the same x-axis. This cannot be
taken for granted.
I am aware of the height_ratios option in the gridspec. I can compute
the dimensions of the data, and set the ratios, but this unfortunately
does not control the graphs directly, but rather their bounding boxes,
so (depending on the axis labels), graphs of different widths still occur.
Ideally, the plots' canvas would be aligned exactly.
Another unsolved question is similar, but slightly more convoluted.
Any ideas and suggestions are very welcome, and I'm happy to specify the question further, if required. Thank you very much for considering this!
Have you tried to fix the width with fig.set_figwidth()?

Categories

Resources