有没有办法在Matplotlib中创建不连续的轴?

92 投票
7 回答
52227 浏览
提问于 2025-04-16 15:41

我正在尝试使用pyplot创建一个图表,这个图表的x轴是断开的。通常,这种情况的绘制方式是这样的:

(数值)----//----(后面的数值)

这里的//表示你跳过了(数值)和(后面的数值)之间的所有内容。

我找不到任何相关的例子,所以我在想这是否可能。我知道可以在数据有间断的情况下连接数据,比如金融数据,但我想让这个跳跃在轴上更明显。目前我只是使用子图,但我真的希望最后所有内容都能在同一个图表上。

7 个回答

31

我看到很多人对这个功能提出了建议,但没有看到它已经被实现的迹象。这里有一个暂时可用的解决方案。它对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()

在这里输入图片描述

32

看看这个叫做 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')

来自brokenaxes的示例

87

保罗的回答是一种很好的方法。

不过,如果你不想自己做一个特别的转换,可以直接用两个子图来达到同样的效果。

与其从头开始举个例子,不如看看保罗·伊万诺夫写的一个很棒的例子,这个例子在matplotlib的示例中(这个例子是最近几个月才提交的,所以现在只在当前的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()

enter image description here

为了添加破碎轴线的效果,我们可以这样做(同样是从保罗·伊万诺夫的例子修改过来的):

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

enter image description here

撰写回答