How can plot Artists be reused (Line2D)? - python

How can the plot lines from .plot be reused in subsequent plots?
I'd like to make plots on 4 axes, first three individual plot on each axes, and the last all 3 plots on last axes.
Here is the code:
from numpy import *
from matplotlib.pyplot import *
fig=figure()
data=arange(0,10,0.01)
ax1=fig.add_subplot(2,2,1)
ax2=fig.add_subplot(2,2,2)
ax3=fig.add_subplot(2,2,3)
ax4=fig.add_subplot(2,2,4)
line1=ax1.plot(data,data)
line2=ax2.plot(data, data**2/10, ls='--', color='green')
line3=ax3.plot(data, np.sin(data), color='red')
#could I somehow use previous plots, instead recreating them all?
line4=ax4.plot(data,data)
line4=ax4.plot(data, data**2/10, ls='--', color='green')
line4=ax4.plot(data, np.sin(data), color='red')
show()
The resulting picture is:
Is there a way to define plots first and then add them to axes, and then plot them? Here is the logic I had in mind:
#this is just an example, implementation can be different
line1=plot(data, data)
line2=plot(data, data**2/10, ls='--', color='green')
line3=plot(data, np.sin(data), color='red')
line4=[line1, line2, line3]
Now plot line1 on ax1, line2 on ax2, line3 on ax3 and line4 on ax4.

The requested implementation in the OP doesn't work because the Line2D plot Artist returned by plt.plot can't be reused. Trying to do so, will result in a RuntimeError as per def set_figure(self, fig):
line1 in the OP, is not the same as line1 created directly with the Line2D method, because a plotted Artist has different properties.
In regards to seaborn, and API for matplotlib, axes-level plots like seaborn.lineplot return an axes:
p = sns.lineplot(...) then p.get_children() to get the Artist objects.
Plot artists can be created directly, with methods like matplotlib.lines.Line2D, and reused in multiple plots.
Updated code using standard importing practices, subplots, and not using a list-comprehension for a side-effect (a python anti-pattern).
Tested in python 3.8.11, matplotlib 3.4.3
import numpy as np
from copy import copy
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
# crate the figure and subplots
fig, axes = plt.subplots(2, 2)
# flatten axes into 1-D for easy indexing and iteration
axes = axes.ravel()
# test data
data=np.arange(0, 10, 0.01)
# create test lines
line1 = Line2D(data, data)
line2 = Line2D(data, data**2/10, ls='--', color='green')
line3 = Line2D(data, np.sin(data), color='red')
lines = [line1, line2, line3]
# add the copies of the lines to the first 3 subplots
for ax, line in zip(axes[0:-1], lines):
ax.add_line(copy(line))
# add 3 lines to the 4th subplot
for line in lines:
axes[3].add_line(line)
# autoscale all the subplots if needed
for _a in axes:
_a.autoscale()
plt.show()
Original Answer
Here is one possible solution. I'm not sure that it's very pretty, but at least it does not require code duplication.
import numpy as np, copy
import matplotlib.pyplot as plt, matplotlib.lines as ml
fig=plt.figure(1)
data=np.arange(0,10,0.01)
ax1=fig.add_subplot(2,2,1)
ax2=fig.add_subplot(2,2,2)
ax3=fig.add_subplot(2,2,3)
ax4=fig.add_subplot(2,2,4)
#create the lines
line1=ml.Line2D(data,data)
line2=ml.Line2D(data,data**2/10,ls='--',color='green')
line3=ml.Line2D(data,np.sin(data),color='red')
#add the copies of the lines to the first 3 panels
ax1.add_line(copy.copy(line1))
ax2.add_line(copy.copy(line2))
ax3.add_line(copy.copy(line3))
[ax4.add_line(_l) for _l in [line1,line2,line3]] # add 3 lines to the 4th panel
[_a.autoscale() for _a in [ax1,ax2,ax3,ax4]] # autoscale if needed
plt.draw()

I think your usage is fine, but you can pass all of the x,y data pairs to plot like this (although it makes it very horrible to read!):
ax4.plot(data, data, data, data**2 / 10, data, np.sin(data))
An amusing different way to do it is like this:
graph_data = [(data, data), (data, data**2 / 10), (data, np.sin(data))]
[ax4.plot(i,j) for i,j in graph_data]

I had a simpler use case in jupyter notebooks. Given that you have stored a figure object somewhere, how can you replot it.
eg:
Cell 1:
f = plt.figure(figsize=(18, 6))
f.suptitle("Hierarchical Clustring", fontsize=20)
dendrogram(Z, color_threshold=cut_off,
truncate_mode='lastp',
p=20)
Cell 2:
#plot f again, the answer is really simple
f
plt.show()
That's it. The benefit of that is you can store figures in objects and later use them when necessary.

Also this question has a good example of referencing to previous axes using:
fix, ax = plt.subplots(2, 2)
ax[0,1].plot(data, data**2 / 10, ls='--', color='g')
but also explains how to insert a title on each subplot using:
ax[0,1].set_title('Simple plot')
the dimension of ax depends on subplot parameters: if they are just tiled orizontally or vertically, ax will only need one index.

Related

How to invert y axis in subplot Python

I am trying to make a subplot of three figures. All of the figures should have inverted y-axes. But when I use ´gca().invert_yaxis()´ it only inverts the last of the three subplots and not the first two. How can I invert all of the y axes?
import matplotlib.pyplot as plt
import pandas
import numpy as np
# Create the subplot
axes,(p1,p2, p3) = plt.subplots(1, 3)
# Temperature proxies
p1.plot(Ti/Mg, xrf_depth)
p1.set_title("Clastic influx (Ti/Mg) and Ti (ppm)")
plt.gca().invert_yaxis()
p2.plot(Ti/1000, xrf_depth)
ax = plt.gca()
p3.plot(Ca/Si, xrf_depth, '-g')
p3.set_title("Primary production (Ca/Si")
plt.show()
plt.gca() literally means "get current axes", so it makes sense that only the last one is affected. In your case, the three axes are p1,p2, p3, therefore p1.invert_yaxis() etc. should do the job. Delete all lines containing gca() in your code.

set xlim across multiple figures in python? [duplicate]

I'm trying to share two subplots axes, but I need to share the x axis after the figure was created. E.g. I create this figure:
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(1000)/100.
x = np.sin(2*np.pi*10*t)
y = np.cos(2*np.pi*10*t)
fig = plt.figure()
ax1 = plt.subplot(211)
plt.plot(t,x)
ax2 = plt.subplot(212)
plt.plot(t,y)
# some code to share both x axes
plt.show()
Instead of the comment I want to insert some code to share both x axes.
How do I do this? There are some relevant sounding attributes
_shared_x_axes and _shared_x_axes when I check to figure axis (fig.get_axes()) but I don't know how to link them.
The usual way to share axes is to create the shared properties at creation. Either
fig=plt.figure()
ax1 = plt.subplot(211)
ax2 = plt.subplot(212, sharex = ax1)
or
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)
Sharing the axes after they have been created should therefore not be necessary.
However if for any reason, you need to share axes after they have been created (actually, using a different library which creates some subplots, like here might be a reason), there would still be a solution:
Using
ax1.get_shared_x_axes().join(ax1, ax2)
creates a link between the two axes, ax1 and ax2. In contrast to the sharing at creation time, you will have to set the xticklabels off manually for one of the axes (in case that is wanted).
A complete example:
import numpy as np
import matplotlib.pyplot as plt
t= np.arange(1000)/100.
x = np.sin(2*np.pi*10*t)
y = np.cos(2*np.pi*10*t)
fig=plt.figure()
ax1 = plt.subplot(211)
ax2 = plt.subplot(212)
ax1.plot(t,x)
ax2.plot(t,y)
ax1.get_shared_x_axes().join(ax1, ax2)
ax1.set_xticklabels([])
# ax2.autoscale() ## call autoscale if needed
plt.show()
The other answer has code for dealing with a list of axes:
axes[0].get_shared_x_axes().join(axes[0], *axes[1:])
As of Matplotlib v3.3 there now exist Axes.sharex, Axes.sharey methods:
ax1.sharex(ax2)
ax1.sharey(ax3)
Just to add to ImportanceOfBeingErnest's answer above:
If you have an entire list of axes objects, you can pass them all at once and have their axes shared by unpacking the list like so:
ax_list = [ax1, ax2, ... axn] #< your axes objects
ax_list[0].get_shared_x_axes().join(ax_list[0], *ax_list)
The above will link all of them together. Of course, you can get creative and sub-set your list to link only some of them.
Note:
In order to have all axes linked together, you do have to include the first element of the axes_list in the call, despite the fact that you are invoking .get_shared_x_axes() on the first element to start with!
So doing this, which would certainly appear logical:
ax_list[0].get_shared_x_axes().join(ax_list[0], *ax_list[1:])
... will result in linking all axes objects together except the first one, which will remain entirely independent from the others.

Add multiple axes from different sources into same figure

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

Adjusting the x-axis label for multiple subplots [duplicate]

I'm trying to share two subplots axes, but I need to share the x axis after the figure was created. E.g. I create this figure:
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(1000)/100.
x = np.sin(2*np.pi*10*t)
y = np.cos(2*np.pi*10*t)
fig = plt.figure()
ax1 = plt.subplot(211)
plt.plot(t,x)
ax2 = plt.subplot(212)
plt.plot(t,y)
# some code to share both x axes
plt.show()
Instead of the comment I want to insert some code to share both x axes.
How do I do this? There are some relevant sounding attributes
_shared_x_axes and _shared_x_axes when I check to figure axis (fig.get_axes()) but I don't know how to link them.
The usual way to share axes is to create the shared properties at creation. Either
fig=plt.figure()
ax1 = plt.subplot(211)
ax2 = plt.subplot(212, sharex = ax1)
or
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)
Sharing the axes after they have been created should therefore not be necessary.
However if for any reason, you need to share axes after they have been created (actually, using a different library which creates some subplots, like here might be a reason), there would still be a solution:
Using
ax1.get_shared_x_axes().join(ax1, ax2)
creates a link between the two axes, ax1 and ax2. In contrast to the sharing at creation time, you will have to set the xticklabels off manually for one of the axes (in case that is wanted).
A complete example:
import numpy as np
import matplotlib.pyplot as plt
t= np.arange(1000)/100.
x = np.sin(2*np.pi*10*t)
y = np.cos(2*np.pi*10*t)
fig=plt.figure()
ax1 = plt.subplot(211)
ax2 = plt.subplot(212)
ax1.plot(t,x)
ax2.plot(t,y)
ax1.get_shared_x_axes().join(ax1, ax2)
ax1.set_xticklabels([])
# ax2.autoscale() ## call autoscale if needed
plt.show()
The other answer has code for dealing with a list of axes:
axes[0].get_shared_x_axes().join(axes[0], *axes[1:])
As of Matplotlib v3.3 there now exist Axes.sharex, Axes.sharey methods:
ax1.sharex(ax2)
ax1.sharey(ax3)
Just to add to ImportanceOfBeingErnest's answer above:
If you have an entire list of axes objects, you can pass them all at once and have their axes shared by unpacking the list like so:
ax_list = [ax1, ax2, ... axn] #< your axes objects
ax_list[0].get_shared_x_axes().join(ax_list[0], *ax_list)
The above will link all of them together. Of course, you can get creative and sub-set your list to link only some of them.
Note:
In order to have all axes linked together, you do have to include the first element of the axes_list in the call, despite the fact that you are invoking .get_shared_x_axes() on the first element to start with!
So doing this, which would certainly appear logical:
ax_list[0].get_shared_x_axes().join(ax_list[0], *ax_list[1:])
... will result in linking all axes objects together except the first one, which will remain entirely independent from the others.

Is there a function to make scatterplot matrices in matplotlib?

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!

Categories