python - sns - Cómo trazar varias parcelas conjuntas Seaborn en subtrama
sns multiple plots (2)
Tengo problemas para colocar Seaborn
Jointplot
dentro de una
subplot
Jointplot
.
import pandas as pd
import seaborn as sns
df = pd.DataFrame({''C1'': {''a'': 1,''b'': 15,''c'': 9,''d'': 7,''e'': 2,''f'': 2,''g'': 6,''h'': 5,''k'': 5,''l'': 8},
''C2'': {''a'': 6,''b'': 18,''c'': 13,''d'': 8,''e'': 6,''f'': 6,''g'': 8,''h'': 9,''k'': 13,''l'': 15}})
fig = plt.figure();
ax1 = fig.add_subplot(121);
ax2 = fig.add_subplot(122);
sns.jointplot("C1", "C2", data=df, kind=''reg'', ax=ax1)
sns.jointplot("C1", "C2", data=df, kind=''kde'', ax=ax2)
Observe cómo solo una parte de la
jointplot
se coloca dentro de la subtrama y el resto se deja dentro de otros dos marcos de trama.
Lo que me gustaría es tener ambas
distributions
también insertadas dentro de las
subplots
.
¿Alguien puede ayudarme con esto?
No se puede hacer fácilmente sin piratear.
jointplot
llama
JointGrid
método
JointGrid
, que a su vez crea un nuevo objeto de
figure
cada vez que se llama.
Por lo tanto, el truco es hacer dos gráficos de unión (
JG1
JG2
), luego hacer una nueva figura, luego migrar los objetos de los ejes de
JG1
JG2
a la nueva figura creada.
Finalmente, ajustamos los tamaños y las posiciones de las subtramas en la nueva figura que acabamos de crear.
JG1 = sns.jointplot("C1", "C2", data=df, kind=''reg'')
JG2 = sns.jointplot("C1", "C2", data=df, kind=''kde'')
#subplots migration
f = plt.figure()
for J in [JG1, JG2]:
for A in J.fig.axes:
f._axstack.add(f._make_key(A), A)
#subplots size adjustment
f.axes[0].set_position([0.05, 0.05, 0.4, 0.4])
f.axes[1].set_position([0.05, 0.45, 0.4, 0.05])
f.axes[2].set_position([0.45, 0.05, 0.05, 0.4])
f.axes[3].set_position([0.55, 0.05, 0.4, 0.4])
f.axes[4].set_position([0.55, 0.45, 0.4, 0.05])
f.axes[5].set_position([0.95, 0.05, 0.05, 0.4])
Es un truco porque ahora estamos utilizando los métodos privados
_axstack
y
_add_key
, que podrían y no seguir siendo los mismos que ahora están en futuras versiones de
matplotlib
.
Mover ejes en matplotlib no es tan fácil como solía ser en versiones anteriores. Lo siguiente funciona con la versión actual de matplotlib.
Como se ha señalado en varios lugares (
esta pregunta
, también
este problema
), varios de los comandos nacidos en el mar crean su propia figura automáticamente.
Esto está codificado en el código marino, por lo que actualmente no hay forma de producir tales tramas en las figuras existentes.
Esos son
PairGrid
,
FacetGrid
,
JointGrid
,
pairplot
,
jointplot
y
lmplot
.
Hay una
bifurcación naciente disponible
que permitiría suministrar una cuadrícula de subtrama a las clases respectivas de modo que la trama se cree en una figura preexistente.
Para usar esto, necesitará copiar el
axisgrid.py
de la bifurcación a la carpeta naciente.
Tenga en cuenta que actualmente está restringido para ser utilizado con matplotlib 2.1 (posiblemente también 2.0).
Una alternativa podría ser crear una figura marina y copiar los ejes a otra figura.
El principio de esto se muestra en
esta respuesta
y podría extenderse a las parcelas de Searborn.
La implementación es un poco más complicada de lo que inicialmente esperaba.
La siguiente es una clase
SeabornFig2Grid
que se puede llamar con una instancia de cuadrícula nacida en el mar (el retorno de cualquiera de los comandos anteriores), una figura matplotlib y una
subplot_spec
, que es una posición de una cuadrícula
gridspec
.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import seaborn as sns
import numpy as np
class SeabornFig2Grid():
def __init__(self, seaborngrid, fig, subplot_spec):
self.fig = fig
self.sg = seaborngrid
self.subplot = subplot_spec
if isinstance(self.sg, sns.axisgrid.FacetGrid) or /
isinstance(self.sg, sns.axisgrid.PairGrid):
self._movegrid()
elif isinstance(self.sg, sns.axisgrid.JointGrid):
self._movejointgrid()
self._finalize()
def _movegrid(self):
""" Move PairGrid or Facetgrid """
self._resize()
n = self.sg.axes.shape[0]
m = self.sg.axes.shape[1]
self.subgrid = gridspec.GridSpecFromSubplotSpec(n,m, subplot_spec=self.subplot)
for i in range(n):
for j in range(m):
self._moveaxes(self.sg.axes[i,j], self.subgrid[i,j])
def _movejointgrid(self):
""" Move Jointgrid """
h= self.sg.ax_joint.get_position().height
h2= self.sg.ax_marg_x.get_position().height
r = int(np.round(h/h2))
self._resize()
self.subgrid = gridspec.GridSpecFromSubplotSpec(r+1,r+1, subplot_spec=self.subplot)
self._moveaxes(self.sg.ax_joint, self.subgrid[1:, :-1])
self._moveaxes(self.sg.ax_marg_x, self.subgrid[0, :-1])
self._moveaxes(self.sg.ax_marg_y, self.subgrid[1:, -1])
def _moveaxes(self, ax, gs):
#https://.com/a/46906599/4124317
ax.remove()
ax.figure=self.fig
self.fig.axes.append(ax)
self.fig.add_axes(ax)
ax._subplotspec = gs
ax.set_position(gs.get_position(self.fig))
ax.set_subplotspec(gs)
def _finalize(self):
plt.close(self.sg.fig)
self.fig.canvas.mpl_connect("resize_event", self._resize)
self.fig.canvas.draw()
def _resize(self, evt=None):
self.sg.fig.set_size_inches(self.fig.get_size_inches())
El uso de esta clase se vería así:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import seaborn as sns; sns.set()
import SeabornFig2Grid as sfg
iris = sns.load_dataset("iris")
tips = sns.load_dataset("tips")
# An lmplot
g0 = sns.lmplot(x="total_bill", y="tip", hue="smoker", data=tips,
palette=dict(Yes="g", No="m"))
# A PairGrid
g1 = sns.PairGrid(iris, hue="species")
g1.map(plt.scatter, s=5)
# A FacetGrid
g2 = sns.FacetGrid(tips, col="time", hue="smoker")
g2.map(plt.scatter, "total_bill", "tip", edgecolor="w")
# A JointGrid
g3 = sns.jointplot("sepal_width", "petal_length", data=iris,
kind="kde", space=0, color="g")
fig = plt.figure(figsize=(13,8))
gs = gridspec.GridSpec(2, 2)
mg0 = sfg.SeabornFig2Grid(g0, fig, gs[0])
mg1 = sfg.SeabornFig2Grid(g1, fig, gs[1])
mg2 = sfg.SeabornFig2Grid(g2, fig, gs[3])
mg3 = sfg.SeabornFig2Grid(g3, fig, gs[2])
gs.tight_layout(fig)
#gs.update(top=0.7)
plt.show()
Tenga en cuenta que puede haber varios inconvenientes al copiar ejes y lo anterior (todavía) no se ha probado exhaustivamente.