How to Align Gridlines for Two Y-Axis Scales Using Matplotlib

How do I align gridlines for two y-axis scales using Matplotlib?

I am not sure if this is the prettiest way to do it, but it does fix it with one line:

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

Double y axis with matching gridlines

Aligning the yticks of the second axis manually is tricky because (a) not all yticks are shown (do for example a print(ax.get_yticks()) and compare to your plot) and (b) because set_yticks() also affects ylims. Replacing your sections #The math and #My best attempt with the below works for me:

# The math
ylim1 = ax.get_ylim()
len1 = ylim1[1]-ylim1[0]
yticks1 = ax.get_yticks()
rel_dist = [(y-ylim1[0])/len1 for y in yticks1]
ylim2 = ax2.get_ylim()
len2 = ylim2[1]-ylim2[0]
yticks2 = [ry*len2+ylim2[0] for ry in rel_dist]

#My best attempt
ax2.set_yticks(yticks2)
ax2.set_ylim(ylim2) #<-- this line is needed to re-adjust the limits to the original values
ax.yaxis.grid(which="major", color='black', linestyle='-')
ax2.yaxis.grid(which="major", color='green', linestyle='--')
ax.legend(loc='upper left')
ax2.legend(loc='upper right')

and the resulting graph looks like this:

Sample Image

Hope this helps.

How do I align gridlines for two y-axis scales using Matplotlib?

I am not sure if this is the prettiest way to do it, but it does fix it with one line:

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

matplotlib align twinx tick marks

You need to manually set the yticks as it stands these are automatically calculated resulting in a variation. Adding something like this:

ax1.set_yticks(np.linspace(ax1.get_ybound()[0], ax1.get_ybound()[1], 5))
ax2.set_yticks(np.linspace(ax2.get_ybound()[0], ax2.get_ybound()[1], 5))

where we set the ytick locations using an array of 5 points between the bounds of the axis. Since you have a histogram you could just set the lower value to zero in each case, and you may want to have the upper bound somewhat larger, so I would instead have

ax1.set_yticks(np.linspace(0, ax1.get_ybound()[1]+1, 5))
ax2.set_yticks(np.linspace(0, ax2.get_ybound()[1]+1, 5))

Giving a plot (with a change of color and transparency (alpha) for clarity):

Sample Image

How to put grid lines from the secondary axis behind the primary plot?

Your desired drawing order is (first is most to the back)

  • grid for axes
  • grid for twin axes
  • plot in axes
  • plot in twin axes

However this is not possible as seen by the comment

you can't interleave the drawing orders of artists from one Axes with those from another

What this means is that you need 4 axes instead of two.

  • axes for grid of primary y scale
  • axes for grid of secondary y scale
  • axes for plot on primary y scale
  • axes for plot on secondary y scale

This could look like this:

import matplotlib.pyplot as plt
import numpy as np

np.random.seed(42)

foo = np.random.randn(1000)

fig, ax1a = plt.subplots() # ax1a for the histogram grid
ax2a = ax1a.twinx() # ax2a for the cumulative step grid
ax1b = ax1a.twinx() # ax1b for the histogram plot
ax2b = ax1a.twinx() # ax2a for the cumulative step plot
# Link the respective y-axes for grid and plot
ax1a.get_shared_y_axes().join(ax1a, ax1b)
ax2a.get_shared_y_axes().join(ax2a, ax2b)
# Remove ticks and labels and set which side to label
ticksoff = dict(labelleft=False, labelright=False, left=False, right=False)
ax1a.tick_params(axis="y", **ticksoff)
ax2a.tick_params(axis="y", **ticksoff)
ax1b.tick_params(axis="y", labelleft=True, labelright=False, left=True, right=False)
ax2b.tick_params(axis="y", labelleft=False, labelright=True, left=False, right=True)
# Spines off
for ax in [ax1a, ax2a, ax1b]:
for k,v in ax.spines.items():
v.set_visible(False)

ax1b.hist(foo, bins=50)

ax2b.hist(
foo, bins=50, density=True, cumulative=True, histtype="step", color="tab:orange"
)
ax1a.grid()
ax2a.grid()
plt.show()

Sample Image

trouble aligning ticks for matplotlib twinx axes

Aligning the tick locations of two different scales would mean to give up on the nice automatic tick locator and set the ticks to the same positions on the secondary axes as on the original one.

The idea is to establish a relation between the two axes scales using a function and set the ticks of the second axes at the positions of those of the first.

Sample Image

import matplotlib.pyplot as plt
import matplotlib.ticker

fig, ax = plt.subplots()
# creates double-y axis
ax2 = ax.twinx()

ax.plot(range(5), [1,2,3,4,5])
ax2.plot(range(6), [13,17,14,13,16,12])
ax.grid()

l = ax.get_ylim()
l2 = ax2.get_ylim()
f = lambda x : l2[0]+(x-l[0])/(l[1]-l[0])*(l2[1]-l2[0])
ticks = f(ax.get_yticks())
ax2.yaxis.set_major_locator(matplotlib.ticker.FixedLocator(ticks))

plt.show()

Note that this is a solution for the general case and it might result in totally unreadable labels depeding on the use case. If you happen to have more a priori information on the axes range, better solutions may be possible.

Also see this question for a case where automatic tick locations of the first axes is sacrificed for an easier setting of the secondary axes tick locations.

How to align the bar and line in matplotlib two y-axes chart?

Just change the final line to:

ax2.plot(ax.get_xticks(),
df[['sales_gr','net_pft_gr']].values,
linestyle='-',
marker='o', linewidth=2.0)

You will be all set.

Sample Image

I don't quite get your second question. The 1st and 2nd y axis are of different scale, what do you mean by aligning them to the same line? They can't be aligned to the same grid line (yes you can but the right axis will look ugly, having values like 0.687 and alike). Anyway, you can do:

ax.set_ylim((-10, 80.))

to align them, and the plot now looks ugly:

Sample Image

Matplotlib -- twiny: how to align values of two x-axes in one plot?

Yay! I've managed to get the sought result without defining a new scale class! Here are the relevant code parts which have been added/modified in the script from the question (the variable step will be later read from the user command line input, or I might find another way of automated tick frequency setting):

x_ut = []
x_phi = []
x_phi_ticks = []
x_phi_ticklabels = []
y_brightness = []

# populate lists for the phase angle ticks and labels

i = 0
step = 15
while i <= (len(x_ut)-step):
x_phi_ticks.append(x_ut[i])
x_phi_ticklabels.append(x_phi[i])
i += step
x_phi_ticks.append(x_ut[-1])
x_phi_ticklabels.append(x_phi[-1])

# plot'em all

fig, ax1 = plt.subplots()

ax1.plot(x_ut, y_brightness, marker='o', label='apparent brightness')
ax1.xaxis.set_major_locator(dates.MinuteLocator(interval=1))
ax1.xaxis.set_major_formatter(dates.DateFormatter('%H:%M'))
ax1.tick_params(axis='x', rotation=45)
ax1.minorticks_on()
ax1.legend()
ax1.grid(which='major', linestyle='-', color='#000000')
ax1.grid(which='minor', linestyle='--')
ax1.set_xlabel('time [h:m, UT]')
ax1.set_ylabel('apparent brightness [mag, CR]')

ax2 = ax1.twiny()
ax2.set_xlim(ax1.get_xlim())
ax2.set_xticks(x_phi_ticks)
ax2.set_xticklabels(x_phi_ticklabels)
ax2.set_xlabel('phase angle (phi) [deg]')

plt.gca().invert_yaxis()
plt.tight_layout(pad=0)
plt.show()

Sample Image



Related Topics



Leave a reply



Submit