16

I am refering to the question already asked here.
In this example the users have solved the second axis problem by adding it to the upper part of the graph where it coincide with the title.

Question: Is it possible to add the second x-axis at the bottom of the first one?

Code:

import numpy as np
import matplotlib.pyplot as plt

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

X = np.linspace(0,1,1000)
Y = np.cos(X*20)

ax1.plot(X,Y)
ax1.set_xlabel(r"Original x-axis: $X$")

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

def tick_function(X):
    V = 1/(1+X)
    return ["%.3f" % z for z in V]

ax2.set_xticks(new_tick_locations)
ax2.set_xticklabels(tick_function(new_tick_locations))
ax2.set_xlabel(r"Modified x-axis: $1/(1+X)$")
plt.show()
Mel
  • 5,837
  • 10
  • 37
  • 42
Amritbir Singh Gill
  • 366
  • 1
  • 3
  • 12

3 Answers3

27

As an alternative to the answer from @DizietAsahi, you can use spines in a similar way to the matplotlib example posted here.

import numpy as np
import matplotlib.pyplot as plt

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

# Add some extra space for the second axis at the bottom
fig.subplots_adjust(bottom=0.2)

X = np.linspace(0,1,1000)
Y = np.cos(X*20)

ax1.plot(X,Y)
ax1.set_xlabel(r"Original x-axis: $X$")

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

def tick_function(X):
    V = 1/(1+X)
    return ["%.3f" % z for z in V]

# Move twinned axis ticks and label from top to bottom
ax2.xaxis.set_ticks_position("bottom")
ax2.xaxis.set_label_position("bottom")

# Offset the twin axis below the host
ax2.spines["bottom"].set_position(("axes", -0.15))

# Turn on the frame for the twin axis, but then hide all 
# but the bottom spine
ax2.set_frame_on(True)
ax2.patch.set_visible(False)

# as @ali14 pointed out, for python3, use this
# for sp in ax2.spines.values():
# and for python2, use this
for sp in ax2.spines.itervalues():
    sp.set_visible(False)
ax2.spines["bottom"].set_visible(True)

ax2.set_xticks(new_tick_locations)
ax2.set_xticklabels(tick_function(new_tick_locations))
ax2.set_xlabel(r"Modified x-axis: $1/(1+X)$")
plt.show()

enter image description here

tmdavison
  • 64,360
  • 12
  • 187
  • 165
  • 1
    Works totally correctly. Use `for sp in ax2.spines.values():` for Python 3.x. – ali14 May 01 '20 at 08:56
  • 2
    In general you must set the xlim for ax2 manually, so it will be the same as for ax1: `ax2.set_xlim(ax1.get_xlim())` – fanyul Dec 01 '22 at 11:50
  • @fanyul. Exact. I think the answer could be completed – Stef1611 Jan 14 '23 at 13:00
  • @ttrndavison. See the comment of Fanyul. I had problem to have correctly aligned values when I got `new_ticks_location` from `ax1.get_xticks()` and this comment solved my problem. – Stef1611 Jan 14 '23 at 13:07
7

I think you have to create a second Axes with 0 height (and hide the yaxis) to have a second xaxis that you can place wherever you like.

for example:

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
ax1 = fig.add_axes((0.1,0.3,0.8,0.6)) # create an Axes with some room below

X = np.linspace(0,1,1000)
Y = np.cos(X*20)

ax1.plot(X,Y)
ax1.set_xlabel(r"Original x-axis: $X$")


# create second Axes. Note the 0.0 height
ax2 = fig.add_axes((0.1,0.1,0.8,0.0))
ax2.yaxis.set_visible(False) # hide the yaxis

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

def tick_function(X):
    V = 1/(1+X)
    return ["%.3f" % z for z in V]

ax2.set_xticks(new_tick_locations)
ax2.set_xticklabels(tick_function(new_tick_locations))
ax2.set_xlabel(r"Modified x-axis: $1/(1+X)$")
plt.show()

enter image description here

Diziet Asahi
  • 38,379
  • 7
  • 60
  • 75
0

Not really an answer to the question, but it took me quite long time until I figured out how to do the same with logscale. There are a bunch of strange behaviours in that case. Here's my code to apply some simple scaling to the original y axis:

def set_scaled_y_axis(ax, label1, label2, scale):
    #define the minor and major ticks
    #might give an error for too small or large exponents (e.g. 1e-20 or 1e+20)
    log_ticks_major=[]
    log_ticks_minor=[]
    tick_labels=[]
    for k in range(-15,16,1):
        log_ticks_major.append(10**k)
        tick_labels.append("10$^{"+f"{k}"+"}$")
        for kk in range(2,10):
            log_ticks_minor.append(kk*10**k)

    log_ticks_major=np.array(log_ticks_major)
    log_ticks_minor=np.array(log_ticks_minor)

    #update the original label
    ax.set_ylabel(label2)

    # make a twin axis and set the position
    # to make the same with x axis you need "ax.twiny()" instead
    ax22 = ax.twinx()
    ax22.yaxis.set_ticks_position("left")
    ax22.yaxis.set_label_position("left")
    ax22.spines["left"].set_position(("axes", -0.15))
    
    # draw only the left y axis
    ax22.xaxis.set_visible(False)

    # set the log scale for the 2nd axis
    ax22.set_yscale("log")
    ax22.set_yticks(log_ticks_minor/scale, minor=True) # set minor ticks
    ax22.set_yticks(log_ticks_major/scale) # set normal(/major?) ticks
    ax22.set_yticklabels(tick_labels) #must be after "ax22.set_yticks(log_ticks_major/scale)"
    ax22.tick_params('y', which="minor", labelleft=False) #some "random" minor tick labels would appear
    
    # set the 2nd y axis label
    ax22.set_ylabel(label1)

    # set the limits of the 2nd y axis to be the same as the 1st one
    ax22.set_ylim(ax.get_ylim())
fanyul
  • 148
  • 6