Rounding to significant figures in numpy

后端 未结 13 1420
无人及你
无人及你 2020-12-09 16:00

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

相关标签:
13条回答
  • 2020-12-09 16:26

    I got quite frustrated after scouring the internet and not finding an answer for this, so I wrote my own piece of code. Hope this is what you're looking for

    import numpy as np
    from numpy import ma
    
    exp = np.floor(ma.log10(abs(X)).filled(0))
    ans = np.round(X*10**-exp, sigfigs-1) * 10**exp
    

    Just plug in your np array X and the required number of significant figures. Cheers!

    0 讨论(0)
  • 2020-12-09 16:36

    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.
    
    0 讨论(0)
  • 2020-12-09 16:38

    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)
    
    0 讨论(0)
  • 2020-12-09 16:40

    There is a simple solution that uses the logic built into pythons string formatting system:

    def round_sig(f, p):
        return float(('%.' + str(p) + 'e') % f)
    

    Test with the following example:

    for f in [0.01, 0.1, 1, 10, 100, 1000, 1000]:
        f *= 1.23456789
        print('%e --> %f' % (f, round_sig(f,3)))
    

    which yields:

    1.234568e-02 --> 0.012350
    1.234568e-01 --> 0.123500
    1.234568e+00 --> 1.235000
    1.234568e+01 --> 12.350000
    1.234568e+02 --> 123.500000
    1.234568e+03 --> 1235.000000
    1.234568e+03 --> 1235.000000
    

    Best of luck!

    (If you like lambdas use:

    round_sig = lambda f,p: float(('%.' + str(p) + 'e') % f)
    

    )

    0 讨论(0)
  • 2020-12-09 16:40

    For (display-) formatting in exponential notation, numpy.format_float_scientific(x, precision = n) (where x is the number to be formatted) seems to work well. The method returns a string. (This is similar to @Autumn's answer)

    Here is an example:

    >>> x = 7.92398e+05
    >>> print(numpy.format_float_scientific(x, precision = 3))
    7.924e+05
    

    Here, the argument precision = n fixes the number of decimals in the mantissa (by rounding off). It is possible to re-convert back this to float type...and that would obviously keep only the digits present in the string. It would be converted to a positional float format though... more work would be required - so I guess the re-conversion is probably quite a bad idea for large set of numbers.

    Also, this doesn't work with iterables...look the docs up for more info.

    0 讨论(0)
  • 2020-12-09 16:40

    One more solution which works well. Doing the test from @ScottGigante, it would be second best with a timing of 1.75ms.

    import math
    
    def sig_dig(x, n_sig_dig = 5):
      num_of_digits = len(str(x).replace(".", ""))
      if n_sig_dig >= num_of_digits:
          return x
      n = math.floor(math.log10(abs(x)) + 1 - n_sig_dig)
      result = round(x * 10**(-n)) * 10**n
      return result
    

    And if it should be applied also to list/arrays you can vectorize it as

    sig_dig_vec = np.vectorize(sig_dig)
    

    Credit: answer inspired by this post

    0 讨论(0)
提交回复
热议问题