pandasで四捨五入・偶数丸め・切り捨て・切り上げ

Modified: | Tags: Python, pandas

pandas.DataFrame, pandas.Seriesの数値を丸めるにはround()メソッドを使う。一般的な四捨五入ではなく偶数への丸めとなるので注意。

一般的な四捨五入や小数点以下の切り捨て・切り上げを行うには、NumPyの関数を使うか、Python標準ライブラリのdecimalモジュールを使う。

Pythonの組み込み関数round()やNumPyのnp.round()については以下の記事を参照。

pandasで数値を文字列に変換する方法、表示設定を変更する方法については以下の記事を参照。

本記事のサンプルコードのpandasのバージョンは以下の通り。バージョンによって仕様が異なる可能性があるので注意。

import pandas as pd

print(pd.__version__)
# 2.1.2

pandas.Seriesのround()メソッドの使い方

浮動小数点数floatSeriesを例とする。

s_f = pd.Series([123.456, 987.654])
print(s_f)
# 0    123.456
# 1    987.654
# dtype: float64

round()の第一引数decimalsに何桁に丸めるかを整数値で指定する。デフォルトはdecimals=0で整数に丸められるがデータ型はfloatのまま。整数intに変換するにはastype()メソッドを使う。

print(s_f.round())
# 0    123.0
# 1    988.0
# dtype: float64

print(s_f.round().astype(int))
# 0    123
# 1    988
# dtype: int64

第一引数decimalsが正だと小数点以下の桁数指定、負だと整数部分の桁数指定になる。-1は十の位、-2は百の位に丸められる。

print(s_f.round(2))
# 0    123.46
# 1    987.65
# dtype: float64

print(s_f.round(-2))
# 0     100.0
# 1    1000.0
# dtype: float64

整数intSeriesの場合、round()の第一引数decimals0(省略した場合も含む)や正の場合は変化なし。負の場合は対応する位に丸められる。

s_i = pd.Series([123, 987])
print(s_i)
# 0    123
# 1    987
# dtype: int64

print(s_i.round())
# 0    123
# 1    987
# dtype: int64

print(s_i.round(2))
# 0    123
# 1    987
# dtype: int64

print(s_i.round(-2))
# 0     100
# 1    1000
# dtype: int64

round()メソッドは数値が丸められた新しいオブジェクトを返し、元のオブジェクトは変更されない。DataFrameround()メソッドでも同様。

s_i_round = s_i.round(-2)
print(s_i_round)
# 0     100
# 1    1000
# dtype: int64

print(s_i)
# 0    123
# 1    987
# dtype: int64

なお、round()メソッドによる数値の丸めは四捨五入ではなく偶数への丸めとなる。詳しくは後述。

pandas.DataFrameのround()メソッドの使い方

浮動小数点数float、整数int、文字列str(列のデータ型はobject)の列を含むDataFrameを例とする。

df = pd.DataFrame({'f': [123.456, 987.654], 'i': [123, 987], 's': ['abc', 'xyz']})
print(df)
#          f    i    s
# 0  123.456  123  abc
# 1  987.654  987  xyz

print(df.dtypes)
# f    float64
# i      int64
# s     object
# dtype: object

全ての列に一律で桁数指定

round()の引数を省略した場合および整数を指定した場合は、全ての列が対応する桁数に丸められる。文字列は変化なし。

print(df.round())
#        f    i    s
# 0  123.0  123  abc
# 1  988.0  987  xyz

print(df.round(2))
#         f    i    s
# 0  123.46  123  abc
# 1  987.65  987  xyz

print(df.round(-2))
#         f     i    s
# 0   100.0   100  abc
# 1  1000.0  1000  xyz

各列に個別に桁数指定

round()の引数にキーを列名、値を桁数とする辞書を指定すると、各列に個別に桁数を指定できる。文字列に指定しても変化なし。指定しない列も変化なし。

print(df.round({'f': 2, 'i': -1, 's': 2}))
#         f    i    s
# 0  123.46  120  abc
# 1  987.65  990  xyz

print(df.round({'i': -2}))
#          f     i    s
# 0  123.456   100  abc
# 1  987.654  1000  xyz

pandasのround()メソッドは偶数への丸め

pandasのround()メソッドによる丸めは一般的な四捨五入ではなく偶数への丸め。

「偶数への丸め」(round to even)は、端数が0.5より小さいなら「切り捨て」、端数が0.5より大きいならば「切り上げ」、端数がちょうど0.5なら「切り捨て」と「切り上げ」のうち結果が偶数となる方へ丸める(つまり偶数+0.5なら「切り捨て」、奇数+0.5ならば「切り上げ」となる)。 端数処理 - 偶数への丸め(round to even) - Wikipedia

0.50.0に丸められたり、2.52.0に丸められたりする。

s = pd.Series([0.5, 1.5, 2.5, 3.5, 4.5])
print(s)
# 0    0.5
# 1    1.5
# 2    2.5
# 3    3.5
# 4    4.5
# dtype: float64

print(s.round())
# 0    0.0
# 1    2.0
# 2    2.0
# 3    4.0
# 4    4.0
# dtype: float64

偶数へ丸められるのは端数がちょうど0.5のときなので、例えば2.52.0に丸められるが2.513.0に丸められる。

s = pd.Series([2.49, 2.5, 2.51])
print(s)
# 0    2.49
# 1    2.50
# 2    2.51
# dtype: float64

print(s.round())
# 0    2.0
# 1    2.0
# 2    3.0
# dtype: float64

桁数を指定する場合も同様。

s = pd.Series([249, 250, 251])
print(s)
# 0    249
# 1    250
# 2    251
# dtype: int64

print(s.round(-2))
# 0    200
# 1    200
# 2    300
# dtype: int64

NumPyの関数で四捨五入・小数点以下の切り捨て・切り上げ

バージョン2.1時点で、pandasには一般的な四捨五入や小数点以下の切り捨て・切り上げを行うメソッドは提供されていない。

NumPyの関数でSeriesDataFrameを処理できるので、該当の関数を使えばよい。

import numpy as np

s = pd.Series([-0.5, -0.25, 0.25, 0.5])
print(s)
# 0   -0.50
# 1   -0.25
# 2    0.25
# 3    0.50
# dtype: float64

print(np.floor(s))
# 0   -1.0
# 1   -1.0
# 2    0.0
# 3    0.0
# dtype: float64

print(np.trunc(s))
# 0   -0.0
# 1   -0.0
# 2    0.0
# 3    0.0
# dtype: float64

print(np.ceil(s))
# 0   -0.0
# 1   -0.0
# 2    1.0
# 3    1.0
# dtype: float64

np.floor()は負の無限大への丸め、np.trunc()はゼロへの丸め、np.ceil()は正の無限大への丸め。詳細は以下の記事を参照。

NumPyにも一般的な四捨五入をする関数はないが、例えば以下のように実現できる。

def my_round(x, decimals=0):
    return np.floor(x * 10**decimals + 0.5) / 10**decimals

s = pd.Series([0.5, 1.5, 2.5, 3.5, 4.5])
print(s)
# 0    0.5
# 1    1.5
# 2    2.5
# 3    3.5
# 4    4.5
# dtype: float64

print(my_round(s))
# 0    1.0
# 1    2.0
# 2    3.0
# 3    4.0
# 4    5.0
# dtype: float64

print(my_round(s * 10, -1))
# 0    10.0
# 1    20.0
# 2    30.0
# 3    40.0
# 4    50.0
# dtype: float64

詳細は以下の記事を参照。

文字列など数値以外の列を含むDataFrameに対してNumPyの関数を適用するとエラーになるので注意。select_dtypes()を使うと数値列のみ抽出できる。

df = pd.DataFrame({'f': [123.456, 987.654], 'i': [123, 987], 's': ['abc', 'xyz']})
print(df)
#          f    i    s
# 0  123.456  123  abc
# 1  987.654  987  xyz

# print(np.floor(df))
# TypeError: must be real number, not str

print(np.floor(df.select_dtypes('number')))
#        f      i
# 0  123.0  123.0
# 1  987.0  987.0

所望の列を処理して新たな列として追加することもできる。

df['f_floor'] = np.floor(df['f'])
print(df)
#          f    i    s  f_floor
# 0  123.456  123  abc    123.0
# 1  987.654  987  xyz    987.0

decimalモジュールで四捨五入・小数点以下の切り捨て・切り上げ

Pythonの標準ライブラリdecimalモジュールを使うと、正確な十進浮動小数点数を扱うことができる。

当然ながらpandasやNumPyのメソッド・関数より低速だが、厳密に小数を処理したい場合はSeriesDataFrameの要素としてDecimalオブジェクトを使うこともできる。

ここでは以下のようにインポートする。

from decimal import Decimal, ROUND_HALF_UP, ROUND_FLOOR, ROUND_CEILING

astype()で文字列に変換してからmap()で各要素にDecimal()を適用する。

s = pd.Series([0.123, 0.456, 0.789])
print(s)
# 0    0.123
# 1    0.456
# 2    0.789
# dtype: float64

s_decimal = s.astype(str).map(Decimal)
print(s_decimal)
# 0    0.123
# 1    0.456
# 2    0.789
# dtype: object

print(type(s_decimal[0]))
# <class 'decimal.Decimal'>

文字列に変換しないと浮動小数点数floatで表現できる値として扱われてしまうので注意。

print(s.map(Decimal))
# 0    0.12299999999999999822364316059974953532218933...
# 1    0.45600000000000001643130076445231679826974868...
# 2    0.78900000000000003463895836830488406121730804...
# dtype: object

Decimalオブジェクトのquantize()メソッドで任意の桁数・丸めモードで丸めることが可能。ラムダ式で各要素に適用する。

print(s_decimal.map(lambda x: x.quantize(Decimal('0.1'), ROUND_HALF_UP)))
# 0    0.1
# 1    0.5
# 2    0.8
# dtype: object

print(s_decimal.map(lambda x: x.quantize(Decimal('0.1'), ROUND_FLOOR)))
# 0    0.1
# 1    0.4
# 2    0.7
# dtype: object

print(s_decimal.map(lambda x: x.quantize(Decimal('0.1'), ROUND_CEILING)))
# 0    0.2
# 1    0.5
# 2    0.8
# dtype: object

第一引数に求めたい桁数と同じ桁数のDecimalを指定する。Decimal()に文字列で'0.1''0.01'などを指定すればよい。整数部分を丸めたい場合は'1E1'のように指数表記を使う。詳細は以下の記事を参照。

第二引数に丸めモードを指定する。上の例のROUND_HALF_UPは四捨五入、ROUND_FLOORは小数点以下切り捨て(負の無限大への丸め)、ROUND_CEILINGは小数点以下切り上げ(正の無限大への丸め)。その他の丸めモードについては以下の公式ドキュメントを参照。偶数丸めやゼロへの丸めなどもある。

Decimalオブジェクトを要素とするSeriesDataFrameastype()で浮動小数点数floatに戻すことも可能。ただし、もちろんfloatで表現可能な値に変換されるので誤差を含む。

print(s_decimal.astype(float))
# 0    0.123
# 1    0.456
# 2    0.789
# dtype: float64

print(s_decimal.astype(float).map(Decimal))
# 0    0.12299999999999999822364316059974953532218933...
# 1    0.45600000000000001643130076445231679826974868...
# 2    0.78900000000000003463895836830488406121730804...
# dtype: object

関連カテゴリー

関連記事