74

With matplotlib when a log scale is specified for an axis, the default method of labeling that axis is with numbers that are 10 to a power eg. 10^6. Is there an easy way to change all of these labels to be their full numerical representation? eg. 1, 10, 100, etc.

Note that I do not know what the range of powers will be and want to support an arbitrary range (negatives included).

Nat Dempkowski
  • 2,331
  • 1
  • 19
  • 36

6 Answers6

77

Sure, just change the formatter.

For example, if we have this plot:

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.axis([1, 10000, 1, 100000])
ax.loglog()

plt.show()

enter image description here

You could set the tick labels manually, but then the tick locations and labels would be fixed when you zoom/pan/etc. Therefore, it's best to change the formatter. By default, a logarithmic scale uses a LogFormatter, which will format the values in scientific notation. To change the formatter to the default for linear axes (ScalarFormatter) use e.g.

from matplotlib.ticker import ScalarFormatter
for axis in [ax.xaxis, ax.yaxis]:
    axis.set_major_formatter(ScalarFormatter())

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • Thanks, exactly what I wanted. Surprised none of the other answers on here were this simple. – Nat Dempkowski Feb 20 '14 at 22:11
  • 4
    What if I wanted to change the numbers to, 1, 5, 10, 20? – aloha Jul 10 '15 at 13:26
  • 1
    @Joe Kington: I would like to add ticks in between, like `50,200, etc..`, How can I do that? I tried, `set_xticks[50.0,200.0]` but that doesn't seem to work! – Srivatsan Aug 03 '15 at 12:54
  • 2
    But with ax.axis([1, 100, 1, 100]), ScalarFormatter gives 1.0, 10.0, ... which is not what I desire. I want it to give integers... – CPBL Dec 07 '15 at 20:22
  • 3
    I also had to use [`set_powerlimits()`](http://matplotlib.org/api/ticker_api.html#matplotlib.ticker.ScalarFormatter.set_powerlimits) to avoid the scientific notation. – Don Kirkby Aug 23 '16 at 21:43
  • @DonKirkby can you provide a snippet of code? I can't seem to get set_powerlimits or set_scientific to produce what I want. – Brandon Dube Dec 17 '17 at 23:41
  • According to the docs, @BrandonDube, `formatter.set_powerlimits((-3, 4))` sets the pre-2007 default in which scientific notation is used for numbers less than 1e-3 or greater than 1e4. In this answer's snippet, you'll have to assign the formatter to local variable, set power limits, then assign it on the axis. – Don Kirkby Dec 18 '17 at 01:26
  • Sure - the lines aren't right but here's a snippet with semicolons for linebreaks: `fig, ax = plt.subplots(); ax.plot(x,y); tick = ticker.ScalarFormatter(); ticket.set_powerlimits((-3,20)); ax.xaxis.set_major_formatter(ticket); ax.set(xscale='log'); plt.show();` <- yields major ticks in scientific notation. – Brandon Dube Dec 18 '17 at 03:13
  • 1
    And I had to use `xaxis.set_minor_formatter(..)` too. – Timmmm Mar 15 '18 at 10:36
  • 4
    @DonKirkby: You can use `.set_scientific(False)` instead of `set_powerlimits()` to disable the scientific notation. – Timmmm Mar 15 '18 at 10:39
  • Thanks, @Timmmm, I posted a summary with your hint and the others in a [new answer](https://stackoverflow.com/a/49306588/4794). – Don Kirkby Mar 15 '18 at 18:25
  • How to set the decimal place with this one – Xue Aug 03 '23 at 08:29
59

I've found that using ScalarFormatter is great if all your tick values are greater than or equal to 1. However, if you have a tick at a number <1, the ScalarFormatter prints the tick label as 0.

enter image description here

We can use a FuncFormatter from the matplotlib ticker module to fix this issue. The simplest way to do this is with a lambda function and the g format specifier (thanks to @lenz in comments).

import matplotlib.ticker as ticker

ax.yaxis.set_major_formatter(ticker.FuncFormatter(lambda y, _: '{:g}'.format(y)))

Note in my original answer I didn't use the g format, instead I came up with this lambda function with FuncFormatter to set numbers >= 1 to their integer value, and numbers <1 to their decimal value, with the minimum number of decimal places required (i.e. 0.1, 0.01, 0.001, etc). It assumes that you are only setting ticks on the base10 values.

import matplotlib.ticker as ticker
import numpy as np

ax.yaxis.set_major_formatter(ticker.FuncFormatter(lambda y,pos: ('{{:.{:1d}f}}'.format(int(np.maximum(-np.log10(y),0)))).format(y)))

enter image description here

For clarity, here's that lambda function written out in a more verbose, but also more understandable, way:

def myLogFormat(y,pos):
    # Find the number of decimal places required
    decimalplaces = int(np.maximum(-np.log10(y),0))     # =0 for numbers >=1
    # Insert that number into a format string
    formatstring = '{{:.{:1d}f}}'.format(decimalplaces)
    # Return the formatted tick label
    return formatstring.format(y)

ax.yaxis.set_major_formatter(ticker.FuncFormatter(myLogFormat))
tmdavison
  • 64,360
  • 12
  • 187
  • 165
  • 7
    This follow-up is exactly what I needed, thanks! One suggestion: wouldn't the `g` format specifier do the trick as well? With `FuncFormatter(lambda y, _: '{:g}'.format(y))` I got the same output. – lenz Apr 24 '17 at 14:37
  • 1
    Yes I think you're right. I learnt about the 'g' formatter after I wrote this answer :) – tmdavison Apr 24 '17 at 16:53
21

I found Joe's and Tom's answers very helpful, but there are a lot of useful details in the comments on those answers. Here's a summary of the two scenarios:

Ranges above 1

Here's the example code like Joe's, but with a higher range:

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.axis([1, 10000, 1, 1000000])
ax.loglog()

plt.show()

That shows a plot like this, using scientific notation: Default plot with scientific notation

As in Joe's answer, I use a ScalarFormatter, but I also call set_scientific(False). That's necessary when the scale goes up to 1000000 or above.

import matplotlib.pyplot as plt
from matplotlib.ticker import ScalarFormatter

fig, ax = plt.subplots()
ax.axis([1, 10000, 1, 1000000])
ax.loglog()
for axis in [ax.xaxis, ax.yaxis]:
    formatter = ScalarFormatter()
    formatter.set_scientific(False)
    axis.set_major_formatter(formatter)

plt.show()

Plot with integer ticks

Ranges below 1

As in Tom's answer, here's what happens when the range goes below 1:

import matplotlib.pyplot as plt
from matplotlib.ticker import ScalarFormatter

fig, ax = plt.subplots()
ax.axis([0.01, 10000, 1, 1000000])
ax.loglog()
for axis in [ax.xaxis, ax.yaxis]:
    formatter = ScalarFormatter()
    formatter.set_scientific(False)
    axis.set_major_formatter(formatter)

plt.show()

That displays the first two ticks on the x axis as zeroes.

Plot with ticks labelled as zero

Switching to a FuncFormatter handles that. Again, I had problems with numbers 1000000 or higher, but adding a precision to the format string solved it.

import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter

fig, ax = plt.subplots()
ax.axis([0.01, 10000, 1, 1000000])
ax.loglog()
for axis in [ax.xaxis, ax.yaxis]:
    formatter = FuncFormatter(lambda y, _: '{:.16g}'.format(y))
    axis.set_major_formatter(formatter)

plt.show()

enter image description here

Don Kirkby
  • 53,582
  • 27
  • 205
  • 286
  • I found your answer very helpful and I've used it [in this partial answer](https://stackoverflow.com/a/55827453/3904031) to my own question. However my question asks *why* I am getting a specific behavior. Do you have any thoughts on that? Thanks! – uhoh Apr 24 '19 at 10:20
  • 2
    Need to add `axis.set_minor_formatter(formatter)` as well for minor ticks. Also, the 16g sometimes causes labels like `0.700000000000001`. It helps to reduce the number of digits. – germ Feb 15 '22 at 19:33
7

regarding these questions

What if I wanted to change the numbers to, 1, 5, 10, 20?
– aloha Jul 10 '15 at 13:26

I would like to add ticks in between, like 50,200, etc.., How can I do that? I tried, set_xticks[50.0,200.0] but that doesn't seem to work! – ThePredator Aug 3 '15 at 12:54

But with ax.axis([1, 100, 1, 100]), ScalarFormatter gives 1.0, 10.0, ... which is not what I desire. I want it to give integers... – CPBL Dec 7 '15 at 20:22

you can solve those issue like this with MINOR formatter:

ax.yaxis.set_minor_formatter(matplotlib.ticker.ScalarFormatter())
ax.yaxis.set_minor_formatter(matplotlib.ticker.FormatStrFormatter("%.8f"))
ax.set_yticks([0.00000025, 0.00000015, 0.00000035])

in my application I'm using this format scheme, which I think solves most issues related to log scalar formatting; the same could be done for data > 1.0 or x axis formatting:

plt.ylabel('LOGARITHMIC PRICE SCALE')
plt.yscale('log')
ax.yaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter())
ax.yaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter("%.8f"))
ax.yaxis.set_minor_formatter(matplotlib.ticker.ScalarFormatter())
ax.yaxis.set_minor_formatter(matplotlib.ticker.FormatStrFormatter("%.8f"))
#####################################################
#force 'autoscale'
#####################################################
yd = [] #matrix of y values from all lines on plot
for n in range(len(plt.gca().get_lines())):
        line = plt.gca().get_lines()[n]
        yd.append((line.get_ydata()).tolist())
yd = [item for sublist in yd for item in sublist]
ymin, ymax = np.min(yd), np.max(yd)
ax.set_ylim([0.9*ymin, 1.1*ymax])
#####################################################
z = []
for i in [0.0000001, 0.00000015, 0.00000025, 0.00000035,
          0.000001, 0.0000015, 0.0000025, 0.0000035,
          0.00001,  0.000015, 0.000025, 0.000035,
          0.0001, 0.00015, 0.00025, 0.00035,
          0.001, 0.0015, 0.0025, 0.0035,
          0.01, 0.015, 0.025, 0.035,
          0.1, 0.15, 0.25, 0.35]:

    if ymin<i<ymax:
        z.append(i)
        ax.set_yticks(z)                

for comments on "force autoscale" see: Python matplotlib logarithmic autoscale

which yields:

enter image description here

then to create a general use machine:

# user controls
#####################################################
sub_ticks = [10,11,12,14,16,18,22,25,35,45] # fill these midpoints
sub_range = [-8,8] # from 100000000 to 0.000000001
format = "%.8f" # standard float string formatting

# set scalar and string format floats
#####################################################
ax.yaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter())
ax.yaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter(format))
ax.yaxis.set_minor_formatter(matplotlib.ticker.ScalarFormatter())
ax.yaxis.set_minor_formatter(matplotlib.ticker.FormatStrFormatter(format))

#force 'autoscale'
#####################################################
yd = [] #matrix of y values from all lines on plot
for n in range(len(plt.gca().get_lines())):
        line = plt.gca().get_lines()[n]
        yd.append((line.get_ydata()).tolist())
yd = [item for sublist in yd for item in sublist]
ymin, ymax = np.min(yd), np.max(yd)
ax.set_ylim([0.9*ymin, 1.1*ymax])

# add sub minor ticks
#####################################################
set_sub_formatter=[]
for i in sub_ticks:
    for j in range(sub_range[0],sub_range[1]):
        set_sub_formatter.append(i*10**j)
k = []
for l in set_sub_formatter:
    if ymin<l<ymax:
        k.append(l)
ax.set_yticks(k)
#####################################################

yields:

enter image description here

Community
  • 1
  • 1
litepresence
  • 3,109
  • 1
  • 27
  • 35
1

The machinery outlined in the accepted answer works great, but sometimes a simple manual override is easier. To get ticks at 1, 10, 100, 1000, for example, you could say:

ticks = 10**np.arange(4)
plt.xticks(ticks, ticks)

Note that it is critical to specify both the locations and the labels, otherwise matplotlib will ignore you.

This mechanism can be used to obtain arbitrary formatting. For instance:

plt.xticks(ticks, [ f"{x:.0f}" for x in ticks ])

or

plt.xticks(ticks, [ f"10^{int(np.log10(x))}" for x in ticks ])

or

plt.xticks(ticks, [ romannumerals(x) for x in ticks ])

(where romannumerals is an imagined function that converts its argument into Roman numerals).

As an aside, this technique also works if you want ticks at arbitrary intervals, e.g.,

ticks = [1, 2, 5, 10, 20, 50, 100]

etc.

Daniel Wagenaar
  • 111
  • 1
  • 5
1
import matplotlib.pyplot as plt

plt.rcParams['axes.formatter.min_exponent'] = 2
plt.xlim(1e-5, 1e5)
plt.loglog()

plt.show()

This will become default for all plots in a session.

See also: LogFormatter tickmarks scientific format limits