Округление до значительных цифр в numpy

Я пробовал искать это и не могу найти удовлетворительный ответ.

Я хочу взять список/массив чисел и округлить их до n значительных цифр. Я написал функцию, чтобы сделать это, но мне было интересно, есть ли стандартный метод для этого? Я искал, но не могу его найти. Пример:

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

Спасибо

Ответ 1

Сначала критика: вы считаете количество значащих цифр неправильным. В вашем примере вы хотите n = 3, а не 2.

Можно обойти большинство крайних случаев, позволяя простым библиотечным функциям обрабатывать их, если вы используете функцию, которая делает двоичную версию этого алгоритма простой: frexp. В качестве бонуса этот алгоритм также будет работать намного быстрее, потому что он никогда не вызывает функцию log.

#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.
    """
    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.isreal( x ):
        raise TypeError( "RoundToSigFigs_fp: x must be real." )

    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=sigfigs - 1 ) * 10.0**omag

И он обрабатывает все ваши дела правильно, включая бесконечное, nan, 0.0 и субнормальное число:

>>> 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])

Изменение: 2016/10/12 Я обнаружил крайний случай, что исходный код обрабатывается неправильно. Я поместил более полную версию кода в репозиторий GitHub.

Изменение: 2019/03/01 Заменить на перекодированную версию.

Ответ 2

Является numpy.set_printoptions, что вы ищете?

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

дает:

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

Edit:

numpy.around, как представляется, решает аспекты этой проблемы, если вы пытаетесь преобразовать данные. Однако он не делает то, что вы хотите, в тех случаях, когда показатель отрицательный.

Ответ 3

Из приведенных номеров примеров, я думаю, вы имеете в виду значимые цифры, а не десятичные знаки (-1.2366e22 до 0 десятичных знаков по-прежнему -1.2366e22).

Эта часть кода работает для меня, я всегда думал, что должна быть встроенная функция:

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

Ответ 4

Хорошо, так разумно безопасно сказать, что это запрещено в стандартных функциях. Чтобы закрыть это, это моя попытка надежного решения. Это довольно уродливое/непитоновое и пробное иллюстрирует лучше, чем все, почему я задал этот вопрос, поэтому, пожалуйста, не стесняйтесь исправлять или бить:)

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])
    """
    import numpy as np
    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

Ближайший я добираюсь до аккуратного не учитывает 0.0, nan, inf или complex:

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

даяние:

>>> m = [0.0, -1.2366e22, 1.2544444e-15, 0.001222]
>>> signifFig(m,2)
array([ nan, -1.24e+22,   1.25e-15,   1.22e-03])

Ответ 5

У людей довольно слабое понимание "значимых цифр", что является причиной того, что для него мало встроенных языков программирования.

0.00 - это две значимые цифры, поскольку нулевой слева от десятичной точки не является существенным, а два справа.

1.234568e-01 → 0.123500 Номер правой руки имеет 7 значащих цифр, поскольку конечные нули во фракции ARE значительны, если они показаны. Поэтому отображение неверно, так как правильная версия 0.1234568, до 4 sig fig равна 0,1235. Написание его как 0.1234500 означает, что эти последние два нуля являются реальными и точными, что в этом случае они не являются.

В некоторых случаях число сиг-фигов даже не может быть вычислено с учетом числового значения, поэтому история или источник значения должны быть известны. Например, если рН раствора указывается как 7,00, он имеет только два сиг-фига, и они ".00". 7 не имеет значения, так как рН является логарифмической функцией, а 7 - степенью десяти в базовом числе. Например, 1.0e-7, -log10 (1.00e-7) = 7.00. Исходный номер имел 2 sig fig (1.0), у -log10 есть 2 sig fig's, = ( ".00" ), а 7 - не сиг.

На практике иногда число сиг-фига неоднозначно. Если человек пишет 1234000, правила говорят, что 000 является незначительным. Но что, если они действительно значительны? Если писатель хочет сделать это ПРАВИЛЬНО, и НЕПРАВИЛЬНО, они напишут 1.234000e6 (1.234000x10 ^ 6). Эта версия имеет 7-значный рисунок.

12300     3 sig fig (but may be ambiguous to some)
1.23e4    3 sig fig (unambiguous)
1.2300e4  5 sig fig
12300.0   6 sig fig
12300.100 8 sig fig

Ответ 6

Существует простое решение, использующее логику, встроенную в систему форматирования строк pythons:

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

Протестируйте в следующем примере:

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

который дает:

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

Удачи!

(Если вам нравится использование лямбда:

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

)

Ответ 7

Мне нравится Грег очень короткая эффективная рутина выше. Тем не менее, он страдает от двух недостатков. Во-первых, это не работает для x<0, во всяком случае, для меня. (Это np.sign(x) должен быть удален.) Другое - то, что он не работает, если x является массивом. Я исправил обе эти проблемы с помощью процедуры ниже. Обратите внимание, что я изменил определение n.

def Round_n_sig_dig(x, n):

    import numpy as np               
    xr = (np.floor(np.log10(np.abs(x)))).astype(int)
    xr=10.**xr*np.around(x/10.**xr,n-1)   
    return xr    

Ответ 8

Там нет встроенного метода.

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

пример

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

Ответ 9

Большинство из приведенных здесь решений либо (а) не дают правильных значащих цифр, либо (б) излишне сложны.

Если вашей целью является форматирование отображения, то numpy.format_float_positional напрямую поддерживает желаемое поведение. Следующий фрагмент возвращает число с плавающей точкой x, отформатированное до 4 значащих цифр, с подавленной научной нотацией.

import numpy as np
x=12345.6
np.format_float_positional(x, precision=4, unique=False, fractional=False, trim='k')
> 12340.