NumPy配列ndarrayを四捨五入・偶数丸めするnp.round
NumPy配列ndarrayの要素の値を任意の桁で丸めるにはnp.round()を使う。一般的な四捨五入ではなく偶数への丸めで、0.5が0.0に丸められたりするので注意。
本記事では、一般的な四捨五入の実装例も紹介する。
四捨五入ではなく小数点以下の切り捨て・切り上げについては以下の記事を参照。
本記事のサンプルコードのNumPyのバージョンは以下の通り。バージョンによって仕様が異なる可能性があるので注意。
import numpy as np
print(np.__version__)
# 1.26.1
np.round()の使い方
基本的な使い方
np.round()は指定されたndarrayの各要素を丸めたndarrayを返す。デフォルトでは小数点以下の桁数が0になるように丸められる。
a = np.array([12.3, 45.6, 78.9])
print(np.round(a))
# [12. 46. 79.]
print(np.round(a).dtype)
# float64
元の型と同じ型のndarrayが返される。上の例のように、元が浮動小数点数floatだと、小数点以下の桁数が0でも浮動小数点数floatのndarrayが返される。
整数intにしたい場合はnp.round()のあとでastype()を使う。なお、浮動小数点数floatを整数intに型変換すると小数点以下切り捨て(0への丸め)となる。
print(np.round(a).astype(int))
# [12 46 79]
print(a.astype(int))
# [12 45 78]
リストなどのarray-likeオブジェクトも指定可能だが、返り値はndarray。
l = [12.3, 45.6, 78.9]
print(np.round(l))
# [12. 46. 79.]
print(type(np.round(l)))
# <class 'numpy.ndarray'>
スカラー値を指定することもできる。
print(np.round(12.3))
# 12.0
丸める桁数を指定: 引数decimals
第二引数decimalsに何桁に丸めるかを整数値で指定する。デフォルトはdecimals=0。
正の整数を指定すると小数点以下の桁、負の整数を指定すると整数の桁(位)の指定となる。-1は十の位、-2は百の位に丸める。
print(np.round(123.456))
# 123.0
print(np.round(123.456, 2))
# 123.46
print(np.round(123.456, -2))
# 100.0
整数intの場合、引数decimalsに負の値を指定すると任意の位に丸められる。0や正の値を指定すると変化なし。元が整数intだと、返り値も整数int。
print(np.round(123456))
# 123456
print(np.round(123456, 2))
# 123456
print(np.round(123456, -2))
# 123500
a = np.array([12345, 67890])
print(np.round(a, -3))
# [12000 68000]
print(np.round(a, -3).dtype)
# int64
np.round()は四捨五入ではなく、偶数への丸め
np.round()による丸めは、一般的な四捨五入ではなく、偶数への丸め。
「偶数への丸め」(round to even)は、端数が0.5より小さいなら「切り捨て」、端数が0.5より大きいならば「切り上げ」、端数がちょうど0.5なら「切り捨て」と「切り上げ」のうち結果が偶数となる方へ丸める(つまり偶数+0.5なら「切り捨て」、奇数+0.5ならば「切り上げ」となる)。 端数処理 - 偶数への丸め(round to even) - Wikipedia
0.5が0.0に丸められたり、2.5が2.0に丸められたりする。
print(np.round([-3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5]))
# [-4. -2. -2. -0. 0. 2. 2. 4.]
print(np.round([-350, -250, -150, -50, 50, 150, 250, 350], -2))
# [-400 -200 -200 0 0 200 200 400]
偶数へ丸められるのは端数がちょうど0.5のときなので、例えば2.5は2.0に丸められるが2.51は3.0に丸められる。
print(np.round([2.49, 2.5, 2.51]))
# [2. 2. 3.]
print(np.round([249, 250, 251], -2))
# [200 200 300]
一般的な四捨五入の実装例は後述。
np.round()とPythonの組み込み関数round()との違い
Pythonの組み込み関数round()でも値の丸めができる。
np.round()もround()も偶数への丸めで、引数に丸める桁数を指定するなど使い方も同じだが、値によって結果が異なる場合がある。
print(np.round(0.15, 1))
# 0.2
print(round(0.15, 1))
# 0.1
一見、np.round()のほうが正しそうだが、これは公式ドキュメントで注意されているようにnp.round()で高速だが厳密でないアルゴリズムが使用されているのが原因。
np.rounduses a fast but sometimes inexact algorithm to round floating-point datatypes. For positive decimals it is equivalent tonp.true_divide(np.rint(a * 10**decimals), 10**decimals), which has error due to the inexact representation of decimal fractions in the IEEE floating point standard and errors introduced when scaling by powers of ten. numpy.round — NumPy v1.26 Manual
引用文中のnp.true_divide()は/演算子に相当する関数。np.rint()については後述。
表示桁数を増やして確認すると浮動小数点数floatの0.15は実際には0.14999....であり、浮動小数点数を正確に扱うと0.1に丸められるのが正しい。
print(f'{0.15:.20}')
# 0.14999999999999999445
公式ドキュメントには他の例も挙げられている。
print(np.round(56294995342131.5, 3))
# 56294995342131.51
print(round(56294995342131.5, 3))
# 56294995342131.5
厳密に処理したい場合は組み込み関数round()や標準ライブラリのdecimalモジュールを使う。
np.around()とndarrayのround()メソッド
np.round()のエイリアスとしてnp.around()が定義されている。使い方は同じ。
a = np.array([12.3, 45.6, 78.9])
print(np.around(a))
# [12. 46. 79.]
print(np.around(a, -1))
# [10. 50. 80.]
また、ndarrayのメソッドとしてround()が定義されている。
print(a.round())
# [12. 46. 79.]
print(a.round(-1))
# [10. 50. 80.]
四捨五入の実装例
一般的な四捨五入を実現する関数の実装例は以下の通り。
np.round()と同じように引数decimalsを指定可能。桁数を揃えたあと0.5を足してnp.floor()で小数点以下を切り捨て(負の無限大への丸め)、桁数を戻している。
def my_round(x, decimals=0):
return np.floor(x * 10**decimals + 0.5) / 10**decimals
a = np.array([-3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5])
print(np.round(a))
# [-4. -2. -2. -0. 0. 2. 2. 4.]
print(my_round(a))
# [-3. -2. -1. 0. 1. 2. 3. 4.]
print(a / 10)
# [-0.35 -0.25 -0.15 -0.05 0.05 0.15 0.25 0.35]
print(np.round(a / 10, 1))
# [-0.4 -0.2 -0.2 -0. 0. 0.2 0.2 0.4]
print(my_round(a / 10, 1))
# [-0.3 -0.2 -0.1 0. 0.1 0.2 0.3 0.4]
上の関数では-0.5が0.0となる。-0.5を-1.0としたい場合は以下のようにする。np.abs()を使って絶対値で計算してnp.sign()で元の符号にしている。
def my_round2(x, decimals=0):
return np.sign(x) * np.floor(np.abs(x) * 10**decimals + 0.5) / 10**decimals
print(a)
# [-3.5 -2.5 -1.5 -0.5 0.5 1.5 2.5 3.5]
print(my_round(a))
# [-3. -2. -1. 0. 1. 2. 3. 4.]
print(my_round2(a))
# [-4. -3. -2. -1. 1. 2. 3. 4.]
print(a / 10)
# [-0.35 -0.25 -0.15 -0.05 0.05 0.15 0.25 0.35]
print(my_round(a / 10, 1))
# [-0.3 -0.2 -0.1 0. 0.1 0.2 0.3 0.4]
print(my_round2(a / 10, 1))
# [-0.4 -0.3 -0.2 -0.1 0.1 0.2 0.3 0.4]
整数に丸めるnp.rint()
整数に丸めるnp.rint()も提供されている。decimals=0のnp.round()と同等の処理。
ndarrayやリストなどのarray-likeオブジェクト、スカラー値を指定可能。
a = np.array([12.3, 45.6, 78.9])
print(np.rint(a))
# [12. 46. 79.]
l = [12.3, 45.6, 78.9]
print(np.rint(l))
# [12. 46. 79.]
print(np.rint(12.3))
# 12.0
rintという名前だが、整数intに変換されるわけではなく元の型と同じ型のndarrayが返される。
print(np.rint(a).dtype)
# float64
np.round()と同じく、一般的な四捨五入ではなく偶数への丸め。
print(np.rint([-3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5]))
# [-4. -2. -2. -0. 0. 2. 2. 4.]