Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rounding to significant figures in numpy

Tags:

python

numpy

I've tried searching this and can't find a satisfactory answer.

I want to take a list/array of numbers and round them all to n significant figures. I have written a function to do this, but I was wondering if there is a standard method for this? I've searched but can't find it. Example:

In:  [  0.0, -1.2366e22, 1.2544444e-15, 0.001222 ], n=2
Out: [ 0.00, -1.24e22,        1.25e-15,  1.22e-3 ]

Thanks

like image 959
dmon Avatar asked Sep 20 '13 11:09

dmon


People also ask

How do you round a number to significant figures in Python?

Use the round() Function to Round a Number to the Given Significant Digit in Python. The round() function rounds the given floating values to the nearest integer or specified decimal position. We can use a negative number to round the integer to the significant floor value by assigning it to the round() function.

How do you print to 3 sig figs in Python?

To print a number with a specific number of significant digits we do this: print '{0:1.3g}'. format(1./3.) print '{0:1.3g}'.


4 Answers

First a criticism: you're counting the number of significant figures wrong. In your example you want n=3, not 2.

It is possible to get around most of the edge cases by letting numpy library functions handle them if you use the function that makes the binary version of this algorithm simple: frexp. As a bonus, this algorithm will also run much faster because it never calls the log function.

#The following constant was computed in maxima 5.35.1 using 64 bigfloat digits of precision __logBase10of2 = 3.010299956639811952137388947244930267681898814621085413104274611e-1  import numpy as np  def RoundToSigFigs_fp( x, sigfigs ): """ Rounds the value(s) in x to the number of significant figures in sigfigs. Return value has the same type as x.  Restrictions: sigfigs must be an integer type and store a positive value. x must be a real value or an array like object containing only real values. """ if not ( type(sigfigs) is int or type(sigfigs) is long or          isinstance(sigfigs, np.integer) ):     raise TypeError( "RoundToSigFigs_fp: sigfigs must be an integer." )  if sigfigs <= 0:     raise ValueError( "RoundToSigFigs_fp: sigfigs must be positive." )  if not np.all(np.isreal( x )):     raise TypeError( "RoundToSigFigs_fp: all x must be real." )  #temporarily suppres floating point errors errhanddict = np.geterr() np.seterr(all="ignore")  matrixflag = False if isinstance(x, np.matrix): #Convert matrices to arrays     matrixflag = True     x = np.asarray(x)  xsgn = np.sign(x) absx = xsgn * x mantissas, binaryExponents = np.frexp( absx )  decimalExponents = __logBase10of2 * binaryExponents omags = np.floor(decimalExponents)  mantissas *= 10.0**(decimalExponents - omags)  if type(mantissas) is float or isinstance(mantissas, np.floating):     if mantissas < 1.0:         mantissas *= 10.0         omags -= 1.0          else: #elif np.all(np.isreal( mantissas )):     fixmsk = mantissas < 1.0,      mantissas[fixmsk] *= 10.0     omags[fixmsk] -= 1.0  result = xsgn * np.around( mantissas, decimals=sigfigs - 1 ) * 10.0**omags if matrixflag:     result = np.matrix(result, copy=False)  np.seterr(**errhanddict) return result 

And it handles all of your cases correctly, including infinite, nan, 0.0, and a subnormal number:

>>> eglist = [  0.0, -1.2366e22, 1.2544444e-15, 0.001222, 0.0,  ...        float("nan"), float("inf"), float.fromhex("0x4.23p-1028"),  ...        0.5555, 1.5444, 1.72340, 1.256e-15, 10.555555  ] >>> eglist [0.0, -1.2366e+22, 1.2544444e-15, 0.001222, 0.0,  nan, inf, 1.438203867284623e-309,  0.5555, 1.5444, 1.7234, 1.256e-15, 10.555555] >>> RoundToSigFigs(eglist, 3) array([  0.00000000e+000,  -1.24000000e+022,   1.25000000e-015,          1.22000000e-003,   0.00000000e+000,               nan,                      inf,   1.44000000e-309,   5.56000000e-001,          1.54000000e+000,   1.72000000e+000,   1.26000000e-015,          1.06000000e+001]) >>> RoundToSigFigs(eglist, 1) array([  0.00000000e+000,  -1.00000000e+022,   1.00000000e-015,          1.00000000e-003,   0.00000000e+000,               nan,                      inf,   1.00000000e-309,   6.00000000e-001,          2.00000000e+000,   2.00000000e+000,   1.00000000e-015,          1.00000000e+001]) 

Edit: 2016/10/12 I found an edge case that the original code handled wrong. I have placed a fuller version of the code in a GitHub repository.

Edit: 2019/03/01 Replace with recoded version.

Edit: 2020/11/19 Replace with vectorized version from Github that handles arrays. Note that preserving input data types, where possible, was also a goal of this code.

like image 78
Sean Lake Avatar answered Sep 30 '22 13:09

Sean Lake


Testing all of the already proposed solutions, I find they either

  1. convert to and from strings, which is inefficient
  2. can't handle negative numbers
  3. can't handle arrays
  4. have some numerical errors.

Here's my attempt at a solution which should handle all of these things. (Edit 2020-03-18: added np.asarray as suggested by A. West.)

def signif(x, p):     x = np.asarray(x)     x_positive = np.where(np.isfinite(x) & (x != 0), np.abs(x), 10**(p-1))     mags = 10 ** (p - 1 - np.floor(np.log10(x_positive)))     return np.round(x * mags) / mags 

Testing:

def scottgigante(x, p):     x_positive = np.where(np.isfinite(x) & (x != 0), np.abs(x), 10**(p-1))     mags = 10 ** (p - 1 - np.floor(np.log10(x_positive)))     return np.round(x * mags) / mags  def awest(x,p):     return float(f'%.{p-1}e'%x)  def denizb(x,p):     return float(('%.' + str(p-1) + 'e') % x)  def autumn(x, p):     return np.format_float_positional(x, precision=p, unique=False, fractional=False, trim='k')  def greg(x, p):     return round(x, -int(np.floor(np.sign(x) * np.log10(abs(x)))) + p-1)  def user11336338(x, p):              xr = (np.floor(np.log10(np.abs(x)))).astype(int)     xr=10.**xr*np.around(x/10.**xr,p-1)        return xr  def dmon(x, p):     if np.all(np.isfinite(x)):         eset = np.seterr(all='ignore')         mags = 10.0**np.floor(np.log10(np.abs(x)))  # omag's         x = np.around(x/mags,p-1)*mags             # round(val/omag)*omag         np.seterr(**eset)         x = np.where(np.isnan(x), 0.0, x)           # 0.0 -> nan -> 0.0     return x  def seanlake(x, p):     __logBase10of2 = 3.010299956639811952137388947244930267681898814621085413104274611e-1     xsgn = np.sign(x)     absx = xsgn * x     mantissa, binaryExponent = np.frexp( absx )      decimalExponent = __logBase10of2 * binaryExponent     omag = np.floor(decimalExponent)      mantissa *= 10.0**(decimalExponent - omag)      if mantissa < 1.0:         mantissa *= 10.0         omag -= 1.0      return xsgn * np.around( mantissa, decimals=p - 1 ) * 10.0**omag  solns = [scottgigante, awest, denizb, autumn, greg, user11336338, dmon, seanlake]  xs = [     1.114, # positive, round down     1.115, # positive, round up     -1.114, # negative     1.114e-30, # extremely small     1.114e30, # extremely large     0, # zero     float('inf'), # infinite     [1.114, 1.115e-30], # array input ] p = 3  print('input:', xs) for soln in solns:     print(f'{soln.__name__}', end=': ')     for x in xs:         try:             print(soln(x, p), end=', ')         except Exception as e:             print(type(e).__name__, end=', ')     print() 

Results:

input: [1.114, 1.115, -1.114, 1.114e-30, 1.114e+30, 0, inf, [1.114, 1.115e-30]] scottgigante: 1.11, 1.12, -1.11, 1.11e-30, 1.11e+30, 0.0, inf, [1.11e+00 1.12e-30],  awest: 1.11, 1.11, -1.11, 1.11e-30, 1.11e+30, 0.0, inf, TypeError,  denizb: 1.11, 1.11, -1.11, 1.11e-30, 1.11e+30, 0.0, inf, TypeError,  autumn: 1.11, 1.11, -1.11, 0.00000000000000000000000000000111, 1110000000000000000000000000000., 0.00, inf, TypeError,  greg: 1.11, 1.11, -1.114, 1.11e-30, 1.11e+30, ValueError, OverflowError, TypeError,  user11336338: 1.11, 1.12, -1.11, 1.1100000000000002e-30, 1.1100000000000001e+30, nan, nan, [1.11e+00 1.12e-30],  dmon: 1.11, 1.12, -1.11, 1.1100000000000002e-30, 1.1100000000000001e+30, 0.0, inf, [1.11e+00 1.12e-30],  seanlake: 1.11, 1.12, -1.11, 1.1100000000000002e-30, 1.1100000000000001e+30, 0.0, inf, ValueError,  

Timing:

def test_soln(soln):     try:         soln(np.linspace(1, 100, 1000), 3)     except Exception:         [soln(x, 3) for x in np.linspace(1, 100, 1000)]  for soln in solns:     print(soln.__name__)     %timeit test_soln(soln) 

Results:

scottgigante 135 µs ± 15.3 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each) awest 2.23 ms ± 430 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) denizb 2.18 ms ± 352 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) autumn 2.92 ms ± 206 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) greg 14.1 ms ± 1.21 ms per loop (mean ± std. dev. of 7 runs, 100 loops each) user11336338 157 µs ± 50.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) dmon 142 µs ± 8.52 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each) seanlake 20.7 ms ± 994 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) 
like image 32
Scott Gigante Avatar answered Sep 30 '22 14:09

Scott Gigante


Most of the solutions given here either (a) don't give correct significant figures, or (b) are unnecessarily complex.

If your goal is display formatting, then numpy.format_float_positional supports the desired behaviour directly. The following fragment returns the float x formatted to 4 significant figures, with scientific notation suppressed.

import numpy as np
x=12345.6
np.format_float_positional(x, precision=4, unique=False, fractional=False, trim='k')
> 12340.
like image 21
Autumn Avatar answered Sep 30 '22 13:09

Autumn


Is numpy.set_printoptions what you're looking for?

import numpy as np
np.set_printoptions(precision=2)
print np.array([  0.0, -1.2366e22, 1.2544444e-15, 0.001222 ])

Gives:

[  0.00e+00  -1.24e+22   1.25e-15   1.22e-03]

Edit:

numpy.around appears to solve aspects of this problem if you're trying to transform the data. However, it doesn't do what you want in cases where the exponent is negative.

like image 41
Andrew Walker Avatar answered Sep 30 '22 13:09

Andrew Walker