如何使用多部分seaborn图迭代地填充matplotlib gridspec?

时间:2017-11-28 16:01:39

标签: python pandas matplotlib seaborn

我正在处理的一些最小代码。有些参数可能看起来多余,但我并不打算删除所有这些参数。

import matplotlib
import matplotlib.gridspec as gridspec
matplotlib.use("macosx")
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

def plot_overlaid_2d_hist(data,
                          plot_axis_x,
                          plot_axis_y,
                          plot_axis_x_lab,
                          plot_axis_y_lab,
                          group_by = "group_name"):

    # don't mind this for now
    df = data

    # Figure aspect
    w, h = plt.figaspect(1)
    fig = plt.figure(figsize = (w, h))

    # Count the number of groups to make plots for
    n_groups = len(df.groupby(group_by))
    gs = gridspec.GridSpec(nrows = n_groups, ncols = 1)
    subplot_id = 0

    # Reshape data to make it work
    for name, group in df.groupby(group_by, sort = False):

        # Initialize subplot
        fig.add_subplot(gs[subplot_id, 0])

        # Check if we get subplots with pyplot
        if subplot_id == 0:
            col = "red"
        else:
            col = "blue"

        plt.plot(x, y, color = col)


        # instantiate JointGrid
        # g = sns.JointGrid(group[plot_axis_x],
        #                   group[plot_axis_y],
        #                   data = group,
        #                   space = 0,
        #                   xlim = (0, 1.2),
        #                   ylim = (0, 1))
        # 
        # # Fix labels
        # g = g.set_axis_labels(xlabel = str(plot_axis_x_lab),
        #                   ylabel = str(plot_axis_y_lab))
        # 
        # # center scatter plot on top
        # g = g.plot_joint(plt.scatter,
        #              s = 0.5,
        #              alpha = 1,
        #              linewidth = 1)
        # 
        # # marginals plot
        # g = g.plot_marginals(sns.distplot,
        #                  kde = True,
        #                  kde_kws = dict(linewidth = 2,
        #                                 alpha = 1,
        #                                 bw = "Scott"),
        #                  hist_kws = dict(alpha = 1))


        # Next plot in row +1
        subplot_id += 1

    # Output
    plt.tight_layout()  # Attempts to fix alignment of subplot layout and axis titles

    plt.show()

# quick data to check if the plots end up where they should
x = [0.5, 0.5, 0.4, 0.4]
y = [0.6, 0.4, 0.3, 0.4]
grp = ["a", "a", "b", "b"]


df = pd.DataFrame({"x":x,
                   "y":y,
                   "grp": grp})

plot_overlaid_2d_hist(data = df,
                      group_by = "grp",
                      plot_axis_x_lab = "x",
                      plot_axis_y_lab = "x",
                      plot_axis_y = "x",
                      plot_axis_x = "x")

运行带有所有seaborn图(g)的代码注释显示它适用于原生pyplot,但是当我添加多部分seaborn图时,它们显示在单独的图中。我想要的是让每个2D-histogram-margin-and-scatter填充他们自己的gridspec行/列。

1 个答案:

答案 0 :(得分:1)

看到here之前已经问过这个问题我把这个答案移到了旧问题上。我想在这里删除它,但不能这样做,因为它已被接受

正如在几个地方(this question,还有this issue)所指出的,一些seaborn命令会自动创建自己的数字。这被硬编码到seaborn代码中,因此目前无法在现有数字中生成这样的图。这些是PairGridFacetGridJointGridpairplotjointplotlmplot

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

另一种方法是创建一个seaborn图形并将轴复制到另一个图形。 this answer中显示了这一原则,可以扩展到Searborn情节。实现比我最初预期的要复杂一些。以下是可以使用seaborn网格实例(上述任何命令的返回),matplotlib图和SeabornFig2Grid调用的类subplot_spec,它是{{1}的位置网格。

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

enter image description here

请注意,复制轴可能存在一些缺点,而且上述内容尚未经过彻底测试。