代码之家  ›  专栏  ›  技术社区  ›  bbengfort

当数据限制不同时,是否创建具有多个轴的等宽(正方形)绘图?

  •  7
  • bbengfort  · 技术社区  · 7 年前

    我想创建一个正方形的绘图使用多个轴使用 make_axes_locateable matplotlib documentation . 但是,虽然这在x和y数据具有相同范围的绘图上有效,但在范围相差几个数量级时不起作用。

    import numpy as np
    import matplotlib.pyplot as plt
    
    from mpl_toolkits.axes_grid1 import make_axes_locatable
    
    x = np.random.normal(512, 112, 240)
    y = np.random.normal(0.5, 0.1, 240)
    
    _, ax = plt.subplots()
    divider = make_axes_locatable(ax)
    
    xhax = divider.append_axes("top", size=1, pad=0.1, sharex=ax)
    yhax = divider.append_axes("right", size=1, pad=0.1, sharey=ax)
    
    ax.scatter(x, y)
    xhax.hist(x)
    yhax.hist(y, orientation="horizontal")
    
    x0,x1 = ax.get_xlim()
    y0,y1 = ax.get_ylim()
    ax.set_aspect(abs(x1-x0)/abs(y1-y0))
    
    plt.show()
    

    set_aspect 回答 How do I make a matplotlib scatter plot square?

    enter image description here

    我试图用以下方法修复此问题:

    ax.set_aspect(abs(x1-x0)/abs(y1-y0), share=True)
    

    但这导致了以下情况:

    enter image description here

    enter image description here

    使现代化 :此问题的关键限制之一是使用 使轴可定位 而不是 GridSpec

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.gridspec as grid
    
    from mpl_toolkits.axes_grid1 import make_axes_locatable, axes_size
    
    
    def joint_plot(x, y, ax=None):
        """
        Create a square joint plot of x and y.
        """
        if ax is None:
            ax = plt.gca()
    
        divider = make_axes_locatable(ax)
        xhax = divider.append_axes("top", size=1, pad=0.1, sharex=ax)
        yhax = divider.append_axes("right", size=1, pad=0.1, sharey=ax)
    
        ax.scatter(x, y)
        xhax.hist(x)
        yhax.hist(y, orientation="horizontal")
    
        x0,x1 = ax.get_xlim()
        y0,y1 = ax.get_ylim()
        ax.set_aspect(abs(x1-x0)/abs(y1-y0))
    
        plt.sca(ax)
        return ax, xhax, yhax
    
    
    def color_plot(x, y, colors, ax=None):
        if ax is None:
            ax = plt.gca()
    
        divider = make_axes_locatable(ax)
        cbax = divider.append_axes("right", size="5%", pad=0.1)
    
        sc = ax.scatter(x, y, marker='o', c=colors, cmap='RdBu')
        plt.colorbar(sc, cax=cbax)
    
        ax.set_aspect("equal")
    
        plt.sca(ax)
        return ax, cbax
    
    
    if __name__ == "__main__":
        _, axes = plt.subplots(nrows=2, ncols=2, figsize=(9,6))
    
        # Plot 1
        x = np.random.normal(100, 17, 120)
        y = np.random.normal(0.5, 0.1, 120)
        joint_plot(x, y, axes[0,0])
    
        # Plot 2
        x = np.random.normal(100, 17, 120)
        y = np.random.normal(100, 17, 120)
        c = np.random.normal(100, 17, 120)
        color_plot(x, y, c, axes[0,1])
    
        # Plot 3
        x = np.random.normal(100, 17, 120)
        y = np.random.normal(0.5, 0.1, 120)
        c = np.random.uniform(0.0, 1.0, 120)
        color_plot(x, y, c, axes[1,0])
    
        # Plot 4
        x = np.random.normal(0.5, 0.1, 120)
        y = np.random.normal(0.5, 0.1, 120)
        joint_plot(x, y, axes[1,1])
    
        plt.tight_layout()
        plt.show()
    

    enter image description here

    这个问题扩展了以下问题: Set equal aspect in plot with colorbar python interplay between axis('square') and set_xlim 因为只有轴约束。

    2 回复  |  直到 7 年前
        1
  •  4
  •   Thomas Kühn    7 年前

    ax.set_aspect('equal') 一切正常。当然,如果只执行此操作,则记号标签的范围仅为0到1,因此必须应用一点matplotlib魔术将记号标签调整为原始数据范围。答案 here 显示了如何使用 FuncFormatter . 但是,由于原始记号是相对于间隔[0,1]选择的,因此使用 函数格式化程序 AutoLocator tick_values() 作用然后,这些刻度可以再次缩放到间隔[0,1],然后 函数格式化程序

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.ticker as mticker
    
    from mpl_toolkits.axes_grid1 import make_axes_locatable
    
    x = np.random.normal(512, 112, 240)
    y = np.random.normal(0.5, 0.1, 240)
    
    
    fig,ax=plt.subplots()
    
    divider = make_axes_locatable(ax)
    
    
    ##increased pad from 0.1 to 0.2 so that tick labels don't overlap
    xhax = divider.append_axes("top", size=1, pad=0.2, sharex=ax)
    yhax = divider.append_axes("right", size=1, pad=0.2, sharey=ax)
    
    ##'normalizing' x and y values to be between 0 and 1:
    xn = (x-min(x))/(max(x)-min(x))
    yn = (y-min(y))/(max(y)-min(y))
    
    ##producinc the plots
    ax.scatter(xn, yn)
    xhax.hist(xn)
    yhax.hist(yn, orientation="horizontal")
    
    ##turning off duplicate ticks (if needed):
    plt.setp(xhax.get_xticklabels(), visible=False)
    plt.setp(yhax.get_yticklabels(), visible=False)
    
    ax.set_aspect('equal')
    
    
    ##setting up ticks and labels to simulate real data:
    locator = mticker.AutoLocator()
    
    xticks = (locator.tick_values(min(x),max(x))-min(x))/(max(x)-min(x))
    ax.set_xticks(xticks)
    ax.xaxis.set_major_formatter(mticker.FuncFormatter(
        lambda t, pos: '{0:g}'.format(t*(max(x)-min(x))+min(x))
    ))
    
    yticks = (locator.tick_values(min(y),max(y))-min(y))/(max(y)-min(y))
    ax.set_yticks(yticks)
    ax.yaxis.set_major_formatter(mticker.FuncFormatter(
        lambda t, pos: '{0:g}'.format(t*(max(y)-min(y))+min(y))
    ))
    
    fig.tight_layout()
    plt.show()
    

    生成的图片看起来与预期的一样,并且在调整图像大小时也保持方形。

    :

    这与其说是解决方案,不如说是一种变通方法:

    而不是使用 ax.set_aspect() figsize=(n,n) plt.subplots 哪里 n xhax 以及 yhax 都是1英寸,这意味着 ax 也变成正方形。

    import numpy as np
    import matplotlib.pyplot as plt
    
    from mpl_toolkits.axes_grid1 import make_axes_locatable
    
    x = np.random.normal(512, 112, 240)
    y = np.random.normal(0.5, 0.1, 240)
    
    fig, ax = plt.subplots(figsize=(5,5))
    
    divider = make_axes_locatable(ax)
    
    xhax = divider.append_axes("top", size=1, pad=0.1, sharex=ax)
    yhax = divider.append_axes("right", size=1, pad=0.1, sharey=ax)
    
    ax.scatter(x, y)
    xhax.hist(x)
    yhax.hist(y, orientation="horizontal")
    
    ##turning off duplicate ticks:
    plt.setp(xhax.get_xticklabels(), visible=False)
    plt.setp(yhax.get_yticklabels(), visible=False)
    
    plt.show()
    

    enter image description here

    当然,一旦你调整你的身材,正方形的外观就会消失。但是,如果你已经知道你的身材的最终尺寸,并且只想保存它以供进一步使用,那么这应该是一个足够好的快速修复方法。

        2
  •  2
  •   ImportanceOfBeingErnest    7 年前

    这个 axes_grid1 Divider 绝对坐标。

    如果需要,可以在绝对坐标中手动指定轴的大小,以获得方形子地块。

    import numpy as np
    import matplotlib.pyplot as plt
    from mpl_toolkits.axes_grid1 import make_axes_locatable, axes_size
    
    x = np.random.normal(512, 112, 240)
    y = np.random.normal(0.5, 0.1, 240)
    
    _, ax = plt.subplots()
    divider = make_axes_locatable(ax)
    
    xhax = divider.append_axes("top", size=1, pad=0.1, sharex=ax)
    yhax = divider.append_axes("right", size=1, pad=0.1, sharey=ax)
    
    horiz = [axes_size.Fixed(2.8), axes_size.Fixed(.1), axes_size.Fixed(1)]
    vert = [axes_size.Fixed(2.8), axes_size.Fixed(.1), axes_size.Fixed(1)]
    divider.set_horizontal(horiz)
    divider.set_vertical(vert)
    
    ax.scatter(x, y)
    xhax.hist(x)
    yhax.hist(y, orientation="horizontal")
    
    plt.setp(xhax.get_xticklabels(), visible=False)
    plt.setp(yhax.get_yticklabels(), visible=False)
    
    plt.show()
    

    enter image description here

    enter image description here

    要有一个仍然可以随体形大小缩放的自适应解决方案,可以定义一个自定义 Size ,它获取绝对尺寸填充轴和边缘轴的剩余值,并返回绝对坐标(英寸)中的最小值,用于两个方向,以便轴始终为正方形。

    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.transforms import Bbox
    from mpl_toolkits.axes_grid1 import make_axes_locatable, axes_size
    
    class RemainderFixed(axes_size.Scaled):
        def __init__(self, xsizes, ysizes, divider):
            self.xsizes =xsizes
            self.ysizes =ysizes
            self.div = divider
    
        def get_size(self, renderer):
            xrel, xabs = axes_size.AddList(self.xsizes).get_size(renderer)
            yrel, yabs = axes_size.AddList(self.ysizes).get_size(renderer)
            bb = Bbox.from_bounds(*self.div.get_position()).transformed(self.div._fig.transFigure)
            w = bb.width/self.div._fig.dpi - xabs
            h = bb.height/self.div._fig.dpi - yabs
            return 0, min([w,h])
    
    x = np.random.normal(512, 112, 240)
    y = np.random.normal(0.5, 0.1, 240)
    
    fig, ax = plt.subplots()
    divider = make_axes_locatable(ax)
    
    margin_size = axes_size.Fixed(1)
    pad_size = axes_size.Fixed(.1)
    xsizes = [pad_size, margin_size]
    ysizes = xsizes
    
    xhax = divider.append_axes("top", size=margin_size, pad=pad_size, sharex=ax)
    yhax = divider.append_axes("right", size=margin_size, pad=pad_size, sharey=ax)
    
    divider.set_horizontal([RemainderFixed(xsizes, ysizes, divider)] + xsizes)
    divider.set_vertical([RemainderFixed(xsizes, ysizes, divider)] + ysizes)
    
    ax.scatter(x, y)
    xhax.hist(x)
    yhax.hist(y, orientation="horizontal")
    
    plt.setp(xhax.get_xticklabels(), visible=False)
    plt.setp(yhax.get_yticklabels(), visible=False)
    
    plt.show()
    

    enter image description here enter image description here