16

I am looking to get :

input:

arange(0.0,0.6,0.2)

output:

0.,0.4

I want

0.,0.2,0.4,0.6

how do i achieve using range or arange. If not what is alternate ?

cs95
  • 330,695
  • 80
  • 606
  • 657
WPFKK
  • 1,289
  • 3
  • 18
  • 40
  • 4
    Have you seen https://stackoverflow.com/questions/477486/how-to-use-a-decimal-range-step-value?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa? – Mazdak May 11 '18 at 19:46
  • 4
    `linspace` gives better end point control. – hpaulj May 11 '18 at 21:24
  • But in [`linspace`](https://numpy.org/doc/stable/reference/generated/numpy.linspace.html) the step size cannot be passed directly (only returned). – Friedrich Dec 04 '20 at 09:41

6 Answers6

9

In short

I wrote a function crange, which does what you require. In the example below, orange does the job of numpy.arange

crange(1, 1.3, 0.1) >>> [1.  1.1 1.2 1.3]
orange(1, 1.3, 0.1) >>> [1.  1.1 1.2]
crange(0.0, 0.6, 0.2) >>> [0.  0.2 0.4 0.6]
orange(0.0, 0.6, 0.2) >>> [0.  0.2 0.4]

Background information

I had your problem a view times as well. I usually quick-fixed it with adding a small value to stop. As mentioned by Kasrâmvd in the comments, the issue is a bit more complex, as floating point rounding errors can occur in numpy.arange (see here and here).

Unexpected behavior can be found in this example:

>>> numpy.arange(1, 1.3, 0.1)
array([1. , 1.1, 1.2, 1.3])

To clear up things a bit for myself, I decided to stop using numpy.arange if not needed specifically. I instead use my self defined function orange to avoid unexpected behavior. This combines numpy.isclose and numpy.linspace.

Here is the Code

Enough bla bla - here is the code ^^

import numpy as np

def cust_range(*args, rtol=1e-05, atol=1e-08, include=[True, False]):
    """
    Combines numpy.arange and numpy.isclose to mimic
    open, half-open and closed intervals.
    Avoids also floating point rounding errors as with
    >>> numpy.arange(1, 1.3, 0.1)
    array([1. , 1.1, 1.2, 1.3])

    args: [start, ]stop, [step, ]
        as in numpy.arange
    rtol, atol: floats
        floating point tolerance as in numpy.isclose
    include: boolean list-like, length 2
        if start and end point are included
    """
    # process arguments
    if len(args) == 1:
        start = 0
        stop = args[0]
        step = 1
    elif len(args) == 2:
        start, stop = args
        step = 1
    else:
        assert len(args) == 3
        start, stop, step = tuple(args)

    # determine number of segments
    n = (stop-start)/step + 1

    # do rounding for n
    if np.isclose(n, np.round(n), rtol=rtol, atol=atol):
        n = np.round(n)

    # correct for start/end is exluded
    if not include[0]:
        n -= 1
        start += step
    if not include[1]:
        n -= 1
        stop -= step

    return np.linspace(start, stop, int(n))

def crange(*args, **kwargs):
    return cust_range(*args, **kwargs, include=[True, True])

def orange(*args, **kwargs):
    return cust_range(*args, **kwargs, include=[True, False])

print('crange(1, 1.3, 0.1) >>>', crange(1, 1.3, 0.1))
print('orange(1, 1.3, 0.1) >>>', orange(1, 1.3, 0.1))
print('crange(0.0, 0.6, 0.2) >>>', crange(0.0, 0.6, 0.2))
print('orange(0.0, 0.6, 0.2) >>>', orange(0.0, 0.6, 0.2))
Markus Dutschke
  • 7,177
  • 2
  • 46
  • 45
7

A simpler approach to get the desired output is to add the step size in the upper limit. For instance,

np.arange(start, end + step, step)

would allow you to include the end point as well. In your case:

np.arange(0.0, 0.6 + 0.2, 0.2)

would result in

array([0. , 0.2, 0.4, 0.6]).
abm17
  • 71
  • 1
  • 1
3

Interesting that you get that output. Running arange(0.0,0.6,0.2) I get:

array([0. , 0.2, 0.4])

Regardless, from the numpy.arange docs: Values are generated within the half-open interval [start, stop) (in other words, the interval including start but excluding stop).

Also from the docs: When using a non-integer step, such as 0.1, the results will often not be consistent. It is better to use numpy.linspace for these cases

The only thing I can suggest to achieve what you want is to modify the stop parameter and add a very small amount, for example

np.arange(0.0, 0.6 + 0.001 ,0.2)

Returns

array([0. , 0.2, 0.4, 0.6])

Which is your desired output.

Anyway, it is better to use numpy.linspace and set endpoint=True

Yuca
  • 5,558
  • 3
  • 21
  • 39
  • 1
    It seems hacky to have to change the middle parameter instead of passing it through a function normally. – chevybow May 11 '18 at 19:50
  • 2
    Well, I tried to tailor my answer to his current implementation instead of the classic 'why don't you use XXX?'. As far as I'm aware, np.arange does not have a parameter to easily include the endpoint. Even more, for floating point steps it's usually better to use linspace – Yuca May 11 '18 at 20:08
  • Sure. I only added the comment because it seems like OP wanted everything done in one function. If we were to pass values through to arrange we would have to add logic to check if its an endpoint or not (and if it is- increment it by a small enough value such that it gets included). I think this answer technically works- but I'm curious if there's anything better. – chevybow May 11 '18 at 20:12
  • 1
    @chevybow I'm pretty sure linspace is the way to go here :) – Yuca May 24 '18 at 12:01
  • 1
    `linspace` doesn't allow you to directly control the step size... But I I love how the Docs explain the bug and now pass the torch to every programmer to work a solution instead of fixing this directly. – nimig18 Feb 06 '21 at 20:21
3

Old question, but it can be done much easier.

def arange(start, stop, step=1, endpoint=True):
    arr = np.arange(start, stop, step)

    if endpoint and arr[-1]+step==stop:
        arr = np.concatenate([arr,[end]])

    return arr


print(arange(0, 4, 0.5, endpoint=True))
print(arange(0, 4, 0.5, endpoint=False))

which gives

[0.  0.5 1.  1.5 2.  2.5 3.  3.5 4. ]
[0.  0.5 1.  1.5 2.  2.5 3.  3.5]
Mehdi
  • 634
  • 8
  • 7
  • Hi, i didn’t know about endpoint but I don’t think this worked for me. inputs = [1.0, 48.0, 5.0] returned. [ 1. 6. 11. 16. 21. 26. 31. 36. 41. 46.] – Windy71 Sep 13 '21 at 17:24
  • 1
    It shouldn't. It returns the end point if the last point + step = stop. Your end, 48, does not fulfill the step requirement, so it's not included (48-46=2). If it was 51, it was returned. – Mehdi Sep 29 '21 at 20:18
  • This is the only simple solution that doesn't have problems with the end-point. However what is [end] doing there? where was it defined? – Caterina Mar 25 '22 at 00:08
  • I suppose you should define it as arr[-1]+step? Am I right? I would also change arr[-1]+step==stop to arr[-1]+step<=stop, because of the .__999999 decimals – Caterina Mar 25 '22 at 00:15
3

A simple example using np.linspace (mentioned numerous times in other answers, but no simple examples were present):

import numpy as np

start = 0.0
stop = 0.6
step = 0.2

num = round((stop - start) / step) + 1   # i.e. length of resulting array
np.linspace(start, stop, num)

>>> array([0.0, 0.2, 0.4, 0.6])

Assumption: stop is a multiple of step. round is necessary to correct for floating point error.

David Parks
  • 28,443
  • 44
  • 166
  • 295
  • What if it doesn't start at 0? – Caterina Mar 24 '22 at 22:50
  • 1
    Oops, you're right @Caterina, that was a bug. I updated it to compute `num` correctly when `start` is non-zero. Thanks for catching that. – David Parks Mar 24 '22 at 22:55
  • Just in case this doesn't seem to work for integers: np.linspace(4, 9, 3, dtype=int) doesn't give the expected 4, 6, 8 output :( it gives 4, 6, 9. I was looking for a solution that worked for both floats and integers – Caterina Mar 24 '22 at 23:24
  • For this to work I did have to enforce that `stop` is a multiple of `step`. In your example `start=4` and `stop=9` with a difference of 5, which isn't an even multiple of `stop`. In this case it gets more complicated. I realized that, but avoided trying to create a more complex answer because it deviated too much from the OPs original question. I think you can find a solution, but this answer won't work copy/paste for that problem. It would work for integers with even multiples. – David Parks Mar 24 '22 at 23:47
0

Ok I will leave this solution, here. First step is to calculate the fractional portion of number of items given the bounds [a,b] and the step amount. Next calculate an appropriate amount to add to the end that will not effect the size of the result numpy array and then call the np.arrange().

import numpy as np

def np_arange_fix(a, b, step):
    nf = (lambda n: n-int(n))((b - a)/step+1)
    bb = (lambda x: step*max(0.1, x) if x < 0.5 else 0)(nf)
    arr = np.arange(a, b+bb, step)

    if int((b-a)/step+1) != len(arr):
        print('I failed, expected {} items, got {} items, arr-out{}'.format(int((b-a)/step), len(arr), arr))
        raise

    return arr


print(np_arange_fix(1.0, 4.4999999999999999, 1.0))
print(np_arange_fix(1.0, 4 + 1/3, 1/3))
print(np_arange_fix(1.0, 4 + 1/3, 1/3 + 0.1))
print(np_arange_fix(1.0, 6.0, 1.0))
print(np_arange_fix(0.1, 6.1, 1.0))

Prints:

[1. 2. 3. 4.]
[1.         1.33333333 1.66666667 2.         2.33333333 2.66666667
 3.         3.33333333 3.66666667 4.         4.33333333]
[1.         1.43333333 1.86666667 2.3        2.73333333 3.16666667
 3.6        4.03333333]
[1. 2. 3. 4. 5. 6.]
[0.1 1.1 2.1 3.1 4.1 5.1 6.1]

If you want to compact this down to a function:

def np_arange_fix(a, b, step):
    b += (lambda x: step*max(0.1, x) if x < 0.5 else 0)((lambda n: n-int(n))((b - a)/step+1))
    return np.arange(a, b, step)
nimig18
  • 667
  • 7
  • 9