20

I am trying to produce a graph and I am having some issues annotating it.

My graph has a log scale on the x-axis, showing time. What I want to be able to do is keep the existing (but not predictable) numeric tick labels at 100 units, 1000 units, 10000 units, etc but also add custom tick labels to the x-axis that make it clear where more "human readable" time intervals occur---for instance I want to be able to label 'one week', 'one month', '6 months', etc.

I can use matplotlib.pyplot.annotate() to mark the points but it doesn't really do what I want. I don't really want text and arrows on top of my graph, I just want to add a few extra custom tick marks. Any ideas?

Charles
  • 50,943
  • 13
  • 104
  • 142
FakeDIY
  • 1,425
  • 2
  • 14
  • 23

4 Answers4

17

If you really want to add extra ticks, you can get the existing ones using axis.xaxis.get_majorticklocs(), add whatever you want to add, and then set the ticks using axis.xaxis.set_ticks(<your updated array>).

An alternative would be to add vertical lines using axvline. The advantage is that you don't have to worry about inserting your custom tick into the existing array, but you'll have to annotate the lines manually.

Yet another alternative would be to add a linked axis with your custom ticks.

Steve Barnes
  • 27,618
  • 6
  • 63
  • 73
ev-br
  • 24,968
  • 9
  • 65
  • 78
  • 1
    Thanks. I think it makes more sense to add a secondary x-axis. Mixing numerical and string axes labels leads to annoying overlap and formatting problems so I will just go with twiny(). :-) – FakeDIY May 16 '12 at 12:23
  • 1
    than this question might be of use to you: http://stackoverflow.com/questions/10514315/how-to-add-a-second-x-axis-in-matplotlib – ev-br May 16 '12 at 13:44
15

From http://matplotlib.sourceforge.net/api/pyplot_api.html#matplotlib.pyplot.xticks:

# return locs, labels where locs is an array of tick locations and
# labels is an array of tick labels.
locs, labels = xticks()

So all you should need to do is obtain the locs and labels and then modify labels to your liking (dummy example):

labels = ['{0} (1 day)','{0} (1 weak)', '{0} (1 year)']
new_labels = [x.format(locs[i]) for i,x  in enumerate(labels)]

and then run:

xticks(locs, new_labels)
deinonychusaur
  • 7,094
  • 3
  • 30
  • 44
  • This works, but its not quite what I want to do. I can see that I could use xticks() to get the desired result eventually but I actually think it will be neater and easier to just plot a second x-axis as suggested below. Thanks. – FakeDIY May 16 '12 at 12:20
  • OK, then you should accept that answer. I agree this is more of a hack (but works for in-house use / quick fixes). – deinonychusaur May 16 '12 at 12:38
5

This is my solution. The main advantages are:

  • You can specify the axes (useful for twin axes or if working with multiple axes simultaneously)
  • You can specify the axis (put ticks on x-axis or y-axis)
  • You can easily add new ticks while keeping the automatic ones
  • It automatically replaces if you add a tick that already exists.

Code:

#!/usr/bin/python
from __future__ import division
import matplotlib.pyplot as plt
import numpy as np

#Function to add ticks
def addticks(ax,newLocs,newLabels,pos='x'):
    # Draw to get ticks
    plt.draw()

    # Get existing ticks
    if pos=='x':
        locs = ax.get_xticks().tolist()
        labels=[x.get_text() for x in ax.get_xticklabels()]
    elif pos =='y':
        locs = ax.get_yticks().tolist()
        labels=[x.get_text() for x in ax.get_yticklabels()]
    else:
        print("WRONG pos. Use 'x' or 'y'")
        return

    # Build dictionary of ticks
    Dticks=dict(zip(locs,labels))

    # Add/Replace new ticks
    for Loc,Lab in zip(newLocs,newLabels):
        Dticks[Loc]=Lab

    # Get back tick lists
    locs=list(Dticks.keys())
    labels=list(Dticks.values())

    # Generate new ticks
    if pos=='x':
        ax.set_xticks(locs)
        ax.set_xticklabels(labels)
    elif pos =='y':
        ax.set_yticks(locs)
        ax.set_yticklabels(labels)


#Get numpy arrays
x=np.linspace(0,2)
y=np.sin(4*x)

#Start figure
fig = plt.figure()
ax=fig.add_subplot(111)

#Plot Arrays
ax.plot(x,y)
#Add a twin axes
axr=ax.twinx()

#Add more ticks
addticks(ax,[1/3,0.75,1.0],['1/3','3/4','Replaced'])
addticks(axr,[0.5],['Miguel'],'y')

#Save figure
plt.savefig('MWE.pdf')  
Miguel
  • 1,293
  • 1
  • 13
  • 30
1

I like Miguel's answer above. Worked quite well. However, a small adjustment has to be made. The following:

# Get back tick lists
locs=Dticks.keys()
labels=Dticks.values()

must be changed to

# Get back tick lists
locs=list(Dticks.keys())
labels=list(Dticks.values())

since, in Python 2.7+/3, Dict.keys() and Dict.values() return dict_keys and dict_values objects, which matplotlib does not like (apparently). More about those two objects in PEP 3106.

selimb
  • 360
  • 1
  • 4
  • 11