Rounding to significant figures in numpy

后端 未结 13 1419
无人及你
无人及你 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:17

    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.

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

    For Scalars

    sround = lambda x,p: float(f'%.{p-1}e'%x)
    

    Example

    >>> print( sround(123.45, 2) )
    120.0
    

    For Arrays

    Use Scott Gigante's signif(x, p) fig1 fig2

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

    Okay, so reasonably safe to say this is not allowed for in standard functionality. To close this off then, this is my attempt at a robust solution. It's rather ugly/non-pythonic and prob illustrates better then anything why I asked this question, so please feel free to correct or beat :)

    import numpy as np
    
    def round2SignifFigs(vals,n):
        """
        (list, int) -> numpy array
        (numpy array, int) -> numpy array
    
        In: a list/array of values
        Out: array of values rounded to n significant figures
    
        Does not accept: inf, nan, complex
    
        >>> m = [0.0, -1.2366e22, 1.2544444e-15, 0.001222]
        >>> round2SignifFigs(m,2)
        array([  0.00e+00,  -1.24e+22,   1.25e-15,   1.22e-03])
        """
    
        if np.all(np.isfinite(vals)) and np.all(np.isreal((vals))):
            eset = np.seterr(all='ignore')
            mags = 10.0**np.floor(np.log10(np.abs(vals)))  # omag's
            vals = np.around(vals/mags,n)*mags             # round(val/omag)*omag
            np.seterr(**eset)
            vals[np.where(np.isnan(vals))] = 0.0           # 0.0 -> nan -> 0.0
        else:
            raise IOError('Input must be real and finite')
        return vals
    

    Nearest I get to neat does not account for 0.0, nan, inf or complex:

    >>> omag      = lambda x: 10**np.floor(np.log10(np.abs(x)))
    >>> signifFig = lambda x, n: (np.around(x/omag(x),n)*omag(x))
    

    giving:

    >>> m = [0.0, -1.2366e22, 1.2544444e-15, 0.001222]
    >>> signifFig(m,2)
    array([ nan, -1.24e+22,   1.25e-15,   1.22e-03])
    
    0 讨论(0)
  • 2020-12-09 16:20

    Here is a version of Autumns answer which is vectorized so it can be applied to an array of floats not just a single float.

    x = np.array([12345.6, 12.5673])
    def sf4(x):
        x = float(np.format_float_positional(x, precision=4, unique=False, fractional=False,trim='k'))
        return x
    vec_sf4 = np.vectorize(sf4)
    
    vec_sf4(x)
    
    >>>np.array([12350., 12.57])
    
    0 讨论(0)
  • 2020-12-09 16:21

    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.

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

    From the example numbers you have I think you mean significant figures rather than decimal places (-1.2366e22 to 0 decimal places is still -1.2366e22).

    This piece of code works for me, I've always thought there should be an inbuilt function though:

    def Round_To_n(x, n):
        return round(x, -int(np.floor(np.sign(x) * np.log10(abs(x)))) + n)
    
    >>> Round_To_n(1.2544444e-15,2)
    1.25e-15
    
    >>> Round_To_n(2.128282321e3, 6)
    2130.0
    
    0 讨论(0)
提交回复
热议问题