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.
Related
I was trying to put a summary of the data under its figure. However, since the length of the summary text varies, it is hard to keep the textbox and the figure vertically aligned (more specifically, the textbox and the figure should have the same width). Is there any way to do this?
You can try to do a subplot right below the figure. This guarantees that the width will be the same:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(1, 10, 50)
y = np.sin(x)
plt.subplot(2,1,1)
plt.plot(x,y)
plt.subplot(2,1,2)
description = '''
Sin Function:
In the figure avobe we see how the sin function oscillates and behaves
between 0 and 10
'''
plt.text(0.5, 0.5, description, va='top', ha='center')
plt.axis('off')
plt.show()
However, I am afraid you'll have to insert the line breaks yourself as matplotlib doesn't support text wrapping. Here there's something you can try though.
Yes. You can put a long summary of the plot under the plot. Technically the caption doesn't go into the axes object, it goes into the figure object that the axes was created in, but the reader can't tell that.
Here I estimate the length of a long caption and then add the needed vertical space to the figure in which the axes object plots. With some rough calculations of text size, this makes room for matplotlib's text wrapping to fit very long captions below a figure:
import matplotlib.pyplot as plt
t = "A point is that which has no part. A line is breadthless length. The ends of a line are points. A straight line is a line which lies evenly with the points on itself. A surface is that which has length and breadth only. The edges of a surface are lines. A plane surface is a surface which lies evenly with the straight lines on itself. A plane angle is the inclination to one another of two lines in a plane which meet one another and do not lie in a straight line. "
#t = 3*t
AestheticFigWidth = 6.4
AestheticPlotHeight = 2.4
# 12pt fonts should be 6 lines per vertical inch.
# Very rough estimate of 10 12pt characters per horizontal inch -- it varies!
# Calculate how many more inches of fig you need for your caption,
# add extra for whitespace and labels:
CaptionHeight = (len(t)/(6 * (AestheticFigWidth * 10))) + 0.5
fig = plt.figure(figsize=(AestheticFigWidth,
AestheticPlotHeight + CaptionHeight))
CaptionProportion = CaptionHeight / (AestheticPlotHeight + CaptionHeight)
ax = fig.add_axes((.1, #location proportional to figure
CaptionProportion + .03,
.85, # size proportional to figure
.85 - CaptionProportion))
fig.suptitle("Make space for a big caption")
ax.plot([1,2,3,4,5], [0,5,1,8,0], 'o-')
ax.set_ylabel('Angle')
ax.set_xlabel("Let's not overlap this")
fig.text(.05,.03, t, ha='left', rotation=0, wrap=True, fontsize=12)
plt.show()
With a very long caption:
With a middling caption:
If you need something more elegant or automatic, I recommend generating the plots in matplotlib and then generating a template in LaTeX to pick up the plots and their captions.
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.
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()?
I have some data of the following type:
grid = np.array([posx, posy]) where posx and posy are the X/Y position some, stored in another array.
The (transposed) grid may look like:
grid = np.array([posx, posy])
print grid.T
[[ 2.47685286 2.51629155]
[ 2.47685286 8.51629155]
[ 2.47685286 14.51629155]
[ 8.47685286 5.51629155]
[ 8.47685286 11.51629155]
[ 14.47685286 2.51629155]
[ 14.47685286 8.51629155]
[ 14.47685286 14.51629155]]
Especially the y-Position is not identical in each "row" and the number of points differs, which I assume to be one of my problems.
Additionally, the corresponding data is stored in another (1D-)array like data = [2.3 4.7 -0.3 .....] having the same amount of entrys as I have points.
My aim is to plot this data in kind of a smooth heatmap displaying by colours indicating position of high / low values. So far I used:
import numpy as np
import matplotlib.pyplot as p
p.imshow(data, interpolation=None)
p.colorbar()
p.show()
Obviously my problem is that I need to adjust the positon of my points.
I searched some other posts but with this shape of data it never worked out.
Also I tried to adjust this by simply reshaping the data but this didn't work due to the irregular number of points
As I am new here I am also happy for comments on how to improve my post (more input needed etc.)
Thanks in advance!
There are several solutions to this problem.
If what you want is simply to have the points shown as markers of some size, with colors depending on the values in the z array, then a scatterplot will do nicely. If the space between the points should also be colored however, you should use interpolation and contouring. Fortunately those things have also been implemented in matplotlib for irregularly spaced data (data on an "unstructured grid"), which is what you have as the points cannot be easily mapped to a regular grid (although in the small example you've given, there does seem to be a tendency for equal-sized triangles).
Here are 3 examples that illustrate the functions you might want to look further into: plt.scatter, plt.tripcolor and plt.tricontourf. I've made the dataset to play with a bit larger, so that you can get a feeling of the function that is represented by z.
x,y = (2*np.random.rand(50)-1 for _ in range(2))
z = np.exp(-x*x - y*y) - np.cos(x) # turtle-surface around origin
f, ax = plt.subplots(1,3, sharex=True, sharey=True, num=2, subplot_kw={'xlim': (-1,1), 'ylim': (-1, 1)})
ax[0].scatter(x,y, s=500*(z-z.min()), c=z, cmap='hot') # scatterplot with variable-sized markers and colors
ax[1].tripcolor(x, y, z, cmap='hot') # creates a tesselation and colors the formed triangles based on the values in the 3 nodes
ax[2].tricontourf(x, y, z, cmap='hot') # estimates the underlying surface
for indx in (1,2):
ax[indx].triplot(x,y, 'ko ') # add the locations of the points
for axes in ax: # turn off the needless clutter
axes.tick_params(axis='both', which='both', bottom='off', left='off', labelbottom='off', labelleft='off')
ax[0].set_title('scatter')
ax[1].set_title('tripcolor')
ax[2].set_title('tricontourf')
I think your problem could be solved by creating a regular 2d-matrix and then using scipy.interpolate to interpolate the data between your data points. Example can be found at: http://docs.scipy.org/doc/scipy/reference/tutorial/interpolate.html#id1
I have a pair of lists of numbers representing points in a 2-D space, and I want to represent the y/x ratios for these points as a 1-dimensional heatmap, with a diverging color map centered around 1, or the logs of my ratios, with a diverging color map centered around 0.
How do I do that?
My current attempt (borrowing somewhat from Heatmap in matplotlib with pcolor?):
from matplotlib import numpy as np
import matplotlib.pyplot as plt
# There must be a better way to generate arrays of random values
x_values = [np.random.random() for _ in range(10)]
y_values = [np.random.random() for _ in range(10)]
labels = list("abcdefghij")
ratios = np.asarray(y_values) / np.asarray(x_values)
axis = plt.gca()
# I transpose the array to get the points arranged vertically
heatmap = axis.pcolor(np.log2([ratios]).T, cmap=plt.cm.PuOr)
# Put labels left of the colour cells
axis.set_yticks(np.arange(len(labels)) + 0.5, minor=False)
# (Not sure I get the label order correct...)
axis.set_yticklabels(labels)
# I don't want ticks on the x-axis: this has no meaning here
axis.set_xticks([])
plt.show()
Some points I'm not satisfied with:
The coloured cells I obtain are horizontally-elongated rectangles. I would like to control the width of these cells and obtain a column of cells.
I would like to add a legend for the color map. heatmap.colorbar = plt.colorbar() fails with RuntimeError: No mappable was found to use for colorbar creation. First define a mappable such as an image (with imshow) or a contour set (with contourf).
One important point:
matplotlib/pyplot always leaves me confused: there seems to be a lot of ways to do things and I get lost in the documentation. I never know what would be the "clean" way to do what I want: I welcome suggestions of reading material that would help me clarify my very approximative understanding of these things.
Just 2 more lines:
axis.set_aspect('equal') # X scale matches Y scale
plt.colorbar(mappable=heatmap) # Tells plt where it should find the color info.
Can't answer your final question very well. Part of it is due to we have two branches of doing things in matplotlib: the axis way (axis.do_something...) and the MATLAB clone way plt.some_plot_method. Unfortunately we can't change that, and it is a good feature for people to migrate into matplotlib. As far as the "Clean way" is concerned, I prefer to use whatever produces the shorter code. I guess that is inline with Python motto: Simple is better than complex and Readability counts.