Ah77
Ah77

Reputation: 85

Parasite x-axis in loglog plot

I have a graph where the x-axis is the temperature in GeV, but I also need to put a reference of the temperature in Kelvin, so I thought of putting a parasite axis with the temperature in K. Trying to follow this answer How to add a second x-axis in matplotlib , Here is the example of the code. I get a second axis at the top of my graph, but it is not the temperature in K as I need.

import numpy as np
import matplotlib.pyplot as plt

tt = np.logspace(-14,10,100)
yy = np.logspace(-10,-2,100)

fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twiny()

ax1.loglog(tt,yy)
ax1.set_xlabel('Temperature (GeV')

new_tick_locations = np.array([.2, .5, .9])

def tick_function(X):
    V = X*1.16e13
    return ["%.1f" % z for z in V]

ax2.set_xlim(ax1.get_xlim())
ax2.set_xticks(new_tick_locations)
ax2.set_xticklabels(tick_function(ax1Xs))
ax2.set_xlabel('Temp (Kelvin)')
plt.show()

This is what I get when I run the code.

loglog plot loglog plot

I need the parasite axis be proportional to the original x-axis. And that it can be easy to read the temperature in Kelvin when anyone sees the graph. Thanks in advance.

Upvotes: 1

Views: 308

Answers (2)

ImportanceOfBeingErnest
ImportanceOfBeingErnest

Reputation: 339775

A general purpose solution may look as follows. Since you have a non-linear scale, the idea is to find the positions of nice ticks in Kelvin, convert to GeV, set the positions in units of GeV, but label them in units of Kelvin. This sounds complicated, but the advantage is that you do not need to find the ticks yourself, just rely on matplotlib for finding them. What this requires though is the functional dependence between the two scales, i.e. the converion between GeV and Kelvin and its inverse.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker

tt = np.logspace(-14,10,100)
yy = np.logspace(-10,-2,100)

fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twiny()

plt.setp([ax1,ax2], xscale="log", yscale="log")
ax1.get_shared_x_axes().join(ax1, ax2)

ax1.plot(tt,yy)

ax1.set_xlabel('Temperature (GeV)')
ax2.set_xlabel('Temp (Kelvin)')

fig.canvas.draw()

# 1 GeV == 1.16 × 10^13 Kelvin
Kelvin2GeV = lambda k:  k / 1.16e13
GeV2Kelvin = lambda gev: gev * 1.16e13

loc = mticker.LogLocator()
locs = loc.tick_values(*GeV2Kelvin(np.array(ax1.get_xlim())))

ax2.set_xticks(Kelvin2GeV(locs))
ax2.set_xlim(ax1.get_xlim())

f = mticker.ScalarFormatter(useOffset=False, useMathText=True)
g = lambda x,pos : "${}$".format(f._formatSciNotation('%1.10e' % GeV2Kelvin(x)))
fmt = mticker.FuncFormatter(g)
ax2.xaxis.set_major_formatter(mticker.FuncFormatter(fmt))

plt.show()

enter image description here

Upvotes: 1

Sheldore
Sheldore

Reputation: 39072

The problem appears to be the following: When you use ax2.set_xlim(ax1.get_xlim()), you are basically setting the limit of upper x-axis to be the same as that of the lower x-axis. Now if you do

print(ax1.get_xlim()) 
print(ax2.get_xlim()) 

you get for both axes the same values as

(6.309573444801943e-16, 158489319246.11108) 
(6.309573444801943e-16, 158489319246.11108) 

but your lower x-axis is having a logarithmic scale. When you assign the limits using ax2.set_xlim(), the limits of ax2 are the same but the scale is still linear. That's why when you set the ticks at [.2, .5, .9], these values appear as ticks on the far left of the upper x-axis as in your figure.

The solution is to set the upper x-axis also to be a logarithmic scale. This is required because your new_tick_locations corresponds to the actual values on the lower x-axis. You just want to rename these values to show the ticklabels in Kelvin. It is clear from your variable names that new_tick_locations corresponds to the new tick locations. I use some modified values of new_tick_locations to highlight the problem.

I am using scientific formatting '%.0e' because 1 GeV = 1.16e13 K and so 0.5 GeV would be a very large value with many zeros.

Below is a sample answer:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick

tt = np.logspace(-14,10,100)
yy = np.logspace(-10,-2,100)

fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twiny()

ax1.loglog(tt,yy)
ax1.set_xlabel('Temperature (GeV)')

new_tick_locations = np.array([0.000002, 0.05, 9000])

def tick_function(X):
    V = X*1.16e13
    return ["%.1f" % z for z in V]

ax2.set_xscale('log') # Setting the logarithmic scale
ax2.set_xlim(ax1.get_xlim())

ax2.set_xticks(new_tick_locations)
ax2.set_xticklabels(tick_function(new_tick_locations))
ax2.xaxis.set_major_formatter(mtick.FormatStrFormatter('%.0e'))

ax2.set_xlabel('Temp (Kelvin)')
plt.show()

enter image description here

Upvotes: 1

Related Questions