I have created a grid of subplots to my liking.
I initiated the plotting by defining fig,ax = plt.subplots(2,6,figsize=(24,8))
So far so good. I filled those subplots with their respective content. Now I want to plot a single or two particular subplot in isolation. I tried:
ax[idx][idx].plot()
This does not work and returns an empty list
I have tried:
fig_single,ax_single = plt.subplots(2,1)
ax_single[0]=ax[idx][0]
ax_single[1]=ax[idx][1]
This returns:
TypeError: 'AxesSubplot' object does not support item assignment
How do I proceed without plotting those subplots again by calling the respective plot functions?
You're close.
fig,ax = plt.subplots(nrows=2,ncols=6,sharex=False,sharey=False,figsize=(24,8))
#set either sharex=True or sharey=True if you wish axis limits to be shared
#=> very handy for interactive exploration of timeseries data, ...
r=0 #first row
c=0 #first column
ax[r,c].plot() #plot your data, instead of ax[r][c].plot()
ax[r,c].set_title() #name title for a subplot
ax[r,c].set_ylabel('Ylabel ') #ylabel for a subplot
ax[r,c].set_xlabel('X axis label') #xlabel for a subplot
A more complete/flexible method is to assign r,c:
for i in range(nrows*ncols):
r,c = np.divmod(i,ncols)
ax[r,c].plot() #....
You can afterwards still make modifications, e.g. set_ylim, set_title, ...
So if you want to name the label of the 11th subplot:
ax[2,4].set_ylabel('11th subplot ylabel')
You will often want to make use of fig.tight_layout() at the end, so that the figure uses the available area correctly.
Complete example:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0,180,180)
nrows = 2
ncols = 6
fig,ax = plt.subplots(nrows=nrows,ncols=ncols,sharex=False,sharey=False,figsize=(24,8))
for i in range(nrows*ncols):
r,c = np.divmod(i,ncols)
y = np.sin(x*180/np.pi*(i+1))
ax[r,c].plot(x,y)
ax[r,c].set_title('%s'%i)
fig.suptitle('Overall figure title')
fig.tight_layout()
Related
I'm trying to display a figure that contains 3 plots, and each of the plots is a plot of (8,1)-shaped subplots.
Essentially, I want one big figure with three sections each containing (8,1)-shaped subplots.
I'm looking for a way to do this without having to manually set all the proportions and spacings. The reason I'm doing this is to visualize an 8-channel neural signal compared to three other pre-defined signals, each signal being 8 channels.
If it makes any sense this way, I'm trying for something like this (ficticious code):
fig, ax = plt.subplots(n_figures = 3, n_rows = 8, n_cols = 1)
ax[figure_i, row_j, col_k].imshow(image)
Is there a way to do this?
Here is an example of what I am talking about. Ideally it would three subplots, and in each of the subplots there is a set of subplots of shape 8x1. I understand how to plot this all out by going through all the margins and setting the proportions, but I'm wondering if there's a simpler way to do this without having to go through all the additional code and settings as described in the above example code I've written.
You can create this kind of figure by first creating a subplot grid with the appropriate layout using the plt.subplots() function and then looping through the array of axes to plot the data, like in this example:
import numpy as np # v 1.19.2
import matplotlib.pyplot as plt # v 3.3.2
# Create sample signal data as a 1-D list of arrays representing 3x8 channels
signal_names = ['X1', 'X2', 'X3']
nsignals = len(signal_names) # ncols of the subplot grid
nchannels = 8 # nrows of the subplot grid
nsubplots = nsignals*nchannels
x = np.linspace(0, 14*np.pi, 100)
y_signals = nsubplots*[np.cos(x)]
# Set subplots width and height
subp_w = 10/nsignals # 10 corresponds the figure width in inches
subp_h = 0.25*subp_w
# Create figure and subplot grid with the appropriate layout and dimensions
fig, axs = plt.subplots(nchannels, nsignals, sharex=True, sharey=True,
figsize=(nsignals*subp_w, nchannels*subp_h))
# Optionally adjust the space between the subplots: this can also be done by
# adding 'gridspec_kw=dict(wspace=0.1, hspace=0.3)' to the above function
# fig.subplots_adjust(wspace=0.1, hspace=0.3)
# Loop through axes to create plots: note that the list of axes is transposed
# in this example to plot the signals one after the other column-wise, as
# indicated by the colors representing the channels
colors = nsignals*plt.get_cmap('tab10').colors[:nchannels]
for idx, ax in enumerate(axs.T.flat):
ax.plot(x, y_signals[idx], c=colors[idx])
if ax.is_first_row():
ax.set_title(signal_names[idx//nchannels], pad=15, fontsize=14)
plt.show()
I am using Python/matplotlib to create a figure whereby it has three subplots, each returned from a different 'source' or class method.
For example, I have a script called 'plot_spectra.py' that contains the Spectra() class with method Plot().
So, calling Spectra('filename.ext').Plot() will return a tuple, as per the code below:
# create the plot
fig, ax = plt.subplots()
ax.contour(xx, yy, plane, levels=cl, cmap=cmap)
ax.set_xlim(ppm_1h_0, ppm_1h_1)
ax.set_ylim(ppm_13c_0, ppm_13c_1)
# return the contour plot
return fig, ax
It is my understanding that the 'figure' is the 'window' in matplotlib, and the 'ax' is an individual plot. I would then want to say, plot three of these 'ax' objects in the same figure, but I am struggling to do so because I keep getting an empty window and I think I have misunderstood what each object actually is.
Calling:
hnca, hnca_ax = Spectra('data/HNCA.ucsf', type='sparky').Plot(plane_ppm=resi.N(), vline=resi.H())
plt.subplot(2,2,1)
plt.subplot(hnca_ax)
eucplot, barplot = PlotEucXYIntensity(scores, x='H', y='N')
plt.subplot(2,2,3)
plt.subplot(eucplot)
plt.subplot(2,2,4)
plt.subplot(barplot)
plt.show()
Ultimately, what I am trying to obtain is a single window that looks like this:
Where each plot has been returned from a different function or class method.
What 'object' do I need to return from my functions? And how do I incorporate these three objects into a single figure?
I would suggest this kind of approach, where you specify the ax on which you want to plot in the function:
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
def Spectra(data, ax):
ax.plot(data)
def PlotIntensity(data, ax):
ax.hist(data)
def SeabornScatter(data, ax):
sns.scatterplot(data, data, ax = ax)
spectrum = np.random.random((1000,))
plt.figure()
ax1 = plt.subplot(1,3,1)
Spectra(spectrum, ax1)
ax2 = plt.subplot(1,3,2)
SeabornScatter(spectrum, ax2)
ax3 = plt.subplot(1,3,3)
PlotIntensity(spectrum, ax3)
plt.tight_layout()
plt.show()
You can specify the grid for the subplots in very different ways, and you probably also want to have a look on the gridspec module.
One way to do this is:
f = plt.figure()
gs = f.add_gridspec(2,2)
ax = f.add_subplot(gs[0,:])
Think of the '2,2' as adding 2 row x 2 columns.
On the third line 'gs[0,:]' is telling to add a chart on row 0, all columns. This will create the chart on the top of your top. Note that indices begin with 0 and not with 1.
To add the 'eucplot' you will have to call a different ax on row 1 and column 0:
ax2 = f.add_subplot(gs[1,0])
Lastly, the 'barplot' will go in yet a different ax on row 1, column 1:
ax3 = f.add_subplot(gs[1,1])
See this site here for further reference: Customizing Figure Layouts Using GridSpec and Other Functions
Is there a way to add a secondary legend to a scatterplot, where the size of the scatter is proportional to some data?
I have written the following code that generates a scatterplot. The color of the scatter represents the year (and is taken from a user-defined df) while the size of the scatter represents variable 3 (also taken from a df but is raw data):
import pandas as pd
colors = pd.DataFrame({'1985':'red','1990':'b','1995':'k','2000':'g','2005':'m','2010':'y'}, index=[0,1,2,3,4,5])
fig = plt.figure()
ax = fig.add_subplot(111)
for i in df.keys():
df[i].plot(kind='scatter',x='variable1',y='variable2',ax=ax,label=i,s=df[i]['variable3']/100, c=colors[i])
ax.legend(loc='upper right')
ax.set_xlabel("Variable 1")
ax.set_ylabel("Variable 2")
This code (with my data) produces the following graph:
So while the colors/years are well and clearly defined, the size of the scatter is not.
How can I add a secondary or additional legend that defines what the size of the scatter means?
You will need to create the second legend yourself, i.e. you need to create some artists to populate the legend with. In the case of a scatter we can use a normal plot and set the marker accordingly.
This is shown in the below example. To actually add a second legend we need to add the first legend to the axes, such that the new legend does not overwrite the first one.
import matplotlib.pyplot as plt
import matplotlib.colors
import numpy as np; np.random.seed(1)
import pandas as pd
plt.rcParams["figure.subplot.right"] = 0.8
v = np.random.rand(30,4)
v[:,2] = np.random.choice(np.arange(1980,2015,5), size=30)
v[:,3] = np.random.randint(5,13,size=30)
df= pd.DataFrame(v, columns=["x","y","year","quality"])
df.year = df.year.values.astype(int)
fig, ax = plt.subplots()
for i, (name, dff) in enumerate(df.groupby("year")):
c = matplotlib.colors.to_hex(plt.cm.jet(i/7.))
dff.plot(kind='scatter',x='x',y='y', label=name, c=c,
s=dff.quality**2, ax=ax)
leg = plt.legend(loc=(1.03,0), title="Year")
ax.add_artist(leg)
h = [plt.plot([],[], color="gray", marker="o", ms=i, ls="")[0] for i in range(5,13)]
plt.legend(handles=h, labels=range(5,13),loc=(1.03,0.5), title="Quality")
plt.show()
Have a look at http://matplotlib.org/users/legend_guide.html.
It shows how to have multiple legends (about halfway down) and there is another example that shows how to set the marker size.
If that doesn't work, then you can also create a custom legend (last example).
For a single boxplot, the tick labels alignment can be controlled like so:
import matplotlib.pyplot as plt
import matplotlib as mpl
%matplotlib inline
fig,ax = plt.subplots()
df.boxplot(column='col1',by='col2',rot=45,ax=ax)
plt.xticks(ha='right')
This is necessary because when the tick labels are long, it is impossible to read the plot if the tick labels are centered (the default behavior).
Now on to the case of multiple subplots. (I am sorry I am not posting a complete code example). I build the main figure first:
fig,axarr = plt.subplots(ny,nx,sharex=True,sharey=True,figsize=(12,6),squeeze=False)
then comes a for loop that iterates over all the subplot axes and calls a function that draws a boxplot in each of the axes objects:
for key,gr in grouped:
ix = i/ny # Python 2
iy = i%ny
add_box_plot(gr,xcol,axarr[iy,ix])
where
def add_box_plot(gs,xcol,ax):
gs.boxplot(column=xcol,by=keyCol,rot=45,ax=ax)
I have not found a way to get properly aligned tick labels.
If I add
plt.xticks(ha='right')
after the boxplot command in the function, only the last subplot gets the ticks aligned correctly (why?).
If I add plt.xticks(ha='right') after the boxplot command in the function, only the last subplot gets the ticks aligned correctly (why?).
This happens because plt.xticks refers to the last active axes. When you crate subplots, the one created last is active. You then access the axes opbjects directly(although they are called gs or gr in your code, whatever that means). However, this does not change the active axis.
Solution 1:
Use plt.sca() to set the current axis:
def add_box_plot(gs, xcol, ax):
gs.boxplot(column=xcol, by=keyCol, rot=45, ax=ax)
plt.sca(ax)
plt.xticks(ha='right')
Solution 2:
Use Axes.set_xticklabels() instead:
def add_box_plot(gs, xcol, ax):
gs.boxplot(column=xcol,by=keyCol,rot=45,ax=ax)
plt.draw() # This may be required to update the labels
labels = [l.get_text() for l in ax.get_xticklabels()]
ax.set_xticklabels(labels, ha='right')
I'm not sure if the call to plt.draw() is always required, but if I leave it out I only get empty labels.
Since you are using the mpl object oriented interface, you can set the tick parameters for each axis individually.
add a line to set the xticklabels within your add_box_plot function (after gs.boxplot). Unlike plt.xticks, you cannot just give set_xticklabels the ha keyword, it also requires you to give it a list of tick labels. Here, we can just grab the existing labels with get_xticklabels:
def add_box_plot(gs,xcol,ax):
gs.boxplot(column=xcol,by=keyCol,rot=45,ax=ax)
ax.set_xticklabels(ax.get_xticklabels(),ha='right')
Here's a minimal example to show this working:
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
# We'll create two subplots, to test out different alignments
fig,(ax1,ax2) = plt.subplots(2)
# A sample dataframe
df = pd.DataFrame(np.random.rand(10, 5), columns=['A', 'B', 'C', 'D', 'E'])
# Boxplot on first subplot
df.boxplot(ax=ax1)
# Boxplot on second subplot
df.boxplot(ax=ax2)
# Set xticklabels to right alignment
ax1.set_xticklabels(ax1.get_xticklabels(),ha='right')
# Set xticklabels to left alignment
ax2.set_xticklabels(ax2.get_xticklabels(),ha='left')
plt.show()
Notice the xticklabels are right-aligned on the top subplot, and left-aligned on the bottom.
Example of scatterplot matrix
Is there such a function in matplotlib.pyplot?
For those who do not want to define their own functions, there is a great data analysis libarary in Python, called Pandas, where one can find the scatter_matrix() method:
from pandas.plotting import scatter_matrix
df = pd.DataFrame(np.random.randn(1000, 4), columns = ['a', 'b', 'c', 'd'])
scatter_matrix(df, alpha = 0.2, figsize = (6, 6), diagonal = 'kde')
Generally speaking, matplotlib doesn't usually contain plotting functions that operate on more than one axes object (subplot, in this case). The expectation is that you'd write a simple function to string things together however you'd like.
I'm not quite sure what your data looks like, but it's quite simple to just build a function to do this from scratch. If you're always going to be working with structured or rec arrays, then you can simplify this a touch. (i.e. There's always a name associated with each data series, so you can omit having to specify names.)
As an example:
import itertools
import numpy as np
import matplotlib.pyplot as plt
def main():
np.random.seed(1977)
numvars, numdata = 4, 10
data = 10 * np.random.random((numvars, numdata))
fig = scatterplot_matrix(data, ['mpg', 'disp', 'drat', 'wt'],
linestyle='none', marker='o', color='black', mfc='none')
fig.suptitle('Simple Scatterplot Matrix')
plt.show()
def scatterplot_matrix(data, names, **kwargs):
"""Plots a scatterplot matrix of subplots. Each row of "data" is plotted
against other rows, resulting in a nrows by nrows grid of subplots with the
diagonal subplots labeled with "names". Additional keyword arguments are
passed on to matplotlib's "plot" command. Returns the matplotlib figure
object containg the subplot grid."""
numvars, numdata = data.shape
fig, axes = plt.subplots(nrows=numvars, ncols=numvars, figsize=(8,8))
fig.subplots_adjust(hspace=0.05, wspace=0.05)
for ax in axes.flat:
# Hide all ticks and labels
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
# Set up ticks only on one side for the "edge" subplots...
if ax.is_first_col():
ax.yaxis.set_ticks_position('left')
if ax.is_last_col():
ax.yaxis.set_ticks_position('right')
if ax.is_first_row():
ax.xaxis.set_ticks_position('top')
if ax.is_last_row():
ax.xaxis.set_ticks_position('bottom')
# Plot the data.
for i, j in zip(*np.triu_indices_from(axes, k=1)):
for x, y in [(i,j), (j,i)]:
axes[x,y].plot(data[x], data[y], **kwargs)
# Label the diagonal subplots...
for i, label in enumerate(names):
axes[i,i].annotate(label, (0.5, 0.5), xycoords='axes fraction',
ha='center', va='center')
# Turn on the proper x or y axes ticks.
for i, j in zip(range(numvars), itertools.cycle((-1, 0))):
axes[j,i].xaxis.set_visible(True)
axes[i,j].yaxis.set_visible(True)
return fig
main()
You can also use Seaborn's pairplot function:
import seaborn as sns
sns.set()
df = sns.load_dataset("iris")
sns.pairplot(df, hue="species")
Thanks for sharing your code! You figured out all the hard stuff for us. As I was working with it, I noticed a few little things that didn't look quite right.
[FIX #1] The axis tics weren't lining up like I would expect (i.e., in your example above, you should be able to draw a vertical and horizontal line through any point across all plots and the lines should cross through the corresponding point in the other plots, but as it sits now this doesn't occur.
[FIX #2] If you have an odd number of variables you are plotting with, the bottom right corner axes doesn't pull the correct xtics or ytics. It just leaves it as the default 0..1 ticks.
Not a fix, but I made it optional to explicitly input names, so that it puts a default xi for variable i in the diagonal positions.
Below you'll find an updated version of your code that addresses these two points, otherwise preserving the beauty of your code.
import itertools
import numpy as np
import matplotlib.pyplot as plt
def scatterplot_matrix(data, names=[], **kwargs):
"""
Plots a scatterplot matrix of subplots. Each row of "data" is plotted
against other rows, resulting in a nrows by nrows grid of subplots with the
diagonal subplots labeled with "names". Additional keyword arguments are
passed on to matplotlib's "plot" command. Returns the matplotlib figure
object containg the subplot grid.
"""
numvars, numdata = data.shape
fig, axes = plt.subplots(nrows=numvars, ncols=numvars, figsize=(8,8))
fig.subplots_adjust(hspace=0.0, wspace=0.0)
for ax in axes.flat:
# Hide all ticks and labels
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
# Set up ticks only on one side for the "edge" subplots...
if ax.is_first_col():
ax.yaxis.set_ticks_position('left')
if ax.is_last_col():
ax.yaxis.set_ticks_position('right')
if ax.is_first_row():
ax.xaxis.set_ticks_position('top')
if ax.is_last_row():
ax.xaxis.set_ticks_position('bottom')
# Plot the data.
for i, j in zip(*np.triu_indices_from(axes, k=1)):
for x, y in [(i,j), (j,i)]:
# FIX #1: this needed to be changed from ...(data[x], data[y],...)
axes[x,y].plot(data[y], data[x], **kwargs)
# Label the diagonal subplots...
if not names:
names = ['x'+str(i) for i in range(numvars)]
for i, label in enumerate(names):
axes[i,i].annotate(label, (0.5, 0.5), xycoords='axes fraction',
ha='center', va='center')
# Turn on the proper x or y axes ticks.
for i, j in zip(range(numvars), itertools.cycle((-1, 0))):
axes[j,i].xaxis.set_visible(True)
axes[i,j].yaxis.set_visible(True)
# FIX #2: if numvars is odd, the bottom right corner plot doesn't have the
# correct axes limits, so we pull them from other axes
if numvars%2:
xlimits = axes[0,-1].get_xlim()
ylimits = axes[-1,0].get_ylim()
axes[-1,-1].set_xlim(xlimits)
axes[-1,-1].set_ylim(ylimits)
return fig
if __name__=='__main__':
np.random.seed(1977)
numvars, numdata = 4, 10
data = 10 * np.random.random((numvars, numdata))
fig = scatterplot_matrix(data, ['mpg', 'disp', 'drat', 'wt'],
linestyle='none', marker='o', color='black', mfc='none')
fig.suptitle('Simple Scatterplot Matrix')
plt.show()
Thanks again for sharing this with us. I have used it many times! Oh, and I re-arranged the main() part of the code so that it can be a formal example code or not get called if it is being imported into another piece of code.
While reading the question I expected to see an answer including rpy. I think this is a nice option taking advantage of two beautiful languages. So here it is:
import rpy
import numpy as np
def main():
np.random.seed(1977)
numvars, numdata = 4, 10
data = 10 * np.random.random((numvars, numdata))
mpg = data[0,:]
disp = data[1,:]
drat = data[2,:]
wt = data[3,:]
rpy.set_default_mode(rpy.NO_CONVERSION)
R_data = rpy.r.data_frame(mpg=mpg,disp=disp,drat=drat,wt=wt)
# Figure saved as eps
rpy.r.postscript('pairsPlot.eps')
rpy.r.pairs(R_data,
main="Simple Scatterplot Matrix Via RPy")
rpy.r.dev_off()
# Figure saved as png
rpy.r.png('pairsPlot.png')
rpy.r.pairs(R_data,
main="Simple Scatterplot Matrix Via RPy")
rpy.r.dev_off()
rpy.set_default_mode(rpy.BASIC_CONVERSION)
if __name__ == '__main__': main()
I can't post an image to show the result :( sorry!