31

I'm trying to make a plot similar to this excel example:

example

I would like to know if there is anyways to have a second layer on the x tick labels (e.g. "5 Year Statistical Summary"). I know I can make multi-line tick labels using \n but I want to be able to shift the two levels independently.

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
HHains
  • 683
  • 2
  • 9
  • 17
  • 1
    A good example of multiple axes is here: http://stackoverflow.com/questions/7761778/matplotlib-adding-second-axes-with-transparent-background Is this close enough? – tom10 Dec 12 '13 at 04:11
  • This is similar to what I want, but I don't want there to be a physical second x-axis. I basically want to add a second line of text to the first axis which can be moved irrespective of the first. i.e. 'Background' is always in the same position, but '5 Year Summary' can be moved to the left or right depending on how many plots are added to the graph. – HHains Dec 12 '13 at 13:26
  • 1
    It's easy enough to hide the second axis. Overall, it's not quite clear how you want things to vary.. what's locked to what? You can use `text`, `annotate` or `axis` and `spines` (even if you don't show the actual axis associated with these two). Which will be easiest for you is dependent not on the picture you show, but on what you want to different items to register to (for both when you scale the plot and when you change the inputs). Currently, that's all unclear (ie, when you move '5 year Summary' moved wrt what?). – tom10 Dec 12 '13 at 17:38

5 Answers5

27

this gets close:

xtick

fig = plt.figure( figsize=(8, 4 ) )
ax = fig.add_axes( [.05, .1, .9, .85 ] )
ax.set_yticks( np.linspace(0, 200, 11 ) )

xticks = [ 2, 3, 4, 6, 8, 10 ]
xticks_minor = [ 1, 5, 7, 9, 11 ]
xlbls = [ 'background', '5 year statistical summary', 'future build',
          'maximum day', '90th percentile day', 'average day' ]

ax.set_xticks( xticks )
ax.set_xticks( xticks_minor, minor=True )
ax.set_xticklabels( xlbls )
ax.set_xlim( 1, 11 )

ax.grid( 'off', axis='x' )
ax.grid( 'off', axis='x', which='minor' )

# vertical alignment of xtick labels
va = [ 0, -.05, 0, -.05, -.05, -.05 ]
for t, y in zip( ax.get_xticklabels( ), va ):
    t.set_y( y )

ax.tick_params( axis='x', which='minor', direction='out', length=30 )
ax.tick_params( axis='x', which='major', bottom='off', top='off' )
behzad.nouri
  • 74,723
  • 18
  • 126
  • 124
14

I'm unable to comment on behzad's answer due to lack of reputation. I found his solution to be immensely helpful, but I thought I'd share that instead of controlling the vertical alignment by using set_y(), I just added a newline character to vertically offset the labels. So, for the above example:

xlbls = [ 'background', '\n5 year statistical summary', 'future build',
      '\nmaximum day', '\n90th percentile day', '\naverage day' ]

For me, this was a better solution for keeping multi-lined labels and multi-layered labels vertically aligned.

omegamanda
  • 388
  • 3
  • 7
1

I am also unable to comment due to reputation but have a minor fix for behzad's answer.

The tick_params() 'bottom' and 'top' keywords take booleans, not strings (at least for py3.6). For example, using bottom = 'off' for me still produces ticks whereas using bottom = False removes ticks.

so replace

ax.tick_params( axis='x', which='major', bottom='off', top='off' )

with

ax.tick_params( axis='x', which='major', bottom=False, top=False )

and it works!

aputman
  • 131
  • 1
  • 7
1

Here is a Matplotlib reference/example for multiple x-tick lines: Multiline

it uses the same approach as omegamanda

ax1.set_xticks([0.2, 0.4, 0.6, 0.8, 1.],
               labels=["Jan\n2009", "Feb\n2009", "Mar\n2009", "Apr\n2009",
                       "May\n2009"])
asccondor
  • 31
  • 2
0

The best solution I've found is to use the plt.annotate function. It's described well here: in the last comment

Community
  • 1
  • 1
HHains
  • 683
  • 2
  • 9
  • 17