简体   繁体   English

"如何在子图中绘制多个 Seaborn 联合图"

[英]How to plot multiple Seaborn Jointplot in Subplot

I'm having problem placing Seaborn Jointplot inside a multicolumn subplot .我在将 Seaborn Jointplot放置在多列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)

Notice how only a portion of the jointplot is placed inside the subplot and the rest left inside another two plot frames.请注意,只有一部分jointplot图被放置在子图中,其余部分则留在另外两个图框内。 What I'd want is to have both the distributions also inserted inside the subplots .我想要的是将两个distributions也插入到subplots中。

Can anyone help with this?有人能帮忙吗?

Moving axes in matplotlib is not as easy as it used to be in previous versions.在 matplotlib 中移动轴不像以前的版本那么容易。 The below is working with the current version of matplotlib.下面是使用当前版本的 matplotlib。

As has been pointed out at several places ( this question , also this issue ) several of the seaborn commands create their own figure automatically.正如在几个地方( 这个问题,也是这个问题)所指出的,一些 seaborn 命令会自动创建自己的图形。 This is hardcoded into the seaborn code, so there is currently no way to produce such plots in existing figures.这被硬编码到 seaborn 代码中,因此目前无法在现有图形中生成此类图。 Those are PairGrid , FacetGrid , JointGrid , pairplot , jointplot and lmplot .这些是PairGridFacetGridJointGridpairplotjointplotlmplot

There is a seaborn fork available which would allow to supply a subplot grid to the respective classes such that the plot is created in a preexisting figure.有一个seaborn fork 可用,它允许为相应的类提供子图网格,以便在预先存在的图形中创建图。 To use this, you would need to copy the axisgrid.py from the fork to the seaborn folder.要使用它,您需要将axisgrid.py从fork 复制到seaborn 文件夹。 Note that this is currently restricted to be used with matplotlib 2.1 (possibly 2.0 as well).请注意,这目前仅限于与 matplotlib 2.1(也可能是 2.0)一起使用。

An alternative could be to create a seaborn figure and copy the axes to another figure.另一种方法是创建一个 seaborn 图形并将轴复制到另一个图形。 The principle of this is shown in this answer and could be extended to Searborn plots.此答案中显示了这一原理,并且可以扩展到 Searborn 图。 The implementation is a bit more complicated that I had initially expected.实现比我最初预期的要复杂一些。 The following is a class SeabornFig2Grid that can be called with a seaborn grid instance (the return of any of the above commands), a matplotlib figure and a subplot_spec , which is a position of a gridspec grid.下面是一个SeabornFig2Grid类,可以用一个 seaborn 网格实例(上述任何命令的返回)、一个 matplotlib 图和一个subplot_spec ,它是gridspec网格的位置。

Note: This is a proof of concept , it may work for most easy cases, but I would not recommend using it in production code.注意:这是一个概念证明,它可能适用于大多数简单的情况,但我不建议在生产代码中使用它。

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

The usage of this class would look like this:此类的用法如下所示:

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

在此处输入图片说明

Note that there might be several drawbacks from copying axes and the above is not (yet) tested thoroughly.请注意,复制轴可能存在一些缺点,并且上述内容尚未(尚未)彻底测试。

It can not be easily done without hacking.如果不进行黑客攻击,就无法轻松完成。 jointplot calls JointGrid method, which in turn creates a new figure object every time it is called. jointplot调用JointGrid方法,该方法jointplot调用时JointGrid创建一个新的figure对象。

Therefore, the hack is to make two jointplots ( JG1 JG2 ), then make a new figure, then migrate the axes objects from JG1 JG2 to the new figure created.因此,hack 是制作两个联合图( JG1 JG2 ),然后制作一个新图形,然后将轴对象从JG1 JG2迁移到创建的新图形。

Finally, we adjust the sizes and the positions of subplots in the new figure we just created.最后,我们在刚刚创建的新图形中调整子图的大小和位置。

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

It is a hack because we are now using _axstack and _add_key private methods, which might and might not stay the same as they are now in matplotlib future versions.这是一个 hack,因为我们现在使用_axstack_add_key私有方法,它们可能会也可能不会与它们现在在matplotlib未来版本中保持相同。

在此处输入图片说明

If you get into trouble despite the elegant solution of @ImportanceOfBeingErnest, you can still save seaborn plots to memory as images and use them to build your custom figure.如果您在@ImportanceOfBeingErnest 的优雅解决方案中遇到了麻烦,您仍然可以将seaborn 图作为图像保存到内存中,并使用它们来构建您的自定义图形。 Use other formats than '.png' if you seek a higher resolution.如果您寻求更高的分辨率,请使用“.png”以外的其他格式。

Here is the example is shown above using this nasty (but working) approach:这是上面使用这种讨厌(但有效)方法显示的示例:

import matplotlib.image as mpimg
import matplotlib.pyplot as plt
import seaborn as sns

# data
iris = sns.load_dataset("iris")
tips = sns.load_dataset("tips")

############### 1. CREATE PLOTS
# 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")

############### 2. SAVE PLOTS IN MEMORY TEMPORALLY
g0.savefig('g0.png')
plt.close(g0.fig)

g1.savefig('g1.png')
plt.close(g1.fig)

g2.savefig('g2.png')
plt.close(g2.fig)

g3.savefig('g3.png')
plt.close(g3.fig)

############### 3. CREATE YOUR SUBPLOTS FROM TEMPORAL IMAGES
f, axarr = plt.subplots(2, 2, figsize=(25, 16))

axarr[0,0].imshow(mpimg.imread('g0.png'))
axarr[0,1].imshow(mpimg.imread('g1.png'))
axarr[1,0].imshow(mpimg.imread('g3.png'))
axarr[1,1].imshow(mpimg.imread('g2.png'))

# turn off x and y axis
[ax.set_axis_off() for ax in axarr.ravel()]

plt.tight_layout()
plt.show()

The four subplots are shown together in the following image四个子图一起显示在下图中

Recently, I'm developing patchworklib , which is a subplot manager for matplotlib, inspired by patchwork.最近,我正在开发patchworklib ,它是 matplotlib 的子图管理器,受拼凑的启发。 It allows you to quickly arrange multiple gridded seaborn plots using only / and |它允许您仅使用/|快速排列多个网格化 seaborn 图。 operators.运营商。

Here is the example code, which you can also run on Google colab :这是示例代码, 您也可以在 Google colab 上运行

import seaborn as sns
import patchworklib as pw 
sns.set_theme()
pw.overwrite_axisgrid() 

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"))
g0 = pw.load_seaborngrid(g0, label="g0")

# A Pairplot
g1 = sns.pairplot(iris, hue="species")
g1 = pw.load_seaborngrid(g1, label="g1")

# A relplot
g2 = sns.relplot(data=tips, x="total_bill", y="tip", col="time", hue="time", 
                 size="size", style="sex", palette=["b", "r"], sizes=(10, 100))
g2 = pw.load_seaborngrid(g2, label="g2")

# A JointGrid
g3 = sns.jointplot("sepal_width", "petal_length", data=iris,
                   kind="kde", space=0, color="g")

g3 = pw.load_seaborngrid(g3, label="g3")
(((g0|g1)["g0"]/g3)["g3"]|g2).savefig("seaborn_subplots.png")

seaborn_subplots.png

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM