次の形式の配列があります。
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]
_の各要素を検討します。最初の要素と2番目の要素である_x₀
_と_x₁
_を検討してください。
IEEEに従って、_x₀ + x₁
_は完全な精度で計算され、、次に丸められます。したがって、それだけで十分だったとしたら、それが正解でしょう。
_(x₀ + x₁) / 2
_はその値の半分です。これは、次の2つの場合を除いて、ほとんどの場合、指数を1減らすことで実行できます。
_x₀ + x₁
_オーバーフロー。これにより、無限大(どちらかの符号)になります。それは望んでいることではないので、計算はwrongになります。
_x₀ + x₁
_アンダーフロー。サイズが縮小であるため、丸めは完璧になり、計算は正しいになります。
他のすべての場合では、計算は正しいになります。
次に、x[:-1] + numpy.diff(x) / 2
について考えます。これは、ソースの検査により、直接評価されます
_x[:-1] + (x[1:] - x[:-1]) / 2
_
そして、もう一度_x₀
_と_x₁
_を検討してください。
_x₁ - x₀
_には、多くの値に対してunderflowを伴う深刻な「問題」があります。これも大きなキャンセルにより精度を失います。エラーが追加で効果的にキャンセルされるため、これが兆候が同じであるかどうかが問題ではないことはすぐには明らかではありません。重要なのは、丸めが発生することです。
_(x₁ - x₀) / 2
_も同様に丸められますが、x₀ + (x₁ - x₀) / 2
はanother丸めを含みます。これは、エラーが忍び寄ることを意味します。証明:
_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%の回答が正しくないことを示しています。予想通り、私のバージョンは常に正しいです。
underflowを考えてみましょう。私の亜種にはオーバーフローの問題がありますが、これらはキャンセルの問題ほど大した問題ではありません。上記のロジックからの二重丸めが非常に問題である理由は明らかです。証明:
_...
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つだけ最下位ビットオフだと思います。
だからあなたはそれを持っています。合計が_1.7976931348623157e+308
_を超えるか、_-1.7976931348623157e+308
_より小さい2つの値の平均を見つけない限り、私の答えが最良です。
短くて甘い:
x[:-1] + np.diff(x)/2
つまり、最後の要素を除くx
の各要素を取得し、それと後続の要素との差の半分を追加します。
これを試して:
midpoints = x[:-1] + np.diff(x)/2
それはかなり簡単で、高速でなければなりません。
>>> 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. ])
必要に応じて、最後の要素を削除できます
私はこの操作を多次元配列にまとめて使用するので、私のソリューションを投稿します(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. ]])