3

I wish to format numbers when passing to a plot annotation so they are 'pretty printed' with "k" for thousand, etc. and precision rounding, for example for 3 digit precision:

13.1    -> 13.1
13.1676 -> 13.2
1246    -> 1.25k
560254  -> 560k
6.234e7 -> 62.3M

I have written a function to do this, but it seems overly complicated:

import math


def num_fmt(num):
    i_offset = 15 # change this if you extend the symbols!!!
    prec = 3
    fmt = '.{p}g'.format(p=prec)
    symbols = ['Y', 'T', 'G', 'M', 'k', '', 'm', 'u', 'n']

    e = math.log10(abs(num))
    if e >= i_offset + 3:
        return '{:{fmt}}'.format(num, fmt=fmt)
    for i, sym in enumerate(symbols):
        e_thresh = i_offset - 3 * i
        if e >= e_thresh:
            return '{:{fmt}}{sym}'.format(num/10.**e_thresh, fmt=fmt, sym=sym)
    return '{:{fmt}}'.format(num, fmt=fmt)
Calvin
  • 381
  • 3
  • 7

1 Answers1

1

Something like this might work:

from bisect import bisect

# A mapping of breakpoints for suffixes.
suffixes = {
    1e-9: 'n',
    1e-6: 'u',
    1e-3: 'm',
    1: '',
    1e3: 'k',
    1e6: 'M',
    1e9: 'G',
    1e12: 'T',
    1e15: 'Y',
}

# List of sorted breakpoints' values.
suffix_breakpoints = sorted(suffixes.iterkeys())

def format_with_suffix(num):
    num_format = '{:.2f}{}'
    if not num:
       return num_format.format(num, '')

    if num in suffixes:
       return num_format.format(1.0, suffixes[num])

    # Find the index of first breakpoint `x`, that's greater than `num`
    # using binary search.
    breakpoint_idx = bisect(suffix_breakpoints, num)

    # Get the breakpoint's value. If `num` is lower than the first breakpoint, use
    # that instead.
    breakpoint = suffix_breakpoints[breakpoint_idx - 1 if breakpoint_idx else 0]

    # Get the suffix.
    suffix = suffixes[breakpoint]
    return num_format.format(float(num) / breakpoint, suffix)

>>> for x in xrange(-10, 20):
>>>     print format_with_suffix(10.0 ** x), 10.0 ** x
0.10n 1e-10
1.00n 1e-09
10.00n 1e-08
100.00n 1e-07
1.00u 1e-06
10.00u 1e-05
100.00u 0.0001
1.00m 0.001
10.00m 0.01
100.00m 0.1
1.00 1.0
10.00 10.0
100.00 100.0
1.00k 1000.0
10.00k 10000.0
100.00k 100000.0
1.00M 1000000.0
10.00M 10000000.0
100.00M 100000000.0
1.00G 1000000000.0
10.00G 10000000000.0
100.00G 1e+11
1.00T 1e+12
10.00T 1e+13
100.00T 1e+14
1.00Y 1e+15
10.00Y 1e+16
100.00Y 1e+17
1000.00Y 1e+18
10000.00Y 1e+19
David Folkner
  • 1,171
  • 1
  • 13
  • 27
Maciej Gol
  • 15,394
  • 4
  • 33
  • 51