optional context feel free to skip: I'm currently using cartopy and matplotlib to read in and plot weather model data on a map. I have three different fields I'm plotting: temperature, wind, and surface pressure. I'm using contourf, barbs, and contour respectively to plot each field. I want one image for each field, and then I'd like one image that contains all three fields overlaid on a single map. Currently I'm doing this by plotting each field individually, saving each of the individual images, then replotting all three fields on a single ax and a new fig, and saving that fig. Since the data takes a while to plot, I would like to be able to plot each of the single fields, then combine the axes into one final image.
I'd like to be able to combine multiple matplotlib axes without replotting the data on the axes. I'm not sure if this is possible, but doing so would be a pretty major time and performance saver. An example of what I'm talking about:
from matplotlib import pyplot as plt
import numpy as np
x1 = np.linspace(0, 2*np.pi, 100)
x2 = x1 + 5
y = np.sin(x1)
firstFig = plt.figure()
firstAx = firstFig.gca()
firstAx.scatter(x1, y, 1, "red")
firstAx.set_xlim([0, 12])
secondFig = plt.figure()
secondAx = secondFig.gca()
secondAx.scatter(x2, y, 1, "blue")
secondAx.set_xlim([0, 12])
firstFig.savefig("1.png")
secondFig.savefig("2.png")
This generates two images, 1.png and 2.png.
Is it possible to save a third file, 3.png that would look something like the following, but without calling scatter again, because for my dataset, the actual plotting takes a long time?
If you just want to save images of your plots and you don't intend to further use the Figure objects, you can use the following after saving "2.png".
# get the scatter object from the first figure
scatter = firstAx.get_children()[0]
# remove it from this collection so you can assign it to a new axis
# the axis reassignment will raise an error if it already belongs to another axis
scatter.remove()
scatter.axes = secondAx
# now you can add it to your new axis
secondAx.add_artist(scatter)
secondFig.savefig("3.png")
This modifies both figures, as it removes a scatter from one and adds it to another. If for some reason you want to preserve them, you can copy the contents of secondFig to a new one and then add the scatter to that. However, this will still modify the first plot as you have to remove the scatter from there.
Related
N.B.: I have edited the question as it was probably unclear: I am looking for the best method to understand the type of plot in a given axis.
QUESTION:
I am trying to make a generic function which can arrange multiple figures as subplots.
As I loop over the subplots to set some properties (e.g. axis range) iterating over fig.axes, I need to understand which type every plot is in order to determine which properties I want to set for each of them (e.g. I want to set x range on images and line plots, but not on colorbar, otherwise my plot will explode).
My question is then how I can distinguish between different types.
I tried to play with try and except and select on the basis of different properties for different plot types, but they seem to be the same for all of them, so, at the moment, the best way I found is to check the content of each axis: in particular ax.images is a non empty list if a plot is an image, and ax.lines is not empty if it is a line plot, (and a colorbar has both empty).
This works for simple plots, but I wonder if this is still the best way and still working for more complex cases (e.g. insets, overlapped lines and images, subclasses)?
This is just an example to illustrate how the different type of plots can be accessed, with the following code creating three axes l, i and cb (respectively line, image, colorbar):
# create test figure
plt.figure()
b = np.arange(12).reshape([4,3])
plt.subplot(121)
plt.plot([1,2,3],[4,5,6])
plt.subplot(122)
plt.imshow(b)
plt.colorbar()
# create test objects
ax=plt.gca()
fig=plt.gcf()
l,i,cb = fig.axes
# do a simple test, images are different:
for o in l,i,cb: print(len(o.images))
# this also doesn't work in finding properties not in common between lines and colobars, gives empty list.
[a for a in dir(l) if a not in dir(cb)]
After creating the image above in IPython
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.cm import ScalarMappable
fig, ax = plt.subplots()
ax.imshow(((0,1),(2,3)))
ax.scatter((0,1),(0,1), fc='w', ec='k')
ax.plot((0,1),(0,1))
fig.colorbar(ScalarMappable(), ax=ax)
plt.show()
I tried to investigate
In [48]: fig.axes
Out[48]: [<AxesSubplot:>, <AxesSubplot:label='<colorbar>'>]
I can recognize that one of the two axes is a colorbar — but it's easy to inspect the content of the individual axes
In [49]: fig.axes[0]._children
Out[49]:
[<matplotlib.image.AxesImage at 0x7fad9dda2b30>,
<matplotlib.collections.PathCollection at 0x7fad9dad04f0>,
<matplotlib.lines.Line2D at 0x7fad9dad09d0>]
In [50]: fig.axes[1]._children
Out[50]:
[<matplotlib.patches.Polygon at 0x7fad9db525f0>,
<matplotlib.collections.LineCollection at 0x7fad9db52830>,
<matplotlib.collections.QuadMesh at 0x7fad9dad2320>]
I have to remind you that
Matplotib provides you with many different container objects,
You can store the Axes destination in a list, or a dictionary, when you use it — you can even say ax.ax_type = 'lineplot'.
That said, e.g.,
from matplotlib.pyplot import subplots, plot
fig, ax = subplots()
plot((1, 2), (2, 1))
...
axes_types = []
for ax_i in fig.axes:
try:
ax_i.__getattr__('get_clabel')
axes_types.append('colorbar')
except AttributeError:
axes_types.append('lineplot')
...
In other word, chose a method that is unique to each one of the differnt types you're testing and check if it's available.
I am working on a visualization script for a linear algebra class at the university and I am trying to show multiple vectors using the quiver function in python. I am trying to plot vectors coming from a 2x2 matrix in one quiver function, however, now that I am trying to label them I would like to access each vector individually.
import numpy as np
import matplotlib.pyplot as plt
A = np.array([[1,3], [2,2]])
# create figure
fig = plt.figure()
# creates variable containing current figure
ax = fig.gca()
baseArrow = ax.quiver(*origin, A[0,:], A[1,:], color=['r','g']', angles='xy', scale_units='xy', scale=1)
ax.quiverkey(baseArrow,.85,.85,0.8,'i-hat',labelcolor='k',labelpos='S', coordinates = 'figure')
# display grid
plt.grid()
# display figure
plt.show()
This alows me to label the first vector with the respective color (red). Now what I would like to do is label the second vector in green with a different label?
Maybe something like:
ax.quiverkey(baseArrow**[2]**,.85,.85,0.8,'i-hat',labelcolor='k',labelpos='S', coordinates = 'figure')
Is there any way to pull out each vector by itself or would it be better to plot them individually instead of as a vector? I looked at the following question but it doesn't really solve my issue. Matplotlib Quiver plot matching key label color with arrow color
My feeling is that the quiver function is better suited/intended to plot numerous vectors as you would find in a graph depicting magnetic forces, vortices (sic) or gradients (see meshgrid for example). And it's API reflects that, in that it accepts end and start coordinates separately: i.e. you need to split the components of your vectors as you have done above.
May I suggest you look into the plot or arrow functions which will give you greater control over your visualization (e.g. vector-independent labels) and will also provide greater clarity in your code, as you will be able to declare vectors (as np.arrays of course) and use them directly.
Finally note that you can obtain fig and ax in one call: fib, ax = plt.subplots().
Hope this helps!
I've started working more with figures and axes, and at first blush it seems to be really nice: an axes object can be independently created and manipulated (either by adding plots to it, or changing scale/etc), however the issue I'm running into is that it appears that "Figure" is the only class that can control layout of axes objects.
I would like to do something like this:
def plot_side_by_side(lefts, rights, coupled=True, width_ratios=[2,1]):
import matplotlib.gridspec as gridspec
# lefts and rights are lists of functions that
# take axes objects as keywords, the length of this
# object is the number of subplots we have:
plots = list(zip(lefts, rights))
y_size = len(plots)
# create figure with a number of subplots:
fig = plt.figure(figsize=(10,y_size * 4))
gs = gridspec.GridSpec(y_size,2,width_ratios=width_ratios,height_ratios=[1 for _ in plots])
#get axes on the left
cleft_axes = [plt.subplot(gs[0,0])]
if y_size > 1:
cleft_axes += [plt.subplot(gs[i,0], sharex=cleft_axes[0]) for i in range(1,y_size)]
[plt.setp(ax.get_xticklabels(), visible=False) for ax in cleft_axes[:-1]]
# get axes on the right, if coupled we fix the yaxes
# together, otherwise we don't
if coupled:
yaxes = cleft_axes
else:
yaxes = [None for _ in cleft_axes]
cright_axes = [plt.subplot(gs[0,1], sharey=yaxes[0])]
if y_size > 1:
cright_axes += [plt.subplot(gs[i,1], sharey=yaxes[i], sharex=cright_axes[0]) for i in range(1,y_size)]
[plt.setp(ax.get_xticklabels(), visible=False) for ax in cright_axes[:-1]]
# for each plot in our list, give it an axes object if it is on
# the left or right. Now this function will plot on that axes
for (pl, pr), l, r, name in zip(plots,cleft_axes,cright_axes,names):
pl(ax=l)
pr(ax=r)
return fig
And I would like to be able to create a function that takes a axes object as a keyword and puts two plots on it:
def twoplots(ax=ax):
# make a grid of axes, allow them to be plotted to, etc.
# this is all within the space given me by `ax`.
Is this possible? How would I go about doing such a thing? I know that I can get the figure from the axes object that is passed, is it possible to modify the parent gridspec without messing up every other gridspec?
Hope I'm not committing a foul reviving a thread this old. I wanted to give some extra context on what I think the OP is trying to do. (At least I hope it is what he's trying to do, because I'm trying to do the same thing.)
Suppose I have a statistical model that is composed of K submodels of different types. I want the submodels to plot themselves. Most of the time, in the typical case, each submodel will plot itself on an axes object. Occasionally, a submodel might need multiple axes to plot itself.
For example: suppose a model is a time series model, and the submodels are showing trend, seasonality, regression effects, holiday effects, etc. If the seasonal effect is showing annual seasonality it will plot itself just like the trend model (its effect vs time). But if it is showing day of week seasonality the plot vs time will be ineffective, because the lines will wiggle too fast. It would be more effective to plot the time series of Mondays, then the time series of Tuesdays, etc. To fit in with the larger scheme you want this cluster of 7 plots to be "the seasonality plot."
With K submodels you can often start off with
fig, ax = plt.submodels(K) and then pass ax[k] to the submodel as model.submodel[k].plot(ax[k]). The question is what to do when you'd like to plot the day-of-week seasonality effect described above on ax[k].
One answer might be "don't use this mechanism: use GridSpec or something else." But that's what I think the question is asking.
Let's look at a swarmplot, made with Python 3.5 and Seaborn on some data (which is stored in a pandas dataframe df with column lables stored in another class. This does not matter for now, just look at the plot):
ax = sns.swarmplot(x=self.dte.label_temperature, y=self.dte.label_current, hue=self.dte.label_voltage, data = df)
Now the data is more readable if plotted in log scale on the y-axis because it goes over some decades.
So let's change the scaling to logarithmic:
ax.set_yscale("log")
ax.set_ylim(bottom = 5*10**-10)
Well I have a problem with the gaps in the swarms. I guess they are there because they have been there when the plot is created with a linear axis in mind and the dots should not overlap there. But now they look kind of strange and there is enough space to from 4 equal looking swarms.
My question is: How can I force seaborn to recalculate the position of the dots to create better looking swarms?
mwaskom hinted to me in the comments how to solve this.
It is even stated in the swamplot doku:
Note that arranging the points properly requires an accurate transformation between data and point coordinates. This means that non-default axis limits should be set before drawing the swarm plot.
Setting an existing axis to log-scale and use this for the plot:
fig = plt.figure() # create figure
rect = 0,0,1,1 # create an rectangle for the new axis
log_ax = fig.add_axes(rect) # create a new axis (or use an existing one)
log_ax.set_yscale("log") # log first
sns.swarmplot(x=self.dte.label_temperature, y=self.dte.label_current, hue=self.dte.label_voltage, data = df, ax = log_ax)
This yields in the correct and desired plotting behaviour:
I am looping through a bunch of CSV files containing various measurements.
Each file might be from one of 4 different data sources.
In each file, I merge the data into monthly datasets, that I then plot in a 3x4 grid. After this plot has been saved, the loop moves on and does the same to the next file.
This part I got figured out, however I would like to add a visual clue to the plots, as to what data it is. As far as I understand it (and tried it)
plt.subplot(4,3,1)
plt.hist(Jan_Data,facecolor='Red')
plt.ylabel('value count')
plt.title('January')
does work, however this way, I would have to add the facecolor='Red' by hand to every 12 subplots. Looping through the plots wont work for this situation, since I want the ylabel only for the leftmost plots, and xlabels for the bottom row.
Setting facecolor at the beginning in
fig = plt.figure(figsize=(20,15),facecolor='Red')
does not work, since it only changes the background color of the 20 by 15 figure now, which subsequently gets ignored when I save it to a PNG, since it only gets set for screen output.
So is there just a simple setthecolorofallbars='Red' command for plt.hist(… or plt.savefig(… I am missing, or should I just copy n' paste it to all twelve months?
You can use mpl.rc("axes", color_cycle="red") to set the default color cycle for all your axes.
In this little toy example, I use the with mpl.rc_context block to limit the effects of mpl.rc to just the block. This way you don't spoil the default parameters for your whole session.
import matplotlib as mpl
import matplotlib.pylab as plt
import numpy as np
np.random.seed(42)
# create some toy data
n, m = 2, 2
data = []
for i in range(n*m):
data.append(np.random.rand(30))
# and do the plotting
with mpl.rc_context():
mpl.rc("axes", color_cycle="red")
fig, axes = plt.subplots(n, m, figsize=(8,8))
for ax, d in zip(axes.flat, data):
ax.hist(d)
The problem with the x- and y-labels (when you use loops) can be solved by using plt.subplots as you can access every axis seperately.
import matplotlib.pyplot as plt
import numpy.random
# creating figure with 4 plots
fig,ax = plt.subplots(2,2)
# some data
data = numpy.random.randn(4,1000)
# some titles
title = ['Jan','Feb','Mar','April']
xlabel = ['xlabel1','xlabel2']
ylabel = ['ylabel1','ylabel2']
for i in range(ax.size):
a = ax[i/2,i%2]
a.hist(data[i],facecolor='r',bins=50)
a.set_title(title[i])
# write the ylabels on all axis on the left hand side
for j in range(ax.shape[0]):
ax[j,0].set_ylabel(ylabel[j])
# write the xlabels an all axis on the bottom
for j in range(ax.shape[1]):
ax[-1,j].set_xlabel(xlabels[j])
fig.tight_layout()
All features (like titles) which are not constant can be put into arrays and placed at the appropriate axis.