0

I would like to "round" (not exact a mathematical rounding) the elements of a numpy array in the following way:

Given a numpy NxN or NxM 2D array with digit between 0.00001 to 9.99999 like

 a=np.array([[1.232, 1.872,2.732,0.123],
             [0.0019, 0.025, 1.854, 0.00017],
             [1.457, 0.0021, 2.34 , 9.99],
             [1.527, 3.3, 0.012 , 0.005]]
    )

I would like basically to "round" this numpy array by selecting the first non-zero digit (irregardless of the digit that follows the first non-zero digit) of each element giving the output:

output =np.array([[1.0, 1.0, 2.0, 0.1],
                 [0.001, 0.02, 1.0, 0.0001],
                 [1.0, 0.002, 2 , 9.0],
                 [1, 3, 0.01 , 0.005]]
        )

thanks for any help!

3
  • Use numpy.around Commented Oct 23, 2019 at 21:31
  • in numpy around does not work properly because the when I set the values numpy.around(a, decimals=0) the numbers 0.0019 , 0.025 , 0.00017 etc they become all 0 instead of 0.001, 0.02, 0.0001 Commented Oct 23, 2019 at 21:38
  • Possible duplicate of Rounding to significant figures in numpy Commented Oct 23, 2019 at 22:34

3 Answers 3

1

You could use np.logspace and np.seachsorted to determine the order of magnitude of each element and then floor divide and multiply back

po10 = np.logspace(-10,10,21)
oom = po10[po10.searchsorted(a)-1]
a//oom*oom
# array([[1.e+00, 1.e+00, 2.e+00, 1.e-01],
#        [1.e-03, 2.e-02, 1.e+00, 1.e-04],
#        [1.e+00, 2.e-03, 2.e+00, 9.e+00],
#        [1.e+00, 3.e+00, 1.e-02, 5.e-03]])
Sign up to request clarification or add additional context in comments.

2 Comments

Would this be faster / numerically more stable than just computing the logarithm?
@norok2 Depends on problem size. The logspace has to be computed only once and since it has only a handful of entries the searchsorted is certainly faster than taking the log and power. You could even push it further and instead of the logspace 10^-10, 10^-9, 10^-8, use all possible outcomes ,i.e. xpo10 = 1 x 10^-10, 2 x 10^-10, 3 x 10^-10, ... 9 x 10^-10, 1 x 10^-9, 2 x 10^-9, etc. That is still a very manageable number of terms and we wouldn't have to do any arithmetic anymore just xpo10[xpo10.searchsorted(a)-1]
1

What you would want to do is to keep a fixed number of significant figures.

This functionality is not integrated into NumPy.

To get only the 1 significant figure, you could look into either @PaulPanzer or @darcamo answers (assuming that you only have positive values).

If you want something that works a specified number of significant figures, you could use something like:

def significant_figures(arr, num=1):
    # : compute the order of magnitude
    order = np.zeros_like(arr)  
    mask = arr != 0
    order[mask] = np.floor(np.log10(np.abs(arr[mask])))
    del mask  # free unused memory
    # : compute the corresponding precision
    prec = num - order - 1
    return np.round(arr * 10.0 ** prec) / 10.0 ** prec


print(significant_figures(a, 1))
# [[1.e+00 2.e+00 3.e+00 1.e-01]
#  [2.e-03 2.e-02 2.e+00 2.e-04]
#  [1.e+00 2.e-03 2.e+00 1.e+01]
#  [2.e+00 3.e+00 1.e-02 5.e-03]]

print(significant_figures(a, 2))
# [[1.2e+00 1.9e+00 2.7e+00 1.2e-01]
#  [1.9e-03 2.5e-02 1.9e+00 1.7e-04]
#  [1.5e+00 2.1e-03 2.3e+00 1.0e+01]
#  [1.5e+00 3.3e+00 1.2e-02 5.0e-03]]

EDIT

For truncated output use np.floor() instead of np.round() just before the return.

2 Comments

Small nitpick: OP says "round" but what they really do is truncate, i.e. always round down / towards zero.
@PaulPanzer Thanks for spotting this! Fixed.
0

First get the powers of 10 for each number in the array with

powers = np.floor(np.log10(a))

In your example this gives us

array([[ 0.,  0.,  0., -1.],
       [-3., -2.,  0., -4.],
       [ 0., -3.,  0.,  0.],
       [ 0.,  0., -2., -3.]])

Now, if we divide the i-th element in the array by 10**power_i we essentially move each number non-zero element in the array to the first position. Now we can simple take the floor to remove the other non-zero digits and then multiply the result by 10**power_i to get back to the original scale.

The complete solution is then only the code below

powers = np.floor(np.log10(a))
10**powers * np.floor(a/10**powers)

What about numbers greater than or equal to 10?

For this you can simply take np.floor of the original value in the array. We can do this easily with a mask. You can modify the answer as below

powers = np.floor(np.log10(a))
result = 10**powers * np.floor(a/10**powers)

mask = a >= 10
result[mask] = np.floor(a[mask])

You can also use a mask to avoid computing the powers and logarithm for numbers that will just be replaced later.

3 Comments

Thanks! works very nice and simple. Is there a way to kind of generalize this procedure with numbers bigger than 10 , let's say if it's 12.32 , 15.78 , 121.34 to get out 12 , 15 , 121?
Note that here a may not have non-positve values.
I have edited the answer with a section to make it work with numbers >= 10.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.