0

What i basically want is instead of plotting and having offsets 10^8 10^5 or 1e8 1e5 etc to have them in stadard units such as 10^3 10^6 1e3 1e6 etc.

Is this possible? and if so how?

Thanks in advance

1 Answers1

1
plt.gca().get_xaxis().get_major_formatter().set_useOffset(False)

Do you mean this? (same goes for y axis). This is a possible duplicate of this question in which the second answer gives a more canonical description than me. Upvote him if that's what you're looking for.

EDIT

I've played around with this on and off for the whole afternoon and it seems that setting ax.yaxis.set_scientific(True) automatically trumps any ax.yaxis.setOffset = False. No matter in which way you do it. I couldn't work around that and then I got aggravated.

Here's a workaround though, Py3.4, mpl 1-3-1 win7 tested.

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
from decimal import Decimal

x_large = np.arange(10, 20000, 100)
y_large = np.arange(10, 20000, 100)

x_small = np.arange(-1, 1, 0.000001)
y_small = np.arange(-1, 1, 0.000001)

metric = [0,1,2,3,6,9, 12, 16]

def format_large(x):
    x = float(x)
    sx = str(x)
    sx = sx.split(".")[0]
    if len(sx)-1 in metric:
        return "%se%s" % (sx[0], len(sx)-1)
    add = - max(exp-len(sx)-1 for exp in metric if exp<len(sx)-1)
    return "%se%s" % (sx[0:add], len(sx)-add)

#love thy DRY principle
def check_metric_small(num, exp):
    if exp in metric:
            return "%se-%s" % (num[0], exp)
    add = min(mexp-exp for mexp in metric if mexp>exp)
    num = num.ljust(add+1, "0")
    return "%se-%s" % (num[0:add+1], exp+add)

def format_small(x):
    sx = str(x)
    #string representation of numbers after e-4 is
    #done in scientific manner 1e-5, 1e-6 ...
    if sx.find("e") != -1:
        num, exp = sx.split("e")
        exp = -int(exp)
        return check_metric_small(num, exp)
    #numbers up to e-4 are represented in string form
    #as is, therefore 0.1, 0.01
    s = sx.split(".")[-1]
    num = s.strip("0")
    exp = len(s)-len(num)
    return check_metric_small(num, exp+1)


def formatter(x, p):
    if x<0: #make sure we don't send a negative number
        res = "-" #rather just tack on the minus sign
        x = -x
    else: res = ""         
    if abs(x)<1 and x!=0.0:
            return res+format_small(x)
    return res+format_large(x)

fig, ax = plt.subplots()
y_formatter = mpl.ticker.FuncFormatter(formatter)
ax.yaxis.set_major_formatter(y_formatter)
ax.plot(x_large,y_large)

plt.show()

Where the important functions are contained within the format_large and format_small. They will round the numbers to the next closest smaller exponent defined by the metric list. By themselves, they aren't able to handle negative numbers so use the formatter function.

Change between a large zoom and un-zoom doesn't seem to leave behind any weird effects and neither does passing over zero. Bellow is the gist of those functions:

for i in [0, 10, 50, ......]:
    print(format_large(i), float(format_large(i)) == i)

0e0 True           5e6 True
1e1 True           5e6 True
5e1 True           50e6 True
5e2 True           500e6 True
5e3 True           50e9 True
50e3 True          500e9 True
500e3 True         5e12 True

for i in [0.05, 0.005, ......]:
    print(format_small(i), float(format_small(i)) == i)

5e-2 True         50e-9 True
5e-3 True         5e-9 True
500e-6 True       500e-12 True   
50e-6 True        50e-12 True 
5e-6 True         50e-12 True 
500e-9 True       5000e-16 True 

Be careful though, because I round the numbers, you can only test with rounded numbers.

What happens for large numbers, is that I take their string representation i.e. "500.0". Lenght of the number subtracted by one gives me the number of zeroes\order of the number. If it's a metric order I return the simplest string containing only the leading digit of the number and its exponent.

If the order of the number isn't metric, I find the first lower metric exponent: max(exp-len(sx) for exp in metric if exp<len(sx)) (this means, find me the largest difference between an exponent of metric system and my current number order for all metric exponents that are lower than the current number order). This guarantees that all the differences will be negative, and the largest number will always point to the closest metric exponent. Return string where "main" number has more than 1 digit, how much more is determined by the difference between the closest metric exponent and current number order.

Small numbers get really complex because there are 2 cases to consider. When a number is larger than 0.0004 its representation will not be changed. However for numbers smaller than that, their representation will change to scientific.

I split the scientific representation on the letter "e" to get the actual number i.e. 1234 and the exponent. If the exponent belongs to the metric system, return the string. Otherwise get me the next smaller metric index and pad the number until it fits: mexp-exp for mexp in metric if mexp>exp (this means: for all exponents that are larger than current number order, and remember I changed current order to a positive number therefore larger metric exponent correlates to a smaller actual number, find me the minimal difference of current and metric exponent). Before I return the string I pad the right hand side of my number with enough zeroes with ljust so I don't risk raising a index out of bounds error.

For non scientifically represented small numbers, i.e. 0.000432, I split them at the ., strip the leading zeroes to get just the number 432. Length of that subtracted from length of the whole split number 000432 determines how many zeroes I have. From there I repeat the procedure for finding the next closest metric exponent, or return a string immediately.

Functions don't have a number of decimals option, but it should be trivial to add that. An if statement before returning the result and amending the return statements to something like i.e. "%s%se%s" % (sx[0:add], sx[add:ndecimals], len(sx)-add) Should take care of that problem.

I'd like to say this was fun. But it wasn't :D it was msotly clobbering through the manual until I gave up. But I said I'd get back to you so there you go.

Community
  • 1
  • 1
ljetibo
  • 3,048
  • 19
  • 25
  • nope. I want to keep the offset. But use standard units. (Kilo Mega etc so 10^3 10^6 etc) – Orestis Ioannou Mar 02 '15 at 14:35
  • well it is no duplicate. I am not trying to shut the offset but force the use of standard units. Instead of using random power or exponents such as 1e5 or 10^5 i want to format scale and offset to use 10^6 or 1e6 which is the standard unit for mega. https://en.wikipedia.org/wiki/International_System_of_Units#Prefixes – Orestis Ioannou Mar 02 '15 at 14:46
  • @OrestisIoannou Yes, I understand. I can't take the flag back but I assure you it won't get accepted. There's `set_scientific` option you can use from `ScalarFormater` but I can't get it working yet. I'll edit in a minute or two – ljetibo Mar 02 '15 at 14:46
  • @OrestisIoannou There you go. I don't like it particularly like this, but this is the only thing I could've come up with. I hope someone will post a correct usage of the Formatters from matplotlib, but I just couldn't figure it out for the life of me. – ljetibo Mar 02 '15 at 19:49
  • wowww i was certainly not expecting that detailed answer :) I ll try to understand and implement this. From a copy/paste i haven't had much success but i ll certainly dig in and try to understand how you did. – Orestis Ioannou Mar 03 '15 at 08:31
  • @OrestisIoannou What is the error report you get when you try to run my example? Which python/np/matplotlib? – ljetibo Mar 03 '15 at 08:56
  • am really sorry didn't have the time to check this out yet. one thing i noticed is that the offset disappeared. i am on python 2.7 matplotlib 1.4.3and numpy 1.9.1 – Orestis Ioannou Mar 03 '15 at 15:30
  • @OrestisIoannou Oh, I thought you said it didn't work. I misunderstood I guess, I didn't realize that you were talking about understanding the snippet and not the result of the snippet. – ljetibo Mar 03 '15 at 15:39