Make matplotlib autoscaling ignore some of the plots - python

I use matplotib's Axes API to plot some figures. One of the lines I plot represents the theoretical expected line. It has no meaning outside of the original y and x limits. What I want, is for matlplotlib to ignore it when autoscaling the limits. What I used to do, is to check what are the current limits, then plot, and reset the limits. The problem is that when I plot a third plot, the limits get recalculated together with the theoretical line, and that really expands the graph.
# Boilerplate
from matplotlib.figure import Figure
from matplotlib.backends.backend_pdf import FigureCanvasPdf
from numpy import sin, linspace
fig = Figure()
ax = fig.add_subplot(1,1,1)
x1 = linspace(-1,1,100)
ax.plot(x1, sin(x1))
ax.plot(x1, 3*sin(x1))
# I wish matplotlib would not consider the second plot when rescaling
ax.plot(x1, sin(x1/2.0))
# But would consider the first and last
canvas_pdf = FigureCanvasPdf(fig)
canvas_pdf.print_figure("test.pdf")

The obvious way is to just manually set the limits to what you want. (e.g. ax.axis([xmin, xmax, ymin, ymax]))
If you don't want to bother with finding out the limits manually, you have a couple of options...
As several people (tillsten, Yann, and Vorticity) have mentioned, if you can plot the function you want to ignore last, then you can disable autoscaling before plotting it or pass the scaley=False kwarg to plot
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
ax.plot(x1, np.sin(x1))
ax.plot(x1, np.sin(x1 / 2.0))
ax.autoscale(False) #You could skip this line and use scalex=False on
ax.plot(x1, 3 * np.sin(x1)) #the "theoretical" plot. It has to be last either way
fig.savefig('test.pdf')
Note that you can adjust the zorder of the last plot so that it's drawn in the "middle", if you want control over that.
If you don't want to depend on the order, and you do want to just specify a list of lines to autoscale based on, then you could do something like this: (Note: This is a simplified version assuming you're dealing with Line2D objects, rather than matplotlib artists in general.)
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.transforms as mtransforms
def main():
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
line1, = ax.plot(x1, np.sin(x1))
line2, = ax.plot(x1, 3 * np.sin(x1))
line3, = ax.plot(x1, np.sin(x1 / 2.0))
autoscale_based_on(ax, [line1, line3])
plt.show()
def autoscale_based_on(ax, lines):
ax.dataLim = mtransforms.Bbox.unit()
for line in lines:
xy = np.vstack(line.get_data()).T
ax.dataLim.update_from_data_xy(xy, ignore=False)
ax.autoscale_view()
if __name__ == '__main__':
main()

Use the scalex/scaley kw arg:
plot(x1, 3*sin(x1), scaley=False)

LineCollection objects can be ignored by using the autolim=False argument:
from matplotlib.collections import LineCollection
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
# Will update limits
ax.plot(x1, np.sin(x1))
# Will not update limits
col = LineCollection([np.column_stack((x1, 3 * np.sin(x1)))], colors='g')
ax.add_collection(col, autolim=False)
# Will still update limits
ax.plot(x1, np.sin(x1 / 2.0))

This can be done regardless of plotting order by creating another axes to work on.
In this version, we create a twin axes and disable the autoscaling on that twin axes. In this way, the plot is scaled based on anything plotted in the original axes, but is not scaled by anything put into the twin axes.
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
twin_ax = ax.twinx() # Create a twin axes.
twin_ax.autoscale(False) # Turn off autoscaling on the twin axes.
twin_ax.set_yticks([]) # Remove the extra tick numbers from the twin axis.
ax.plot(x1, np.sin(x1))
twin_ax.plot(x1, 3 * np.sin(x1), c='green') # Plotting the thing we don't want to scale on in the twin axes.
ax.plot(x1, np.sin(x1 / 2.0))
twin_ax.set_ylim(ax.get_ylim()) # Make sure the y limits of the twin matches the autoscaled of the original.
fig.savefig('test.pdf')
Note, the above only prevents the un-twined axis from auto scaling (y in the above case). To get it to work for both x and y, we can do the twinning process for both x and y (or create the new axes from scratch):
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
x2 = np.linspace(-2,2,100) # Would extend the x limits if auto scaled
twin_ax = ax.twinx().twiny() # Create a twin axes.
twin_ax.autoscale(False) # Turn off autoscaling on the twin axes.
twin_ax.set_yticks([]) # Remove the extra tick numbers from the twin axis.
twin_ax.set_xticks([]) # Remove the extra tick numbers from the twin axis.
ax.plot(x1, np.sin(x1))
twin_ax.plot(x2, 3 * np.sin(x2), c='green') # Plotting the thing we don't want to scale on in the twin axes.
ax.plot(x1, np.sin(x1 / 2.0))
twin_ax.set_ylim(ax.get_ylim()) # Make sure the y limits of the twin matches the autoscaled of the original.
twin_ax.set_xlim(ax.get_xlim()) # Make sure the x limits of the twin matches the autoscaled of the original.
fig.savefig('test.png')

As a generalisation of jam's answer, a collection object can be obtained from any of matplotlib's plotting functions and then re-added with autolim=False. For example,
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
# Get hold of collection
collection = ax.plot(x1, np.sin(x1))
# Remove collection from the plot
collection.remove()
# Rescale
ax.relim()
# Add the collection without autoscaling
ax.add_collection(collection, autolim=False)

Related

How to change length of one plot in subplot?

How to change the length of one plot in a subplot?
It may be a simple problem but I have difficulty solving this.
To represent the result of signal analysis, I represented three plots in a subplot.
But, because the third graph had a colorbar, only this is short.
How can I solve this problem?
I added some parts that draw each plot in a subplot in my code except detail.
To avoid misunderstanding, I added figure.
In the below figure, the length of the spectrogram plot in the python figure(left) is shorter than the above two plots. But the length of the spectrogram plot in the Matlab figure(right) is equal to the above plots. How can make the length of the third plot be equal with the above plots, like the result of Matlab?
import matplotlib.pyplot as plt
fig, (ax1, ax2, ax3, cbar) = plt.subplots(3, 2)
ax1.plot(sb['Seconds'], sb['Real'], 'dodgerblue', linewidth = 0.5)
ax2.plot(f2, np.log(P3), 'k', linewidth = 0.5)
s, freqs, bins, im = ax3.specgram(y, NFFT = N, Fs=Fs1, cmap='jet')
cbar = plt.colorbar(im, ax=ax3, orientation = 'vertical', pad = 0.009)
If you already have the figure object use:
f.set_figheight(15)
f.set_figwidth(15)
But if you use the .subplots() command (as in the examples you're showing) to create a new figure you can also use:
f, axs = plt.subplots(2,2,figsize=(15,15))
For example: -
Alternatively, create a figure() object using the figsize argument and then use add_subplot to add your subplots. E.g.
import matplotlib.pyplot as plt
import numpy as np
f = plt.figure(figsize=(10,3))
ax = f.add_subplot(121)
ax2 = f.add_subplot(122)
x = np.linspace(0,4,1000)
ax.plot(x, np.sin(x))
ax2.plot(x, np.cos(x), 'r:')
Benefits of this method are that the syntax is closer to calls of subplot() instead of subplots(). E.g. subplots doesn't seem to support using a GridSpec for controlling the spacing of the subplots, but both subplot() and add_subplot() do.

Matplotlib: Plot two x axes, one linear and one with logarithmic ticks

(Heavily edited:)
In python matplotlib, I want to plot y against x with two xscales, the lower one with linear ticks and the upper one with logarithmic ticks.
The lower x values are an arbitrary function of the upper ones (in this case the mapping is func(x)=np.log10(1.0+x)). Corollary: The upper x tick positions are the same arbitrary function of the lower ones.
The positions of the data points and the tick positions for both axes must be decoupled.
I want the upper axis's logarithmic tick positions and labels to be as tidy as possible.
What is the best way to produce such a plot?
Related: http://matplotlib.1069221.n5.nabble.com/Two-y-axis-with-twinx-only-one-of-them-logscale-td18255.html
Similar (but unanswered) question?: Matplotlib: how to set ticks of twinned axis in log plot
Could be useful: https://stackoverflow.com/a/29592508/1021819
You may find Axes.twiny() and Axes.semilogx() useful.
import numpy as np
import matplotlib.pyplot as plt
fig, ax1 = plt.subplots()
x = np.arange(0.01, 10.0, 0.01) # x-axis range
y = np.sin(2*np.pi*x) # simulated signal to plot
ax1.plot(x, y, color="r") # regular plot (red)
ax1.set_xlabel('x')
ax2 = ax1.twiny() # ax1 and ax2 share y-axis
ax2.semilogx(x, y, color="b") # semilog plot (blue)
ax2.set_xlabel('semilogx')
plt.show()
Here is an attempt at an answer after speaking to a few people and with thanks to #BusyBeaver.
I agree the question was ill-posed and will amend it to clarify (help welcome!).
I do think this is a useful one to have written down on stackoverflow.
Code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import AutoMinorLocator
# Necessary functions
def tick_function(x):
"""Specify tick format"""
return ["%2.f" % i for i in x]
def func(x):
"""This can be anything you like"""
funcx=np.log10(1.0+x)
return funcx
z=np.linspace(0.0,4.0,20)
np.random.seed(seed=1234)
y=np.random.normal(10.0,1.0,len(z))
# Set up the plot
fig,ax1 = subplots()
ax1.xaxis.set_minor_locator(AutoMinorLocator())
ax1.yaxis.set_minor_locator(AutoMinorLocator())
# Set up the second axis
ax2 = ax1.twiny()
# The tick positions can be at arbitrary positions
zticks=np.arange(z[0],z[-1]+1)
ax2.set_xticks(func(zticks))
ax2.set_xticklabels(tick_function(zticks))
ax2.set_xlim(func(z[0]),func(z[-1]))
ax1.set_ylim(5.0,15.0)
ax1.set_xlabel(r'$\log_{10}\left(1+z\right)$')
ax2.set_xlabel(r'$z$')
ax1.set_ylabel('amplitude/arb. units')
plt.tick_params(axis='both',which = 'major', labelsize=8, width=2)
plt.tick_params(axis='both',which = 'minor', labelsize=8, width=1)
_=ax1.plot(func(z),y,'k.')
plt.savefig('lnopz2.png')
I am not sure how to control the upper ax2 minor ticks (e.g. every 0.5).

Preserving the xticks in multiple bar plots in matplotlib

1) I am not able to see the text-based xticks which are stored as list in the variable x. When I have only one single column based bar plot, I can see the xticks as text but not for more.
2)how can I control the font properties of xticks and the values in y axis?
Thank you.
import matplotlib.pyplot as plt
import pylab as pl
import numpy as np
#load text and columns into different variables
data = np.genfromtxt('a', names=True, dtype=None, usecols=("X", "N2", "J2", "V2", "asd", "xyz"))
x = data['X']
n = data['N2']
j = data['J2']
v = data['V2']
#make x axis string based labels
r=np.arange(1,25,1.5)
plt.xticks(r,x) #make sure dimension of x and n matches
plt.figure(figsize=(3.2,2), dpi=300, linewidth=3.0)
ax = plt.subplot(111)
ax.bar(r,v,width=0.9,color='red',edgecolor='black', lw=0.5, align='center')
plt.axhline(y=0,linewidth=1.0,color='black') #horizontal line at y=0
plt.axis([0.5,16.5,-0.4,0.20])
ax.bar(r,j,width=0.6,color='green',edgecolor='black', lw=0.5, align='center')
ax.bar(r,n,width=0.3,color='blue',edgecolor='black', lw=0.5, align='center')
plt.axhline(y=0,linewidth=1,color='black') #horizontal line at y=0
plt.axis([0.5,24.5,-0.36,0.15])
plt.savefig('fig',dpi=300,format='png',orientation='landscape')
The way you're doing it, you just need to move the call to plt.xticks(r,x) to somewhere after you create the figure you're working on. Otherwise pyplot will create a new figure for you.
However, I would also consider switching to the more explicit object-oriented interface to matplotlib.
This way you'd use:
fig, ax = plt.subplots(1,1) # your only call to plt
ax.bar(r,v,width=0.9,color='red',edgecolor='black', lw=0.5, align='center')
ax.bar(r,j,width=0.6,color='green',edgecolor='black', lw=0.5, align='center')
ax.bar(r,n,width=0.3,color='blue',edgecolor='black', lw=0.5, align='center')
ax.set_xticks(r)
ax.set_xticklabels(x)
ax.axhline(y=0,linewidth=1,color='black')
fig.savefig('fig',dpi=300,format='png',orientation='landscape')
# or use plt.show() to see the figure interactively or inline, depending on backend
# (see Joe Kington's comment below)

How to plot figures in subplots (Matplotlib)

I understand there are various ways to plot multiple graphs in one figure. One such way is using axes, e.g.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([range(8)])
ax.plot(...)
Since I have a function that beautifies my graphs and subsequently returns a figure, I would like to use that figure to be plotted in my subplots. It should look similar to this:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot(figure1) # where figure is a plt.figure object
ax.plot(figure2)
This does not work but how can I make it work? Is there a way to put figures inside subplots or a workaround to plot multiple figures in one overall figure?
Any help on this is much appreciated.
Thanks in advance for your comments.
If the goal is just to customize individual subplots, why not change your function to change the current figure on the fly rather than return a figure. From matplotlib and seaborn, can you just change the plot settings as they are being plotted?
import numpy as np
import matplotlib.pyplot as plt
plt.figure()
x1 = np.linspace(0.0, 5.0)
x2 = np.linspace(0.0, 2.0)
y1 = np.cos(2 * np.pi * x1) * np.exp(-x1)
y2 = np.cos(2 * np.pi * x2)
plt.subplot(2, 1, 1)
plt.plot(x1, y1, 'ko-')
plt.title('A tale of 2 subplots')
plt.ylabel('Damped oscillation')
import seaborn as sns
plt.subplot(2, 1, 2)
plt.plot(x2, y2, 'r.-')
plt.xlabel('time (s)')
plt.ylabel('Undamped')
plt.show()
Perhaps I don't understand your question entirely. Is this 'beautification' function complex?...
A possible solution is
import matplotlib.pyplot as plt
# Create two subplots horizontally aligned (one row, two columns)
fig, ax = plt.subplots(1,2)
# Note that ax is now an array consisting of the individual axis
ax[0].plot(data1)
ax[1].plot(data2)
However, in order to work data1,2 needs to be data. If you have a function which already plots the data for you I would recommend to include an axis argument to your function. For example
def my_plot(data,ax=None):
if ax == None:
# your previous code
else:
# your modified code which plots directly to the axis
# for example: ax.plot(data)
Then you can plot it like
import matplotlib.pyplot as plt
# Create two subplots horizontally aligned
fig, ax = plt.subplots(2)
# Note that ax is now an array consisting of the individual axis
my_plot(data1,ax=ax[0])
my_plot(data2,ax=ax[1])

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

I'm plotting two datasets with different units on the y-axis. Is there a way to make the ticks and gridlines aligned on both y-axes?
The first image shows what I get, and the second image shows what I would like to get.
This is the code I'm using to plot:
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')
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()
I could solve it by deactivating ax.grid(None) in one of the grid`s axes:
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()
I wrote this function that takes Matplotlib axes objects ax1, ax2, and floats 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))
It calculates and sets the ticks such that there are seven ticks. The lowest tick corresponds to the current lowest tick and increases the highest tick such that the separation between each tick is integer multiples of minrexax1 or minrexax2.
To make it general, you can set the total number of ticks you want by changing ever 7 you see to the total number of ticks, and change 6 to the total number of ticks minus 1.
I put a pull request in to incorporate some this into matplotlib.ticker.LinearLocator:
https://github.com/matplotlib/matplotlib/issues/6142
In the future (Matplotlib 2.0 perhaps?), try:
import matplotlib.ticker
nticks = 11
ax1.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks))
ax2.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks))
That should just work and choose convenient ticks for both y-axes.
I created a method to align the ticks of multiple y- axes (could be more than 2), with possibly different scales in different axes.
Below is an example figure:
There are 3 y- axes, one blue on the left, and a green and a red on the right. The 3 curves are plotted onto the y-axis with the corresponding color. Note that they all have very different order of magnitudes.
Left plot: No alignment.
Mid plot: Aligned at (approximately) the lower bound of each y axis.
Right plot: Aligned at specified values: 0 for blue, 2.2*1e8 for red, and 44 for green. Those are chosen arbitrarily.
What I'm doing is to scale each y array to be within the range of 1-100, then merge all scaled y-values into a single array, from which a new set of ticks is created using MaxNLocator. Then this new set of ticks is scaled back using the corresponding scaling factor to get the new ticks for each axis. If some specific alignment is required, y arrays are shifted before scaling, and shifted back afterwards.
Complete code here (the key function is alignYaxes()):
import matplotlib.pyplot as plt
import numpy as np
def make_patch_spines_invisible(ax):
'''Used for creating a 2nd twin-x axis on the right/left
E.g.
fig, ax=plt.subplots()
ax.plot(x, y)
tax1=ax.twinx()
tax1.plot(x, y1)
tax2=ax.twinx()
tax2.spines['right'].set_position(('axes',1.09))
make_patch_spines_invisible(tax2)
tax2.spines['right'].set_visible(True)
tax2.plot(x, y2)
'''
ax.set_frame_on(True)
ax.patch.set_visible(False)
for sp in ax.spines.values():
sp.set_visible(False)
def alignYaxes(axes, align_values=None):
'''Align the ticks of multiple y axes
Args:
axes (list): list of axes objects whose yaxis ticks are to be aligned.
Keyword Args:
align_values (None or list/tuple): if not None, should be a list/tuple
of floats with same length as <axes>. Values in <align_values>
define where the corresponding axes should be aligned up. E.g.
[0, 100, -22.5] means the 0 in axes[0], 100 in axes[1] and -22.5
in axes[2] would be aligned up. If None, align (approximately)
the lowest ticks in all axes.
Returns:
new_ticks (list): a list of new ticks for each axis in <axes>.
A new sets of ticks are computed for each axis in <axes> but with equal
length.
'''
from matplotlib.pyplot import MaxNLocator
nax=len(axes)
ticks=[aii.get_yticks() for aii in axes]
if align_values is None:
aligns=[ticks[ii][0] for ii in range(nax)]
else:
if len(align_values) != nax:
raise Exception("Length of <axes> doesn't equal that of <align_values>.")
aligns=align_values
bounds=[aii.get_ylim() for aii in axes]
# align at some points
ticks_align=[ticks[ii]-aligns[ii] for ii in range(nax)]
# scale the range to 1-100
ranges=[tii[-1]-tii[0] for tii in ticks]
lgs=[-np.log10(rii)+2. for rii in ranges]
igs=[np.floor(ii) for ii in lgs]
log_ticks=[ticks_align[ii]*(10.**igs[ii]) for ii in range(nax)]
# put all axes ticks into a single array, then compute new ticks for all
comb_ticks=np.concatenate(log_ticks)
comb_ticks.sort()
locator=MaxNLocator(nbins='auto', steps=[1, 2, 2.5, 3, 4, 5, 8, 10])
new_ticks=locator.tick_values(comb_ticks[0], comb_ticks[-1])
new_ticks=[new_ticks/10.**igs[ii] for ii in range(nax)]
new_ticks=[new_ticks[ii]+aligns[ii] for ii in range(nax)]
# find the lower bound
idx_l=0
for i in range(len(new_ticks[0])):
if any([new_ticks[jj][i] > bounds[jj][0] for jj in range(nax)]):
idx_l=i-1
break
# find the upper bound
idx_r=0
for i in range(len(new_ticks[0])):
if all([new_ticks[jj][i] > bounds[jj][1] for jj in range(nax)]):
idx_r=i
break
# trim tick lists by bounds
new_ticks=[tii[idx_l:idx_r+1] for tii in new_ticks]
# set ticks for each axis
for axii, tii in zip(axes, new_ticks):
axii.set_yticks(tii)
return new_ticks
def plotLines(x, y1, y2, y3, ax):
ax.plot(x, y1, 'b-')
ax.tick_params('y',colors='b')
tax1=ax.twinx()
tax1.plot(x, y2, 'r-')
tax1.tick_params('y',colors='r')
tax2=ax.twinx()
tax2.spines['right'].set_position(('axes',1.2))
make_patch_spines_invisible(tax2)
tax2.spines['right'].set_visible(True)
tax2.plot(x, y3, 'g-')
tax2.tick_params('y',colors='g')
ax.grid(True, axis='both')
return ax, tax1, tax2
#-------------Main---------------------------------
if __name__=='__main__':
# craft some data to plot
x=np.arange(20)
y1=np.sin(x)
y2=x/1000+np.exp(x)
y3=x+x**2/3.14
figure=plt.figure(figsize=(12,4),dpi=100)
ax1=figure.add_subplot(1, 3, 1)
axes1=plotLines(x, y1, y2, y3, ax1)
ax1.set_title('No alignment')
ax2=figure.add_subplot(1, 3, 2)
axes2=plotLines(x, y1, y2, y3, ax2)
alignYaxes(axes2)
ax2.set_title('Default alignment')
ax3=figure.add_subplot(1, 3, 3)
axes3=plotLines(x, y1, y2, y3, ax3)
alignYaxes(axes3, [0, 2.2*1e8, 44])
ax3.set_title('Specified alignment')
figure.tight_layout()
figure.show()
This code will ensure that grids from both axes align to each other, without having to hide gridlines from either set. In this example, it allows you to match whichever has the finer grid lines. This builds off of the idea from #Leo. Hope it helps!
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()
If you're using axis labels, Leo's solution can push them off the side, due to the precision of the numbers in the ticks.
So in addition to something like Leo's solution (repeated here),
ax2.set_yticks(np.linspace(ax2.get_yticks()[0],ax2.get_yticks()[-1],len(ax1.get_yticks())))
you can use the autolayout setting, as mentioned in this answer; e.g., earlier in your script you can update rcParams:
from matplotlib import rcParams
rcParams.update({'figure.autolayout': True})
In a few test cases, this appears to produce the expected result, with both lined-up ticks and labels fully contained in the output.
I had the same issue except this was for a secondary x axis. I solved by setting my secondary x axis equal to the limit of my primary axis.The example below is without setting the limit of the second axis equal to the first:ax2 = ax.twiny()
Once I set the limit of the second axis equal to the first ax2.set_xlim(ax.get_xlim()) here is my result:
fix the limits for both axis (from any number to any number)
divide both axis into same n parts
ax1.set_ylim(a,b)
ax1.set_yticks(np.linspace(a,b, n))
ax2.set_ylim(c,d)
ax2.set_yticks(np.linspace(c,d, n))

Categories