Matplotlib: Repositioning a subplot in a grid of subplots - python

I am trying to make a plot with 7 subplots. At the moment I am plotting two columns, one with four plots and the other with three, i.e. like this:
I am constructing this plot in the folowing way:
#! /usr/bin/env python
import numpy as plotting
import matplotlib
from pylab import *
x = np.random.rand(20)
y = np.random.rand(20)
fig = figure(figsize=(6.5,12))
subplots_adjust(wspace=0.2,hspace=0.2)
iplot = 420
for i in range(7):
iplot += 1
ax = fig.add_subplot(iplot)
ax.plot(x,y,'ko')
ax.set_xlabel("x")
ax.set_ylabel("y")
savefig("subplots_example.png",bbox_inches='tight')
However, for publication I think this looks a bit ugly -- what I would like to do is move the last subplot into the centre between the two columns. So, what is the best way to adjust the position of the last subplot so that it is centred? I.e. to have the first 6 subplots in a 3X2 grid and the last subplot underneath centred between the two columns. If possible, I'd like to be able to keep the for loop so that I can simply use:
if i == 6:
# do something to reposition/centre this plot
Thanks,
Alex

Use grid spec (doc) with a 4x4 grid, and have each plot span 2 columns as such:
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(4, 4)
ax1 = plt.subplot(gs[0, 0:2])
ax2 = plt.subplot(gs[0,2:])
ax3 = plt.subplot(gs[1,0:2])
ax4 = plt.subplot(gs[1,2:])
ax5 = plt.subplot(gs[2,0:2])
ax6 = plt.subplot(gs[2,2:])
ax7 = plt.subplot(gs[3,1:3])
fig = gcf()
gs.tight_layout(fig)
ax_lst = [ax1,ax2,ax3,ax4,ax5,ax6,ax7]

If you want to keep the for loop, you can arrange your plots with subplot2grid, which allows for a colspan parameter:
import numpy as np
import matplotlib.pyplot as plt
x = np.random.rand(20)
y = np.random.rand(20)
fig = plt.figure(figsize=(6.5,12))
plt.subplots_adjust(wspace=0.2,hspace=0.2)
iplot = 420
for i in range(7):
iplot += 1
if i == 6:
ax = plt.subplot2grid((4,8), (i//2, 2), colspan=4)
else:
# You can be fancy and use subplot2grid for each plot, which doesn't
# require keeping the iplot variable:
# ax = plt.subplot2grid((4,2), (i//2,i%2))
# Or you can keep using add_subplot, which may be simpler:
ax = fig.add_subplot(iplot)
ax.plot(x,y,'ko')
ax.set_xlabel("x")
ax.set_ylabel("y")
plt.savefig("subplots_example.png",bbox_inches='tight')

Related

Move or copy patches between figures

How can I move (or copy) patches between figures in matplotlib?
I'm working with a set of pickled figures, and would like to combine them to one plot.
This is no problem when working with line plots, as I can access the data through ax.get_lines.
However, when working with histograms, ax.get_lines returns <a list of 0 Line2D objects>. As far as I can see, the only way to access the plotted data is through ax.patches.
If I try to set a patch from one figure to another with ax.add_patch, I get RuntimeError: Can not put single artist in more than one figure.
Edit
I'm using matplotlib2.0.0.
The following example illustrates the problem
import numpy as np
import matplotlib.pylab as plt
import copy
# Creating the two figures
x = np.random.rand(20)
fig1, ax1 = plt.subplots()
fig2, ax2 = plt.subplots()
nr = 0
for color, ax in zip(("red", "blue"), (ax1, ax2)):
x = np.random.rand(20) + nr
ax.hist(x, color=color)
nr += 0.5
# Copying from ax1 to ax2
for patch in ax1.patches:
patch_cpy = copy.copy(patch)
# del patch # Uncommenting seems this makes no difference
ax2.add_patch(patch_cpy)
# RuntimeError: Can not put single artist in more than one figure
I would like to copy the red patches to the figure with the blue patches.
Edit 2
Although #ImportanceOfBeingErnest's answer worked for the case above, it did not work in the real-life problem I had.
I ended up making a new axis, and manually created new patches like so:
import numpy as np
import matplotlib.pylab as plt
from matplotlib import patches
# Creating the two figures
x = np.random.rand(20)
fig1, ax1 = plt.subplots()
fig2, ax2 = plt.subplots()
nr = 0
for color, ax in zip(("red", "blue"), (ax1, ax2)):
x = np.random.rand(20) + nr
ax.hist(x, color=color)
nr += 0.5
# Create another axis
fig3, ax3 = plt.subplots()
# Copy the properties of the patches to the new axis
for p in ax1.patches:
ax3.add_patch(patches.Rectangle(p.get_xy(),\
p.get_width(),\
p.get_height(),\
color = "red"))
for p in ax2.patches:
ax3.add_patch(patches.Rectangle(p.get_xy(),\
p.get_width(),\
p.get_height(),\
color = "blue"))
ax3.autoscale()
plt.show()
Apparently, the old solution of just deleting the artist doesn't work any more in matplotlib 2.0.
The patch_cpy will still be connected to the same axis as the original. You can see this by print patch_cpy.axes == ax1 which prints True.
So the solution can be to just set the axes and figure attribute of patch_cpy to None. I have to admit that I'm not sure if this hasn't got any side effects, but, at least the example below works.
Additionally, the copied patch wil still have the data transform of the old axes incorporated. This needs to be updated using patch_cpy.set_transform(ax2.transData).
Finally, to make sure the plot limits cover both the old and newly copied artists, use ax2.autoscale().
import numpy as np
import matplotlib.pylab as plt
import copy
# Creating the two figures
x = np.random.rand(20)
fig1, ax1 = plt.subplots()
fig2, ax2 = plt.subplots()
nr = 0
for color, ax in zip(("red", "blue"), (ax1, ax2)):
x = np.random.rand(20) + nr
ax.hist(x, color=color)
nr += 0.5
# Copying from ax1 to ax2
for patch in ax1.patches:
patch_cpy = copy.copy(patch)
# cut the umbilical cord the hard way
patch_cpy.axes = None
patch_cpy.figure = None
patch_cpy.set_transform(ax2.transData)
ax2.add_patch(patch_cpy)
ax2.autoscale()
plt.show()
You can make a copy of each patch. Here is an example where all the pathces are copied from one axes to another:
import copy
x = np.random.rand(20)
fig, ax = plt.subplots()
for color in ("red", "blue"):
x = np.random.rand(20)
ax.hist(x, color=color)
fig2, ax2 = plt.subplots()
for patch in ax.patches:
patch_cpy = copy.copy(patch)
ax2.add_patch(patch_cpy)
If you want to remove the patches from the first axes you can use del to do that, for example deleting every other patch:
del ax.patches[::2]
Remember to redraw the figure afterward with:
fig.canvas.draw()

python matplotlib gridspec, unwanted arbitrary axis labels

I have some code to plot a grid, with the data in each cell being distinct and having a very specific position. The easiest way I found to do this was to create the grid with gridspec and use it to precisely position my subplots, however I'm having a problem where the overall grid is labelled from 0 to 1 along each axis. This happens every time, even when the dimensions of the grid are changed. Obviously these numbers have no relevance to my data, and as what I am aiming to display is qualitative rather than quantitative I would like to remove all labels from this plot entirely.
Here is a link to an image with an example of my problem
And here is the MWE that I used to create that image:
import numpy as np
import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
# mock-up of data being used
x = 6
y = 7
table = np.zeros((x, y))
# plotting
fig = plt.figure(1)
gs = gridspec.GridSpec(x, y, wspace=0, hspace=0)
plt.title('Example Plot')
for (j, k), img in np.ndenumerate(table):
ax = fig.add_subplot(gs[x - j - 1, k])
ax.set_xticklabels('')
ax.set_yticklabels('')
plt.show()
I have not been able to find note of anything like this problem, so any help would be greatly appreciated.
If you just want to draw a grid over the plot, use this code:
import numpy as np
import matplotlib.pyplot as plt
# mock-up of data being used
x = 6
y = 7
table = np.zeros((x, y))
# plotting
fig = plt.figure(1)
plt.title('Example Plot')
plt.gca().xaxis.grid(True, color='darkgrey', linestyle='-')
plt.gca().yaxis.grid(True, color='darkgrey', linestyle='-')
plt.show()
Another variant is used gridspec:
...
# hide ticks of main axes
ax0 = plt.gca()
ax0.get_xaxis().set_ticks([])
ax0.get_yaxis().set_ticks([])
gs = gridspec.GridSpec(x, y, wspace=0, hspace=0)
plt.title('Example Plot')
for (j, k), img in np.ndenumerate(table):
ax = fig.add_subplot(gs[x - j - 1, k])
# hide ticks of gribspec axes
ax.get_xaxis().set_ticks([])
ax.get_yaxis().set_ticks([])

Python's matplotlib legend in separate axis with gridspec

Let suppose I have a matplotlib's gridspec instance in a python script. What I want to do is to create two axis and have the plot in one axis and the legend in the other one. Something like
import numpy as np
from matplotlib import gridspec, pyplot as plt
x = np.linspace(0,100)
y = np.sin(x)
gs = gridspec.GridSpec( 100, 100 )
ax1 = fig.add_subplot(gs[ :50, : ])
ax2 = fig.add_subplot(gs[ 55:, : ])
ax1.plot( s, y, label=r'sine' )
ax2.legend() # ?? Here I want legend of ax1
plt.show()
Is there any way of doing that?
You can grab the legend handles and labels from the first subplot using ax1.get_legend_handles_labels(), and then use them when you create the legend on the second subplot.
From the docs:
get_legend_handles_labels(legend_handler_map=None)
Return handles and labels for legend
ax.legend() is equivalent to:
h, l = ax.get_legend_handles_labels()
ax.legend(h, l)
import numpy as np
from matplotlib import gridspec, pyplot as plt
x = np.linspace(0, 100)
y = np.sin(x)
fig = plt.figure()
gs = gridspec.GridSpec(100, 100 )
ax1 = fig.add_subplot(gs[:50, :])
ax2 = fig.add_subplot(gs[55:, :])
ax1.plot(x, y, label=r'sine')
h, l = ax1.get_legend_handles_labels() # get labels and handles from ax1
ax2.legend(h, l) # use them to make legend on ax2
plt.show()

Matplotlib: adding a third subplot in the plot

I am completely new to Matplotlib and I have written this code to plot two series that so far is working fine:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
list1 = [1,2,3,4]
list2 = [4,3,2,1]
somecondition = True
plt.figure(1) #create one of the figures that must appear with the chart
gs = gridspec.GridSpec(3,1)
if not somecondition:
ax = plt.subplot(gs[:,:]) #create the first subplot that will ALWAYS be there
ax.plot(list1) #populate the "main" subplot
else:
ax = plt.subplot(gs[:2, :])
ax.plot(list1)
ax = plt.subplot(gs[2, :]) #create the second subplot, that MIGHT be there
ax.plot(list2) #populate the second subplot
plt.show()
What I would like to do is adding a third series to this plot, let's say:
list3 = [4,1,2,4]
What matters is that the first subplot (list1) has to be twice as bigger than the other two; for doing this I have used gridspace, but as I am really new I'm not being able to understand how I should set the parameter for this sample code to get the third one. Can anyone explain me how I should edit the block somecondition == True to get 3 subplots (first 1 twice bigger than the other 2 below) rather than just two?
P.S. the code is executable.
This is an example with Matplotlib subplots
import matplotlib.pyplot as plt
import numpy as np
x,y = np.random.randn(2,100)
fig = plt.figure()
ax1 = fig.add_subplot(211)
ax1.xcorr(x, y, usevlines=True, maxlags=50, normed=True, lw=2)
ax1.grid(True)
ax1.axhline(0, color='black', lw=2)
ax2 = fig.add_subplot(212, sharex=ax1)
ax2.acorr(x, usevlines=True, normed=True, maxlags=50, lw=2)
ax2.grid(True)
ax2.axhline(0, color='black', lw=2)
plt.show()
it is using pyplot, and add_subplot with a quite straightforward syntax.
To get 2:1 ratio, you can use 4 rows, and make plots take 2, 1, 1 row respectively:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
list1 = [1,2,3,4]
list2 = [4,3,2,1]
list3 = [4,1,2,4]
somecondition = True
plt.figure(1) #create one of the figures that must appear with the chart
gs = gridspec.GridSpec(4,1)
if not somecondition:
ax = plt.subplot(gs[:,:]) #create the first subplot that will ALWAYS be there
ax.plot(list1) #populate the "main" subplot
else:
ax = plt.subplot(gs[:2, :])
ax.plot(list1)
ax = plt.subplot(gs[2, :]) #create the second subplot, that MIGHT be there
ax.plot(list2) #populate the second subplot
ax = plt.subplot(gs[3, :]) #create the second subplot, that MIGHT be there
ax.plot(list3)
plt.show()

How to add a second x-axis in matplotlib

I have a very simple question. I need to have a second x-axis on my plot and I want that this axis has a certain number of tics that correspond to certain position of the first axis.
Let's try with an example. Here I am plotting the dark matter mass as a function of the expansion factor, defined as 1/(1+z), that ranges from 0 to 1.
semilogy(1/(1+z),mass_acc_massive,'-',label='DM')
xlim(0,1)
ylim(1e8,5e12)
I would like to have another x-axis, on the top of my plot, showing the corresponding z for some values of the expansion factor. Is that possible? If yes, how can I have xtics ax
I'm taking a cue from the comments in #Dhara's answer, it sounds like you want to set a list of new_tick_locations by a function from the old x-axis to the new x-axis. The tick_function below takes in a numpy array of points, maps them to a new value and formats them:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twiny()
X = np.linspace(0,1,1000)
Y = np.cos(X*20)
ax1.plot(X,Y)
ax1.set_xlabel(r"Original x-axis: $X$")
new_tick_locations = np.array([.2, .5, .9])
def tick_function(X):
V = 1/(1+X)
return ["%.3f" % z for z in V]
ax2.set_xlim(ax1.get_xlim())
ax2.set_xticks(new_tick_locations)
ax2.set_xticklabels(tick_function(new_tick_locations))
ax2.set_xlabel(r"Modified x-axis: $1/(1+X)$")
plt.show()
You can use twiny to create 2 x-axis scales. For Example:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twiny()
a = np.cos(2*np.pi*np.linspace(0, 1, 60.))
ax1.plot(range(60), a)
ax2.plot(range(100), np.ones(100)) # Create a dummy plot
ax2.cla()
plt.show()
Ref: http://matplotlib.sourceforge.net/faq/howto_faq.html#multiple-y-axis-scales
Output:
From matplotlib 3.1 onwards you may use ax.secondary_xaxis
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(1,13, num=301)
y = (np.sin(x)+1.01)*3000
# Define function and its inverse
f = lambda x: 1/(1+x)
g = lambda x: 1/x-1
fig, ax = plt.subplots()
ax.semilogy(x, y, label='DM')
ax2 = ax.secondary_xaxis("top", functions=(f,g))
ax2.set_xlabel("1/(x+1)")
ax.set_xlabel("x")
plt.show()
If You want your upper axis to be a function of the lower axis tick-values you can do as below. Please note: sometimes get_xticks() will have a ticks outside of the visible range, which you have to allow for when converting.
import matplotlib.pyplot as plt
fig, ax1 = plt.subplots()
ax1 = fig.add_subplot(111)
ax1.plot(range(5), range(5))
ax1.grid(True)
ax2 = ax1.twiny()
ax2.set_xticks( ax1.get_xticks() )
ax2.set_xbound(ax1.get_xbound())
ax2.set_xticklabels([x * 2 for x in ax1.get_xticks()])
title = ax1.set_title("Upper x-axis ticks are lower x-axis ticks doubled!")
title.set_y(1.1)
fig.subplots_adjust(top=0.85)
fig.savefig("1.png")
Gives:
Answering your question in Dhara's answer comments: "I would like on the second x-axis these tics: (7,8,99) corresponding to the x-axis position 10, 30, 40. Is that possible in some way?"
Yes, it is.
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(111)
a = np.cos(2*np.pi*np.linspace(0, 1, 60.))
ax1.plot(range(60), a)
ax1.set_xlim(0, 60)
ax1.set_xlabel("x")
ax1.set_ylabel("y")
ax2 = ax1.twiny()
ax2.set_xlabel("x-transformed")
ax2.set_xlim(0, 60)
ax2.set_xticks([10, 30, 40])
ax2.set_xticklabels(['7','8','99'])
plt.show()
You'll get:
I'm forced to post this as an answer instead of a comment due to low reputation.
I had a similar problem to Matteo. The difference being that I had no map from my first x-axis to my second x-axis, only the x-values themselves. So I wanted to set the data on my second x-axis directly, not the ticks, however, there is no axes.set_xdata. I was able to use Dhara's answer to do this with a modification:
ax2.lines = []
instead of using:
ax2.cla()
When in use also cleared my plot from ax1.

Categories