2

I ran into a problem with the plt.twinx() function of matplotlib.pyplot when I tried to plot a secondary x-axis for a primary ln(x)-axis. They should show corresponding values, but with different ticks. For clarity here is what I tried so far in a MWE:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator

fig = plt.figure(1)
ax1 = fig.add_subplot(111)

ax1.set_xlabel(r'$ln(\sigma)$')
ax1.set_xticks([5.2,5.3,5.4,5.5,5.6,5.7,5.8])
ax1.set_xlim([5.2,5.8])
ax1.plot(5.6,0.5,'o')

ax2 = ax1.twiny()
ax2.set_xlabel(r'$\sigma$')
ax2.set_xlim(np.exp(ax1.get_xlim()))
ax2.xaxis.set_major_locator(MultipleLocator(base=25))

plt.show()

This produces the following plot, which looks as desired at first but has the problem, that the secondary x-ticks are wrong.

plot with wrong secondary x-values

The point is located at x1 = 0.5 but the corresponding secondary x-value is at x2 =~ 280 but should be after all at x2 = math.exp(5.6) =~ 270

I'm not really sure if this is a plotting problem or a deeper-going mathematical problem with the different scales.

It works when I don't set the ax2.xlim() but just double the primary x-ticks and use matplotlib.ticker.FuncFormatter to format the secondary x-ticks to np.exp(ax1.get_xticlocs()) but then the secondary ticks are at "strange" values.

3
  • It looks like it's at the correct value in both scales to me. e^5.62 is about 275. Commented Aug 1, 2017 at 14:41
  • @ngoldbaum Unfortunatly it is not. It is a simplyfied dataset but imho clearifys the error. I'm doing a mechanical Weibull-analysis and get a sigma value that is definitly plotted a the wrong place, which might cause confusion. My guess is that it is a rounding/float error when doing the exponential conversion… Commented Aug 1, 2017 at 15:33
  • See also this answer to a similar question. Commented Feb 23, 2021 at 20:27

1 Answer 1

4

Here's what's going wrong

It's because the mapping between your two x-scales is non-linear (it's exponential/logarithmic). In effect you've got one axis as a log scale and the other as a normal scale. The two coincide at the endpoints based on how you defined your limits, but not in between. This idea is demonstrated below. The "mapped value" of x2 is plotted on the y-axis versus your x1 values. The blue line which I labeled "endpoints only" is what you expect, but the "full domain" mapping is what happens in reality.

import matplotlib.pyplot as plt
import numpy as np

# Endpoints only
x01 = np.array([5.2,5.8])
y01 = np.exp(x01)

# Full domain
x = np.linspace(5.2,5.8,100)
y = np.exp(x)

plt.plot(x01,y01,label='endpoints only')
plt.plot(x,y, label='full domain')
plt.legend()
plt.show()

enter image description here

Here's one way around it

Instantiate both axes on log scales. In your case you want natural log, so we pass basex=np.e. You then need to manually specify the tick locations on both axes. In ax1 we just use the pre-specified locations; for ax2 you can use the locations generated after specifying the MultipleLocator.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator

fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.set_xscale('log', basex=np.e)

# Array of tick locations...use the true value (not log value)
locs = np.exp(np.array([5.2,5.3,5.4,5.5,5.6,5.7,5.8]))

ax1.set_xlabel(r'$ln(\sigma)$')
ax1.set_xlim([locs[0],locs[-1]])
ax1.set_xticks(locs)
ax1.set_xticklabels(np.log(locs))

ax2 = ax1.twiny()
ax2.set_xscale('log', basex=np.e)
ax2.set_xlabel(r'$\sigma$')
ax2.set_xlim((ax1.get_xlim()))
ax2.xaxis.set_major_locator(MultipleLocator(base=25))
# Manually set the tick labels to match the positions your set with the locator
ax2.set_xticklabels(['{:.0f}'.format(k) for k in ax2.get_xticks()])  

ax1.plot(locs,locs*0+.4,'o')
ax2.plot(locs,locs*0+.6,'o',color='C1')
ax1.set_ylim([0,1])

plt.show()

enter image description here

Sign up to request clarification or add additional context in comments.

4 Comments

Right, I see the problem now. Any suggestions how to fix the wrong tickmarks with the plt.twiny()? I'm still a bit confused how to get rid of this problem,
I'm trying a solution in which I instantiate ax1 as a log scale with basex=np.e, but it doesn't seem to solve the problem. I'll come back to it later. Hopefully someone else can solve it in the mean time.
@Fabian There's a one work around for you. If this has solved your question please consider accepting it by clicking the check-mark. This indicates to the wider community that you've found a solution and gives some reputation to both the answerer and yourself. There is no obligation to do this.
Thank you very much. With a bit of fuddling around I got it working! I don't know my ticklocations prior, so I had to do it a bit different, but your code hinted the perfect solution so I cheked it as the solution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.