Python3 Seaborn PairGrid legend outside subplots - legend

I'm making a large PairGrid figure and I am unable to set the legend outside the plots (on the right). PairGrid doesn't seem to inherit the legend_out option of FaceGrid. Here is my attempt so far as you can see the legend overlaps the figure.
from random import choice
from numpy import random
from matplotlib import pyplot as plt
import seaborn as sns
import pandas as pd
def my_kdeplot(dfx, dfy, *args, **kwargs):
ax = sns.kdeplot(dfx, dfy, alpha=0.7,
cmap=sns.light_palette(kwargs['color'], as_cmap=True))
names = [choice('ABCDE') for _ in range(1000)]
df = pd.DataFrame(list(zip(names, *[random.random(1000) for _ in range(5)])),
columns=['names','A','B','C','D','E'])
g = sns.PairGrid(df, hue='names')
g.map_lower(my_kdeplot)
g.map_upper(plt.scatter, alpha=0.7)
g.map_diag(plt.hist)
g = g.add_legend(fontsize=14)
sns.plt.savefig('fig.png')

You can adjust the location of your legend using bbox_to_anchor=(horizontal, vertical):
g = g.add_legend(fontsize=14, bbox_to_anchor=(1.5,1))
You'll need to play with the numbers a little to find the right legend position.

Related

Equivalent of Hist()'s Layout hyperparameter in Sns.Pairplot?

Am trying to find hist()'s figsize and layout parameter for sns.pairplot().
I have a pairplot that gives me nice scatterplots between the X's and y. However, it is oriented horizontally and there is no equivalent layout parameter to make them vertical to my knowledge. 4 plots per row would be great.
This is my current sns.pairplot():
sns.pairplot(X_train,
x_vars = X_train.select_dtypes(exclude=['object']).columns,
y_vars = ["SalePrice"])
This is what I would like it to look like: Source
num_mask = train_df.dtypes != object
num_cols = train_df.loc[:, num_mask[num_mask == True].keys()]
num_cols.hist(figsize = (30,15), layout = (4,10))
plt.show()
What you want to achieve isn't currently supported by sns.pairplot, but you can use one of the other figure-level functions (sns.displot, sns.catplot, ...). sns.lmplot creates a grid of scatter plots. For this to work, the dataframe needs to be in "long form".
Here is a simple example. sns.lmplot has parameters to leave out the regression line (fit_reg=False), to set the height of the individual subplots (height=...), to set its aspect ratio (aspect=..., where the subplot width will be height times aspect ratio), and many more. If all y ranges are similar, you can use the default sharey=True.
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
# create some test data with different y-ranges
np.random.seed(20230209)
X_train = pd.DataFrame({"".join(np.random.choice([*'uvwxyz'], np.random.randint(3, 8))):
np.random.randn(100).cumsum() + np.random.randint(100, 1000) for _ in range(10)})
X_train['SalePrice'] = np.random.randint(10000, 100000, 100)
# convert the dataframe to long form
# 'SalePrice' will get excluded automatically via `melt`
compare_columns = X_train.select_dtypes(exclude=['object']).columns
long_df = X_train.melt(id_vars='SalePrice', value_vars=compare_columns)
# create a grid of scatter plots
g = sns.lmplot(data=long_df, x='SalePrice', y='value', col='variable', col_wrap=4, sharey=False)
g.set(ylabel='')
plt.show()
Here is another example, with histograms of the mpg dataset:
import matplotlib.pyplot as plt
import seaborn as sns
mpg = sns.load_dataset('mpg')
compare_columns = mpg.select_dtypes(exclude=['object']).columns
mpg_long = mpg.melt(value_vars=compare_columns)
g = sns.displot(data=mpg_long, kde=True, x='value', common_bins=False, col='variable', col_wrap=4, color='crimson',
facet_kws={'sharex': False, 'sharey': False})
g.set(xlabel='')
plt.show()

How do I subplot each contour with a legenda?

I want to subplot correctly and get two legends. I think that if you open the added image you get what I am trying to achieve because it sucks right now. I am learning to code so I don't understand everyone else's code on here so I am probably asking something that has been asked tons but I don't understand anyone else's code. My current code exists of the following
from IPython import get_ipython
get_ipython().magic('reset -f')
# Set up your graphics environment
get_ipython().magic('matplotlib')
# Import the modules you always need
import numpy as np
import matplotlib.pyplot as plt
# Import the modules for 3D plotting
from mpl_toolkits.mplot3d.axes3d import Axes3D
from matplotlib import cm
plt.close('all')
slopeangle = np.arange(5, 45, 1)
intangle = np.arange(20, 45, 1)
slopeangle_m, intangle_m = np.meshgrid(slopeangle, intangle)
#F = np.zeros(np.shape(slopeangle_m)
F = (((15.2-(9.81*0.5))*2.0*((np.cos(slopeangle_m*np.pi/180))**2)*np.tan(((np.pi*intangle_m)/180)))/(15.2*2.0*np.sin(slopeangle_m*np.pi/180)*(np.cos(slopeangle_m*np.pi/180))))
M0 = (((15.2-(9.81*0))*2.0*((np.cos(slopeangle_m*np.pi/180))**2)*np.tan(((np.pi*intangle_m)/180)))/(15.2*2.0*np.sin(slopeangle_m*np.pi/180)*(np.cos(slopeangle_m*np.pi/180))))
M75 = (((15.2-(9.81*0.75))*2.0*((np.cos(slopeangle_m*np.pi/180))**2)*np.tan(((np.pi*intangle_m)/180)))/(15.2*2.0*np.sin(slopeangle_m*np.pi/180)*(np.cos(slopeangle_m*np.pi/180))))
fig2 = plt.figure()
ax = fig2.add_subplot(211)
plt.contourf(slopeangle, intangle, M0, levels=[np.min(M0),1 ,np.max(M0)], cmap=plt.cm.seismic)
ax.legend
ax=plt.gca()
ax.set_title("Factor m as value 0")
ax.set_xlabel('Slope angle (°)')
ax.set_ylabel('Internal angle (°)')
ax2 = fig2.add_subplot(212)
plt.contourf(slopeangle, intangle, M75, levels=[np.min(M75),1 ,np.max(M75)], cmap=plt.cm.seismic)
ax2=plt.gca()
ax2.set_title("Factor m as value 0.75")
ax2.set_xlabel('Slope angle (°)')
ax2.set_ylabel('Internal angle (°)')
I get the following

Matplotlib adjust inset_axes based on loc parameter instead of bbox?

I'm using inset_axes() to control the placement of my colorbar legend. The label hangs off the plot just a little bit. Is there a way to just nudge it over without having to do bbox_to_anchor()? Some way to do an offset from the loc parameter? I do want to keep it in the lower left.
import pandas as pd
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
set1 = ax2.scatter(df.x, df.y,
edgecolors = 'none',
c = df.recommended_net_preferred_for_analysis_meters,
norm = mcolors.LogNorm(), cmap='jet')
cbaxes = inset_axes(ax2, width="30%", height="3%", loc=3)
plt.colorbar(set1, cax=cbaxes, format = '%1.2f', orientation='horizontal')
cbaxes.xaxis.set_ticks_position("top")

Control gridline spacing in seaborn

I'd like to change the spacing of the horizontal grid lines on a seaborn chart, I've tried setting the style with no luck:
seaborn.set_style("whitegrid", {
"ytick.major.size": 0.1,
"ytick.minor.size": 0.05,
'grid.linestyle': '--'
})
bar(range(len(data)),data,alpha=0.5)
plot(avg_line)
The gridlines are set automatically desipite me trying to overide the tick size
Any suggestions? Thanks!
you can set the tick locations explicitly later, and it will draw the grid at those locations.
The neatest way to do this is to use a MultpleLocator from the matplotlib.ticker module.
For example:
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
sns.set_style("whitegrid", {'grid.linestyle': '--'})
fig,ax = plt.subplots()
ax.bar(np.arange(0,50,1),np.random.rand(50)*0.016-0.004,alpha=0.5)
ax.yaxis.set_major_locator(ticker.MultipleLocator(0.005))
plt.show()
The OP asked about modifying tick distances in Seaborn.
If you are working in Seaborn and you use a plotting feature that returns an Axes object, then you can work with that just like any other Axes object in matplotlib. For example:
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
from matplotlib.ticker import MultipleLocator
df = sm.datasets.get_rdataset("Guerry", "HistData").data
ax = sns.scatterplot('Literacy', 'Lottery', data=df)
ax.yaxis.set_major_locator(MultipleLocator(10))
ax.xaxis.set_major_locator(MultipleLocator(10))
plt.show()
Put if you are working with one of the Seaborn processes that involve FacetGrid objects, you will see precious little help on how to modify the tick marks without manually setting them. You have dig out the Axes object from the numpy array inside FacetGrid.axes .
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.ticker import MultipleLocator
tips = sns.load_dataset("tips")
g = sns.lmplot(x="total_bill", y="tip", hue="smoker", data=tips, )
g.axes[0][0].yaxis.set_major_locator(MultipleLocator(3))
Note the double subscript required. g is a FacetGrid object, which holds a two-dimensional numpy array of dtype=object, whose entries are matplotlib AxesSubplot objects.
If you are working with a FacetGrid that has multiple axes, then each one will have to be extracted and modified.

Matplotlib animation not working in IPython Notebook (blank plot)

I've tried multiple animation sample codes and cannot get any of them working. Here's a basic one I've tried from the Matplotlib documentation:
"""
A simple example of an animated plot
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
x = np.arange(0, 2*np.pi, 0.01) # x-array
line, = ax.plot(x, np.sin(x))
def animate(i):
line.set_ydata(np.sin(x+i/10.0)) # update the data
return line,
#Init only required for blitting to give a clean slate.
def init():
line.set_ydata(np.ma.array(x, mask=True))
return line,
ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), init_func=init,
interval=25, blit=True)
plt.show()
When I execute the above in an IPython Notebook, I just see a blank plot generated. I've tried running this from multiple servers (including Wakari), on multiple machines, using multiple browsers (Chrome, FF, IE).
I can save the animation to an mp4 file just fine and it looks good when played.
Any help is appreciated!
To summarize the options you have:
Using display in a loop Use IPython.display.display(fig) to display a figure in the output. Using a loop you would want to clear the output before a new figure is shown. Note that this technique gives in general not so smooth resluts. I would hence advice to use any of the below.
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
from IPython.display import display, clear_output
t = np.linspace(0,2*np.pi)
x = np.sin(t)
fig, ax = plt.subplots()
l, = ax.plot([0,2*np.pi],[-1,1])
animate = lambda i: l.set_data(t[:i], x[:i])
for i in range(len(x)):
animate(i)
clear_output(wait=True)
display(fig)
plt.show()
%matplotlib notebook Use IPython magic %matplotlib notebook to set the backend to the notebook backend. This will keep the figure alive instead of displaying a static png file and can hence also show animations.
Complete example:
%matplotlib notebook
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
t = np.linspace(0,2*np.pi)
x = np.sin(t)
fig, ax = plt.subplots()
l, = ax.plot([0,2*np.pi],[-1,1])
animate = lambda i: l.set_data(t[:i], x[:i])
ani = matplotlib.animation.FuncAnimation(fig, animate, frames=len(t))
plt.show()
%matplotlib tk Use IPython magic %matplotlib tk to set the backend to the tk backend. This will open the figure in a new plotting window, which is interactive and can thus also show animations.
Complete example:
%matplotlib tk
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
t = np.linspace(0,2*np.pi)
x = np.sin(t)
fig, ax = plt.subplots()
l, = ax.plot([0,2*np.pi],[-1,1])
animate = lambda i: l.set_data(t[:i], x[:i])
ani = matplotlib.animation.FuncAnimation(fig, animate, frames=len(t))
plt.show()
Convert animation to mp4 video:
from IPython.display import HTML
HTML(ani.to_html5_video())
or use plt.rcParams["animation.html"] = "html5" at the beginning of the notebook.
This will require to have ffmpeg video codecs available to convert to HTML5 video. The video is then shown inline. This is therefore compatible with %matplotlib inline backend. Complete example:
%matplotlib inline
import matplotlib.pyplot as plt
plt.rcParams["animation.html"] = "html5"
import matplotlib.animation
import numpy as np
t = np.linspace(0,2*np.pi)
x = np.sin(t)
fig, ax = plt.subplots()
l, = ax.plot([0,2*np.pi],[-1,1])
animate = lambda i: l.set_data(t[:i], x[:i])
ani = matplotlib.animation.FuncAnimation(fig, animate, frames=len(t))
ani
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
t = np.linspace(0,2*np.pi)
x = np.sin(t)
fig, ax = plt.subplots()
l, = ax.plot([0,2*np.pi],[-1,1])
animate = lambda i: l.set_data(t[:i], x[:i])
ani = matplotlib.animation.FuncAnimation(fig, animate, frames=len(t))
from IPython.display import HTML
HTML(ani.to_html5_video())
Convert animation to JavaScript:
from IPython.display import HTML
HTML(ani.to_jshtml())
or use plt.rcParams["animation.html"] = "jshtml" at the beginning of the notebook.
This will display the animation as HTML with JavaScript. This highly compatible with most new browsers and also with the %matplotlib inline backend. It is available in matplotlib 2.1 or higher.
Complete example:
%matplotlib inline
import matplotlib.pyplot as plt
plt.rcParams["animation.html"] = "jshtml"
import matplotlib.animation
import numpy as np
t = np.linspace(0,2*np.pi)
x = np.sin(t)
fig, ax = plt.subplots()
l, = ax.plot([0,2*np.pi],[-1,1])
animate = lambda i: l.set_data(t[:i], x[:i])
ani = matplotlib.animation.FuncAnimation(fig, animate, frames=len(t))
ani
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
t = np.linspace(0,2*np.pi)
x = np.sin(t)
fig, ax = plt.subplots()
l, = ax.plot([0,2*np.pi],[-1,1])
animate = lambda i: l.set_data(t[:i], x[:i])
ani = matplotlib.animation.FuncAnimation(fig, animate, frames=len(t))
from IPython.display import HTML
HTML(ani.to_jshtml())
According to this answer, you can get animation (and full interactivity support) working in an IPython notebook enabling the nbagg backend with %matplotlib nbagg.
I was having the exact same problem as you until a moment ago. I am a complete novice, so tcaswell's answer was a bit cryptic to me. Perhaps you figured out what he meant or found your own solution. In case you have not, I will put this here.
I googled "matplotlib inline figures" and found this site, which mentions that you have to enable matplotlib mode. Unfortunately, just using %maplotlib didn't help at all.
Then I typed %matplotlib qt into the IPython console on a lark and it works just fine now, although the plot appears in a separate window.
I ran into this issue as well and found I needed to understand the concept of matplotlib backends, how to enable a specific backend, and which backends work with FuncAnimation. I put together an ipython notebook that explains the details and summarizes which backends work with FuncAnimation on Mac, Windows, and wakari.io. The notebook also summarizes which backends work with the ipython interact() widget, and where plots appear (inline or secondary window) for basic matplotlib plotting. Code and instructions are included so you can reproduce any of the results.
The bottom line is that you can't get an animation created with FuncAnimation to display inline in an ipython notebook. However, you can get it to display in a separate window. It turns out that I needed this to create visualizations for an undergraduate class I am teaching this semester, and while I would much prefer the animations to be inline, at least I was able to create some useful visualizations to show during class.
No inline video in Jupyter at the end of an animation also happens when
HTML(ani.to_html5_video())
is not at the very end of a notebook cell, as the output is then suppressed.
You may use it then as follows
out = HTML(ani.to_html5_video())
and just type out` in a new cell to get the video online.