24

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 ?

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
WPFKK
  • 1,419
  • 3
  • 20
  • 42

7 Answers7

13

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
  • 131
  • 1
  • 2
11

Update 2023-04-21

Had a bug in the code for stop - start being a non-integer number of steps => fixed

In short / TLDR

unexpected behavior:

>>> np.arange(1, 1.3, .1)  # UNEXPECTED
array([1. , 1.1, 1.2, 1.3])

fix:

>>> from arange_cust import *
>>> np_arange_closed(1, 1.3, .1)
array([1. , 1.1, 1.2, 1.3])
>>> np_arange_open(1, 1.3, .1)
array([1. , 1.1, 1.2])

Background information

I had your problem a few 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:

>>> np.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 be very careful with np.arange.

Code

arange_cust.py:

import numpy as np

def np_arange_cust(
        *args, rtol: float=1e-05, atol: float=1e-08, include_start: bool=True, include_stop: bool = False, **kwargs
):
    """
    Combines numpy.arange and numpy.isclose to mimic open, half-open and closed intervals.

    Avoids also floating point rounding errors as with
    >>> np.arange(1, 1.3, 0.1)
    array([1., 1.1, 1.2, 1.3])

    Parameters
    ----------
    *args : float
        passed to np.arange
    rtol : float
        if last element of array is within this relative tolerance to stop and include[0]==False, it is skipped
    atol : float
        if last element of array is within this relative tolerance to stop and include[1]==False, it is skipped
    include_start: bool
        if first element is included in the returned array
    include_stop: bool
        if last elements are included in the returned array if stop equals last element
    kwargs :
        passed to np.arange

    Returns
    -------
    np.ndarray :
        as np.arange but eventually with first and last element stripped/added
    """
    # 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)

    arr = np.arange(start, stop, step, **kwargs)
    if not include_start:
        arr = np.delete(arr, 0)

    if include_stop:
        if np.isclose(arr[-1] + step, stop, rtol=rtol, atol=atol):
            arr = np.c_[arr, arr[-1] + step]
    else:
        if np.isclose(arr[-1], stop, rtol=rtol, atol=atol):
            arr = np.delete(arr, -1)
    return arr

def np_arange_closed(*args, **kwargs):
    return np_arange_cust(*args, **kwargs, include_start=True, include_stop=True)

def np_arange_open(*args, **kwargs):
    return np_arange_cust(*args, **kwargs, include_start=True, include_stop=False)

Pytests

To avoid bugs in future, here is a testing module. In case we find something again, lets add a testcase. test_arange_cust.py:

import numpy as np
from arange_cust import np_arange_cust, np_arange_closed, np_arange_open
import pytest

class Test_np_arange_cust:
    paras_minimal_working_example = {
        "arange simple": {
            "start": 0, "stop": 7, "step": 1, "include_start": True, "include_stop": False,
            "res_exp": np.array([0, 1, 2, 3, 4, 5, 6])
        },
        "stop not on grid": {
            "start": 0, "stop": 6.5, "step": 1, "include_start": True, "include_stop": False,
            "res_exp": np.array([0, 1, 2, 3, 4, 5, 6])
        },
        "arange failing example: stop excl": {
            "start": 1, "stop": 1.3, "step": .1, "include_start": True, "include_stop": False,
            "res_exp": np.array([1., 1.1, 1.2])
        },
        "arange failing example: stop incl": {
            "start": 1, "stop": 1.3, "step": .1, "include_start": True, "include_stop": True,
            "res_exp": np.array([1., 1.1, 1.2, 1.3])
        },
        "arange failing example: stop excl + start excl": {
            "start": 1, "stop": 1.3, "step": .1, "include_start": False, "include_stop": False,
            "res_exp": np.array([1.1, 1.2])
        },
        "arange failing example: stop incl + start excl": {
            "start": 1, "stop": 1.3, "step": .1, "include_start": False, "include_stop": True,
            "res_exp": np.array([1.1, 1.2, 1.3])
        },

    }

    @pytest.mark.parametrize(
        argnames=next(iter(paras_minimal_working_example.values())).keys(),
        argvalues=[tuple(paras.values()) for paras in paras_minimal_working_example.values()],
        ids=paras_minimal_working_example.keys(),
    )
    def test_minimal_working_example(self, start, stop, step, include_start, include_stop, res_exp):
        res = np_arange_cust(start, stop, step, include_start=include_start, include_stop=include_stop)
        assert np.allclose(res, res_exp), f"Unexpected result: {res=}, {res_exp=}"
Markus Dutschke
  • 9,341
  • 4
  • 63
  • 58
  • This is very misleading, in case the length of the range is not divisible by `step`, the results will not be distanced by `step`. It is a significantly different behavior from arange, I don’t see how this can substitute it. – Gianmarco Apr 21 '23 at 14:31
  • @Gianmarco I am so sorry!!! Thanks for the feedback -> **fixed** and testing module added to prevent situations like this – Markus Dutschke Apr 21 '23 at 16:13
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
  • 6,010
  • 3
  • 22
  • 42
  • 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
  • 2
    `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
  • 999
  • 13
  • 11
  • 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
  • This doesn't work consistently due to rounding issues. E.g. `arange(1, 1.7, 0.1, endpoint=True)` returns `array([1. , 1.1, 1.2, 1.3, 1.4, 1.5, 1.6])` – kadee Aug 15 '23 at 13:53
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
  • 30,789
  • 47
  • 185
  • 328
  • 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
  • 797
  • 7
  • 10
0

When it's easier to type the end value than the step, I do:

np.arange(0, 100e3+1)/100e3
Abhishek Divekar
  • 1,131
  • 2
  • 15
  • 31