Python / Matplotlib-有没有办法制作不连续的轴?


78

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

(值)---- // ----(后值)

// //表示您正在跳过(值)和(后值)之间的所有内容。

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

Answers:


83

保罗的答案是做到这一点的完美方法。

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

保罗·伊万诺夫(Paul Ivanov)在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()

在此处输入图片说明

要添加折断的轴线//效果,我们可以这样做(同样,从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()

在此处输入图片说明


11
我自己不能说得更好;)
Paul Ivanov

3
//仅当子图的比例为1:1时,实现效果的方法才似乎有效。您知道如何以例如引入的任何比例工作GridSpec(width_ratio=[n,m])吗?
Frederick Nord 2014年

太棒了 进行较小的修改,即可适用于任意数量的x轴截面。
Christian Madsen

弗雷德里克·诺德(Frederick Nord)是正确的。此外,该/效果并不能抑制正常的滴答声,这在美学上是
令人讨厌的

30

我看到有关此功能的许多建议,但没有任何迹象表明已实现。这是一个可行的解决方案。它将阶跃函数变换应用于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()

在此处输入图片说明


我想那只是现在要做。这是我第一次搞怪自定义轴,所以我们只需要看看它如何进行即可。
贾斯汀S

有一个小错字def transformInvertedCustomTransform,它应该读self.upper的不是upper。不过,感谢您的出色榜样!
David Zwicker 2012年

您可以添加几行来显示如何使用您的课程吗?
Ruggero Turra 2015年

@RuggeroTurra在我的示例中就到了。您可能只需要滚动到代码块的底部。
保罗

2
该示例在matplotlib 1.4.3上对我不起作用:imgur.com/4yHa9be。看来此版本只能识别transform_non_affine而不是transform。请参阅我的补丁程序stackoverflow.com/a/34582476/1214547
Pastafarianist,2016年

25

检查破损包:

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

断斧的例子


from brokenaxes import brokenaxes安装后无法在Pycharm社区2016.3.2中运行。@ ben.dichter
emmmphd '17

1
有一个错误。我修好了它。请运行pip install brokenaxes==0.2以安装代码的固定版本。
ben.dichter

似乎与ax.grid(真)不好互动
悦诗风吟

1
断轴可以抑制滴答声吗?还是将轴在水平方向上彼此靠近设置?
ifly6

1
嗨,本,我想删除y轴,但是,我尝试了一些命令,但是与断轴组合无法正常工作,(请注意x轴是断轴),thx
user3737702

0

解决弗雷德里克·诺德(Frederick Nord)的问题,当使用比率不等于1:1的网格规格时如何启用对角线“折断”线的平行定向,基于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()

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.