9

I read that numpy is unbiased in rounding and that it works the way its designed. That "if you always round 0.5 up to the next largest number, then the average of a bunch rounded numbers is likely to be slightly larger than the average of the unrounded numbers: this bias or drift can have very bad effects on some numerical algorithms and make them inaccurate."

Disregarding this information and assuming that I always want to round up, how can I do it in numpy? Assuming my array can be quite large.

For simplicity, lets assume i have the array:

import numpy as np

A = [ [10, 15, 30], [25, 134, 41], [134, 413, 51]]
A = np.array(A, dtype=np.int16)

decimal = A * .1
whole = np.round(decimal)

decimal looks like:

[[  1.    1.5   3. ]
 [  2.5  13.4   4.1]
 [ 13.4  41.3   5.1]]

whole looks like:

[[  1.   2.   3.]
 [  2.  13.   4.]
 [ 13.  41.   5.]]

As you can see, 1.5 rounded to 2 and 2.5 also rounded to 2. How can I force to always get a round up answer for a XX.5? I know I can loop through the array and use python round() but that would definitely be much slower. Was wondering if there is a way to do it using numpy functions

user1179317
  • 2,693
  • 3
  • 34
  • 62

2 Answers2

3

The answer is almost never np.vectorize. You can, and should, do this in a fully vectorized manner. Let's say that for x >= 0, you want r = floor(x + 0.5). If you want negative numbers to round towards zero, the same formula applies for x < 0. So let's say that you always want to round away from zero. In that case, you are looking for ceil(x - 0.5) for x < 0.

To implement that for an entire array without calling np.vectorize, you can use masking:

def round_half_up(x):
    mask = (x >= 0)
    out = np.empty_like(x)
    out[mask] = np.floor(x[mask] + 0.5)
    out[~mask] = np.ceil(x[~mask] - 0.5)
    return out

Notice that you don't need to use a mask if you round all in one direction:

def round_up(x):
    return np.floor(x + 0.5)

Now if you want to make this really efficient, you can get rid of all the temp arrays. This will use the full power of ufuncs:

def round_half_up(x):
    out = x.copy()
    mask = (out >= 0)
    np.add(out, 0.5, where=mask, out=out)
    np.floor(out, where=mask, out=out)
    np.invert(mask, out=mask)
    np.subtract(out, 0.5, where=mask, out=out)
    np.ceil(out, where=mask, out=out)
    return out

And:

def round_up(x):
    out = x + 0.5
    np.floor(out, out=out)
    return out
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
1
import numpy as np
A = [ [1.0, 1.5, 3.0], [2.5, 13.4, 4.1], [13.4, 41.3, 5.1]]
A = np.array(A)

print(A)

def rounder(x):
    if (x-int(x) >= 0.5):
        return np.ceil(x)
    else:
        return np.floor(x)

rounder_vec = np.vectorize(rounder)
whole = rounder_vec(A)
print(whole)

Alternatively, you can also look at numpy.ceil, numpy.floor, numpy.trunc for other rounding styles

Andreas
  • 2,455
  • 10
  • 21
  • 24
  • np.ceil will always round it up. I still want 1.3 to round to 1 and 1.5 to round to 2. Maybe that wasnt clear in my question, sorry – user1179317 Nov 08 '18 at 04:22
  • If that is the case, then there is no function for that in numpy. What you could do is to loop through every element and check for round up or round down – Andreas Nov 08 '18 at 04:29
  • 1
    You really shoudn't be looping in numpy. – Mad Physicist Jun 30 '20 at 20:14
  • your answer requires caller to set a temporary rounder_vec before calling your function. it's not the request - the request would be direct call of input to output – GM1 May 12 '21 at 18:43