2016-01-27 11 views
11

mam problemu ze złożeniem Seaborn Jointplot wewnątrz wielokolumnowy subplot.Jak wykreślić wiele Seaborn Jointplot w subplot

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) 

Wskazówki jak tylko część jointplot jest umieszczony wewnątrz poletko, a resztę pozostawić wewnątrz dwóch kolejnych klatek fabuły. Chciałbym również wstawić zarówno distributions do wnętrza subplots.

Czy ktoś może w tym pomóc?

+1

@mwaskom, haha, Michael, z wystarczającą ilością hakowania może być to możliwe, :) –

+1

@mwaskom jesteś na pewno ekspertem tutaj nie ja, ale poważnie myślę, że jest to bardzo pożądana cecha, która powinna być możliwa z konieczności. – Afloz

Odpowiedz

16

Nie można tego łatwo zrobić bez hakowania. Metoda jointplot wywołuje metodę JointGrid, która z kolei tworzy nowy obiekt figure za każdym razem, gdy zostanie wywołany.

Dlatego hack jest, aby dwie jointplots (JG1JG2), a następnie utworzyć nową postać, a następnie przenieść obiekty osie z JG1JG2 do nowego rysunku stworzonego.

Wreszcie możemy dostosować rozmiary i położenia wątków w nowym rysunku po prostu stworzony.

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]) 

Jest to hack, ponieważ jesteśmy teraz za pomocą _axstack i _add_key prywatnych metod, które mogą i nie mogą pozostać bez zmian, ponieważ są one teraz w matplotlib przyszłych wersjach.

enter image description here

+1

Trudno mi sobie wyobrazić, że zmierzenie się z tymi problemami jest lepszym pomysłem, niż tylko ustawienie siatki podplota i narysowanie na niej 'distplot',' regplot' i 'kdeplot'. – mwaskom

+1

Całkowicie się zgadzam. Ale mądrość przydatności do ponownego użycia oznaczałaby skopiowanie wielu kodów zapisanych w 'joint_plot'. Może 'JointGird' może przyjąć parametr opcjonalny do określenia osi/osi drukowania. Obecne zachowanie oznaczałoby, że jeśli poda się 'ax = some_axis', tylko wykres' regplot' przejdzie do 'some_axis'. –

+1

To jest po prostu zachowanie, które wynika z tego, że ktoś używa tej funkcji w sposób, który nigdy nie był zamierzony. – mwaskom

4

Moving osie w matplotlib nie jest tak proste, jak to było kiedyś w poprzednich wersjach. Poniższy działa z aktualną wersją matplotlib.

Jak zauważono w kilku miejscach (this question, również this issue) kilka poleceń z seaborn tworzy własną postać automatycznie. Jest to zakodowane na stałe w kodzie seaborn, więc obecnie nie ma sposobu na stworzenie takich działek na istniejących figurach. Są PairGrid, FacetGrid, JointGrid, pairplot, jointplot i lmplot.

Istnieje seaborn fork available, który pozwoliłby na dostarczenie siatki podplotu do odpowiednich klas tak, że wykres zostanie utworzony na wcześniejszej figurze. Aby z tego skorzystać, musisz skopiować axisgrid.py z widelca do folderu seaborn. Zauważ, że obecnie jest to ograniczone do użycia z matplotlib 2.1 (prawdopodobnie również 2.0).

Alternatywą mogłoby być stworzenie Seaborn figurę i skopiuj osie do innego rysunku. Zasada tego jest pokazana w this answer i może być rozszerzona na wykresy Searborn. Implementacja jest nieco bardziej skomplikowana, niż się początkowo spodziewałem. Poniżej znajduje się klasa SeabornFig2Grid, która może być wywołana z instancją siatki typu seaborn (zwrot dowolnego z powyższych poleceń), figurą matplotlib i subplot_spec, która jest pozycją siatki 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://stackoverflow.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()) 

Wykorzystanie tej klasie będzie wyglądać następująco:

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() 

enter image description here

Zauważ, że może istnieć kilka wad z kopiowania osie i powyżej nie jest (jeszcze) dokładnie testowane.

+0

To naprawdę świetna odpowiedź. Skopiowałem twoją klasę bezpośrednio do mojego kodu, a przykład uczynił ją jasną, jak z niej korzystać. Wszystko działało dokładnie tak, jak miałem nadzieję :) – CaptainKinematics

Powiązane problemy