修复 x 轴刻度并自动调整 y 轴

36
我想绘制数组的一部分,固定x部分,但让y部分自动缩放。我尝试了下面的方法,但它不起作用。
有什么建议吗?
import numpy as np
import matplotlib.pyplot as plt

data=[np.arange(0,101,1),300-0.1*np.arange(0,101,1)]

plt.figure()

plt.scatter(data[0], data[1])
plt.xlim([50,100])
plt.autoscale(enable=True, axis='y')

plt.show()

enter image description here

5个回答

34

Joe Kington建议仅绘制必要数据时,他提出了最明智的答案,但在某些情况下,最好绘制所有数据并缩放到特定部分。此外,拥有一个“autoscale_y”函数将非常方便,它仅需要轴对象(即不像这里的答案那样直接使用数据)。

这是一个函数,它基于可见x区域中的数据重新调整y轴:

def autoscale_y(ax,margin=0.1):
    """This function rescales the y-axis based on the data that is visible given the current xlim of the axis.
    ax -- a matplotlib axes object
    margin -- the fraction of the total height of the y-data to pad the upper and lower ylims"""

    import numpy as np

    def get_bottom_top(line):
        xd = line.get_xdata()
        yd = line.get_ydata()
        lo,hi = ax.get_xlim()
        y_displayed = yd[((xd>lo) & (xd<hi))]
        h = np.max(y_displayed) - np.min(y_displayed)
        bot = np.min(y_displayed)-margin*h
        top = np.max(y_displayed)+margin*h
        return bot,top

    lines = ax.get_lines()
    bot,top = np.inf, -np.inf

    for line in lines:
        new_bot, new_top = get_bottom_top(line)
        if new_bot < bot: bot = new_bot
        if new_top > top: top = new_top

    ax.set_ylim(bot,top)

这是一种比较取巧的方法,在许多情况下可能不适用,但对于简单的绘图,它效果很好。
以下是使用此函数的简单示例:
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-100,100,1000)
y = x**2 + np.cos(x)*100

fig,axs = plt.subplots(1,2,figsize=(8,5))

for ax in axs:
    ax.plot(x,y)
    ax.plot(x,y*2)
    ax.plot(x,y*10)
    ax.set_xlim(-10,10)

autoscale_y(axs[1])

axs[0].set_title('Rescaled x-axis')
axs[1].set_title('Rescaled x-axis\nand used "autoscale_y"')

plt.show()

enter image description here


我将y_displayed = yd[((xd>=lo) & (xd<=hi))] 替换为if len(xd)==2 and xd[0]==0.0 and xd[1]==1.0: y_displayed=yd #special case to handle axhline else: y_displayed = yd[((xd>=lo) & (xd<=hi))] - EL_DON
1
真的很棒的解决方案!如果x轴是“datetime”,则只需要进行一些小修改:xd = [dt.toordinal() for dt in line.get_xdata()] - bluenote10
1
对于任何感兴趣的人,我已经修改了这个函数,使其适用于任何轴:https://gist.github.com/ArcturusB/613eaba080a50385fa29e2eff8fe203f。 - Arcturus B
有人知道为什么Matplotlib没有调整来包含这种功能(适用于所有绘图类型)吗?也许他们会接受一个可以实现这个功能的补丁... - Neilski
可能Matplotlib没有这个功能的唯一原因是因为没有人费心去实现它 :). 这似乎是一个相当有用的功能。如果你有时间,请打开一个问题并开始讨论。 - DanHickstein
显示剩余4条评论

20

自动缩放始终使用数据的完整范围,因此y轴按照y数据的全部范围进行缩放,而不仅仅是在x限制范围内的数据。

如果您想显示数据的子集,则最简单的方法可能是仅绘制该子集:

import numpy as np
import matplotlib.pyplot as plt

x, y = np.arange(0,101,1) ,300 - 0.1*np.arange(0,101,1)
mask = (x >= 50) & (x <= 100)

fig, ax = plt.subplots()
ax.scatter(x[mask], y[mask])

plt.show()

在此输入图片描述


6

我在 @DanHickstein 的回答基础上进行了扩展,涵盖了绘图、散点图以及 axhline/axvline 等情况下对 x 或 y 轴进行缩放的情况。只需简单地调用 autoscale() 即可在最近的坐标轴上工作。如果您希望进行编辑,请在 gist 上进行分支操作:fork it on gist

def autoscale(ax=None, axis='y', margin=0.1):
    '''Autoscales the x or y axis of a given matplotlib ax object
    to fit the margins set by manually limits of the other axis,
    with margins in fraction of the width of the plot

    Defaults to current axes object if not specified.
    '''
    import matplotlib.pyplot as plt
    import numpy as np
    if ax is None:
        ax = plt.gca()
    newlow, newhigh = np.inf, -np.inf

    for artist in ax.collections + ax.lines:
        x,y = get_xy(artist)
        if axis == 'y':
            setlim = ax.set_ylim
            lim = ax.get_xlim()
            fixed, dependent = x, y
        else:
            setlim = ax.set_xlim
            lim = ax.get_ylim()
            fixed, dependent = y, x

        low, high = calculate_new_limit(fixed, dependent, lim)
        newlow = low if low < newlow else newlow
        newhigh = high if high > newhigh else newhigh

    margin = margin*(newhigh - newlow)

    setlim(newlow-margin, newhigh+margin)

def calculate_new_limit(fixed, dependent, limit):
    '''Calculates the min/max of the dependent axis given 
    a fixed axis with limits
    '''
    if len(fixed) > 2:
        mask = (fixed>limit[0]) & (fixed < limit[1])
        window = dependent[mask]
        low, high = window.min(), window.max()
    else:
        low = dependent[0]
        high = dependent[-1]
        if low == 0.0 and high == 1.0:
            # This is a axhline in the autoscale direction
            low = np.inf
            high = -np.inf
    return low, high

def get_xy(artist):
    '''Gets the xy coordinates of a given artist
    '''
    if "Collection" in str(artist):
        x, y = artist.get_offsets().T
    elif "Line" in str(artist):
        x, y = artist.get_xdata(), artist.get_ydata()
    else:
        raise ValueError("This type of object isn't implemented yet")
    return x, y

像它的前身一样,它有点靠技巧,但这是必要的,因为集合和线条有不同的方法来返回xy坐标,而axhline / axvline很难处理,因为它只有两个数据点。

下面是它的实际操作:

fig, axes = plt.subplots(ncols = 4, figsize=(12,3))
(ax1, ax2, ax3, ax4) = axes

x = np.linspace(0,100,300)
noise = np.random.normal(scale=0.1, size=x.shape)
y = 2*x + 3 + noise

for ax in axes:
    ax.plot(x, y)
    ax.scatter(x,y, color='red')
    ax.axhline(50., ls='--', color='green')
for ax in axes[1:]:
    ax.set_xlim(20,21)
    ax.set_ylim(40,45)

autoscale(ax3, 'y', margin=0.1)
autoscale(ax4, 'x', margin=0.1)

ax1.set_title('Raw data')
ax2.set_title('Specificed limits')
ax3.set_title('Autoscale y')
ax4.set_title('Autoscale x')
plt.tight_layout()

autoscale in action


2
import numpy as np  # for the test data
import pandas as pd

# load the data into the dataframe; there are many ways to do this
df = pd.DataFrame({'x': np.arange(0,101,1), 'y': 300-0.1*np.arange(0,101,1)})

# select and plot the data
ax = df[df.x.between(50, 100)].plot(x='x', y='y', kind='scatter', figsize=(5, 4))

enter image description here


0

我想补充@TomNorway的卓越答案(它节省了我很多时间),以处理某些艺术家部分或完全由NaN组成的情况。

我所做的所有更改都在内部

if len(fixed) > 2:

干杯!

def autoscale(ax=None, axis='y', margin=0.1):
    '''Autoscales the x or y axis of a given matplotlib ax object
    to fit the margins set by manually limits of the other axis,
    with margins in fraction of the width of the plot

    Defaults to current axes object if not specified.
    '''

    
    if ax is None:
        ax = plt.gca()
    newlow, newhigh = np.inf, -np.inf

    for artist in ax.collections + ax.lines:
        x,y = get_xy(artist)
        if axis == 'y':
            setlim = ax.set_ylim
            lim = ax.get_xlim()
            fixed, dependent = x, y
        else:
            setlim = ax.set_xlim
            lim = ax.get_ylim()
            fixed, dependent = y, x

        low, high = calculate_new_limit(fixed, dependent, lim)
        newlow = low if low < newlow else newlow
        newhigh = high if high > newhigh else newhigh

    margin = margin*(newhigh - newlow)

    setlim(newlow-margin, newhigh+margin)

def calculate_new_limit(fixed, dependent, limit):
    '''Calculates the min/max of the dependent axis given 
    a fixed axis with limits
    '''
    if len(fixed) > 2:
        mask = (fixed>limit[0]) & (fixed < limit[1]) & (~np.isnan(dependent)) & (~np.isnan(fixed))
        window = dependent[mask]
        try:
            low, high = window.min(), window.max()
        except ValueError:  # Will throw ValueError if `window` has zero elements
            low, high = np.inf, -np.inf
    else:
        low = dependent[0]
        high = dependent[-1]
        if low == 0.0 and high == 1.0:
            # This is a axhline in the autoscale direction
            low = np.inf
            high = -np.inf
    return low, high

def get_xy(artist):
    '''Gets the xy coordinates of a given artist
    '''
    if "Collection" in str(artist):
        x, y = artist.get_offsets().T
    elif "Line" in str(artist):
        x, y = artist.get_xdata(), artist.get_ydata()
    else:
        raise ValueError("This type of object isn't implemented yet")
    return x, y

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接