This question already has answers here:
How to set some xlim and ylim in Seaborn lmplot facetgrid
(2 answers)
Closed 4 years ago.
Most seaborn plotting functions (e.g. seaborn.barplot, seaborn.regplot) return a matplotlib.pyplot.axes when called, so that you can use this object to further customize the plot as you see fit.
However, I wanted to create an seaborn.lmplot, which doesn't return the axes object. After digging through the documentation of both seaborn.lmplot and seaborn.FacetGrid (which lmplot uses in it's backend), I found no way of accessing the underlying axes objects. Moreover, while most other seaborn functions allow you to pass your own axes as a parameter on which they will draw the plot on, lmplot doesn't.
One thing I thought of is using plt.gca(), but that only returns the last axes object of the grid.
Is there any way of accessing the axes objects in seaborn.lmplot or seaborn.FacetGrid?
Yes, you can access the matplotlib.pyplot.axes object like this:
import seaborn as sns
lm = sns.lmplot(...) # draw a grid of plots
ax = lm.axes # access a grid of 'axes' objects
Here, ax is an array containing all axes objects in the subplot. You can access each one like this:
ax.shape # see the shape of the array containing the 'axes' objects
ax[0, 0] # the top-left (first) subplot
ax[i, j] # the subplot on the i-th row of the j-th column
If there is only one subplot you can either access it as I showed above (with ax[0, 0]) or as you said in your question through (plt.gca())
Related
In a previous answer it was recommended to me to use add_subplot instead of add_axes to show axes correctly, but searching the documentation I couldn't understand when and why I should use either one of these functions.
Can anyone explain the differences?
Common grounds
Both, add_axes and add_subplot add an axes to a figure. They both return a (subclass of a) matplotlib.axes.Axes object.
However, the mechanism which is used to add the axes differs substantially.
add_axes
The calling signature of add_axes is add_axes(rect), where rect is a list [x0, y0, width, height] denoting the lower left point of the new axes in figure coodinates (x0,y0) and its width and height. So the axes is positionned in absolute coordinates on the canvas. E.g.
fig = plt.figure()
ax = fig.add_axes([0,0,1,1])
places a figure in the canvas that is exactly as large as the canvas itself.
add_subplot
The calling signature of add_subplot does not directly provide the option to place the axes at a predefined position. It rather allows to specify where the axes should be situated according to a subplot grid. The usual and easiest way to specify this position is the 3 integer notation,
fig = plt.figure()
ax = fig.add_subplot(231)
In this example a new axes is created at the first position (1) on a grid of 2 rows and 3 columns. To produce only a single axes, add_subplot(111) would be used (First plot on a 1 by 1 subplot grid). (In newer matplotlib versions, add_subplot() without any arguments is possible as well.)
The advantage of this method is that matplotlib takes care of the exact positioning. By default add_subplot(111) would produce an axes positioned at [0.125,0.11,0.775,0.77] or similar, which already leaves enough space around the axes for the title and the (tick)labels. However, this position may also change depending on other elements in the plot, titles set, etc.
It can also be adjusted using pyplot.subplots_adjust(...) or pyplot.tight_layout().
In most cases, add_subplot would be the prefered method to create axes for plots on a canvas. Only in cases where exact positioning matters, add_axes might be useful.
Example
import matplotlib.pyplot as plt
plt.rcParams["figure.figsize"] = (5,3)
fig = plt.figure()
fig.add_subplot(241)
fig.add_subplot(242)
ax = fig.add_subplot(223)
ax.set_title("subplots")
fig.add_axes([0.77,.3,.2,.6])
ax2 =fig.add_axes([0.67,.5,.2,.3])
fig.add_axes([0.6,.1,.35,.3])
ax2.set_title("random axes")
plt.tight_layout()
plt.show()
Alternative
The easiest way to obtain one or more subplots together with their handles is plt.subplots(). For one axes, use
fig, ax = plt.subplots()
or, if more subplots are needed,
fig, axes = plt.subplots(nrows=3, ncols=4)
The initial question
In the initial question an axes was placed using fig.add_axes([0,0,1,1]), such that it sits tight to the figure boundaries. The disadvantage of this is of course that ticks, ticklabels, axes labels and titles are cut off. Therefore I suggested in one of the comments to the answer to use fig.add_subplot as this will automatically allow for enough space for those elements, and, if this is not enough, can be adjusted using pyplot.subplots_adjust(...) or pyplot.tight_layout().
The answer by #ImportanceOfBeingErnest is great.
Yet in that context usually one want to generate an axes for a plot and add_axes() has too much overhead.
So one trick is, as in the answer of #ImportanceOfBeingErnest, is to use add_subplot(111).
Yet more elegant alternative and simple would be:
hAx = plt.figure(figsize = (10, 10)).gca()
If you want 3D projection you can pass any axes property. For instance the projection:
hAx = plt.figure(figsize = (16, 10)).gca(projection = '3d')
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'm trying to create plots which show the correlation of the "value" parameter to different categorical parameters. Here's what I have so far:
plot = sns.pairplot(df, x_vars=['country', 'tier_code', 'industry', 'company_size', 'region'], y_vars=['value'], height=10)
Which produces the following set of plots:
As you can see, the x axis is extremely crowded for the "country" and "industry" plots. I would like to rotate the category labels 90 degrees so that they wouldn't overlap.
All the examples for rotating I could find were for other kinds of plots and didn't work for the pairplot. I could probably get it to work if I made each plot separately using catplot, but I would like to make them all at once. Is that possible?
I am using Google Colab in case it makes any difference. My seaborn version number is 0.10.0.
Manish's answer uses the get_xticklabels method, which doesn't always play well with the higher level seaborn functions in my experience. So here's a solution avoiding that. Since I don't have your data, I'm using seaborn's tips dataset for an example.
I'm naming the object returned by sns.pairplot() grid, just to remind us that it is a PairGrid instance. In general, its axes attribute yields a two-dimensional array of axes objects, corresponding to the subplot grid. So I'm using the flat method to turn this into a one-dimensional array, although it wouldn't be necessary in your special case with only one row of subplots.
In my example I don't want to rotate the labels for the third subplot, as they are single digits, so I slice the axes array accordingly with [:2].
import seaborn as sns
sns.set()
tips = sns.load_dataset("tips")
grid = sns.pairplot(tips, x_vars=['sex', 'day', 'size'], y_vars=['tip'])
for ax in grid.axes.flat[:2]:
ax.tick_params(axis='x', labelrotation=90)
You can rotate x-axis labels as:
plot = sns.pairplot(df, x_vars=['country', 'tier_code', 'industry', 'company_size', 'region'],
y_vars=['value'], height=10)
rotation = 90
for axis in plot.fig.axes: # get all the axis
axis.set_xticklabels(axis.get_xticklabels(), rotation = rotation)
plot.fig.show()
Hope it helps.
This question already has an answer here:
How to return a matplotlib.figure.Figure object from Pandas plot function
(1 answer)
Closed 2 years ago.
I have created a 3x3 subplots by using the pandas df.line.plot:
ax=df.plot.line(subplots=True, grid=True,layout=(3, 3), sharex=True, legend=False,ylim=[-5,25])
It returns 3x3 matrix of Axes objects.
Now I want to create a joint legend for those subplots.
As the other post suggests I should use:
handles, labels = ax.get_legend_handles_labels()
fig.legend(handles, labels, loc='upper center')
The problem is I can't use it because I have no figure created here. I only have axes. How can I make it work?
I edited the post, because I thought I could create a figure and prescribe the axes, but I guess it came from my confusion on subject.
You have two options:
First: Either use double indices (for row and column) as shown in the comments and then use ax[0,0], ax[0,1], ax[0,2] ... ax[2,0], ax[2,1], ax[2,2]. For 3 rows and 3 columns, the indices will run from 0 up to 2 (so 0, 1, 2)
You can also use ax[0][0] and so on. Both formats are equivalent.
Second: If you don't want to use two indices, you can flatten the ax and then use a single index as
ax = ax.flatten()
This will convert ax from 2d object to a 1-d array of 9 subfigures. Then you can use ax[0], ax[1], ax[2], ... ax[8] (9-1 because indexing starts from 0 in python)
I'm trying to set the x-axis limits to different values for each facet a Seaborn facetgrid distplot. I understand that I can get access to all the axes within the subplots through g.axes, so I've tried to iterate over them and set the xlim with:
g = sns.FacetGrid(
mapping,
col=options.facetCol,
row=options.facetRow,
col_order=sorted(cols),
hue=options.group,
)
g = g.map(sns.distplot, options.axis)
for i, ax in enumerate(g.axes.flat): # set every-other axis for testing purposes
if i % 2 == 0[enter link description here][1]:
ax.set_xlim(-400, 500)
else:
ax.set_xlim(-200, 200)
However, when I do this, all axes get set to (-200, 200) not just every other facet.
What am I doing wrong?
mwaskom had the solution; posting here for completeness - just had to change the following line to:
g = sns.FacetGrid(
mapping,
col=options.facetCol,
row=options.facetRow,
col_order=sorted(cols),
hue=options.group,
sharex=False, # <- This option solved the problem!
)
As suggested by mwaskom you can simply use FacetGrid's sharex (respectively sharey) to allow plots to have independent axis scales:
share{x,y} : bool, ‘col’, or ‘row’ optional
If true, the facets will share y axes across columns and/or x axes across rows.
For example, with:
sharex=False each plot has its own axis
sharex='col' each column has its own axis
sharex='row' each row has its own axis (even if this one doesn't make too much sense to me)
sns.FacetGrid(data, ..., sharex='col')
If you use FacetGrid indirectly, for example via displot or relplot, you will have to use the facet_kws keyword argument:
sns.displot(data, ..., facet_kws={'sharex': 'col'})