I have a plot with two y-axis on a shared x-axis, and I want to make them align at y=0 so that I can draw a horizontal line to highlight the zero tick. currently the two axis are not aligned and I have to draw two line which is terrible. How can I achieve this?
4 Answers
Assuming that you created the plots with a shared axis, you just have to modify the range of y to be centered at zero or have a similar offset multiplier in both plots (i.e set ax.set_ylim(-6,6)
for both plots). The following code is an example.
from matplotlib import pyplot as plt
import numpy as np
#Create some Fake Data
x =np.arange(-10,10)
y = x+np.random.rand(20)
y2 = 0.5*x-3.*np.random.rand(20)
#Figure
fig = plt.figure(figsize=(12,6))
#First subplot with zero line not even
ax1 = plt.subplot(121)
ax2 = ax1.twinx()
ax1.plot(x,y,c='r')
ax2.plot(x,y2,c='b')
ax1.axhline(0)
#Second Subplot with zero line the same on both axes
ax3 = plt.subplot(122)
ax4 = ax3.twinx()
ax3.plot(x,y,c='r')
ax4.plot(x,y2,c='b')
ax3.axhline(0)
#If you set your limits on both sides to have the same interval you will get the same zero line
ax3.set_ylim(-10,10)
ax4.set_ylim(-6,6)
plt.show()

- 3,172
- 3
- 18
- 38
-
So we need to define the axis ranges manually to ensure they are proportional. No possibility to keep the scales automatic but with the zeroes linked? – mins Oct 28 '20 at 20:25
-
There are definitely ways to automate this, but when you create the subplot axis in matplotlib the bounds are set based on the range of values. So the automation would be done on the data and finding the best edges to keep the zero lines the same. – BenT Oct 29 '20 at 17:44
I had the same issue, and what I did was to change the extents of the y-axis, depending on the ratio of the min to max limits. If you set the ratio of the y-axes to be the same, the zero point should be the same.
fig, ax1 = plt.subplots()
ax1.plot(...) # Plot first data set
ax2 = ax1.twinx()
ax2.plot(...) # Plot second data set
ax1_ylims = ax1.axes.get_ylim() # Find y-axis limits set by the plotter
ax1_yratio = ax1_ylims[0] / ax1_ylims[1] # Calculate ratio of lowest limit to highest limit
ax2_ylims = ax2.axes.get_ylim() # Find y-axis limits set by the plotter
ax2_yratio = ax2_ylims[0] / ax2_ylims[1] # Calculate ratio of lowest limit to highest limit
# If the plot limits ratio of plot 1 is smaller than plot 2, the first data set has
# a wider range range than the second data set. Calculate a new low limit for the
# second data set to obtain a similar ratio to the first data set.
# Else, do it the other way around
if ax1_yratio < ax2_yratio:
ax2.set_ylim(bottom = ax2_ylims[1]*ax1_yratio)
else:
ax1.set_ylim(bottom = ax1_ylims[1]*ax2_yratio)
plt.tight_layout()
plt.show()
This is my first answer, so I hope it is sufficient and okay.

- 71
- 1
- 3
-
-
1I believe some logic is required, yes. I will need to test and review. When I get a chance I will try to update. I THINK the above should still give the same zero point, but I think there can be situations where the data is not shown properly, yes. – KobusNell May 15 '23 at 14:02
-
Thanks. I could not find this answer with traditional search, but phind pointed me to it as the source for its answer. Your code is now part of the vast body of human knowledge :) – IanS May 23 '23 at 14:02
I found this excellent library which aligns the axes and keeps automatic scaling.
Install
pip install mpl-axes-aligner
Usage
import numpy as np
import matplotlib.pyplot as plt
import mpl_axes_aligner
x = np.arange(0.0, 30, 0.1)
y1 = 0.1 * x * np.sin(x)
y2 = 0.001*x**3 - 0.03*x**2 + 0.12*x
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twinx()
ax1.plot(x, y1, color='blue', label='Plot 1')
ax2.plot(x, y2, color='red', label='Plot 2')
# Align y = 0 of ax1 and ax2 with the center of figure.
mpl_axes_aligner.align.yaxes(ax1, 0, ax2, 0, 0.5)
plt.show()
Output
Credits
This package was developed by ryotuk and the usage example above is from his package's documentation.

- 521
- 5
- 12
-
I tried using this and got an error `AttributeError: module 'mpl_axes_aligner' has no attribute 'align'`. – Dean Jun 17 '21 at 12:59
-
According to [this issue](https://github.com/ryutok/mpl_axes_aligner/issues/7), you should update `mpl_axes_aligner`. – abrac Jan 11 '22 at 20:39
-
1
Here's another way, which I think is more versatile.
import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt
def align_zeros(axes):
ylims_current = {} # Current ylims
ylims_mod = {} # Modified ylims
deltas = {} # ymax - ymin for ylims_current
ratios = {} # ratio of the zero point within deltas
for ax in axes:
ylims_current[ax] = list(ax.get_ylim())
# Need to convert a tuple to a list to manipulate elements.
deltas[ax] = ylims_current[ax][1] - ylims_current[ax][0]
ratios[ax] = -ylims_current[ax][0]/deltas[ax]
for ax in axes: # Loop through all axes to ensure each ax fits in others.
ylims_mod[ax] = [np.nan,np.nan] # Construct a blank list
ylims_mod[ax][1] = max(deltas[ax] * (1-np.array(list(ratios.values()))))
# Choose the max value among (delta for ax)*(1-ratios),
# and apply it to ymax for ax
ylims_mod[ax][0] = min(-deltas[ax] * np.array(list(ratios.values())))
# Do the same for ymin
ax.set_ylim(tuple(ylims_mod[ax]))
x = np.array(range(1,11))
y1 = 5*x-10
y2 = -10*x+5
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(x,y1,'r',label='y1')
ax1.set_ylabel('y1')
ax2 = ax1.twinx()
ax2.plot(x,y2,'g',label='y2')
ax2.set_ylabel('y2')
align_zeros([ax1,ax2])
ax1.legend(loc='upper left')
ax2.legend(loc='upper right')
plt.show()

- 11
- 1