配列、Pandas、Numpyを四捨五入、切り上げ、切り捨て[Python]

本記事では、Pythonで四捨五入・切り上げ・切り捨てを行う方法を、
「変数」・「list(配列)」・「NumPy配列」・「pandas DataFrame」
の4パターンに分けて解説します。

特に、round 関数が「4.5 を 5 ではなく 4 に丸める」理由や、decimal モジュールを用いた
一般的な四捨五入(5 を常に切り上げる方式)の実装方法など、実務でもつまずきやすいポイントを丁寧に扱います。

こんな人におすすめ

  • Pythonの変数を四捨五入・切り上げ・切り捨てしたい
  • Pythonの配列(list)を一気に四捨五入・切り上げ・切り捨てしたい
  • NumPy配列を一気に四捨五入・切り上げ・切り捨てしたい
  • pandasのDataFrameを一気に四捨五入・切り上げ・切り捨てしたい

主なライブラリ

本ページでは、以下のモジュール・ライブラリを利用します。

  • decimal モジュール(Decimal クラスによる厳密な四捨五入)
  • math モジュール(ceil, floor
  • pandas
  • numpy

事前に以下のようにインポートしておきます。

import math
import pandas as pd
import numpy as np
from decimal import Decimal, ROUND_HALF_UP, ROUND_DOWN, ROUND_UP
  

Python変数の丸め処理(四捨五入・切り上げ・切り捨て)

まずは、単一の変数(スカラー値)に対して丸め処理を行う方法を紹介します。
Python標準の round 関数と、decimal モジュールの Decimal クラスを使った方法を比較しながら見ていきます。

Python変数(四捨五入)

decimal モジュールの Decimal クラスと、そのメソッド quantize を使用すると、
「一般的な四捨五入(5 を常に切り上げ)」を実装できます。

  • Decimal の引数:四捨五入したい変数を文字列型で渡す(浮動小数点誤差を避けるため)。
  • quantize の第1引数:四捨五入後の小数点以下の桁数を Decimal('0.1') のように指定。
  • quantize の第2引数rounding=ROUND_HALF_UP を指定すると、5 を常に切り上げる一般的な四捨五入になります。
i = 1.234

print(Decimal(str(i)).quantize(Decimal('1'), rounding=ROUND_HALF_UP))
# ⇒ 1

print(Decimal(str(i)).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP))
# ⇒ 1.2

print(Decimal(str(i)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
# ⇒ 1.23
  

Python変数の四捨五入では round 関数がよく使われますが、
「銀行丸め(偶数丸め)」 という方式で動作するため、
学校で習う一般的な四捨五入(5 以上は一律で切り上げ)とは結果が異なることがあります。

下記は、round 関数が偶数丸めで動作している例です。
一般的な四捨五入では 4.5 → 5 になりますが、Python の round では 4.5 → 4 になります。

# 学校で習う一般的な四捨五入では 4.5 → 5 になるが、
# round 関数は「偶数丸め」をしているため、4 が返ってくる。
i = 4.5
print(round(i))
# ⇒ 4

# この場合は、一般的な四捨五入と同じ結果が返ってくる
j = 5.5
print(round(j))
# ⇒ 6
  

正確な四捨五入(5 を常に切り上げる方式)が必要な場合は、
round ではなく decimal モジュール(Decimal + ROUND_HALF_UP
を使用することをおすすめします。

Python変数(切り上げ)

変数の切り上げには math.ceil を使用します。
小数第1位だけでなく、第2位・第3位など任意の桁で切り上げたい場合は、
いったん数値を 10 倍・100 倍したうえで ceil を適用し、最後に元のスケールに戻します。

i = 1.2345

# 小数点第1位を切り上げる
print(math.ceil(i))
# ⇒ 2

# 小数点第2位を切り上げる
print(math.ceil(i * 10) / 10)
# ⇒ 1.3
  

decimal モジュールを使って、より厳密に切り上げることも可能です。

i = 1.2345

# 小数点第1位を切り上げる
print(Decimal(str(i)).quantize(Decimal('1'), rounding=ROUND_UP))
# ⇒ 2

# 小数点第2位を切り上げる
print(Decimal(str(i)).quantize(Decimal('0.1'), rounding=ROUND_UP))
# ⇒ 1.3
  

Python変数(切り捨て)

変数の切り捨てには math.floor を使用します。考え方は ceil と同様です。

i = 1.2345

# 小数点第1位を切り捨てる
print(math.floor(i))
# ⇒ 1

# 小数点第2位を切り捨てる
print(math.floor(i * 10) / 10)
# ⇒ 1.2
  

decimal モジュールを使って切り捨てることも可能です。

i = 1.2345

# 小数点第1位を切り捨てる
print(Decimal(str(i)).quantize(Decimal('1'), rounding=ROUND_DOWN))
# ⇒ 1

# 小数点第2位を切り捨てる
print(Decimal(str(i)).quantize(Decimal('0.1'), rounding=ROUND_DOWN))
# ⇒ 1.2
  

Python配列(list)の丸め処理

Python の list(配列)そのものに対して、一括で四捨五入する専用関数は用意されていません。
しかし、list はイテラブルなので、内包表記や for を使って要素ごとに丸め処理を行うことができます。

Python配列(四捨五入)

例として、次のような list を四捨五入してみます。

list1 = [1.234, 3.456, 5.678]
  

Decimal を使った場合と、round を使った場合の両方を示します。

# Decimal を使った一般的な四捨五入(5 を常に切り上げ)
print([Decimal(str(i)).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP) for i in list1])
# ⇒ [Decimal('1.2'), Decimal('3.5'), Decimal('5.7')]

# round を使った場合(偶数丸め)
print([round(i, 1) for i in list1])
# ⇒ [1.2, 3.5, 5.7]
  

Decimal を利用した四捨五入処理は、実行結果が
[Decimal('1.2'), Decimal('3.5'), Decimal('5.7')] のように表示されます。
これは通常の小数とほぼ同様に数値計算に利用できますが、
型としては float ではなく Decimalである点に注意してください。

Python配列(切り上げ)

切り上げ・切り捨ても同様に、list の各要素に対して math.ceilmath.floor を適用します。

list1 = [1.234, 3.456, 5.678]

print([math.ceil(i) for i in list1])
# ⇒ [2, 4, 6]
  

Python配列(切り捨て)

list1 = [1.234, 3.456, 5.678]

print([math.floor(i) for i in list1])
# ⇒ [1, 3, 5]
  

NumPy配列の丸め処理

NumPy には np.round, np.ceil, np.floor といった丸め系の関数が用意されています。
ただし np.round も Python の round と同様に
偶数丸め(銀行丸め) で動作します。

「5 を常に切り上げる一般的な四捨五入」が必要な場合は、
Decimal を使った自作関数を np.vectorize などで適用する方法があります。
なお、np.vectorize は見た目はベクトル化されていますが、内部的には Python ループに近いため、
性能がシビアな場合は np.floor などを組み合わせた数式ベースの実装も検討してください。

NumPy配列(四捨五入)

list2 = np.array([
    [1.234, 3.456, 5.678],
    [5.432, 6.543, 8.765]
])

# Decimal を利用する場合(一般的な四捨五入)
def func_ROUND_HALF_UP(x):
    return Decimal(str(x)).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP)

print(np.vectorize(func_ROUND_HALF_UP)(list2))
# [[Decimal('1.2') Decimal('3.5') Decimal('5.7')]
#  [Decimal('5.4') Decimal('6.5') Decimal('8.8')]]

# np.round を利用する場合(偶数丸め)
print(np.round(list2, 1))
# [[1.2 3.5 5.7]
#  [5.4 6.5 8.8]]
  

Decimal を利用した四捨五入処理の結果は
[[Decimal('1.2') ...]] のように Decimal 型で表現されます。
通常の数値とほぼ同様に扱えますが、型が float とは異なるため、
他の NumPy 関数との組み合わせでは注意が必要です。

NumPy配列(切り上げ)

list2 = np.array([
    [1.234, 3.456, 5.678],
    [5.432, 6.543, 8.765]
])

print(np.ceil(list2))
# [[2. 4. 6.]
#  [6. 7. 9.]]
  

NumPy配列(切り捨て)

list2 = np.array([
    [1.234, 3.456, 5.678],
    [5.432, 6.543, 8.765]
])

print(np.floor(list2))
# [[1. 3. 5.]
#  [5. 6. 8.]]
  

pandas DataFrame の丸め処理

pandas の DataFrame には round メソッドが用意されています。
こちらも round / np.round と同様に
偶数丸め で動作します。
一般的な四捨五入が必要な場合は、自作関数と agg を組み合わせて使います。

pandasのDataFrame(四捨五入)

df = pd.DataFrame({'a': [1.23, 2.34, 3.45],
                   'b': [4.4, 4.5, 4.6]})
#       a    b
# 0  1.23  4.4
# 1  2.34  4.5
# 2  3.45  4.6

# 一般的な四捨五入(5 を常に切り上げ)を行う関数
def func_ROUND_HALF_UP(list1):
    return [Decimal(str(i)).quantize(Decimal('1'), rounding=ROUND_HALF_UP) for i in list1]

print(df.agg(func_ROUND_HALF_UP))
#    a  b
# 0  1  4
# 1  2  5
# 2  3  5

# DataFrame.round() を使った場合(偶数丸め)
print(df.round())
#      a    b
# 0  1.0  4.0
# 1  2.0  4.0  # ⇒ 偶数丸め
# 2  3.0  5.0
  

上記では agg を用いて列ごとに自作関数を適用しています。
行方向・列方向にどのように適用されるかは、DataFrame の構造と関数の戻り値に依存しますので、
実際の業務データに合わせて挙動を確認しながら利用してください。

pandasのDataFrame(切り上げ)

DataFrame 全体を切り上げる場合は、agg を用いて np.ceil を適用します。

print(df.agg(np.ceil))
#      a    b
# 0  2.0  5.0
# 1  3.0  5.0
# 2  4.0  5.0
  

pandasのDataFrame(切り捨て)

print(df.agg(np.floor))
#      a    b
# 0  1.0  4.0
# 1  2.0  4.0
# 2  3.0  4.0
  

用途別のおすすめ丸め方法まとめ

最後に、用途ごとにどの方法を選べばよいか、ざっくりとまとめます。

シチュエーション おすすめ手法
学習用・簡単なスクリプト round(偶数丸めであることを理解した上で使用)
金額計算など、厳密な四捨五入が必要 Decimal + ROUND_HALF_UP(一般的な四捨五入)
大規模な配列計算(NumPy) np.round(偶数丸め)や、np.floor 等を組み合わせた独自実装
分析用の表形式データ(pandas DataFrame) DataFrame.round や、自作関数 + agg

よくある質問(FAQ)

Q. なぜ round(4.5) は 5 ではなく 4 になるのですか?

Python の round 関数は、「5 を常に切り上げる一般的な四捨五入」ではなく、
偶数丸め(銀行丸め) を採用しているためです。
小数部分がちょうど 0.5 の場合、近い偶数に丸められる仕様になっています。

Q. 一般的な四捨五入をしたいときはどうすればよいですか?

一般的な四捨五入(5 を常に切り上げ)を行いたい場合は、
decimal モジュールの Decimal クラスと
ROUND_HALF_UP を利用してください。
本記事のサンプルコードのように quantize メソッドを使うと簡単に実現できます。

Q. NumPy配列やpandas DataFrame でも Decimal を使うべきですか?

金額計算など厳密な結果が求められる場合は Decimal を検討すべきですが、
Decimalfloat とは型が異なるため、
NumPy や pandas の一部機能と組み合わせる際に制約が出ることがあります。
性能・保守性・必要な精度のバランスを考慮して選択してください。

記事に問題などありましたら、ページ下部のリンクからお知らせいただけると幸いです。