Python 如何在子图中绘制多个 Seaborn Jointplot

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35042255/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 15:54:39  来源:igfitidea点击:

How to plot multiple Seaborn Jointplot in Subplot

pythonpython-3.xpandasmatplotlibseaborn

提问by Afloz

I'm having problem placing Seaborn Jointplotinside a multicolumn subplot.

我在将 SeabornJointplot放入 multicolumn 时遇到问题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 jointplotis placed inside the subplot and the rest left inside another two plot frames. What I'd want is to have both the distributionsalso inserted inside the subplots.

请注意如何仅将一部分jointplot放置在子图内,而其余部分则留在另外两个图框中。我想要的是distributions同时插入subplots.

Can anyone help with this?

有人能帮忙吗?

回答by CT Zhu

It can not be easily done without hacking. jointplotcalls JointGridmethod, which in turn creates a new figureobject every time it is called.

如果不进行黑客攻击,就无法轻松完成。 jointplotcallJointGrid方法,该方法figure每次调用时都会创建一个新对象。

Therefore, the hack is to make two jointplots (JG1JG2), then make a new figure, then migrate the axes objects from JG1JG2to the new figure created.

因此,hack 是制作两个联合图 ( JG1JG2),然后制作一个新图形,然后将轴对象从中迁移JG1JG2到创建的新图形。

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 _axstackand _add_keyprivate methods, which might and might not stay the same as they are now in matplotlibfuture versions.

这是一个黑客,因为我们现在使用_axstack_add_key私有方法,这可能会,可能不会保持不变,因为他们现在是在matplotlib未来的版本。

enter image description here

在此处输入图片说明

回答by ImportanceOfBeingErnest

Moving axes in matplotlib is not as easy as it used to be in previous versions. The below is working with the current version of matplotlib.

在 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. This is hardcoded into the seaborn code, so there is currently no way to produce such plots in existing figures. Those are PairGrid, FacetGrid, JointGrid, pairplot, jointplotand lmplot.

正如在几个地方(这个问题,也是这个问题)所指出的,一些seaborn 命令会自动创建自己的图形。这被硬编码到 seaborn 代码中,因此目前无法在现有图形中生成此类图。这些都是PairGridFacetGridJointGridpairplotjointplotlmplot

There is a seaborn fork availablewhich would allow to supply a subplot grid to the respective classes such that the plot is created in a preexisting figure. To use this, you would need to copy the axisgrid.pyfrom the fork to the seaborn folder. Note that this is currently restricted to be used with matplotlib 2.1 (possibly 2.0 as well).

有一个seaborn fork 可用,它允许为相应的类提供子图网格,以便在预先存在的图形中创建图。要使用此功能,您需要axisgrid.py将 fork 中的复制到 seaborn 文件夹中。请注意,这目前仅限于与 matplotlib 2.1(也可能是 2.0)一起使用。

An alternative could be to create a seaborn figure and copy the axes to another figure. The principle of this is shown in this answerand could be extended to Searborn plots. The implementation is a bit more complicated that I had initially expected. The following is a class SeabornFig2Gridthat 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 gridspecgrid.

另一种方法是创建一个 seaborn 图形并将轴复制到另一个图形。此答案中显示了这一原理,并且可以扩展到 Searborn 图。实现比我最初预期的要复杂一些。下面是一个SeabornFig2Grid可以使用 seaborn 网格实例(上述任何命令的返回)、一个 matplotlib 图和 a 调用的类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()

enter image description here

在此处输入图片说明

Note that there might be several drawbacks from copying axes and the above is not (yet) tested thoroughly.

请注意,复制轴可能有几个缺点,并且上述内容尚未(尚未)彻底测试。

回答by Sun

Here is my solution, you can consider overwriting class method. No need to copy and paste axes.

这是我的解决方案,您可以考虑覆盖类方法。无需复制和粘贴轴。

import seaborn as sns
class myjoint(sns.JointGrid):
    def __init__(self, x, y, data=None,height=6, ratio=5, space=.2,
                 dropna=True, xlim=None, ylim=None, size=None):
        super(myjoint, self).__init__(x, y, data,height, ratio, space,
                 dropna, xlim, ylim, size)
        plt.close(2)
        # Set up the subplot grid
        self.ax_joint = f.add_subplot(gs[1:, :-1])
        self.ax_marg_x = f.add_subplot(gs[0, :-1], sharex=self.ax_joint)
        self.ax_marg_y = f.add_subplot(gs[1:, -1], sharey=self.ax_joint)
        # Turn off tick visibility for the measure axis on the marginal plots
        plt.setp(self.ax_marg_x.get_xticklabels(), visible=False)
        plt.setp(self.ax_marg_y.get_yticklabels(), visible=False)

Now you will be able to plot multi seaborn jointplot:

现在您将能够绘制多seaborn联合图:

import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
import pandas as pd

tips = sns.load_dataset("tips")

ratio = 3
f = plt.figure(figsize=(10,10))
outer_grid = gridspec.GridSpec(2, 2, wspace=0.3, hspace=0.3)
weekdays   = ["Sun","Sat","Thur","Fri"]
for i,weekday in enumerate(weekdays):
    tips[tips["day"]==weekday]
    gs = gridspec.GridSpecFromSubplotSpec(ratio+1, ratio+1,
            subplot_spec=outer_grid[i], wspace=0.3, hspace=0.3)
    g = myjoint(x="total_bill", y="tip", data=tips, ratio=ratio)
    g = g.plot(sns.regplot, sns.distplot)
f.tight_layout()

You can adjust the plot behavior in the above code clip, pay attention, the default style of seaborn is not suitable for publications (they don't have digits showing on the probability density plot). Hope this can be helpful to students in MLES class as well.

您可以在上面的代码片段中调整绘图行为,注意,seaborn 的默认样式不适用于出版物(它们在概率密度图上没有显示数字)。希望这也能对 MLES 班的学生有所帮助。

回答by Pedro Herruzo

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. Use other formats than '.png' if you seek a higher resolution.

如果您在@ImportanceOfBeingErnest 的优雅解决方案中遇到了麻烦,您仍然可以将seaborn 图作为图像保存到内存中,并使用它们来构建您的自定义图形。如果您寻求更高的分辨率,请使用“.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

四个子图一起显示在下图中