(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).
Related
I'm trying to create a plot with two Y axes (left and right) for the same data, that is, one is a scaled version of the other. I would like also to preserve the tick positions and grid positions, so the grid will match the ticks at both sides.
I'm trying to do this by plotting twice the same data, one as-is and the other scaled, but they are not coincident.
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(17, 27, 0.1)
y1 = 0.05 * x + 100
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.plot(x, y1, 'g-')
ax2.plot(x, y1/max(y1), 'g-')
ax1.set_xlabel('X data')
ax1.set_ylabel('Y data', color='g')
ax2.set_ylabel('Y data normalized', color='b')
plt.grid()
plt.show()
Any help will be appreciated.
Not sure if you can achieve this without getting ugly-looking numbers on your normalized axis. But if that doesn't bother you, try adding this to your code:
ax2.set_ylim([ax1.get_ylim()[0]/max(y1),ax1.get_ylim()[1]/max(y1)])
ax2.set_yticks(ax1.get_yticks()/max(y1))
Probably not the most elegant solution, but it scales your axis limits and tick positions similarly to what you do with the data itself so the grid matches both axes.
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))
import numpy as np
import matplotlib.pyplot as plt
plt.figure(1)
plt.subplot(211)
xs = np.linspace(-5,5,500)
ys = np.sqrt(5**2 - xs**2)
plt.plot(xs,ys)
plt.plot(xs,-ys)
plt.subplot(212)
plt.plot(xs, xs**2)
plt.show()
here is the code i generate, was wondering that if i want keep the upper plot x and y ratio be 1:1 so that the ball will always look round no matter how many subplot inside this figure.
I tried to find it from the website, seems not a simple solution..
When you create your subplot, you can tell it:
plt.subplot(211, aspect='equal')
If you've already created the subplot, you have to grab the current axes, which you can do using plt.gca, then call the set_aspect method:
plt.gca().set_aspect('equal')
Or, you can keep track of the axes from the beginning:
ax = plt.subplot(211)
ax.set_aspect('equal')
You may have to call
plt.draw()
In order to update the plot.
I try to plot two polar plots in one figure. See code below:
fig = super(PlotWindPowerDensity, self).get_figure()
rect = [0.1, 0.1, 0.8, 0.8]
ax = WindSpeedDirectionAxes(fig, rect)
self.values_dict = collections.OrderedDict(sorted(self.values_dict.items()))
values = self.values_dict.items()
di, wpd = zip(*values)
wpd = np.array(wpd).astype(np.double)
wpdmask = np.isfinite(wpd)
theta = self.radar_factory(int(len(wpd)))
# spider plot
ax.plot(theta[wpdmask], wpd[wpdmask], color = 'b', alpha = 0.5)
ax.fill(theta[wpdmask], wpd[wpdmask], facecolor = 'b', alpha = 0.5)
# bar plot
ax.plot_bar(table=self.table, sectors=self.sectors, speedbins=self.wpdbins, option='wind_power_density', colorfn=get_sequential_colors)
fig.add_axes(ax)
return fig
The length of the bar is the data base (how many sampling points for this sector). The colors of the bars show the frequency of certain value bins (eg. 2.5-5 m/s) in the correspondent sector (blue: low, red: high). The blue spider plot shows the mean value for each sector.
In the shown figure, the values of each plot are similar, but this is rare. I need to assign the second plot to another axis and show this axis in another direction.
EDIT:
After the nice answer of Joe, i get the result of the figure.
That's almost everything i wanted to achieve. But there are some points i wasn't able to figure out.
The plot is made for dynamicly changing data bases. Therefore i need a dynamic way to get the same location of the circles. Till now I solve it with:
start, end = ax2.get_ylim()
ax2.yaxis.set_ticks(np.arange(0, end, end / len(ax.yaxis.get_ticklocs())))
means: for second axis i alter the ticks in order to fit the ticklocs to the one's of first axis.
In most cases i get some decimal places, but i don't want that, because it corrupts the clearness of the plot. Is there a way to solve this problem more smartly?
The ytics (the radial one's) range from 0 to the next-to-last circle. How can i achieve that the values range from the first circle to the very last (the border)? The same like for the first axis.
So, as I understand it, you want to display data with very different magnitudes on the same polar plot. Basically you're asking how to do something similar to twinx for polar axes.
As an example to illustrate the problem, it would be nice to display the green series on the plot below at a different scale than the blue series, while keeping them on the same polar axes for easy comparison.:
import numpy as np
import matplotlib.pyplot as plt
numpoints = 30
theta = np.linspace(0, 2*np.pi, numpoints)
r1 = np.random.random(numpoints)
r2 = 5 * np.random.random(numpoints)
params = dict(projection='polar', theta_direction=-1, theta_offset=np.pi/2)
fig, ax = plt.subplots(subplot_kw=params)
ax.fill_between(theta, r2, color='blue', alpha=0.5)
ax.fill_between(theta, r1, color='green', alpha=0.5)
plt.show()
However, ax.twinx() doesn't work for polar plots.
It is possible to work around this, but it's not very straight-forward. Here's an example:
import numpy as np
import matplotlib.pyplot as plt
def main():
numpoints = 30
theta = np.linspace(0, 2*np.pi, numpoints)
r1 = np.random.random(numpoints)
r2 = 5 * np.random.random(numpoints)
params = dict(projection='polar', theta_direction=-1, theta_offset=np.pi/2)
fig, ax = plt.subplots(subplot_kw=params)
ax2 = polar_twin(ax)
ax.fill_between(theta, r2, color='blue', alpha=0.5)
ax2.fill_between(theta, r1, color='green', alpha=0.5)
plt.show()
def polar_twin(ax):
ax2 = ax.figure.add_axes(ax.get_position(), projection='polar',
label='twin', frameon=False,
theta_direction=ax.get_theta_direction(),
theta_offset=ax.get_theta_offset())
ax2.xaxis.set_visible(False)
# There should be a method for this, but there isn't... Pull request?
ax2._r_label_position._t = (22.5 + 180, 0.0)
ax2._r_label_position.invalidate()
# Ensure that original axes tick labels are on top of plots in twinned axes
for label in ax.get_yticklabels():
ax.figure.texts.append(label)
return ax2
main()
That does what we want, but it looks fairly bad at first. One improvement would be to the tick labels to correspond to what we're plotting:
plt.setp(ax2.get_yticklabels(), color='darkgreen')
plt.setp(ax.get_yticklabels(), color='darkblue')
However, we still have the double-grids, which are rather confusing. One easy way around this is to manually set the r-limits (and/or r-ticks) such that the grids will fall on top of each other. Alternately, you could write a custom locator to do this automatically. Let's stick with the simple approach here:
ax.set_rlim([0, 5])
ax2.set_rlim([0, 1])
Caveat: Because shared axes don't work for polar plots, the implmentation I have above will have problems with anything that changes the position of the original axes. For example, adding a colorbar to the figure will cause all sorts of problems. It's possible to work around this, but I've left that part out. If you need it, let me know, and I'll add an example.
At any rate, here's the full, stand-alone code to generate the final figure:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1977)
def main():
numpoints = 30
theta = np.linspace(0, 2*np.pi, numpoints)
r1 = np.random.random(numpoints)
r2 = 5 * np.random.random(numpoints)
params = dict(projection='polar', theta_direction=-1, theta_offset=np.pi/2)
fig, ax = plt.subplots(subplot_kw=params)
ax2 = polar_twin(ax)
ax.fill_between(theta, r2, color='blue', alpha=0.5)
ax2.fill_between(theta, r1, color='green', alpha=0.5)
plt.setp(ax2.get_yticklabels(), color='darkgreen')
plt.setp(ax.get_yticklabels(), color='darkblue')
ax.set_ylim([0, 5])
ax2.set_ylim([0, 1])
plt.show()
def polar_twin(ax):
ax2 = ax.figure.add_axes(ax.get_position(), projection='polar',
label='twin', frameon=False,
theta_direction=ax.get_theta_direction(),
theta_offset=ax.get_theta_offset())
ax2.xaxis.set_visible(False)
# There should be a method for this, but there isn't... Pull request?
ax2._r_label_position._t = (22.5 + 180, 0.0)
ax2._r_label_position.invalidate()
# Bit of a hack to ensure that the original axes tick labels are on top of
# whatever is plotted in the twinned axes. Tick labels will be drawn twice.
for label in ax.get_yticklabels():
ax.figure.texts.append(label)
return ax2
if __name__ == '__main__':
main()
Just to add onto #JoeKington 's (great) answer, I found that the "hack to ensure that the original axes tick labels are on top of whatever is plotted in the twinned axes" didn't work for me so as an alternative I've used:
from matplotlib.ticker import MaxNLocator
#Match the tick point locations by setting the same number of ticks in the
# 2nd axis as the first
ax2.yaxis.set_major_locator(MaxNLocator(nbins=len(ax1.get_yticks())))
#Set the last tick as the plot limit
ax2.set_ylim(0, ax2.get_yticks()[-1])
#Remove the tick label at zero
ax2.yaxis.get_major_ticks()[0].label1.set_visible(False)
I am trying to have two inter-depedent x-axis in a matplotlib imshow() plot. I have bottom x-axis as the radius squared and I want the top as just the radius. I have tried so far:
ax8 = ax7.twiny()
ax8._sharex = ax7
fmtr = FuncFormatter(lambda x,pos: np.sqrt(x) )
ax8.xaxis.set_major_formatter(fmtr)
ax8.set_xlabel("Radius [m]")
where ax7 is the y-axis and the bottom x-axis (or radius squared). Instead of getting the sqrt (x_bottom) as the ticks at the top I just get a range from 0 to 1. How can I fix this?
Thanks a lot in advance.
You're misunderstanding what twiny does. It makes a completely independent x-axis with a shared y-axis.
What you want to do is have a different formatter with a linked axis (i.e. sharing the axis limits but nothing else).
The simple way to do this is to manually set the axis limits for the twinned axis:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
fig, ax1 = plt.subplots()
ax1.plot(range(10))
ax2 = ax1.twiny()
formatter = FuncFormatter(lambda x, pos: '{:0.2f}'.format(np.sqrt(x)))
ax2.xaxis.set_major_formatter(formatter)
ax2.set_xlim(ax1.get_xlim())
plt.show()
However, as soon as you zoom or interact with the plot, you'll notice that the axes are unlinked.
You could add an axes in the same position with both shared x and y axes, but then the tick formatters are shared, as well.
Therefore, the easiest way to do this is using a parasite axes.
As a quick example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
from mpl_toolkits.axes_grid1.parasite_axes import SubplotHost
fig = plt.figure()
ax1 = SubplotHost(fig, 1,1,1)
fig.add_subplot(ax1)
ax2 = ax1.twin()
ax1.plot(range(10))
formatter = FuncFormatter(lambda x, pos: '{:0.2f}'.format(np.sqrt(x)))
ax2.xaxis.set_major_formatter(formatter)
plt.show()
Both this and the previous plot will look identical at first. The difference will become apparent when you interact (e.g. zoom/pan) with the plot.