使用Range或numpy的Arange函数时,结束限制是包含在内的。

24
我想要得到以下内容:
输入:
arange(0.0,0.6,0.2)

输出:

0.,0.4

我想要。
0.,0.2,0.4,0.6

如何使用range或arange函数?如果不行,有什么替代方法吗?

4
linspace 可以更好地控制终点。 - hpaulj
1
但是在linspace中,步长不能直接传递(只能返回)。 - Friedrich -- Слава Україні
7个回答

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)

这将允许您同时包含终点。在您的情况下:

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

会导致

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

6
end + step 有时会多输出1个值。 - Winand
我怀疑它可能会,但我无法想出一个它确实会发生的情况。你手头有这样的例子吗? - abm17
step = 7.06957358127605; len(np.arange(0, 22050 + step, step))==3121. But 22050/step==3119.0 - Winand
start=4,stop=9,step=2也不起作用...太遗憾了:(我一直在寻找一个简单的解决方案。 - Caterina
这样会更安全:np.arange(start, end + step/2, step) - undefined

12

更新 2023-04-21

stop 的代码中存在一个错误 - start 不是整数个 step => 已修复

简而言之 / TLDR

意外行为:

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

修复:

>>> 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])

背景信息

我也遇到过你的问题几次。通常我会通过添加一个小值来快速修复它。正如评论中Kasrâmvd所提到的,问题有点更加复杂,因为在numpy.arange中可能会出现浮点舍入误差(请参见此处此处)。

在这个例子中可以发现意外行为

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

为了澄清一些事情,我决定对np.arange非常小心。

代码

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

为了避免未来出现错误,这里提供一个测试模块。如果我们再次发现问题,让我们添加一个测试用例。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=}"

这非常具有误导性,如果范围的长度不能被“step”整除,结果就不会以“step”为间距。这与arange的行为明显不同,我不认为这可以替代它。 - Gianmarco
@Gianmarco,非常抱歉!!!感谢您的反馈 -> 已修复并添加了测试模块以防止类似情况的发生。 - Markus Dutschke

3

很有趣,你得到了那个输出结果。当我运行arange(0.0,0.6,0.2)时,我得到的是:

array([0. , 0.2, 0.4])

无论如何,从 numpy.arange 文档中可以得知:生成的值在半开区间[start, stop)内(也就是包括start但不包括stop)

同样来自文档:当使用非整数步长(例如0.1)时,结果通常不一致。对于这些情况最好使用numpy.linspace

唯一能建议您做的是修改停止参数并添加一个非常小的量,例如:

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

返回

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

你期望的输出是什么。

无论如何,最好使用 numpy.linspace 并设置 endpoint=True


1
似乎需要更改中间参数而不是正常通过函数传递它,这看起来有些笨拙。 - chevybow
2
我试图根据他当前的实现来调整我的答案,而不是经典的“为什么不使用XXX?”。据我所知,np.arange没有一个参数可以轻松地包含端点。更重要的是,对于浮点步长,最好使用linspace。 - Yuca
当然。我只是添加了这个注释,因为似乎 OP 想要在一个函数中完成所有操作。如果我们要通过 arrange 传递值,我们就必须添加逻辑来检查它是否是端点(如果是,则将其增加一个足够小的值,以便包含在内)。我认为这个答案在技术上是可行的,但我很好奇是否有更好的方法。 - chevybow
1
@chevybow 我相信在这里使用linspace是正确的 :) - Yuca
2
“linspace” 不允许您直接控制步长... 但我喜欢文档如何解释这个错误,并将火炬传递给每个程序员来解决问题,而不是直接修复它。 - nimig18

3

虽然这是一个老问题,但可以更加简单地实现。

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))

这提供了

[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]

嗨,我不知道端点,但我觉得这对我没有用。输入为[1.0,48.0,5.0],返回[1.0, 6.0, 11.0, 16.0, 21.0, 26.0, 31.0, 36.0, 41.0, 46.0]。 - Windy71
1
它不应该这样做。如果最后一个点+步长=停止,则返回终点。 您的终点48不满足步长要求,因此未包括在内(48-46=2)。如果是51,则会返回。 - Mehdi
这是唯一一个不会有端点问题的简单解决方案。但是[end]是做什么的?它在哪里定义的? - Caterina
我想你应该将其定义为arr[-1]+step,对吗?我还会将arr[-1]+step==stop更改为arr[-1]+step<=stop,因为存在.__999999小数。 - Caterina
由于四舍五入问题,这个方法并不总是有效。例如,arange(1, 1.7, 0.1, endpoint=True) 返回的结果是 array([1. , 1.1, 1.2, 1.3, 1.4, 1.5, 1.6]) - kadee

3

一个使用np.linspace的简单例子(其他答案中提到了很多次,但没有简单的示例):

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])

假定:`stop` 是 `step` 的倍数。对于浮点数误差,使用 `round` 进行纠正。

如果它不从0开始怎么办? - Caterina
1
哎呀,@Caterina,你说得对,那是个漏洞。我已经更新了代码,在start不为零时正确计算num。感谢你的指出。 - David Parks
如果这个方法对整数不起作用的话,可以尝试使用np.linspace(4, 9, 3, dtype=int)。但是它并不能得到期望的4、6、8输出 :( 它会输出4、6、9。我正在寻找一个既适用于浮点数又适用于整数的解决方案。 - Caterina
为了使这个工作正常,我必须强制要求stopstep的倍数。在你的例子中,start=4stop=9之间的差为5,这不是stop的偶数倍。在这种情况下,问题变得更加复杂。我意识到了这一点,但避免尝试创建一个更复杂的答案,因为它与OP的原始问题偏离太远。我认为你可以找到一个解决方案,但这个答案对于那个问题不起作用。它只适用于具有偶数倍数的整数。 - David Parks

0

好的,我会在这里留下这个解决方案。第一步是计算给定边界[a,b]step数量的项数的小数部分。接下来计算一个适当的添加到末尾的量,不会影响结果numpy数组的大小,然后调用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))

输出:

[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]

如果你想将其压缩成一个函数:
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)

0
当输入结束值比步长更容易时,我会这样做:
np.arange(0, 100e3+1)/100e3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接