【问题标题】:Is there a way to make a discontinuous axis in Matplotlib?有没有办法在 Matplotlib 中制作不连续的轴?
【发布时间】:2018-03-17 17:40:24
【问题描述】:

我正在尝试使用具有不连续 x 轴的 pyplot 创建一个绘图。通常的绘制方式是轴将具有以下内容:

(值)----//----(后来的值)

其中 // 表示您正在跳过 (values) 和 (later values) 之间的所有内容。

我找不到任何这样的例子,所以我想知道这是否可能。我知道您可以将不连续的数据连接起来,例如财务数据,但我想让轴上的跳跃更加明确。目前我只是在使用子图,但我真的很希望最终将所有内容都放在同一张图上。

【问题讨论】:

标签: python matplotlib


【解决方案1】:

查看brokenaxes 包:

import matplotlib.pyplot as plt
from brokenaxes import brokenaxes
import numpy as np

fig = plt.figure(figsize=(5,2))
bax = brokenaxes(
    xlims=((0, .1), (.4, .7)),
    ylims=((-1, .7), (.79, 1)),
    hspace=.05
)
x = np.linspace(0, 1, 100)
bax.plot(x, np.sin(10 * x), label='sin')
bax.plot(x, np.cos(10 * x), label='cos')
bax.legend(loc=3)
bax.set_xlabel('time')
bax.set_ylabel('value')

【讨论】:

  • 安装后无法在 Pycharm Community 2016.3.2 中 from brokenaxes import brokenaxes。 @ben.dichter
  • 有一个错误。我修好了它。请运行pip install brokenaxes==0.2安装固定版本的代码。
  • 似乎与 ax.grid(True) 交互不好
  • 断轴可以抑制滴答声吗?或者将轴设置为水平方向彼此靠近的格式?
  • 嗨,Ben,我想删除 y 轴,但是,我尝试了许多命令,但与断轴结合时无法正常工作,(注意 x 轴是断轴),谢谢跨度>
【解决方案2】:

对于那些感兴趣的人,我已经扩展了 @Paul 的答案并将其添加到 matplotlib 包装器 proplot。可以做轴"jumps", "speedups", and "slowdowns"。

目前没有办法像乔的回答那样添加表示离散跳跃的“十字架”,但我计划在未来添加这个。我还计划添加一个默认的“tick locator”,根据CutoffScale 参数设置合理的默认tick 位置。

【讨论】:

【解决方案3】:

一个非常简单的技巧是

  1. 散点图矩形在坐标轴的刺和
  2. 在该位置将“//”绘制为文本。

对我来说就像一个魅力:

# FAKE BROKEN AXES
# plot a white rectangle on the x-axis-spine to "break" it
xpos = 10 # x position of the "break"
ypos = plt.gca().get_ylim()[0] # y position of the "break"
plt.scatter(xpos, ypos, color='white', marker='s', s=80, clip_on=False, zorder=100)
# draw "//" on the same place as text
plt.text(xpos, ymin-0.125, r'//', fontsize=label_size, zorder=101, horizontalalignment='center', verticalalignment='center')

示例图:

【讨论】:

    【解决方案4】:

    解决 Frederick Nord 的问题,如何在使用比例不等于 1:1 的 gridspec 时启用对角线“断线”的平行方向,根据 Paul Ivanov 和 Joe Kingtons 的建议进行以下更改可能会有所帮助。可以使用变量 n 和 m 来改变宽度比。

    import matplotlib.pylab as plt
    import numpy as np
    import matplotlib.gridspec as gridspec
    
    x = np.r_[0:1:0.1, 9:10:0.1]
    y = np.sin(x)
    
    n = 5; m = 1;
    gs = gridspec.GridSpec(1,2, width_ratios = [n,m])
    
    plt.figure(figsize=(10,8))
    
    ax = plt.subplot(gs[0,0])
    ax2 = plt.subplot(gs[0,1], sharey = ax)
    plt.setp(ax2.get_yticklabels(), visible=False)
    plt.subplots_adjust(wspace = 0.1)
    
    ax.plot(x, y, 'bo')
    ax2.plot(x, y, 'bo')
    
    ax.set_xlim(0,1)
    ax2.set_xlim(10,8)
    
    # hide the spines between ax and ax2
    ax.spines['right'].set_visible(False)
    ax2.spines['left'].set_visible(False)
    ax.yaxis.tick_left()
    ax.tick_params(labeltop='off') # don't put tick labels at the top
    ax2.yaxis.tick_right()
    
    d = .015 # how big to make the diagonal lines in axes coordinates
    # arguments to pass plot, just so we don't keep repeating them
    kwargs = dict(transform=ax.transAxes, color='k', clip_on=False)
    
    on = (n+m)/n; om = (n+m)/m;
    ax.plot((1-d*on,1+d*on),(-d,d), **kwargs) # bottom-left diagonal
    ax.plot((1-d*on,1+d*on),(1-d,1+d), **kwargs) # top-left diagonal
    kwargs.update(transform=ax2.transAxes) # switch to the bottom axes
    ax2.plot((-d*om,d*om),(-d,d), **kwargs) # bottom-right diagonal
    ax2.plot((-d*om,d*om),(1-d,1+d), **kwargs) # top-right diagonal
    
    plt.show()
    

    【讨论】:

      【解决方案5】:

      我看到了很多关于此功能的建议,但没有迹象表明它已实施。这是一个暂时可行的解决方案。它将阶跃函数变换应用于 x 轴。这是很多代码,但它相当简单,因为其中大部分是样板自定义规模的东西。我没有添加任何图形来指示中断的位置,因为这是风格问题。祝你工作顺利。

      from matplotlib import pyplot as plt
      from matplotlib import scale as mscale
      from matplotlib import transforms as mtransforms
      import numpy as np
      
      def CustomScaleFactory(l, u):
          class CustomScale(mscale.ScaleBase):
              name = 'custom'
      
              def __init__(self, axis, **kwargs):
                  mscale.ScaleBase.__init__(self)
                  self.thresh = None #thresh
      
              def get_transform(self):
                  return self.CustomTransform(self.thresh)
      
              def set_default_locators_and_formatters(self, axis):
                  pass
      
              class CustomTransform(mtransforms.Transform):
                  input_dims = 1
                  output_dims = 1
                  is_separable = True
                  lower = l
                  upper = u
                  def __init__(self, thresh):
                      mtransforms.Transform.__init__(self)
                      self.thresh = thresh
      
                  def transform(self, a):
                      aa = a.copy()
                      aa[a>self.lower] = a[a>self.lower]-(self.upper-self.lower)
                      aa[(a>self.lower)&(a<self.upper)] = self.lower
                      return aa
      
                  def inverted(self):
                      return CustomScale.InvertedCustomTransform(self.thresh)
      
              class InvertedCustomTransform(mtransforms.Transform):
                  input_dims = 1
                  output_dims = 1
                  is_separable = True
                  lower = l
                  upper = u
      
                  def __init__(self, thresh):
                      mtransforms.Transform.__init__(self)
                      self.thresh = thresh
      
                  def transform(self, a):
                      aa = a.copy()
                      aa[a>self.lower] = a[a>self.lower]+(self.upper-self.lower)
                      return aa
      
                  def inverted(self):
                      return CustomScale.CustomTransform(self.thresh)
      
          return CustomScale
      
      mscale.register_scale(CustomScaleFactory(1.12, 8.88))
      
      x = np.concatenate((np.linspace(0,1,10), np.linspace(9,10,10)))
      xticks = np.concatenate((np.linspace(0,1,6), np.linspace(9,10,6)))
      y = np.sin(x)
      plt.plot(x, y, '.')
      ax = plt.gca()
      ax.set_xscale('custom')
      ax.set_xticks(xticks)
      plt.show()
      

      【讨论】:

      • 我想现在只需要这样做。这将是我第一次弄乱自定义轴,所以我们只需要看看它是怎么回事。
      • 在InvertedCustomTransform 的def transform 中有一个小错字,应该写成self.upper 而不是upper。不过,谢谢你的好例子!
      • 你能添加几行来说明如何使用你的类吗?
      • @RuggeroTurra 在我的示例中全部都在那里。您可能只需要滚动到代码块的底部。
      • 该示例在 matplotlib 1.4.3 上对我不起作用:imgur.com/4yHa9be。看起来这个版本只识别transform_non_affine 而不是transform。在stackoverflow.com/a/34582476/1214547 上查看我的补丁。
      【解决方案6】:

      保罗的回答是一个非常好的方法。

      但是,如果您不想进行自定义变换,则可以只使用两个子图来创建相同的效果。

      matplotlib 示例中有an excellent example of this written by Paul Ivanov,而不是从头开始编写一个示例(它仅在当前的 git 提示中,因为它仅在几个月前提交。它还没有在网页上。)。

      这只是对这个示例的简单修改,使用不连续的 x 轴而不是 y 轴。 (这就是我将这篇文章设为 CW 的原因)

      基本上,您只需执行以下操作:

      import matplotlib.pylab as plt
      import numpy as np
      
      # If you're not familiar with np.r_, don't worry too much about this. It's just 
      # a series with points from 0 to 1 spaced at 0.1, and 9 to 10 with the same spacing.
      x = np.r_[0:1:0.1, 9:10:0.1]
      y = np.sin(x)
      
      fig,(ax,ax2) = plt.subplots(1, 2, sharey=True)
      
      # plot the same data on both axes
      ax.plot(x, y, 'bo')
      ax2.plot(x, y, 'bo')
      
      # zoom-in / limit the view to different portions of the data
      ax.set_xlim(0,1) # most of the data
      ax2.set_xlim(9,10) # outliers only
      
      # hide the spines between ax and ax2
      ax.spines['right'].set_visible(False)
      ax2.spines['left'].set_visible(False)
      ax.yaxis.tick_left()
      ax.tick_params(labeltop='off') # don't put tick labels at the top
      ax2.yaxis.tick_right()
      
      # Make the spacing between the two axes a bit smaller
      plt.subplots_adjust(wspace=0.15)
      
      plt.show()
      

      要添加折断轴线// 效果,我们可以这样做(同样,修改自 Paul Ivanov 的示例):

      import matplotlib.pylab as plt
      import numpy as np
      
      # If you're not familiar with np.r_, don't worry too much about this. It's just 
      # a series with points from 0 to 1 spaced at 0.1, and 9 to 10 with the same spacing.
      x = np.r_[0:1:0.1, 9:10:0.1]
      y = np.sin(x)
      
      fig,(ax,ax2) = plt.subplots(1, 2, sharey=True)
      
      # plot the same data on both axes
      ax.plot(x, y, 'bo')
      ax2.plot(x, y, 'bo')
      
      # zoom-in / limit the view to different portions of the data
      ax.set_xlim(0,1) # most of the data
      ax2.set_xlim(9,10) # outliers only
      
      # hide the spines between ax and ax2
      ax.spines['right'].set_visible(False)
      ax2.spines['left'].set_visible(False)
      ax.yaxis.tick_left()
      ax.tick_params(labeltop='off') # don't put tick labels at the top
      ax2.yaxis.tick_right()
      
      # Make the spacing between the two axes a bit smaller
      plt.subplots_adjust(wspace=0.15)
      
      # This looks pretty good, and was fairly painless, but you can get that
      # cut-out diagonal lines look with just a bit more work. The important
      # thing to know here is that in axes coordinates, which are always
      # between 0-1, spine endpoints are at these locations (0,0), (0,1),
      # (1,0), and (1,1). Thus, we just need to put the diagonals in the
      # appropriate corners of each of our axes, and so long as we use the
      # right transform and disable clipping.
      
      d = .015 # how big to make the diagonal lines in axes coordinates
      # arguments to pass plot, just so we don't keep repeating them
      kwargs = dict(transform=ax.transAxes, color='k', clip_on=False)
      ax.plot((1-d,1+d),(-d,+d), **kwargs) # top-left diagonal
      ax.plot((1-d,1+d),(1-d,1+d), **kwargs) # bottom-left diagonal
      
      kwargs.update(transform=ax2.transAxes) # switch to the bottom axes
      ax2.plot((-d,d),(-d,+d), **kwargs) # top-right diagonal
      ax2.plot((-d,d),(1-d,1+d), **kwargs) # bottom-right diagonal
      
      # What's cool about this is that now if we vary the distance between
      # ax and ax2 via f.subplots_adjust(hspace=...) or plt.subplot_tool(),
      # the diagonal lines will move accordingly, and stay right at the tips
      # of the spines they are 'breaking'
      
      plt.show()
      

      【讨论】:

      • 我自己说得再好不过了;)
      • // 效果的制作方法似乎只有在子图形的比例为 1:1 时才有效。你知道如何使它与引入的任何比率一起工作,例如GridSpec(width_ratio=[n,m])?
      • 太棒了。稍作修改,这可以适用于任意数量的 x 轴部分。
      • 弗雷德里克·诺德是正确的。此外,/ 效果不会抑制正常的刻度,这在美学上是不和谐的
      猜你喜欢
      • 2011-08-05
      • 1970-01-01
      • 2021-10-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多