3

numpy.trunc is a floor function based on abs value:

a = np.array([-1.7, -1.5, -0.2, 0.2, 1.5, 1.7, 2.0])
np.floor(a)
Out[122]: array([-2., -2., -1.,  0.,  1.,  1.,  2.])
np.trunc(a)
Out[123]: array([-1., -1., -0.,  0.,  1.,  1.,  2.])

The ceil output is this:

np.ceil(a)
Out[124]: array([-1., -1., -0.,  1.,  2.,  2.,  2.])

But I want an output: array([-2., -2., -1., 1., 2., 2., 2.]) What is the numpy function for this?

edit: Unfortunately there is no build-in round away from zero function. Based on @Mitch and @Divakar answers I did some test and try to optimize the speed and memory usage.

def rawzero1(a): #@Mitch
    return np.sign(a)*np.ceil(np.abs(a))


def rawzero2(a): #@Divakar
    return np.where(a<0, np.floor(a), np.ceil(a))


def rawzero3(a):
    _a = np.abs(a)
    np.ceil(_a, _a) # inplace
    np.copysign(_a, a, out = _a)
    return _a


def rawzero4(a):
    _a = np.ceil(np.abs(a))
    np.copysign(_a, a, out = _a)
    return _a

array size: 762.94 MB

| func     |   t per call (ms) |   max mem use (MB) |   mem to array size (MB) |
|:---------|------------------:|-------------------:|-------------------------:|
| rawzero1 |           1071.34 |            3082.51 |                     4.04 |
| rawzero2 |           1237.74 |            3183.39 |                     4.17 |
| rawzero3 |            543.71 |            1576.41 |                     2.07 |
| rawzero4 |            583.83 |            2328.77 |                     3.05 |

so the best is rawzero3, which use inplace operations to reduce memory usage and copysign to speed up.

update according to @Divakar which requires numexpr >=2.6.4. Any version before that does not have ceil().

rawzero1(a) == rawzero2(a)
rawzero1(a) == rawzero3(a)
rawzero1(a) == rawzero4(a)
rawzero1(a) == numexpr_1(a)
array size: 762.94 MB
| func      |   t per call (ms) |   max mem use (MB) |   mem to array size (MB) |
|:----------|------------------:|-------------------:|-------------------------:|
| rawzero1  |           1108.68 |            3828.35 |                     5.02 |
| rawzero2  |           1226.78 |            3940.69 |                     5.17 |
| rawzero3  |            531.54 |            2323.55 |                     3.05 |
| rawzero4  |            579.55 |            3082.49 |                     4.04 |
| numexpr_1 |            228.00 |            2323.57 |                     3.05 |

There is no surprise at all. numexpr version will give the best speed and same memory footprint as rawzero3. The weird part is rawzero3's memory footprint is kind of unstable. Theoretically it should only use 2x array size instead of 3x.

Wang
  • 7,250
  • 4
  • 35
  • 66
  • Note that neither trunc nor floor use the concept of absolute value. `trunc` rounds towards zero, while `floor` rounds towards negative infinity. `ceil` rounds towards positive infinity, so you're looking for something to round *away* from zero. I don't know of a builtin to do that. – Adam Smith Oct 04 '17 at 19:36
  • Added one more approach based on @Mitch's sign idea in my post that seems to be quite faster than others. Add that into your benchmarking setup for memory test as well? – Divakar Oct 05 '17 at 11:33
  • @Divakar Which version of numexpr are you using? I were trying to use numexpr earlier, but it does not support ceil function. I am very surprising that you can make it work. I am using 2.6.2 – Wang Oct 05 '17 at 12:14
  • @Wang Yeah `ceil` was added recently. I was needed to upgrade it. Can you upgrade yours? On my linux, I did : `sudo pip install numexpr --upgrade`. – Divakar Oct 05 '17 at 12:52
  • @Wang I am on `2.6.4`. – Divakar Oct 05 '17 at 14:45
  • Any update on the upgrade? – Divakar Oct 13 '17 at 13:55

3 Answers3

7

Approach #1 : Use np.where to do the choosing between floor and ceil based on the positivity/negativity -

np.where(a<0, np.floor(a), np.ceil(a))

Sample run -

In [61]: a
Out[61]: array([-1.7, -1.5, -0.2,  0.2,  1.5,  1.7,  2. ])

In [62]: np.where(a<0, np.floor(a), np.ceil(a))
Out[62]: array([-2., -2., -1.,  1.,  2.,  2.,  2.])

Approach #2 : We could extend the sign trick used in @Mitch's post by using a comparison against zero and scaling to get the sign equivalent and also leverages numexpr module to perform the ceil on abs values. Now, we should keep in mind that numexpr performs better with large datasets. Thus, the implementation would be -

import numexpr as ne

ne.evaluate('(2*(a>0)-1)*ceil(abs(a))')

Runtime test

All approaches -

def rawzero1(a):
    return np.sign(a)*np.ceil(np.abs(a))

def rawzero2(a):
    return np.where(a<0, np.floor(a), np.ceil(a))

def rawzero3(a):
    _a = np.abs(a)
    np.ceil(_a, _a) # inplace
    np.copysign(_a, a, out = _a)
    return _a

def rawzero4(a):
    _a = np.ceil(np.abs(a))
    np.copysign(_a, a, out = _a)
    return _a

def numexpr_1(a):
    return ne.evaluate('(2*(a>0)-1)*ceil(abs(a))')

Timings -

In [52]: a = np.random.randn(1000000)

In [53]: %timeit rawzero1(a)
    ...: %timeit rawzero2(a)
    ...: %timeit rawzero3(a)
    ...: %timeit rawzero4(a)
    ...: %timeit numexpr_1(a)
    ...: 
100 loops, best of 3: 11.6 ms per loop
100 loops, best of 3: 13.2 ms per loop
100 loops, best of 3: 4.9 ms per loop
100 loops, best of 3: 6.54 ms per loop
1000 loops, best of 3: 1.65 ms per loop

In [54]: np.allclose(numexpr_1(a), rawzero1(a))
Out[54]: True
Divakar
  • 218,885
  • 19
  • 262
  • 358
7

Another option (though not a built-in) could be taking the ceiling of the absolute values.

np.sign(a) * np.ceil(np.abs(a))
miradulo
  • 28,857
  • 6
  • 80
  • 93
0

You can use this syntax [ np.ceil(x) if x>0 else np.floor(x) for x in lst) ]

to do it manually. Not sure about the function.

Like this post One-line list comprehension: if-else variants

arisAbCy
  • 71
  • 7