Matplotlib - фиксирующий масштаб оси x и ось автомасштабирования y

Я хотел бы построить только часть массива, фиксируя часть 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()

Ответ 1

Автомасштабирование всегда использует весь диапазон данных, поэтому ось 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()

Ответ 2

Хотя Джо Кингтон, безусловно, предлагает наиболее разумный ответ, когда он рекомендует наносить на график только необходимые данные, бывают ситуации, когда лучше всего построить все данные и просто увеличить масштаб до определенного раздела. Кроме того, было бы неплохо иметь функцию "autoscale_y", что требует только объект осей (т.е., в отличие от ответа здесь, что требует непосредственного использования данных.)

Вот функция, которая просто изменяет масштаб оси Y на основе данных, которые находятся в видимой x-области:

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

Ответ 3

Я основывался на ответе @DanHickstein, чтобы охватить случаи plot, scatter и axhline/axvline для масштабирования оси x или y. Его можно вызвать так же просто, как autoscale() чтобы работать с самыми последними осями.

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