Seaborn pairplot legend - how to control position - python

I would like to move the Seaborn pairplot legend outside the scatter plot matrix. The official docs don't give a keyword legend. I am trying to plot the legend outside the plot in 3 different locations simultaneously: bottom center, top center, right center
I have tried this:
import matplotlib.pyplot as plt
import seaborn as sns
iris = sns.load_dataset("iris")
g = sns.pairplot(iris,hue='species', palette='husl', markers='d', size=2.5, plot_kws=
{
"s":40,
"alpha":1.0,
'lw':0.5,
'edgecolor':'k'
})
plt.legend(loc='upper center', bbox_to_anchor=(1.10, 1.0), ncol=1) #vertical legend
plt.legend(loc='lower center', bbox_to_anchor=(0.0, -0.15), ncol=3) #horizontal legend bottom
plt.legend(loc='upper left', bbox_to_anchor=(0.0, 1.15), ncol=3) #horizontal legend top
g.savefig('Test.png', bbox_inches='tight')
The output of the above code is this file: .
All 3 legends are being printed incorrectly. I am not sure if it is doing the positioning that I requested. I am not sure what is happening on the right - it seems that 2 vertical legends are appearing and for some reason and they are fully overlapping each other. One of the two horizontal legends is not appearing at all.
Is there a way to avoid overlapping the vertical legends and also place the legend outside the plot in 3 locations - top center, bottom center, right center?

pairplot already adds a legend outside the plot matrix and it is not clear where you want to move it. The other legends are probably being positioned as you requested even if that might be not what you actually want.
plt.legend will attach a legend to the current axes and there can be only one legend per axes so in your code only the last legend is drawn. But you can attach several legends to a figure and IIUC you want to position the legends relative to the figure so figure legends seems like the best option.
In order to plot figure legends you need to explicitly pass the handlers and labels. I had to use the private attribute PairPlot._legend_data for that, I did not find a way to do it using the public API.
Unfortunately matplotlib won't automatically make room to acomodate these legends and they will overlap with the subplots unless you make some adjustments. I will just use subplot_adjust with some hardcoded values that work for me in this case because calculations are tricky.
So this is the code that hopefully does what you want:
g = sns.pairplot(iris, hue='species', palette='husl', markers='d', size=2.5, plot_kws=
{
"s":40,
"alpha":1.0,
'lw':0.5,
'edgecolor':'k'
})
handles = g._legend_data.values()
labels = g._legend_data.keys()
g.fig.legend(handles=handles, labels=labels, loc='upper center', ncol=1)
g.fig.legend(handles=handles, labels=labels, loc='lower center', ncol=3)
g.fig.legend(handles=handles, labels=labels, loc='upper left', ncol=3)
g.fig.subplots_adjust(top=0.92, bottom=0.08)
The OP asked in a comment whether this can be found in the seaborn documentation. Of course part of this is just pure matplotlib, not specific to seaborn. But I also realized that I had to rely on a couple of undocumented features.
The fact that PairGrid has a fig attribute referencing the Figure instance is not documented. Anyway that was an easy guess and I could have used fig = plt.gcf() instead.
Getting the labels and handles of the legend is trickier. I learnt about the _legend_data attribute by looking at the docstring of PairGrid.add_legend, but the docstring itself is a bit hidden (it does not appear in the web) and the attribute is underscored as if it were private, so using it feels uncomfortable. I find it inconsistent that a private attribute is mentioned in the docstring of a public method, probably we should have legend_data as a public attribute, but I digress.
Alternatively you could try to extract the labels and handles from the subplots. You could guess that the subplots have this information but there are no actual guaranties so this means relying on undocumented behaviour. It turns out that the non-diagonal subplots have it (but the diagonal ones don't, so if you just looked at the first subplot you would be misguided) and you can do handles, labels = fig.get_axes()[1].get_legend_handles_labels(). But as I said this is undocumented behaviour even if you are using only documented API.
You could also create your own handles but it would be cumbersome, that's why I looked for shortcuts, even if undocumented.

To control the position of the default pairplot legend:
g._legend.set_bbox_to_anchor((0.5, 0.5))

this helped me, it removes the default legend and there are a lot of settings here.
g = sns.pairplot(data, hue="Group")
sns.move_legend(g, "lower center",bbox_to_anchor=(0.5, -0.035), labels=labels, ncol=5, title='Group', frameon=False,)

This is actually a lot easier than you'd think it is. It's simply a default setting in the actual matplot output. Just go to the configure subplot button at the top of your output window, and lower your right border upper limit a little and it'll be fixed.

Related

using sns.relplot.set to control aesthetics in seaborn

From How to set some xlim and ylim in Seaborn lmplot facetgrid I've find out that you can specify xlim by using
g = sns.relplot(data=df_weekday,
x="date", y="battery/min", kind="scatter", hue="version")
g.set(xlim=('2019-07-16','2019-11-06'))
This works perfectly however can I add more parameters inside,
instead of calling plt.xticks(rotation=90) and other controls on each line I want to include all inside g.set().
Furthermore when I try to spread my graph by setting height=10, aspect=2 however it makes all the fonts too small and having graph without height, aspect makes my graph too squished.
I want to add height, aspect and change all fonts also inside g.set() if possible.

Hide legend in bokeh plot

LS,
Bokeh plot automatically generates a legend for a plot.
How can I hide (not show at all) the legend in a Bokeh plot ?
I tried: legend = 'none'. But no success.
Thanks
If I can just expand this a little - legend=False is the correct way to make the Bokeh legend invisible, but it's used within the creation of the plot itself, rather than being called as an attribute of the plot object. By which I mean, write
from bokeh.charts import Scatter
myPlot = Scatter(foo, bar, legend=False)
rather than
from bokeh.charts import Scatter
myPlot = Scatter(foo, bar)
myPlot.legend=False.
p1.line(x=data['col'].astype(str), y=data['col'],
color='black',legend_label='legend')
p1.legend.visible=False
The last line hides the legend.

How to retrieve colorbar instance from figure in matplotlib

all. I want to update the colorbar of a figure when the imagedata is changed. So something like:
img = misc.lena()
fig = plt.figure()
ax = plt.imshow(im)
plt.colorbar(ax)
newimg = img+10*np.randn(512,512)
def update_colorbar(fig,ax,newimg):
cbar = fig.axes[1]
ax.set_data(newimg)
cbar.update_normal(ax)
plt.draw()
but it seems that returned results from fig.axes() does not have the colorbar instance like I expected. I can probably just pass the colorbar instance as an argument to the update function, but I thought just passing one fig parameter may be good enough. Can anyone explain a little bit on how to retrieve the colorbar from the figure? Or why 'fig.axes()' doesn't return the AxesImage or Colobar instance but just the Axes or AxesSubplot? I think I just need more understanding of the Axes/Figure stuff.Thank you!
Sometimes it can be useful to retrieve a colorbar even if it was not held in a variable.
In this case, it is possible to retrieve the colorbar from the plot with:
# Create an example image and colourbar
img = np.arange(20).reshape(5,4)
plt.imshow(img)
plt.colorbar()
# Get the current axis
ax = plt.gca()
# Get the images on an axis
im = ax.images
# Assume colorbar was plotted last one plotted last
cb = im[-1].colorbar
# Do any actions on the colorbar object (e.g. remove it)
cb.remove()
EDIT:
or, equivalently, the one liner:
plt.gca().images[-1].colorbar.remove()
N.B.: see also comments for the use of ax.collections[-1] instead of ax.images[-1]. For me it always worked only the first way, I don't know what depends on, maybe the type of data or plot.
Now you can operate on cb as if it were stored using commands described in the colorbar API. For instance you could change xlim or call update as explained in other comments. You could remove it with cb.remove() and recreate it with plt.colorbar().
plt.draw() or show should be called after to update plot.
As the image is the mappable associated to the colorbar and can be obtained with cb.mappable.
First off, I think you're getting a bit confused between the axes (basically, the plot), the figure, the scalar mappable (the image, in this case), and the colorbar instance.
The figure is the window that the plot is in. It's the top-level container.
Each figure usually has one or more axes. These are the plots/subplots.
Colorbars are also inside the figure. Adding a colorbar creates a new axes (unless you specify otherwise) for the colorbar to be displayed in. (It can't normally be displayed in the same axes as the image, because the colorbar needs to have its own x and y limits, etc.)
Some of your confusion is due to the fact that you're mixing the state-machine interface and the OO interface. It's fine to do this, but you need to understand the OO interface.
fig.axes[1] isn't the colorbar instance. It's the axes that the colorbar is plotted in. (Also, fig.axes[1] is just the second axes in the figure. It happens to be the axes that the colorbar is in for a figure with one subplot and one colorbar, but that won't generally be the case.)
If you want to update the colorbar, you'll need to hold on to the colorbar instance that colorbar returns.
Here's an example of how you'd normally approach things:
import matplotlib.pyplot as plt
import numpy as np
data = np.random.random((10,10)) # Generate some random data to plot
fig, ax = plt.subplots() # Create a figure with a single axes.
im = ax.imshow(data) # Display the image data
cbar = fig.colorbar(im) # Add a colorbar to the figure based on the image
If you're going to use update_normal to update the colorbar, it expects a ScalarMappable (e.g. an image created by imshow, the collection that scatter creates, the ContourSet that contour creates, etc) to be passed in. (There are other ways to do it, as well. Often you just want to update the limits, rather than the whole thing.) In the case of the code above, you'd call cbar.update_normal(im).
However, you haven't created a new AxesImage, you've just changed it's data. Therefore, you probably just want to do:
cbar.set_clim(newimg.min(), newimg.max())

Setting spines in matplotlibrc

For a strange reason I cannot find the way to specify spines configuration in Python's matplotlibrc file. Any idea on how to cause matplotlib not to draw upper and right spines by default?
(source: sourceforge.net)
More about info about spines in matplotlib is here
Thank you
In order to hide the right and top spines of a subplot, you need to both set the colour of the relevant spines to 'none', as well as set the tick position to 'left' for the xtick, and 'bottom' for the ytick (in order to hide the tick marks as well as the spines).
Unfortunately, none of these are currently accessible via matplotlibrc. The parameters specified in matplotlibrc are validated, and then stored in a dict called rcParams. It is then up to the individual modules to check for a key in this dict whose value will act as their default. If they don't check it for one of their options, that option is not alterable via the rc file.
Due to the nature of the rc system, and the way that spines are written, altering the code to allow for this would not be straightforward:
Spines currently obtain their colour through the same rc parameter used to define axis colours; you cannot set it to 'none' without hiding all of your axis drawing. They are also agnostic towards whether they are top, right, left, or bottom — these are really just four separate spines stored in a dict. The individual spine objects do not know what side of the plot they compose, so you cannot just add new rc params and assign the proper one during spine initialization.
self.set_edgecolor( rcParams['axes.edgecolor'] )
(./matplotlib/lib/matplotlib/spines.py, __init__(), line 54)
If you have a large amount of existing code, such that adding the axis parameters manually to each one would be too burdensome, you could alternately use a helper function to iterate through all of the Axis objects and set the values for you.
Here's an example:
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.pyplot import show
# Set up a default, sample figure.
fig = plt.figure()
x = np.linspace(-np.pi,np.pi,100)
y = 2*np.sin(x)
ax = fig.add_subplot(1,2,2)
ax.plot(x,y)
ax.set_title('Normal Spines')
def hide_spines():
"""Hides the top and rightmost axis spines from view for all active
figures and their respective axes."""
# Retrieve a list of all current figures.
figures = [x for x in matplotlib._pylab_helpers.Gcf.get_all_fig_managers()]
for figure in figures:
# Get all Axis instances related to the figure.
for ax in figure.canvas.figure.get_axes():
# Disable spines.
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
# Disable ticks.
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
hide_spines()
show()
Just call hide_spines() before show(), and it will hide them in all of the figures that show() displays. I cannot think of a simpler way to alter a large number of figures, outside of spending the time to patch matplotlib and add in rc support for the needed options.
To make matplotlib not to draw upper and right spines, one can set the following in matplotlibrc file
axes.spines.right : False
axes.spines.top : False
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)

How can I show figures separately in matplotlib?

Say that I have two figures in matplotlib, with one plot per figure:
import matplotlib.pyplot as plt
f1 = plt.figure()
plt.plot(range(0,10))
f2 = plt.figure()
plt.plot(range(10,20))
Then I show both in one shot
plt.show()
Is there a way to show them separately, i.e. to show just f1?
Or better: how can I manage the figures separately like in the following 'wishful' code (that doesn't work):
f1 = plt.figure()
f1.plot(range(0,10))
f1.show()
Sure. Add an Axes using add_subplot. (Edited import.) (Edited show.)
import matplotlib.pyplot as plt
f1 = plt.figure()
f2 = plt.figure()
ax1 = f1.add_subplot(111)
ax1.plot(range(0,10))
ax2 = f2.add_subplot(111)
ax2.plot(range(10,20))
plt.show()
Alternatively, use add_axes.
ax1 = f1.add_axes([0.1,0.1,0.8,0.8])
ax1.plot(range(0,10))
ax2 = f2.add_axes([0.1,0.1,0.8,0.8])
ax2.plot(range(10,20))
With Matplotlib prior to version 1.0.1, show() should only be called once per program, even if it seems to work within certain environments (some backends, on some platforms, etc.).
The relevant drawing function is actually draw():
import matplotlib.pyplot as plt
plt.plot(range(10)) # Creates the plot. No need to save the current figure.
plt.draw() # Draws, but does not block
raw_input() # This shows the first figure "separately" (by waiting for "enter").
plt.figure() # New window, if needed. No need to save it, as pyplot uses the concept of current figure
plt.plot(range(10, 20))
plt.draw()
# raw_input() # If you need to wait here too...
# (...)
# Only at the end of your program:
plt.show() # blocks
It is important to recognize that show() is an infinite loop, designed to handle events in the various figures (resize, etc.). Note that in principle, the calls to draw() are optional if you call matplotlib.ion() at the beginning of your script (I have seen this fail on some platforms and backends, though).
I don't think that Matplotlib offers a mechanism for creating a figure and optionally displaying it; this means that all figures created with figure() will be displayed. If you only need to sequentially display separate figures (either in the same window or not), you can do like in the above code.
Now, the above solution might be sufficient in simple cases, and for some Matplotlib backends. Some backends are nice enough to let you interact with the first figure even though you have not called show(). But, as far as I understand, they do not have to be nice. The most robust approach would be to launch each figure drawing in a separate thread, with a final show() in each thread. I believe that this is essentially what IPython does.
The above code should be sufficient most of the time.
PS: now, with Matplotlib version 1.0.1+, show() can be called multiple times (with most backends).
I think I am a bit late to the party but...
In my opinion, what you need is the object oriented API of matplotlib. In matplotlib 1.4.2 and using IPython 2.4.1 with Qt4Agg backend, I can do the following:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1) # Creates figure fig and add an axes, ax.
fig2, ax2 = plt.subplots(1) # Another figure
ax.plot(range(20)) #Add a straight line to the axes of the first figure.
ax2.plot(range(100)) #Add a straight line to the axes of the first figure.
fig.show() #Only shows figure 1 and removes it from the "current" stack.
fig2.show() #Only shows figure 2 and removes it from the "current" stack.
plt.show() #Does not show anything, because there is nothing in the "current" stack.
fig.show() # Shows figure 1 again. You can show it as many times as you want.
In this case plt.show() shows anything in the "current" stack. You can specify figure.show() ONLY if you are using a GUI backend (e.g. Qt4Agg). Otherwise, I think you will need to really dig down into the guts of matplotlib to monkeypatch a solution.
Remember that most (all?) plt.* functions are just shortcuts and aliases for figure and axes methods. They are very useful for sequential programing, but you will find blocking walls very soon if you plan to use them in a more complex way.
Perhaps you need to read about interactive usage of Matplotlib. However, if you are going to build an app, you should be using the API and embedding the figures in the windows of your chosen GUI toolkit (see examples/embedding_in_tk.py, etc).
None of the above solutions seems to work in my case, with matplotlib 3.1.0 and Python 3.7.3. Either both the figures show up on calling show() or none show up in different answers posted above.
Building upon #Ivan's answer, and taking hint from here, the following seemed to work well for me:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1) # Creates figure fig and add an axes, ax.
fig2, ax2 = plt.subplots(1) # Another figure
ax.plot(range(20)) #Add a straight line to the axes of the first figure.
ax2.plot(range(100)) #Add a straight line to the axes of the first figure.
# plt.close(fig) # For not showing fig
plt.close(fig2) # For not showing fig2
plt.show()
As #arpanmangal, the solutions above do not work for me (matplotlib 3.0.3, python 3.5.2).
It seems that using .show() in a figure, e.g., figure.show(), is not recommended, because this method does not manage a GUI event loop and therefore the figure is just shown briefly. (See figure.show() documentation). However, I do not find any another way to show only a figure.
In my solution I get to prevent the figure for instantly closing by using click events. We do not have to close the figure — closing the figure deletes it.
I present two options:
- waitforbuttonpress(timeout=-1) will close the figure window when clicking on the figure, so we cannot use some window functions like zooming.
- ginput(n=-1,show_clicks=False) will wait until we close the window, but it releases an error :-.
Example:
import matplotlib.pyplot as plt
fig1, ax1 = plt.subplots(1) # Creates figure fig1 and add an axes, ax1
fig2, ax2 = plt.subplots(1) # Another figure fig2 and add an axes, ax2
ax1.plot(range(20),c='red') #Add a red straight line to the axes of fig1.
ax2.plot(range(100),c='blue') #Add a blue straight line to the axes of fig2.
#Option1: This command will hold the window of fig2 open until you click on the figure
fig2.waitforbuttonpress(timeout=-1) #Alternatively, use fig1
#Option2: This command will hold the window open until you close the window, but
#it releases an error.
#fig2.ginput(n=-1,show_clicks=False) #Alternatively, use fig1
#We show only fig2
fig2.show() #Alternatively, use fig1
As of November 2020, in order to show one figure at a time, the following works:
import matplotlib.pyplot as plt
f1, ax1 = plt.subplots()
ax1.plot(range(0,10))
f1.show()
input("Close the figure and press a key to continue")
f2, ax2 = plt.subplots()
ax2.plot(range(10,20))
f2.show()
input("Close the figure and press a key to continue")
The call to input() prevents the figure from opening and closing immediately.

Categories