Как выравнивать линии сетки для двух шкал по оси Y, используя Matplotlib?

Я рисую два набора данных с разными единицами по оси y. Есть ли способ сделать метки и линии линий выровненными на обеих осях y?

Первое изображение показывает, что я получаю, а второе изображение показывает, что я хотел бы получить.

Это код, который я использую для построения:

import seaborn as sns
import numpy as np
import pandas as pd

np.random.seed(0)
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(pd.Series(np.random.uniform(0, 1, size=10)))
ax2 = ax1.twinx()
ax2.plot(pd.Series(np.random.uniform(10, 20, size=10)), color='r')

Example of unwanted behavior

Example of wanted behavior

Ответ 1

Я не уверен, что это самый красивый способ сделать это, но он исправляет его одной строкой:

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd

np.random.seed(0)
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(pd.Series(np.random.uniform(0, 1, size=10)))
ax2 = ax1.twinx()
ax2.plot(pd.Series(np.random.uniform(10, 20, size=10)), color='r')

# ADD THIS LINE
ax2.set_yticks(np.linspace(ax2.get_yticks()[0], ax2.get_yticks()[-1], len(ax1.get_yticks())))

plt.show()

Ответ 2

Я написал эту функцию, которая принимает объекты осей Matplotlib ax1, ax2 и плавает minresax1 minresax2:

def align_y_axis(ax1, ax2, minresax1, minresax2):
    """ Sets tick marks of twinx axes to line up with 7 total tick marks

    ax1 and ax2 are matplotlib axes
    Spacing between tick marks will be a factor of minresax1 and minresax2"""

    ax1ylims = ax1.get_ybound()
    ax2ylims = ax2.get_ybound()
    ax1factor = minresax1 * 6
    ax2factor = minresax2 * 6
    ax1.set_yticks(np.linspace(ax1ylims[0],
                               ax1ylims[1]+(ax1factor -
                               (ax1ylims[1]-ax1ylims[0]) % ax1factor) %
                               ax1factor,
                               7))
    ax2.set_yticks(np.linspace(ax2ylims[0],
                               ax2ylims[1]+(ax2factor -
                               (ax2ylims[1]-ax2ylims[0]) % ax2factor) %
                               ax2factor,
                               7))

Он вычисляет и устанавливает тики, чтобы было семь тиков. Самый низкий тик соответствует текущему самому низкому тику и увеличивает максимальный тик, так что разделение между каждым тиком целых кратных minrexax1 или minrexax2.

Чтобы сделать его общим, вы можете установить общее количество тиков, которое вы хотите, изменив значение 7 на общее количество тиков и измените 6 на общее количество тиков минус 1.

Я поставил запрос на pull, чтобы включить его в matplotlib.ticker.LinearLocator:

https://github.com/matplotlib/matplotlib/issues/6142

В будущем (возможно, Matplotlib 2.0?), попробуйте:

import matplotlib.ticker
nticks = 11
ax1.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks))
ax2.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks))

Это должно просто работать и выбирать удобные тики для обеих осей y.

Ответ 3

Я мог бы решить это, дезактивируя ax.grid(None) в одной из осей сетки:

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd

fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(pd.Series(np.random.uniform(0, 1, size=10)))
ax2 = ax1.twinx()
ax2.plot(pd.Series(np.random.uniform(10, 20, size=10)), color='r')
ax2.grid(None)

plt.show()

Figure Result

Ответ 4

Этот код гарантирует, что сетки из обеих осей выровнены друг с другом, без необходимости скрывать линии сетки от любого набора. В этом примере он позволяет вам сопоставлять то, что имеет более тонкие линии сетки. Это отвлекает идею от @Leo. Надеюсь, это поможет!

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd

fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(pd.Series(np.random.uniform(0,1,size=10)))
ax2 = ax1.twinx()
ax2.plot(pd.Series(np.random.uniform(10,20,size=10)),color='r')
ax2.grid(None)

# Determine which plot has finer grid. Set pointers accordingly
l1 = len(ax1.get_yticks())
l2 = len(ax2.get_yticks())
if l1 > l2:
  a = ax1
  b = ax2
  l = l1
else:
  a = ax2
  b = ax1
  l = l2

# Respace grid of 'b' axis to match 'a' axis
b_ticks = np.linspace(b.get_yticks()[0],b.get_yticks()[-1],l)
b.set_yticks(b_ticks)

plt.show()

Ответ 6

Если вы используете метки осей, решение Leo может вытолкнуть их со стороны, из-за точности чисел в тиках.

Так что в дополнение к чем-то вроде Leo solution (повторяется здесь),

ax2.set_yticks(np.linspace(ax2.get_yticks()[0],ax2.get_yticks()[-1],len(ax1.get_yticks())))

вы можете использовать параметр autolayout, как указано в этом ответе; например, ранее в script вы можете обновить rcParams:

from matplotlib import rcParams
rcParams.update({'figure.autolayout': True})

В нескольких тестовых случаях это, как представляется, приводит к ожидаемому результату, причем как выстроенные тики, так и метки полностью содержатся в выводе.

Ответ 7

У меня была та же проблема за исключением того, что это было для вторичной оси х. Я решил, установив мою вторичную ось х, равную пределу моей первичной оси. В приведенном ниже примере без установки предела второй оси, равного первой: ax2 = ax.twiny() enter image description here

Как только я установил предел второй оси равным первому ax2.set_xlim(ax.get_xlim()) вот мой результат: enter image description here

Ответ 8

Отлично. Это первый ответ на SO, который сработал из коробки. Не стучите, так на SO - это невероятный ресурс. Но мне всегда кажется, что у меня не совсем одинаковые проблемы. Этот прибил это. Очевидно, кстати - самые хорошие ответы.