我有一个类似以下形式的数组:
x = np.array([ 1230., 1230., 1227., 1235., 1217., 1153., 1170.])
我希望能够生成另一个数组,其中的值是原始数组中每对值的平均值:
xm = np.array([ 1230., 1228.5, 1231., 1226., 1185., 1161.5])
有人知道最简单和快速的方法来完成它而不使用循环吗?
更短,稍微甜一点:
(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.46
,diff
变体有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
,否则我的答案是最好的。
简短明了:
x[:-1] + np.diff(x)/2
即,取x
的每个元素除了最后一个,并将其与后续元素之间的差值的一半相加。
试试这个:
midpoints = x[:-1] + np.diff(x)/2
这相当容易且应该很快。
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
我最终在多维数组上使用了这个操作,所以我会发布我的解决方案(受到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. ]])
>>> 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. ])
diff
版本则不是。我会在问题中添加一些证据。 - Veedrac.5 * (x[1:] + x[:-1])
,因为浮点数乘法比浮点数除法更快。 - iago-lito