每对numpy.array的中点

34

我有一个类似以下形式的数组:

x = np.array([ 1230.,  1230.,  1227.,  1235.,  1217.,  1153.,  1170.])

我希望能够生成另一个数组,其中的值是原始数组中每对值的平均值:

xm = np.array([ 1230.,  1228.5,  1231.,  1226.,  1185.,  1161.5])

有人知道最简单和快速的方法来完成它而不使用循环吗?

6个回答

75

更短,稍微甜一点:

(x[1:] + x[:-1]) / 2
  • 这样更快:

    >>> python -m timeit -s "import numpy; x = numpy.random.random(1000000)" "x[:-1] + numpy.diff(x)/2"
    100 loops, best of 3: 6.03 msec per loop
    
    >>> python -m timeit -s "import numpy; x = numpy.random.random(1000000)" "(x[1:] + x[:-1]) / 2"
    100 loops, best of 3: 4.07 msec per loop
    
  • 这是完全准确的:

    考虑 x[1:] + x[:-1] 中的每个元素。因此考虑第一个和第二个元素 x₀x₁

    x₀ + x₁ 被计算为完美精度然后按照IEEE标准四舍五入,如果只需要这样就可以得到正确答案了。

    (x₀ + x₁) / 2 就是该值的一半。这几乎总是可以通过将指数减一来完成,除了两种情况:

    • x₀ + x₁ 溢出。这将导致无穷大(任意符号)。这不是想要的结果,因此计算将错误

    • x₀ + x₁ 下溢。由于大小被减小,所以四舍五入将是完美的,因此计算将正确

    在所有其他情况下,计算将正确


    现在考虑 x[:-1] + numpy.diff(x) / 2。通过查看源代码,可以发现其直接计算为

    x[:-1] + (x[1:] - x[:-1]) / 2
    

    因此再次考虑x₀x₁

    x₁ - x₀对于许多值来说,会有严重的“问题”,其中包括下溢,并且在进行大量抵消时会失去精度。虽然当符号相同时,误差在加法上有效地抵消掉了,但这并不意味着这不重要,因为舍入仍然发生了。

    (x₁ - x₀) / 2不会更少舍入,但是x₀ + (x₁ - x₀) / 2涉及另一个舍入。这意味着错误将会渐渐进入。证明:

    import numpy
    
    wins = draws = losses = 0
    
    for _ in range(100000):
        a = numpy.random.random()
        b = numpy.random.random() / 0.146
    
        x = (a+b)/2 
        y = a + (b-a)/2
    
        error_mine   = (a-x) - (x-b)
        error_theirs = (a-y) - (y-b)
    
        if x != y:
            if abs(error_mine) < abs(error_theirs):
                wins += 1
            elif abs(error_mine) == abs(error_theirs):
                draws += 1
            else:
                losses += 1
        else:
            draws += 1
    
    wins / 1000
    #>>> 12.44
    
    draws / 1000
    #>>> 87.56
    
    losses / 1000
    #>>> 0.0
    

    这表明对于精心选择的常数1.46diff变体有12-13%的答案是错误的!正如预期的那样,我的版本总是正确的。

    现在考虑下溢。尽管我的版本存在溢出问题,但这些问题远不及抵消问题严重。很明显,由上述逻辑引起的双重舍入非常具有问题性。证明:

    ...
        a = numpy.random.random()
        b = -numpy.random.random()
    ...
    
    wins / 1000
    #>>> 25.149
    
    draws / 1000
    #>>> 74.851
    
    losses / 1000
    #>>> 0.0
    

    没错,它有25%的错误率!

    实际上,只需要稍微剪辑就能将其提高到50%:

    ...
        a = numpy.random.random()
        b = -a + numpy.random.random()/256
    ...
    
    wins / 1000
    #>>> 49.188
    
    draws / 1000
    #>>> 50.812
    
    losses / 1000
    #>>> 0.0
    

    嗯,情况并不那么糟糕。只要符号相同,最低有效位只会差一个 我想


所以这就是答案了。除非你正在计算两个值的平均数,其总和超过1.7976931348623157e+308或小于-1.7976931348623157e+308,否则我的答案是最好的。


1
@Jaime 你错了。我的程序确实更快,但它也是完全准确的,而 diff 版本则不是。我会在问题中添加一些证据。 - Veedrac
1
@Jaime 证据已添加。 - Veedrac
1
如果你在寻找效率,我甚至建议使用.5 * (x[1:] + x[:-1]),因为浮点数乘法比浮点数除法更快。 - iago-lito
@Iago-lito 边际收益;这只是吞吐量增加了4%,但明显失去了清晰度。远不及我最初建议的吞吐量增加50%和清晰度改善。不过,如果你很赶时间的话,也可以这样做。 - Veedrac
@obachtos更好的建议是:使用现代化的Python版本。 - Veedrac
显示剩余4条评论

11

简短明了:

x[:-1] + np.diff(x)/2

即,取x的每个元素除了最后一个,并将其与后续元素之间的差值的一半相加。


我比你早9秒钟发了帖子,不过我必须承认你的解释更好! - user2379410
如果我在键入代码后立即单击按钮,我会比你早发布9秒钟! :) 很酷我们编写了完全相同的解决方案,同时其他几个人想出了非常不同的想法,我不得不说每一个都有其独特的吸引力。不过我还是喜欢我们的! - John Zwinck

7

试试这个:

midpoints = x[:-1] + np.diff(x)/2

这相当容易且应该很快。


2
如果速度很重要,建议使用乘法而不是除法,可以参考Veedrac的回答:
    0.5 * (x[:-1] + x[1:])

性能分析结果:

    >>> python -m timeit -s "import numpy; x = numpy.random.random(1000000)" "0.5 * (x[:-1] + x[1:])"
    100 loops, best of 3: 4.20 msec per loop

    >>> python -m timeit -s "import numpy; x = numpy.random.random(1000000)" "(x[:-1] + x[1:]) / 2"
    100 loops, best of 3: 5.10 msec per loop

0

我最终在多维数组上使用了这个操作,所以我会发布我的解决方案(受到np.diff()源代码的启发)

def zcen(a, axis=0):
    a = np.asarray(a)
    nd = a.ndim
    slice1 = [slice(None)]*nd
    slice2 = [slice(None)]*nd
    slice1[axis] = slice(1, None)
    slice2[axis] = slice(None, -1)
    return (a[slice1]+a[slice2])/2

>>> a = [[1, 2, 3, 4, 5], [10, 20, 30, 40, 50]]
>>> zcen(a)
array([[  5.5,  11. ,  16.5,  22. ,  27.5]])
>>> zcen(a, axis=1)
array([[  1.5,   2.5,   3.5,   4.5],
       [ 15. ,  25. ,  35. ,  45. ]])

0
>>> x = np.array([ 1230., 1230., 1227., 1235., 1217., 1153., 1170.])

>>> (x+np.concatenate((x[1:], np.array([0]))))/2
array([ 1230. ,  1228.5,  1231. ,  1226. ,  1185. ,  1161.5,   585. ])

现在你可以只剥离最后一个元素,如果你想的话。

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