Copying axis limits from one subplot ('equal' aspect) to another - matplotlib

In a figure with 2x2 subplots, I need both the subplots on the right to share the x-axis, but the ones on the left not to share their axis. In addition, I need the subplot that is determining the x-axis limits to have 'equal' aspect ratio. I tried this:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(2, 2, figsize=(12, 9))
# Subplot [0,1]
ax[0,1].axis('equal')
ax[0,1].plot(...)
[xmin01, xmax01, ymin01, ymax01] = self.ax[0,1].axis()
# Subplot [1,1]
ax[1,1].plot(...)
ax[1,1].set_xlim(left=xmin01, right=xmax01)
This is not working: the limits of the x-axis returned by axis() are near the data limits and are not the real limits shown in the graphed subplot. Changing the position of ax[0,1].axis('equal') after the plot command has no effect. Any idea?

Looking into the pyplot source code I discovered that axis('equal') is calling the method set_aspect(). This latter method is modifying the variable self._aspect but it is not further updating anything related! Then, I looked for and found the method that is really updating the aspect ratio: it is named apply_aspect(). So, it doesn't seem very elegant, but at least my problem is solved as shown:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(2, 2, figsize=(12, 9))
# Subplot [0,1]
ax[0,1].axis('equal')
ax[0,1].plot(...)
ax[0,1].apply_aspect()
[xmin01, xmax01, ymin01, ymax01] = self.ax[0,1].axis()
# Subplot [1,1]
ax[1,1].plot(...)
ax[1,1].set_xlim(left=xmin01, right=xmax01)

Related

Problem with text and annotation x and y coordinates changing while looping through subplots matplotlib

I would like to iterate through subplots, plot data, and annotate the subplots with either the text function or the annotation function in matplotlib. Both functions ask for x and y coordinates in order to place text or annotations. I can get this to work fine, until I plot data. Then the annotations and the text jump all over the place and I cannot figure out why.
My set up is something like this, which produces well-aligned annotations with no data:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
fig, ax=plt.subplots(nrows=3, ncols=3, sharex=True)
fig.suptitle('Axes ylim unpacking error demonstration')
annotation_colors=["red", "lightblue", "tan", "purple", "lightgreen", "black", "pink", "blue", "magenta"]
for jj, ax in enumerate(ax.flat):
bott, top = plt.ylim()
left, right = plt.xlim()
ax.text(left+0.1*(right-left), bott+0.1*(top-bott), 'Annotation', color=annotation_colors[jj])
plt.show
When I add random data (or my real data), the annotations jump:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
#Same as above but but with 9 random data frames plotted.
df_cols = ['y' + str(x) for x in range(1,10)]
df=pd.DataFrame(np.random.randint(0,10, size=(10,9)), columns=df_cols)
df['x']=range(0,10)
#Make a few columns much larger in terms of magnitude of mean values
df['y2']=df['y2']*-555
df['y5']=df['y5']*123
fig, ax=plt.subplots(nrows=3, ncols=3, sharex=True)
fig.suptitle('Axes ylim unpacking error demonstration')
annotation_colors=["red", "lightblue", "tan", "purple", "lightgreen", "black", "pink", "blue", "magenta"]
for jj, ax in enumerate(ax.flat):
ax.plot(df['x'], df['y'+str(jj+1)], color=annotation_colors[jj])
bott, top = plt.ylim()
left, right = plt.xlim()
ax.text(left+0.1*(right-left), bott+0.1*(top-bott), 'Annotation', color=annotation_colors[jj])
plt.show()
This is just to demonstrate the issue that is likely caused by my lack of understanding of how the ax and fig calls are working. It seems to me that the coordinates x and y of the ax.text call may actually apply to the coordinates of of the fig, or something similar. The end result is far worse with my actual data!!! In that case, some of the annotations end up miles above the actual plots and not even within the coordinates of any of the subplot axes. Others completely overlap! What I am misunderstanding?
Edit for more details:
I have tried Stef's solution of using axes coordinates of axes.text(0.1, 0.1, 'Annotation'...)
I get the following plot, which still shows the same problem of moving the text all over the place. Because I am running this example with random numbers, the annotations are moving randomly with every run - i.e. they are not just displaced in the subplots with different axis ranges (y2 and y5).
You can specify the text location in axes coordinates (as opposed to data coordinates as you did implicitely):
ax.text(.1, .1, 'Annotation', color=annotation_colors[jj], transform=ax.transAxes)
See the Transformations Tutorial for further information.

How can I space only one axis away from other axes using matplotlib and gridspec?

I'm using matplotlib and grispec to plot 4 axes, three of which I want tight to one another and the last one I want spaced a little bit away from the above three. Reason being is that I want the top three to share the same x-axis units, and the bottom one to have different x-axis units. I've tried using gs.update right after the third axis, but this spaced out all the axis from one another, instead of the bottom fourth axis from the top three.
Is there a simple gridspec/matplotlib command that I'm missing, or do I have to hack around this somehow?
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(13,12))
gs1=fig.add_gridspec(nrows=4, ncols=2, hspace=0.0)
ax2=fig.add_subplot(gs1[1,:])
ax1=fig.add_subplot(gs1[0,:], sharey=ax2)
ax3=fig.add_subplot(gs1[2,:], sharey=ax2)
ax4=fig.add_subplot(gs1[3,:]) #<- want this spaced farther down than the above three axes
plt.show()
This is one solution -- here I first added a gridspec for the first three plots. This I specified should finish at 0.35 from the bottom (0 being the very bottom, 1 the top). Then I added another gridspec that starts at 0.3 and so has a gap of 0.05. Obviously you can play around with the numbers/placement.
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(13,12))
gs1=fig.add_gridspec(nrows=3, ncols=1, hspace=0, bottom=0.35)
ax1=fig.add_subplot(gs1[0,0])
ax2=fig.add_subplot(gs1[1,0], sharey=ax1)
ax3=fig.add_subplot(gs1[2,0], sharey=ax1)
gs2=fig.add_gridspec(nrows=1, ncols=1, top=0.3)
ax4=fig.add_subplot(gs2[0,0])
for ax in fig.axes:
ax.set_yticks([])
ax.set_xticks([])
plt.show()
Result:

Matplolib subplots what is the purpose of the fig and axs variables

Consider the following code:
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(19680801)
data = np.random.randn(2, 100)
fig, axs = plt.subplots(2, 2, figsize=(5, 5))
axs[0, 0].hist(data[0])
axs[1, 0].scatter(data[0], data[1])
axs[0, 1].plot(data[0], data[1])
axs[1, 1].hist2d(data[0], data[1])
plt.show()
I am aware that in order to create subplots you ned to write the line:
fig, axs = plt.subplots(2, 2, figsize=(5, 5)
However my question is concerning the meaning of this line, as in what does it actually achieve by producing the variables fig and axs, and why later on we use ax[0,0] as opposed to fig[0,0]
fig describes the figure as a whole, but the axs in this case refers to all the subplots within the figure. Since you defined 2 rows and 2 columns of subplots, you call each subplot with axs[0,0] for the top left and axs[1,1] for the bottom right subplot. In order to change the size of a subplot you have to change the size of the overall figure in which the subplots are embedded.
The difference is subtle, but multiple subplots or just one subplot can be found in a figure. So to plot a line you would do this on the subplot axes and not on the figure.

changing the size of subplots with matplotlib

I am trying to plot multiple rgb images with matplotlib
the code I am using is:
import numpy as np
import matplotlib.pyplot as plt
for i in range(0, images):
test = np.random.rand(1080, 720,3)
plt.subplot(images,2,i+1)
plt.imshow(test, interpolation='none')
the subplots appear tiny though as thumbnails
How can I make them bigger?
I have seen solutions using
fig, ax = plt.subplots()
syntax before but not with plt.subplot ?
plt.subplots initiates a subplot grid, while plt.subplot adds a subplot. So the difference is whether you want to initiate you plot right away or fill it over time. Since it seems, that you know how many images to plot beforehand, I would also recommend going with subplots.
Also notice, that the way you use plt.subplot you generate empy subplots in between the ones you are actually using, which is another reason they are so small.
import numpy as np
import matplotlib.pyplot as plt
images = 4
fig, axes = plt.subplots(images, 1, # Puts subplots in the axes variable
figsize=(4, 10), # Use figsize to set the size of the whole plot
dpi=200, # Further refine size with dpi setting
tight_layout=True) # Makes enough room between plots for labels
for i, ax in enumerate(axes):
y = np.random.randn(512, 512)
ax.imshow(y)
ax.set_title(str(i), fontweight='bold')

How to overlay one pyplot figure on another

Searching easily reveals how to plot multiple charts on one figure, whether using the same plotting axes, a second y axis or subplots. Much harder to uncover is how to overlay one figure onto another, something like this:
That image was prepared using a bitmap editor to overlay the images. I have no difficulty creating the individual plots, but cannot figure out how to combine them. I expect a single line of code will suffice, but what is it? Here is how I imagine it:
bigFig = plt.figure(1, figsize=[5,25])
...
ltlFig = plt.figure(2)
...
bigFig.overlay(ltlFig, pos=[x,y], size=[1,1])
I've established that I can use figure.add_axes, but it is quite challenging getting the position of the overlaid plot correct, since the parameters are fractions, not x,y values from the first plot. It also [it seems to me - am I wrong?] places constraints on the order in which the charts are plotted, since the main plot must be completed before the other plots are added in turn.
What is the pyplot method that achieves this?
To create an inset axes you may use mpl_toolkits.axes_grid1.inset_locator.inset_axes.
Position of inset axes in axes coordinates
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
fig, ax= plt.subplots()
inset_axes = inset_axes(ax,
width=1, # inch
height=1, # inch
bbox_transform=ax.transAxes, # relative axes coordinates
bbox_to_anchor=(0.5,0.5), # relative axes coordinates
loc=3) # loc=lower left corner
ax.axis([0,500,-.1,.1])
plt.show()
Position of inset axes in data coordinates
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
fig, ax= plt.subplots()
inset_axes = inset_axes(ax,
width=1, # inch
height=1, # inch
bbox_transform=ax.transData, # data coordinates
bbox_to_anchor=(250,0.0), # data coordinates
loc=3) # loc=lower left corner
ax.axis([0,500,-.1,.1])
plt.show()
Both of the above produce the same plot
(For a possible drawback of this solution see specific location for inset axes)