Pythonで数値を丸める方法

Pythonで数値を丸める方法

それはビッグデータの時代であり、日々、ますます多くの企業が情報に基づいた意思決定を行うためにデータを活用しようとしています。 データサイエンス分野でのPythonの人気の高まりから明らかなように、多くの企業は、データを分析するためにPythonの強力なデータサイエンスエコシステムに注目しています。

すべてのデータサイエンスの専門家が留意しなければならないことの1つは、データセットがどのようにバイアスされるかです。 偏ったデータから結論を導き出すと、コストのかかる間違いにつながる可能性があります。

バイアスがデータセットに忍び込む方法はたくさんあります。 統計情報を調べたことがあれば、レポートバイアス、選択バイアス、サンプリングバイアスなどの用語に精通していることでしょう。 数値データを扱うときに重要な役割を果たす別のタイプのバイアスがあります:丸めバイアス。

この記事では、次のことを学びます。

  • 数字の丸め方が重要な理由

  • さまざまな丸め戦略に従って数値を丸める方法、および純粋なPythonで各メソッドを実装する方法

  • 丸めがデータに与える影響、およびこの影響を最小化する丸め戦略

  • NumPy配列とPandas DataFramesの数値を丸める方法

  • 異なる丸め戦略を適用する場合

  • __クイズに挑戦:*私たちのインタラクティブな「Rounding Numbers in Python」クイズで知識をテストしてください。 完了すると、学習の進捗状況を経時的に追跡できるようにスコアを受け取ります。

link:/quizzes/python-rounding/[クイズに挑戦»]

この記事は、コンピューティングにおける数値の精度に関する論文ではありませんが、主題について簡単に触れます。 Pythonの基本に精通していれば十分であり、ここに含まれる数学は、高校の代数に相当するものに精通している人なら誰でも快適に感じるはずです。

Pythonに組み込まれている丸めメカニズムを見てみましょう。

Pythonの組み込みの `+ round()+`関数

Pythonには、2つの数値引数、「+ n 」と「 ndigits 」を取り、「 ndigits 」に丸められた数値「 n 」を返す組み込みの「 round()」関数があります。 ` ndigits `引数はデフォルトでゼロに設定されているため、省略すると整数に丸められた数値になります。 ご覧のとおり、 ` round()+`は期待どおりに動作しない場合があります。

ほとんどの人が数字を丸める方法は次のようになります。

_ _ 最初に `+ n `の小数点を ` p `桁だけシフトし、 ` n `に10 raised(10の ` p `乗)を掛けることにより、数値 ` n `を小数点以下の桁数 ` p `に丸めます。 )新しい数値 ` m +`を取得します。

次に、「+ m 」の小数点以下1桁の数字「 d 」を確認します。 「 d 」が5未満の場合、「 m 」を最も近い整数に切り捨てます。 それ以外の場合は、「 m +」を切り上げます。

最後に、「+ m 」を10で割って、小数点を「 p +」の位置に戻します。 _ _

それは簡単なアルゴリズムです! たとえば、最も近い整数に丸められた数値「2.5」は「3」です。 小数点以下1桁に丸められた数値「1.64」は「1.6」です。

インタプリタセッションを開き、Pythonの組み込みhttps://docs.python.org/3/library/functions.html#round [`+ round()`を使用して、 ` 2.5 +`を最も近い整数に丸めます] 関数:

>>>

>>> round(2.5)
2

あえぎ!

`+ round()`は数字の ` 1.5 +`をどのように処理しますか?

>>>

>>> round(1.5)
2

ですから、 `+ round()`は ` 1.5 `を ` 2 `に切り上げ、 ` 2.5 `を ` 2 +`に切り捨てます!

Pythonバグトラッカーで問題を提起する前に、 `+ round(2.5)`が ` 2 `を返すことになっていることを確認させてください。 ` round()+`がそのように振る舞うのには十分な理由があります。

この記事では、予想よりも多くの数を丸める方法があり、それぞれ独自の利点と欠点があることを学びます。 `+ round()+`は、特定の丸め戦略に従って動作します。特定の状況で必要な場合とそうでない場合があります。

「私が数字を丸める方法は、実際にそれほど大きな影響を与えることができますか?」丸めの効果がどれほど極端かを見てみましょう。

丸めはどれくらいの影響がありますか?

あなたが信じられないほど幸運な日を過ごして、地面に100ドルを見つけたとします。 一度にすべてのお金を使うのではなく、スマートにプレイし、異なる株の株式を購入してお金を投資することにします。

株式の価値は、需要と供給に依存します。 株を購入したい人が多くなればなるほど、株の価値は高まり、逆もまた同様です。 大量の株式市場では、特定の株式の価値は秒単位で変動する可能性があります。

少し実験してみましょう。 購入した株式の全体的な価値は、毎秒小さな乱数、たとえば0.05ドルから-0.05ドルの間で変動します。 この変動は、必ずしも小数点以下2桁の良い値であるとは限りません。 たとえば、全体の値は1秒間に0.031286ドル増加し、次の1秒間は0.028476ドル減少します。

値を小数点第5位または第6位まで追跡したくないので、小数点第3位以降をすべて切り捨てることにします。 丸めの専門用語では、これは、数値を小数点以下3桁に切り捨てる*と呼ばれます。 ここではいくつかのエラーが予想されますが、小数点以下3桁を維持することで、このエラーは重大なものにはなりません。 右?

Pythonを使用して実験を実行するには、数値を小数点以下3桁に切り捨てる `+ truncate()+`関数を作成することから始めましょう。

>>>

>>> def truncate(n):
...     return int(n * 1000)/1000

`+ truncate()`関数は、最初に ` n `に ` 1000 `を掛けることで、数値 ` n `の小数点を右に3桁シフトすることで機能します。 この新しい数値の整数部分は、 ` int()`で取得されます。 最後に、「 n 」を「+1000」で割ることにより、小数点が3桁左にシフトされます。

次に、シミュレーションの初期パラメーターを定義します。 2つの変数が必要です。1つはシミュレーションが完了した後の実際の株価を追跡するため、もう1つは各ステップで小数点以下3桁に切り捨てた後の株価の値です。

これらの変数を「100」に初期化することから始めます。

>>>

>>> actual_value, truncated_value = 100, 100

次に、1,000,000秒(約11.5日)シミュレーションを実行します。 1秒ごとに、「+ random 」モジュールの「 uniform()」関数を使用して「 -0.05 」と「+0.05」の間のランダムな値を生成し、「+ actual 」と「 truncated + `:

>>>

>>> import random
>>> random.seed(100)

>>> for _ in range(1000000):
...     randn = random.uniform(-0.05, 0.05)
...     actual_value = actual_value + randn
...     truncated_value = truncate(truncated_value + randn)
...

>>> actual_value
96.45273913513529

>>> truncated_value
0.239

シミュレーションの中核は、「+ for 」ループで発生します。このループは、「 0+」と「999,999」の間の数値の「+ range(1000000)」をループします。 各ステップで ` range()`から取得した値は変数 ` _ +`に格納されます。これは、ループ内でこの値を実際に必要としないため、ここで使用します。

ループの各ステップで、「-0.05」と「0.05」の間の新しい乱数が「+ random.randn()」を使用して生成され、変数「 randn 」に割り当てられます。 投資の新しい値は、「 randn 」を「 actual_value 」に追加することによって計算され、切り捨てられた合計は、「 randn 」を「 truncated_value 」に追加し、この値を「 truncate()+」で切り捨てることによって計算されます。

ループを実行した後、 `+ actual_value `変数を調べるとわかるように、約$ 3.55しか失われていません。 ただし、「 truncated_value +」を見ていると、ほとんどすべてのお金が失われたと考えていたでしょう。

*注意:*上記の例では、 `+ random.seed()+`関数を使用して擬似乱数ジェネレータをシードし、ここに示す出力を再現できるようにします。

Pythonのランダム性の詳細については、Real Pythonのhttps://realpython.com/python-random/[Pythonでのランダムデータの生成(ガイド)]をご覧ください。

`+ round()`が期待どおりに動作しないという点を無視して、シミュレーションを再実行してみましょう。 今回は、各ステップで小数点以下3桁に丸めるために ` round()`を使用し、シミュレーションを再度 ` seed()+`して以前と同じ結果を取得します。

>>>

>>> random.seed(100)
>>> actual_value, rounded_value = 100, 100

>>> for _ in range(1000000):
...     randn = random.uniform(-0.05, 0.05)
...     actual_value = actual_value + randn
...     rounded_value = round(rounded_value + randn, 3)
...

>>> actual_value
96.45273913513529

>>> rounded_value
96.258

なんという違いでしょう。

衝撃的なように思えますが、この正確なエラーは、1980年代初頭にhttps://en.wikipedia.org/wiki/Vancouver_Stock_Exchange[Vancouver Stock Exchange]の値を記録するために設計されたシステムが全体のインデックス値を切り捨てたときに、かなりの混乱を引き起こしました。丸めの代わりに小数点以下3桁まで。 丸め誤差にはhttp://mate.uprh.edu/~pnegron/notas4061/parliament.htm [変動選挙]があり、結果としてhttp://www-users.math.umn.edu/~arnold/disasters/patriotが発生しました。 .html [生活の損失]。

数字の丸め方は重要であり、責任ある開発者およびソフトウェアデザイナーとして、一般的な問題とその対処方法を知る必要があります。 さまざまな丸め方法が何であるか、そしてそれぞれを純粋なPythonで実装する方法を詳しく見ていきましょう。

メソッドのメナジェリー

https://en.wikipedia.org/wiki/Rounding [多数の丸め戦略]があり、それぞれに長所と短所があります。 このセクションでは、最も一般的な手法のいくつかと、それらがデータに与える影響について学習します。

切り捨て

数字を丸める最も単純な方法は、最も粗雑ではありますが、指定された桁数に数字を切り捨てることです。 数値を切り捨てる場合、指定された位置の後の各桁を0に置き換えます。 ここではいくつかの例を示します。

Value Truncated To Result

12.345

Tens place

10

12.345

Ones place

12

12.345

Tenths place

12.3

12.345

Hundredths place

12.34

リンクの `+ truncate()+`関数でこれを実装する1つの方法を既に見ました:#how-much-impact-can-rounding-have [どのくらいのインパクトが丸めることができますか?]セクション。 この関数では、次の方法で入力番号が小数点以下3桁に切り捨てられました。

  • 数値に「1000」を乗算して、小数点を3桁右にシフトします

  • + int()+`で新しい数値の整数部分を取得します *+ 1000 +`で割って、小数点以下3桁を左にシフトします。

このプロセスを一般化するには、「+ 1000+」を数値10ᵖ(「10」のpth乗)に置き換えます。ここで、_p_は切り捨てる小数点以下の桁数です。

def truncate(n, decimals=0):
    multiplier = 10*  *decimals
    return int(n* multiplier)/multiplier

このバージョンの `+ truncate()`では、2番目の引数はデフォルトで ` 0 `に設定されているため、2番目の引数が関数に渡されない場合は、 ` truncate()+`は数値の整数部分を返しますそれに渡されました。

`+ truncate()+`関数は正数と負数の両方でうまく機能します:

>>>

>>> truncate(12.5)
12.0

>>> truncate(-5.963, 1)
-5.9

>>> truncate(1.625, 2)
1.62

負の数を `+ decimals +`に渡して、小数点の左側の桁に切り捨てることもできます。

>>>

>>> truncate(125.6, -1)
120.0

>>> truncate(-1374.25, -3)
-1000.0

正数を切り捨てると、切り捨てられます。 同様に、負の数値を切り捨てると、その数値は切り上げられます。 ある意味では、切り捨ては、丸める数値の符号に応じた丸め方法の組み合わせです。

切り上げから始めて、これらの各丸め方法を個別に見てみましょう。

切り上げ

2つ目の丸め戦略は、「切り上げ」と呼ばれます。この戦略は、常に指定された桁数に数値を切り上げます。 次の表に、この戦略の概要を示します。

Value Round Up To Result

12.345

Tens place

20

12.345

Ones place

13

12.345

Tenths place

12.4

12.345

Hundredths place

12.35

Pythonで「切り上げ」戦略を実装するには、httpsからhttps://docs.python.org/3/library/math.html#math.ceil [+ ceil()+]関数を使用します://docs.python.org/3/library/math.html [+ math +]モジュール。

`+ ceil()+`関数は、「天井」という用語から名前を取得します。この用語は、数学で与えられた数以上の最も近い整数を表すために使用されます。

整数ではない数値はすべて、2つの連続する整数の間にあります。 たとえば、数字「1.2」は「1」と「2」の間の間隔にあります。 「天井」は、間隔の2つのエンドポイントのうち大きい方です。 2つのエンドポイントのうち小さい方を「フロア」と呼びます。したがって、「+ 1.2+」の上限は「2」であり、「+ 1.2+」の下限は「1」です。

数学では、*天井関数*と呼ばれる特別な関数がすべての数値をその天井にマッピングします。 天井関数が整数を受け入れることができるように、整数の天井は整数自体になるように定義されています。 したがって、数値「2」の上限は「2」です。

Pythonでは、 `+ math.ceil()+`は上限関数を実装し、常にその入力以上の最も近い整数を返します。

>>>

>>> import math

>>> math.ceil(1.2)
2

>>> math.ceil(2)
2

>>> math.ceil(-0.5)
0

「+ -0.5 」の上限は「 -1 」ではなく「+0」であることに注意してください。 これは、「+ 0+」が「+ -0.5 」以上の「 -0.5 +」に最も近い整数であるため、理にかなっています。

「切り上げ」戦略を実装する `+ round_up()+`という関数を作成しましょう。

def round_up(n, decimals=0):
    multiplier = 10 * *decimals
    return math.ceil(n* multiplier)/multiplier

`+ round_up()`は ` truncate()`によく似ていることに気付くかもしれません。 最初に、「 n 」に「+10 * *decimals +」を乗算することにより、「 n 」の小数点が正しい桁数だけ右にシフトされます。 この新しい値は、「 math.ceil()」を使用して最も近い整数に切り上げられ、「 10* *decimals +」で割ることによって小数点が左にシフトされます。

小数点をシフトし、整数に丸めるために何らかの丸め方法を適用し、小数点を後ろにシフトするこのパターンは、より多くの丸め方法を調査するにつれて何度も繰り返されます。 これは、結局のところ、人間が数字を手で丸めるために使用するメンタルアルゴリズムです。

さまざまな入力に対して `+ round_up()+`がどのように機能するかを見てみましょう。

>>>

>>> round_up(1.1)
2.0

>>> round_up(1.23, 1)
1.3

>>> round_up(1.543, 2)
1.55

`+ truncate()`と同様に、負の値を ` decimals +`に渡すことができます。

>>>

>>> round_up(22.45, -1)
30.0

>>> round_up(1352, -2)
1400

負の数値を `+ decimals `に渡すと、 ` round_up()+`の最初の引数の数値は小数点の左側の正しい桁数に丸められます。

`+ round_up(-1.5)+`が返すものを推測してください:

>>>

>>> round_up(-1.5)
-1.0

「+ -1.0 +」はあなたが期待したものですか?

`+ round_up()`の定義で使用されるロジック、特に ` math.ceil()`関数の動作を調べると、 ` round_up(-1.5)`が ` + -1.0 + `。 ただし、数を丸めるときにゼロを中心に対称性を期待する人もいます。そのため、「 1.5+」が「2」に切り上げられる場合、「-1.5」は「+ -2 +」に切り上げられます。

いくつかの用語を確立しましょう。 この目的のために、次の図に従って「切り上げ」および「切り捨て」という用語を使用します。

切り上げは、常に数値を数値行の右に丸め、切り捨ては常に数値を数値行の左に丸めます。

切り捨て

「切り上げ」に対応するのは「切り捨て」戦略で、常に数値を指定された桁数に切り捨てます。 この戦略を示すいくつかの例を次に示します。

Value Rounded Down To Result

12.345

Tens place

10

12.345

Ones place

12

12.345

Tenths place

12.3

12.345

Hundredths place

12.34

Pythonで「切り捨て」戦略を実装するには、 `+ trunctate()`と ` round_up()+`の両方に使用したのと同じアルゴリズムに従うことができます。 最初に小数点をシフトし、次に整数に丸め、最後に小数点をシフトします。

`+ round_up()`では、 ` math.ceil()+`を使用して、小数点をシフトした後、数値の上限に切り上げました。 ただし、「切り捨て」戦略では、小数点をシフトした後、数値の下限に丸める必要があります。

幸いなことに、https://docs.python.org/3/library/math.html [+ math +]モジュールにはhttps://docs.python.org/3/library/math.html#mathがあります入力のフロアを返す.floor [+ floor()+]関数:

>>>

>>> math.floor(1.2)
1

>>> math.floor(-0.5)
-1

`+ round_down()+`の定義は次のとおりです。

def round_down(n, decimals=0):
    multiplier = 10*  *decimals
    return math.floor(n* multiplier)/multiplier

これは、 `+ math.ceil()`が ` math.floor()`に置き換えられたことを除いて、 ` round_up()+`に似ています。

いくつかの異なる値で `+ round_down()+`をテストできます:

>>>

>>> round_down(1.5)
1

>>> round_down(1.37, 1)
1.3

>>> round_down(-0.5)
-1

`+ round_up()`と ` round_down()+`の効果は非常に極端です。 大きなデータセットの数値を切り上げたり切り下げたりすることで、大量の精度を削除し、データから行われた計算を大幅に変更できる可能性があります。

丸め戦略について説明する前に、停止して、丸めがデータに偏りを生じさせる方法について話を始めましょう。

間奏:丸めバイアス

これで、 + truncate()++ round_up()+、および `+ round_down()+`の3つの丸め方法を見てきました。 これらの3つの手法はすべて、特定の数値に対して妥当な精度を維持するという点ではかなり粗雑です。

`+ truncate()`と ` round_up()`および ` round_down()+`の間には、丸めの重要な側面を強調する重要な違いが1つあります。対称性はゼロです。

`+ round_up()`はゼロを中心に対称ではないことを思い出してください。 数学的には、関数f(x)は、xの任意の値に対してf(x) f(-x)= 0の場合、ゼロを中心に対称です。 たとえば、「+ round_up(1.5)」は「+2」を返しますが、「+ round_up(-1.5)」は「 -1 」を返します。 ` round_down()+`関数も0を中心に対称ではありません。

一方、 `+ truncate()`関数はゼロを中心に対称です。 これは、小数点を右にシフトした後、 ` truncate()`が残りの桁を切り落とすためです。 初期値が正の場合、これは数値を切り捨てることになります。 負の数は切り上げられます。 したがって、 ` truncate(1.5)`は ` 1 `を返し、 ` truncate(-1.5)`は ` -1 +`を返します。

対称性の概念は、*丸めバイアス*という概念を導入します。これは、丸めがデータセットの数値データに与える影響を説明します。

「端数切り上げ」戦略には、値が常に正の無限大の方向に切り上げられるため、「正の無限大バイアスに向かって切り上げ」があります。 同様に、「端数切り捨て」戦略には、負の無限大バイアスに向かう*ラウンドがあります。

「切り捨て」戦略は、正の値では負の無限大バイアスへのラウンド、負の値では正の無限大へのラウンドを示します。 この振る舞いを持つ丸め関数は、一般に「ゼロバイアスに向かって丸める」と言われています。

これが実際にどのように機能するかを見てみましょう。 次のフロートのリストを検討してください。

>>>

>>> data = [1.25, -2.67, 0.43, -1.79, 4.32, -8.19]

https://docs.python.org/3/library/statistics.html#statistics.mean [+ statistics.mean()+]関数を使用して、 `+ data +`の値の平均値を計算しましょう:

>>>

>>> import statistics

>>> statistics.mean(data)
-1.1083333333333332

次に、https://dbader.org/blog/list-dict-set-comprehensions-in-で、それぞれの + round_up()++ round_down()+、および `+ truncate()`を適用します。 python [list comprehension]は、「 data +」の各数値を小数点以下1桁に丸め、新しい平均を計算します。

>>>

>>> ru_data = [round_up(n, 1) for n in data]
>>> ru_data
[1.3, -2.6, 0.5, -1.7, 4.4, -8.1]
>>> statistics.mean(ru_data)
-1.0333333333333332

>>> rd_data = [round_down(n, 1) for n in data]
>>> statistics.mean(rd_data)
-1.1333333333333333

>>> tr_data = [truncate(n, 1) for n in data]
>>> statistics.mean(tr_data)
-1.0833333333333333

「+ data 」のすべての数値が切り上げられた後、新しい平均は約「 -1.033 」となり、これは実際の平均「約+1.108」よりも大きくなります。 切り捨てを行うと、平均が約「+ -1.133 」に下方にシフトします。 切り捨てられた値の平均は約「 -1.08 +」であり、実際の平均に最も近い。

この例は、平均値を可能な限り維持しながら個々の値を丸める必要がある場合は常に切り捨てる必要があることを意味しません。 `+ data `リストには同数の正と負の値が含まれます。 ` truncate()`関数は、すべての正の値のリストでは ` round_up()`のように動作し、すべての負の値のリストでは ` round_down()+`のように動作します。

この例が示しているのは、丸められたデータから計算された値に対する丸めバイアスの影響です。 丸められたデータから結論を引き出す際には、これらの効果を念頭に置く必要があります。

通常、丸めるときは、すべてを単に切り上げまたは切り捨てるのではなく、指定された精度で最も近い数に丸めることに関心があります。

たとえば、「+ 1.23+」と「1.28」の数値を小数点以下1桁に丸めるように誰かに頼まれた場合、おそらく「1.2」と「1.3」ですばやく応答します。 + truncate()++ round_up()+、および `+ round_down()+`関数は、このようなことはしません。

数字「1.25」はどうですか? おそらくすぐにこれを「1.3」に丸めると思いますが、実際には、「+ 1.25+」は「1.2」および「1.3」から等距離にあります。 ある意味で、「+ 1.2+」と「1.3」は、どちらも小数点以下1桁の精度で「1.25」に最も近い数字です。 数値「1.25」は、「+ 1.2+」および「1.3」に関して「タイ」と呼ばれます。 このような場合、タイブレーカーを割り当てる必要があります。

ほとんどの人がネクタイを破る方法は、2つの可能な数値のうち大きい方に丸めることです。

切り上げ

「四捨五入」戦略は、指定された精度ですべての数値を最も近い数値に切り上げ、切り上げによってタイを分割します。 ここではいくつかの例を示します。

Value Round Half Up To Result

13.825

Tens place

10

13.825

Ones place

14

13.825

Tenths place

13.8

13.825

Hundredths place

13.83

Pythonで「端数切り上げ」戦略を実装するには、小数点を希望の桁数だけ右にシフトして、通常どおりに開始します。 ただし、この時点では、シフトされた小数点の直後の桁が「5」以下かどうかを判断する方法が必要です。

これを行う1つの方法は、シフトした値に「0.5」を追加し、「+ math.floor()+」で切り捨てることです。 これは以下の理由で機能します。

  • シフト値の小数点以下1桁が5未満の場合、「+ 0.5+」を追加してもシフト値の整数部分は変更されないため、フロアは整数部分に等しくなります。 *小数点以下の最初の桁が「5」以上の場合、「+ 0.5+」を追加すると、シフトした値の整数部分が「1」だけ増加するため、フロアはこれに等しくなりますより大きな整数。

Pythonでの表示は次のとおりです。

def round_half_up(n, decimals=0):
    multiplier = 10* * decimals
    return math.floor(n*multiplier + 0.5)/multiplier

`+ round_half_up()`は ` round_down()`によく似ていることに注意してください。 これは多少直感に反するかもしれませんが、内部的には「 round_half_up()」は切り捨てられます。 トリックは、小数点のシフト後に「+0.5」を追加して、切り捨ての結果が期待値と一致するようにすることです。

いくつかの値で `+ round_half_up()+`をテストして、機能することを確認します。

>>>

>>> round_half_up(1.23, 1)
1.2

>>> round_half_up(1.28, 1)
1.3

>>> round_half_up(1.25, 1)
1.3

+ round_half_up()+`は常に2つの可能な値の大きい方に丸めてタイを壊すので、 `+ -1.5 +`のような負の値は `+ -2 + 'ではなく + -1 + `に丸めます:

>>>

>>> round_half_up(-1.5)
-1.0

>>> round_half_up(-1.25, 1)
-1.2

すばらしいです! これで、組み込みの `+ round()+`関数が拒否した結果を最終的に取得できます。

>>>

>>> round_half_up(2.5)
3.0

興奮する前に、「-1.225」を「2」の小数位に丸めて何が起こるかを見てみましょう。

>>>

>>> round_half_up(-1.225, 2)
-1.23

待つ。 関係が2つの可能な値のうち大きい方に丸められる方法について説明しました。 「+ -1.225 」は「 -1.22 」と「 -1.23 」の中間のスマックです。 ` -1.22 `はこれら2つの大きい方なので、 ` round_half_up(-1.225、2)`は ` -1.22 `を返すはずです。 しかし代わりに、 ` -1.23 +`を取得しました。

`+ round_half_up()+`関数にバグはありますか?

`+ round_half_up()`が ` -1.225 `を小数点以下2桁に丸める場合、最初に行うことは ` -1.225 `に ` 100 +`を掛けることです。 これが期待どおりに機能することを確認しましょう。

>>>

>>> -1.225 *100
-122.50000000000001

まあ…​それは間違っています! しかし、 `+ round_half_up(-1.225、2)`が-1.23を返す理由は説明しています。 REPLの ` _ `を利用して各ステップで出力された最後の値を呼び出して、ステップバイステップで ` round_half_up()+`アルゴリズムを続けましょう。

>>>

>>> _ + 0.5
-122.00000000000001

>>> math.floor(_)
-123

>>> _/100
-1.23

「+ -122.00000000000001 」は実際には「 -122 」に近いですが、それ以下の最も近い整数は「 -123 」です。 小数点を左に戻すと、最終値は「 -1.23 +」になります。

さて、論理エラーがなくても、 `+ round_half_up(-1.225、2)`がどのように ` -1.23 `を返すか知っていますが、なぜPythonが ` -1.225* 100 `は `- 122.50000000000001 + `? Pythonにバグはありますか?

脇: Pythonインタープリターセッションで、次のように入力します。

>>>

>>> 0.1 + 0.1 + 0.1
0.30000000000000004

これを初めて見るのはかなり衝撃的ですが、これは*浮動小数点表現エラー*の典型的な例です。 Pythonとは関係ありません。 このエラーは、マシンが浮動小数点数をメモリに保存する方法に関係しています。

最新のコンピュータのほとんどは、浮動小数点数を53ビット精度の10進数として保存します。 53ビットで表現できる有限の2進10進表現を持つ数値のみが正確な値として保存されます。 すべての数値に有限の2進10進表現があるわけではありません。

たとえば、10進数の「0.1」には有限の10進数表現がありますが、無限のバイナリ表現があります。 分数1/3が無限に繰り返される10進数 `0.333 ... +`として10進数でのみ表現できるように、分数 ` 1/10 +`は無限に繰り返される10進数 `+0.0001100110011としてバイナリでのみ表現できます。 …​ + `。

無限バイナリ表現の値は、メモリに保存される近似値に丸められます。 ほとんどのマシンが丸めに使用する方法は、https://en.wikipedia.org/wiki/IEEE_754 [IEEE-754]標準に従って決定されます。これは、最も近い表現可能なバイナリ分数への丸めを指定します。

Pythonドキュメントには、https://docs.python.org/3/tutorial/floatingpoint.html [浮動小数点演算:問題と制限]と呼ばれるセクションがあり、0.1について述べています。

_ _ ほとんどのマシンでは、Pythonが `+ 0.1 +`に保存されたバイナリ近似の真の10進値を出力する場合、表示する必要があります。

>>>

>>> 0.1
0.1000000000000000055511151231257827021181583404541015625

これは、ほとんどの人が役に立つと思うよりも多くの桁であるため、Pythonは代わりに丸められた値を表示することで、桁数を管理しやすくしています

>>>

>>> 1/10
0.1

印刷結果が「+ 1/10 +」の正確な値のように見えても、実際に保存されている値は最も近い表現可能なバイナリ分数であることを覚えておいてください。 (https://docs.python.org/3/tutorial/floatingpoint.html [ソース]) _ _

浮動小数点演算に関する詳細な論文については、David Goldbergの記事http://perso.ens-lyon.fr/jean-michel.muller/goldberg.pdfを参照してください[すべてのコンピューター科学者が浮動小数点について知っておくべきこと算術]、元々ジャーナル_ACM Computing Surveys_、Vol。 23、いいえ 1991年3月1日。

Pythonが `+ -1.225 *100 `は ` -122.50000000000001 +`であると言っているという事実は、浮動小数点表現エラーのアーティファクトです。 「OK、でもこれを修正する方法はありますか?」自問すべきより良い質問は、「これを修正する必要がありますか?」です。

浮動小数点数は正確な精度を持たないため、精度が最優先される状況では使用しないでください。 正確な精度が必要なアプリケーションでは、Pythonの `+ decimal `モジュールの ` Decimal `クラスを使用できます。 以下の ` Decimal +`クラスの詳細をご覧ください。

Pythonの標準の `+ float `クラスでアプリケーションに十分であると判断した場合、浮動小数点表現エラーに起因する ` round_half_up()+`のエラーが問題になることはありません。

マシンがメモリ内の数値を丸める方法を理解できたので、引き分けを打破する別の方法を検討して、丸め戦略に関する議論を続けましょう。

切り捨て

「四捨五入」戦略は、「四捨五入」方法と同様に、2つの数値のうち小さい方に丸めてタイを分割することを除いて、希望する精度で最も近い数値に丸めます。 ここではいくつかの例を示します。

Value Round Half Down To Result

13.825

Tens place

10

13.825

Ones place

14

13.825

Tenths place

13.8

13.825

Hundredths place

13.82

Pythonで「+ round_half_up()」関数の「 math.floor()」を「 math.ceil()」に置き換え、「 0.5+」を減算することで、「切り捨て半分」戦略を実装できます。追加する代わりに:

def round_half_down(n, decimals=0):
    multiplier = 10* * decimals
    return math.ceil(n*multiplier - 0.5)/multiplier

いくつかのテストケースに対して `+ round_half_down()+`をチェックしましょう。

>>>

>>> round_half_down(1.5)
1.0

>>> round_half_down(-1.5)
-2.0

>>> round_half_down(2.25, 1)
2.2

`+ round_half_up()`と ` round_half_down()+`の両方にバイアスはありません。 ただし、多くの関係を持つデータを丸めると、バイアスが生じます。 極端な例として、次の数字のリストを検討してください。

>>>

>>> data = [-2.15, 1.45, 4.35, -12.75]

これらの数値の平均を計算しましょう:

>>>

>>> statistics.mean(data)
-2.275

次に、 `+ round_half_up()`と ` round_half_down()+`で小数点以下1桁に丸めた後、データの平均を計算します。

>>>

>>> rhu_data = [round_half_up(n, 1) for n in data]
>>> statistics.mean(rhu_data)
-2.2249999999999996

>>> rhd_data = [round_half_down(n, 1) for n in data]
>>> statistics.mean(rhd_data)
-2.325

`+ data `のすべての数字は、小数点以下1桁への丸めに関して同点です。 「 round_half_up()」関数は正の無限大バイアスへのラウンドを導入し、「 round_half_down()+」は負の無限大バイアスへのラウンドを導入します。

残りの丸め戦略では、これらのバイアスをさまざまな方法で緩和するためのすべての試みについて説明します。

ゼロから半分に丸める

`+ round_half_up()`と ` round_half_down()+`をよく調べてみると、これらの関数はどちらもゼロに関して対称ではないことがわかります。

>>>

>>> round_half_up(1.5)
2.0

>>> round_half_up(-1.5)
-1.0

>>> round_half_down(1.5)
1.0

>>> round_half_down(-1.5)
-2.0

対称性を導入する1つの方法は、常にゼロからタイを丸めることです。 次の表に、この仕組みを示します。

Value Round Half Away From Zero To Result

15.25

Tens place

20

15.25

Ones place

15

15.25

Tenths place

15.3

-15.25

Tens place

-20

-15.25

Ones place

-15

-15.25

Tenths place

-15.3

数値「+ n 」に「ゼロから半分に丸める」戦略を実装するには、通常どおり、指定された桁数だけ小数点を右にシフトします。 次に、この新しい数値の小数点のすぐ右側にある数字「 d +」を確認します。 この時点で、考慮すべき4つのケースがあります。

  1. 「+ n 」が正で「 d> = 5+」の場合、切り上げ

  2. 「+ n 」が正で「 d <5+」の場合、切り捨て

  3. 「+ n 」が負で「 d> = 5+」の場合、切り捨てます

  4. 「+ n 」が負で「 d <5+」の場合、切り上げ

上記の4つのルールのいずれかに従って丸めた後、小数点位置を左に戻します。

数値 `+ n `と ` decimals `の値を指定すると、 ` round_half_up()`と ` round_half_down()+`を使用してPythonでこれを実装できます。

if n >= 0:
    rounded = round_half_up(n, decimals)
else:
    rounded = round_half_down(n, decimals)

それは十分簡単ですが、実際にはもっと簡単な方法があります!

Pythonの組み込みhttps://docs.python.org/3/library/functions.html#abs [+ abs()+]関数を使用して、最初に + n +`の絶対値を取得する場合、 `+ round_half_up()+`を使用して数値を丸めます。 次に、必要なのは、丸められた数値に「+ n +」と同じ符号を付けることだけです。 これを行う1つの方法は、https://docs.python.org/3/library/math.html#math.copysign [+ math.copysign()+`]関数を使用することです。

`+ math.copysign()`は2つの数値 ` a `と ` b `を取り、 ` b `の符号を付けて ` a +`を返します。

>>>

>>> math.copysign(1, -2)
-1.0

引数が両方とも整数であったとしても、 `+ math.copysign()`は ` float +`を返します。

+ abs()+、 `+ round_half_up()`および ` math.copysign()+`を使用すると、「ゼロから半分に丸める」戦略をPythonの2行だけで実装できます。

def round_half_away_from_zero(n, decimals=0):
    rounded_abs = round_half_up(abs(n), decimals)
    return math.copysign(rounded_abs, n)

`+ round_half_away_from_zero()`では、 ` round_half_up()`を使用して ` n `の絶対値が ` decimals `小数点以下の桁に丸められ、この結果が変数 ` rounded_abs `に割り当てられます。 次に、「 math.copysign()」を使用して、「 n 」の元の符号が「 rounded_abs +」に適用され、正しい符号を持つこの最終値が関数によって返されます。

いくつかの異なる値で `+ round_half_away_from_zero()+`をチェックすると、関数が期待どおりに動作することがわかります。

>>>

>>> round_half_away_from_zero(1.5)
2.0

>>> round_half_away_from_zero(-1.5)
-2.0

>>> round_half_away_from_zero(-12.75, 1)
-12.8

`+ round_half_away_from_zero()`関数は、ほとんどの人が日常生活で数値を丸める傾向がある方法で数値を丸めます。 これまで見てきた中で最も馴染みのある丸め関数であることに加えて、 ` round_half_away_from_zero()+`は、同数の正と負の結びつきを持つデータセットの丸めバイアスも除去します。

前のセクションの例で、「+ round_half_away_from_zero()+」が丸めバイアスをどの程度緩和するかを確認しましょう。

>>>

>>> data = [-2.15, 1.45, 4.35, -12.75]
>>> statistics.mean(data)
-2.275

>>> rhaz_data = [round_half_away_from_zero(n, 1) for n in data]
>>> statistics.mean(rhaz_data)
-2.2750000000000004

`+ data `の各数値を ` round_half_away_from_zero()`で小数点以下1桁に丸めると、 ` data +`の数値の平均値はほぼ正確に保持されます!

ただし、「+ round_half_away_from_zero()+」は、データセット内のすべての数値を正のタイのみ、負のタイのみ、または一方の符号がもう一方の符号よりも多いタイで丸める場合、丸めバイアスを示します。 バイアスは、データセット内に同数の正と負の関係がある場合にのみ適切に軽減されます。

正と負の関係の数が大幅に異なる状況をどのように処理しますか? この質問に対する答えは、この記事の冒頭で私たちを欺いた関数に完全な円をもたらします:Pythonの組み込みhttps://docs.python.org/3/library/functions.html#round [`+ round( )+ `]関数。

半分から偶数への丸め

データセット内の値を丸めるときに丸めバイアスを軽減する1つの方法は、目的の精度で最も近い偶数にタイを丸めることです。 以下に、その方法の例をいくつか示します。

Value Round Half To Even To Result

15.255

Tens place

20

15.255

Ones place

15

15.255

Tenths place

15.3

15.255

Hundredths place

15.26

「半分から偶数への丸め戦略」は、Pythonの組み込みの `+ round()+`関数で使用される戦略であり、https://en.wikipedia.org/wiki/IEEE_754#Rounding_rules [IEEEのデフォルトの丸め規則-754標準]。 この戦略は、切り捨てまたは切り上げられるデータセットの同点の確率が等しいという仮定の下で機能します。 実際には、これが通常のケースです。

これで、 `+ round(2.5)`が ` 2 +`を返す理由がわかりました。 それは間違いではありません。 これは、堅実な推奨事項に基づいた意識的な設計上の決定です。

`+ round()+`が実際に偶数に丸められることを証明するには、いくつかの異なる値で試してください:

>>>

>>> round(4.5)
4

>>> round(3.5)
4

>>> round(1.75, 1)
1.8

>>> round(1.65, 1)
1.6

`+ round()+`関数はほとんど偏りがありませんが、完全ではありません。 たとえば、データセットのタイの大部分が切り捨てではなく偶数に切り上げられた場合、丸めバイアスが導入される可能性があります。 バイアスを「半分から偶数に丸める」よりもさらに緩和する戦略

最後に、 `+ round()`は、浮動小数点表現エラーのおかげで ` round_half_up()+`で見たのと同じしゃっくりに悩まされています。

>>>

>>> # Expected value: 2.68
>>> round(2.675, 2)
2.67

アプリケーションで浮動小数点の精度が十分であれば、これらの偶発的なエラーを気にする必要はありません。

精度が最も重要な場合、Pythonの `+ Decimal +`クラスを使用する必要があります。

`+ Decimal +`クラス

Pythonのhttps://docs.python.org/3/library/decimal.html[decimal]モジュールは、Pythonを初めて使用する場合は気付かないかもしれない言語の「バッテリーに含まれる」機能の1つです。 `+ decimal +`モジュールの指針はドキュメントにあります:

_ 10進数は、「人々を念頭に置いて設計された浮動小数点モデルに基づいており、必然的に最も重要なガイド原則を持っています。コンピューターは、人々が学校で学ぶ算術と同じように機能する算術を提供する必要があります。」 10進算術仕様。 (https://docs.python.org/3/library/decimal.html [ソース]) _

`+ decimal +`モジュールの利点は次のとおりです。

  • 正確な10進表記:「0.1」は実際に「0.1」であり、「+ 0.1 + 0.1 + 0.1-0.3+」はご想像のとおり「0」を返します。

  • 有効数字の保存: `+ 1.20 `と ` 2.50 `を追加すると、結果は ` 3.70 +`になり、有効性を示すために末尾のゼロが維持されます。

  • ユーザーが変更可能な精度: `+ decimal +`モジュールのデフォルトの精度は28桁ですが、この値はユーザーが問題に合わせて変更できます。

`+ decimal +`モジュールで丸めがどのように機能するかを見てみましょう。 Python REPLに以下を入力することから始めます。

>>>

>>> import decimal
>>> decimal.getcontext()
Context(
    prec=28,
    rounding=ROUND_HALF_EVEN,
    Emin=-999999,
    Emax=999999,
    capitals=1,
    clamp=0,
    flags=[],
    traps=[
        InvalidOperation,
        DivisionByZero,
        Overflow
    ]
)

`+ decimal.getcontext()`は、 ` decimal `モジュールのデフォルトコンテキストを表す ` Context +`オブジェクトを返します。 コンテキストには、特に、デフォルトの精度とデフォルトの丸め戦略が含まれます。

上記の例でわかるように、 `+ decimal `モジュールのデフォルトの丸め戦略は ` ROUND_HALF_EVEN `です。 これは組み込みの ` round()+`関数と整合しており、ほとんどの目的に適した丸め戦略です。

`+ decimal `モジュールの ` Decimal `クラスを使用して数値を宣言しましょう。 これを行うには、目的の値を含む「 string 」を渡すことで、新しい「 Decimal +」インスタンスを作成します。

>>>

>>> from decimal import Decimal
>>> Decimal("0.1")
Decimal('0.1')

*注意:*浮動小数点数から `+ Decimal `インスタンスを作成することは可能ですが、そうするとすぐに浮動小数点表現エラーが発生します。 たとえば、浮動小数点数 ` 0.1 `から ` Decimal +`インスタンスを作成するとどうなるかを確認します。

>>>

>>> Decimal(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')

正確な精度を維持するには、必要な10進数を含む文字列から `+ Decimal +`インスタンスを作成する必要があります。

楽しみのために、 `+ Decimal +`が正確な10進表現を維持しているという主張をテストしましょう。

>>>

>>> Decimal('0.1') + Decimal('0.1') + Decimal('0.1')
Decimal('0.3')

ああ。 それは満足ですよね?

`+ Decimal `の丸めは、 ` .quantize()+`メソッドを使用して行います。

>>>

>>> Decimal("1.65").quantize(Decimal("1.0"))
Decimal('1.6')

さて、それはおそらく少しファンキーに見えるので、それを分解しましょう。 `+ .quantize()`の ` Decimal(" 1.0 ")`引数は、数値を丸める小数点以下の桁数を決定します。 ` 1.0 `には小数点以下1桁があるため、数値 1.65 + `は小数点以下1桁に丸められます。 デフォルトの丸め戦略は「半分から偶数に丸める」ため、結果は「1.6」になります。

また、「+ round()」関数は「半分から偶数への丸め」戦略を使用しますが、「 2.675+」を小数点以下2桁に正しく丸めることができなかったことを思い出してください。 `+ 2.68 `の代わりに、 ` round(2.675、2)`は ` 2.67 `を返します。 ` decimal `モジュールの正確な10進数表現のおかげで、 ` Decimal +`クラスではこの問題は発生しません。

>>>

>>> Decimal("2.675").quantize(Decimal("1.00"))
Decimal('2.68')

`+ decimal `モジュールのもう1つの利点は、算術実行後の丸めが自動的に処理され、有効数字が保持されることです。 これを実際に見るために、デフォルトの精度を28桁から2桁に変更してから、数字「+1.23」と「2.32」を追加しましょう。

>>>

>>> decimal.getcontext().prec = 2
>>> Decimal("1.23") + Decimal("2.32")
Decimal('3.6')

精度を変更するには、 `+ decimal.getcontext()`を呼び出し、 ` .prec `属性を設定します。 関数呼び出しで属性を設定するのが奇妙に見える場合、 ` .getcontext()`が ` decimal + `モジュール。

「1.23」と「2.32」の正確な値は「3.55」です。 精度が2桁になり、丸め戦略がデフォルトの「半分から偶数に丸める」に設定されているため、値「3.55」は自動的に「3.6」に丸められます。

デフォルトの丸め戦略を変更するには、 `+ decimal.getcontect()。rounding +`プロパティをいくつかのhttps://docs.python.org/3/library/decimal.html#rounding-modes[flagsのいずれかに設定できます]。 次の表は、これらのフラグと、それらが実装する丸め戦略をまとめたものです。

Flag Rounding Strategy

decimal.ROUND_CEILING

Rounding up

decimal.ROUND_FLOOR

Rounding down

decimal.ROUND_DOWN

Truncation

decimal.ROUND_UP

Rounding away from zero

decimal.ROUND_HALF_UP

Rounding half away from zero

decimal.ROUND_HALF_DOWN

Rounding half towards zero

decimal.ROUND_HALF_EVEN

Rounding half to even

decimal.ROUND_05UP

Rounding up and rounding towards zero

最初に気づくのは、 `+ decimal `モジュールで使用される命名スキームが、この記事の前半で合意したものと異なることです。 たとえば、 ` decimal.ROUND_UP +`は、「ゼロからの丸め」戦略を実装します。この戦略は、実際に負の数を切り捨てます。

次に、表に記載されている丸め戦略の一部は、まだ説明していないため、なじみのないものに見える場合があります。 「+ decimal.ROUND_HALF_EVEN +」がどのように機能するかは既に見てきたので、実際に動作している他のそれぞれを見てみましょう。

`+ decimal.ROUND_CEILING `戦略は、先ほど定義した ` round_up()+`関数と同じように機能します。

>>>

>>> decimal.getcontext().rounding = decimal.ROUND_CEILING

>>> Decimal("1.32").quantize(Decimal("1.0"))
Decimal('1.4')

>>> Decimal("-1.32").quantize(Decimal("1.0"))
Decimal('-1.3')

`+ decimal.ROUND_CEILING +`の結果は、ゼロに関して対称ではないことに注意してください。

`+ decimal.ROUND_FLOOR `戦略は、 ` round_down()+`関数と同じように機能します。

>>>

>>> decimal.getcontext().rounding = decimal.ROUND_FLOOR

>>> Decimal("1.32").quantize(Decimal("1.0"))
Decimal('1.3')

>>> Decimal("-1.32").quantize(Decimal("1.0"))
Decimal('-1.4')

`+ decimal.ROUND_CEILING `と同様に、 ` decimal.ROUND_FLOOR +`戦略はゼロに関して対称ではありません。

`+ decimal.ROUND_DOWN `および ` decimal.ROUND_UP `ストラテジーには、やや誤解を招く名前があります。 ` ROUND_DOWN `と ` ROUND_UP +`は両方ともゼロを中心に対称です。

>>>

>>> decimal.getcontext().rounding = decimal.ROUND_DOWN

>>> Decimal("1.32").quantize(Decimal("1.0"))
Decimal('1.3')

>>> Decimal("-1.32").quantize(Decimal("1.0"))
Decimal('-1.3')

>>> decimal.getcontext().rounding = decimal.ROUND_UP

>>> Decimal("1.32").quantize(Decimal("1.0"))
Decimal('1.4')

>>> Decimal("-1.32").quantize(Decimal("1.0"))
Decimal('-1.4')

`+ decimal.ROUND_DOWN `戦略は、 ` truncate()`関数と同様に、数値をゼロに丸めます。 一方、 ` decimal.ROUND_UP `はすべてをゼロから切り上げます。 これは、この記事の前半で合意した用語とは明らかに異なるため、 ` decimal +`モジュールを使用している場合はそのことに留意してください。

`+ decimal `モジュールには、より微妙な丸めを可能にする3つの戦略があります。 ` decimal.ROUND_HALF_UP +`メソッドは、すべてを最も近い数に丸め、ゼロから遠ざけることでタイを分割します。

>>>

>>> decimal.getcontext().rounding = decimal.ROUND_HALF_UP

>>> Decimal("1.35").quantize(Decimal("1.0"))
Decimal('1.4')

>>> Decimal("-1.35").quantize(Decimal("1.0"))
Decimal('-1.4')

`+ decimal.ROUND_HALF_UP `は、 ` round_half_away_from_zero()`と同様に機能し、 ` round_half_up()+`とは機能しないことに注意してください。

また、ゼロに丸めることで関係を破壊する「+ decimal.ROUND_HALF_DOWN +」戦略もあります。

>>>

>>> decimal.getcontext().rounding = decimal.ROUND_HALF_DOWN

>>> Decimal("1.35").quantize(Decimal("1.0"))
Decimal('1.3')

>>> Decimal("-1.35").quantize(Decimal("1.0"))
Decimal('-1.3')

`+ decimal +`モジュールで利用できる最終的な丸め戦略は、これまで見てきたものとは大きく異なります。

>>>

>>> decimal.getcontext().rounding = decimal.ROUND_05UP

>>> Decimal("1.38").quantize(Decimal("1.0"))
Decimal('1.3')

>>> Decimal("1.35").quantize(Decimal("1.0"))
Decimal('1.3')

>>> Decimal("-1.35").quantize(Decimal("1.0"))
Decimal('-1.3')

上記の例では、 `+ decimal.ROUND_05UP `はすべてをゼロに丸めます。 実際、丸めの結果が「+0」または「5」で終了しない限り、これはまさに「+ decimal.ROUND_05UP +」の動作です。 その場合、数値はゼロから四捨五入されます。

>>>

>>> Decimal("1.49").quantize(Decimal("1.0"))
Decimal('1.4')

>>> Decimal("1.51").quantize(Decimal("1.0"))
Decimal('1.6')

最初の例では、数値「1.49」が最初に小数点以下2桁でゼロに丸められ、「+ 1.4+」が生成されます。 `+ 1.4 `は ` 0 `や ` 5 `で終わっていないため、そのまま残されます。 一方、「 1.51+」は小数点以下2桁でゼロに丸められ、「+ 1.5+」という数字になります。 これは「5」で終わるため、小数点第1位はゼロから「1.6」に丸められます。

このセクションでは、 `+ decimal `モジュールの丸めの側面のみに注目しました。 バンキングや科学計算の問題など、標準の浮動小数点精度が不十分なアプリケーションには、「 decimal +」を優れた選択肢にする他の多くの機能があります。

`+ Decimal +`の詳細については、Pythonドキュメントのhttps://docs.python.org/3/library/decimal.html#quick-start-tutorial [クイックスタートチュートリアル]をご覧ください。

次に、Pythonの科学計算とデータサイエンスの2つの主要なスタック、NumPyとPandasに注目しましょう。

NumPy配列の丸め

data scienceおよび科学計算のドメインでは、多くの場合、データをhttps://docs.scipy.org/doc/numpy/reference/arraysとして保存します。 .html [NumPy + array +]。 NumPyの最も強力な機能の1つは、https://realpython.com/numpy-array-programming/[vectorization and broadcast]を使用して、一度に1つの要素ではなく配列全体に操作を適用することです。

擬似乱数の3×4 NumPy配列を作成して、いくつかのデータを生成しましょう:

>>>

>>> import numpy as np
>>> np.random.seed(444)

>>> data = np.random.randn(3, 4)
>>> data
array([[First, we seed the `+np.random+` module so that you can easily reproduce the output. Then a 3×4 NumPy array of floating-point numbers is created with `+np.random.randn()+`.

*Note:* You’ll need to `+pip3 install numpy+` before typing the above code into your REPL if you don’t already have NumPy in your environment. If you installed Python with https://www.anaconda.com/[Anaconda], you’re already set!

If you haven’t used NumPy before, you can get a quick introduction in the https://realpython.com/numpy-array-programming/#getting-into-shape-intro-to-numpy-arrays[Getting Into Shape] section of Brad Solomon’s https://realpython.com/numpy-array-programming/[Look Ma, No For-Loops: Array Programming With NumPy] here at Real Python.

For more information on NumPy’s https://docs.scipy.org/doc/numpy/reference/routines.random.html[random] module, check out the https://realpython.com/python-random/#prngs-for-arrays-numpyrandom[PRNG’s for Arrays] section of Brad’s https://realpython.com/python-random/[Generating Random Data in Python (Guide)].

To round all of the values in the `+data+` array, you can pass `+data+` as the argument to the https://docs.scipy.org/doc/numpy/reference/generated/numpy.around.html#numpy.around[`+np.around()+`] function. The desired number of decimal places is set with the `+decimals+` keyword argument. The round half to even strategy is used, just like Python’s built-in `+round()+` function.

For example, the following rounds all of the values in `+data+` to three decimal places:

[.repl-toggle]#>>>#

[source,python,repl]

>>> np.around(data、decimals = 3)array([[[np.around()+ は、 + round()+ `と同じように、浮動小数点表現エラーに左右されます。

たとえば、 `+ data `配列の最初の列の3行目の値は ` 0.20851975 `です。 「四捨五入」戦略を使用してこれを小数点以下3桁に丸めると、値は「+0.208」になると予想されます。 しかし、 `+ np.around()`の出力では、値が ` 0.209 `に丸められていることがわかります。 ただし、2列目の1行目の値「+0.3775384」は「0.378」に正しく丸められます。

配列内のデータを整数に丸める必要がある場合、NumPyにはいくつかのオプションがあります。

`+ np.ceil()+`関数は、配列内のすべての値を元の値以上の最も近い整数に丸めます:

>>>

>>> np.ceil(data)
array([[Hey, we discovered a new number! Negative zero!

Actually, the https://en.wikipedia.org/wiki/IEEE_754[IEEE-754] standard requires the implementation of both a positive and negative zero. What possible use is there for something like this? Wikipedia knows the answer:

____
Informally, one may use the notation “`+−0+`” for a negative value that was rounded to zero. This notation may be useful when a negative sign is significant; for example, when tabulating Celsius temperatures, where a negative sign means below freezing. (https://en.wikipedia.org/wiki/Signed_zero#Scientific_uses[Source])
____

To round every value down to the nearest integer, use `+np.floor()+`:

[.repl-toggle]#>>>#

[source,python,repl]

>>> np.floor(data)array([[`+ np.trunc()+`で各値を整数コンポーネントに切り捨てることもできます。

>>>

>>> np.trunc(data)
array([[Finally, to round to the nearest integer using the “rounding half to even” strategy, use `+np.rint()+`:

[.repl-toggle]#>>>#

[source,python,repl]

>>> np.rint(data)array([[前に説明した丸め戦略の多くがここに欠けていることに気づいたかもしれません。 ほとんどの場合、必要なのは `+ around()`関数だけです。 ` round_half_up()+`のような別の戦略を実装する必要がある場合、簡単な修正でそれを行うことができます:

def round_half_up(n, decimals=0):
    multiplier = 10* * decimals
    # Replace math.floor with np.floor
    return np.floor(n*multiplier + 0.5)/multiplier

NumPyのhttps://www.safaribooksonline.com/library/view/python-for-data/9781449323592/ch04.html[vectorized operations]のおかげで、これは期待どおりに機能します。

>>>

>>> round_half_up(data, decimals=2)
array([[Now that you’re a NumPy rounding master, let’s take a look at Python’s other data science heavy-weight: the Pandas library.

=== Rounding Pandas `+Series+` and `+DataFrame+`

The https://pandas.pydata.org/pandas-docs/stable/[Pandas] library has become a staple for data scientists and data analysts who work in Python. In the words of Real Python’s own Joe Wyndham:

____
Pandas is a game-changer for data science and analytics, particularly if you came to Python because you were searching for something more powerful than Excel and VBA. (https://realpython.com/fast-flexible-pandas/[Source])
____

*Note:* Before you continue, you’ll need to `+pip3 install pandas+` if you don’t already have it in your environment. As was the case for NumPy, if you installed Python with https://www.anaconda.com/[Anaconda], you should be ready to go!

The two main Pandas data structures are the `+DataFrame+`, which in very loose terms works sort of like an Excel spreadsheet, and the `+Series+`, which you can think of as a column in a spreadsheet. Both `+Series+` and `+DataFrame+` objects can also be rounded efficiently using the `+Series.round()+` and `+DataFrame.round()+` methods:

[.repl-toggle]#>>>#

[source,python,repl]

>>> pandasをpdとしてインポート

>>>#最後の例以降、REPLを閉じた場合はnp.randomを再シード>>> np.random.seed(444)

>>> series = pd.Series(np.random.randn(4))>>> series 0 0.357440 1 0.377538 2 1.382338 3 1.175549 dtype:float64

>>> series.round(2)0 0.36 1 0.38 2 1.38 3 1.18 dtype:float64

>>> df = pd.DataFrame(np.random.randn(3、3)、columns = ["A"、 "B"、 "C"])>>> df ABC 0 -0.939276 -1.143150 -0.542440 1- 0.548708 0.208520 0.212690 2 1.268021 -0.807303 -3.303072

>>> df.round(3)A B C 0 -0.939 -1.143 -0.542 1 -0.549 0.209 0.213 2 1.268 -0.807 -3.303

The `+DataFrame.round()+` method can also accept a dictionary or a `+Series+`, to specify a different precision for each column. For instance, the following examples show how to round the first column of `+df+` to one decimal place, the second to two, and the third to three decimal places:

[.repl-toggle]#>>>#

[source,python,repl]

>>>#辞書で列ごとの精度を指定>>> df.round({"A":1、 "B":2、 "C":3})ABC 0 -0.9 -1.14 -0.542 1 -0.5 0.21 0.213 2 1.3 -0.81 -3.303

>>>#シリーズで列ごとの精度を指定>>> decimals = pd.Series([1、2、3]、index = ["A"、 "B"、 "C"])>>> df.round(decimals)ABC 0 -0.9 -1.14 -0.542 1 -0.5 0.21 0.213 2 1.3 -0.81 -3.303

If you need more rounding flexibility, you can apply NumPy’s `+floor()+`, `+ceil()+`, and `+rint()+` functions to Pandas `+Series+` and `+DataFrame+` objects:

[.repl-toggle]#>>>#

[source,python,repl]

>>> np.floor(df)A B C 0 -1.0 -2.0 -1.0 1 -1.0 0.0 0.0 2 1.0 -1.0 -4.0

>>> np.ceil(df)A B C 0 -0.0 -1.0 -0.0 1 -0.0 1.0 1.0 2 2.0 -0.0 -3.0

>>> np.rint(df)A B C 0 -1.0 -1.0 -1.0 1 -1.0 0.0 0.0 2 1.0 -1.0 -3.0

The modified `+round_half_up()+` function from the previous section will also work here:

[.repl-toggle]#>>>#

[source,python,repl]

>>> round_half_up(df、decimals = 2)A B C 0 -0.94 -1.14 -0.54 1 -0.55 0.21 0.21 2 1.27 -0.81 -3.30

Congratulations, you’re well on your way to rounding mastery! You now know that there are more ways to round a number than there are taco combinations. (Well… maybe not!) You can implement numerous rounding strategies in pure Python, and you have sharpened your skills on rounding NumPy arrays and Pandas `+Series+` and `+DataFrame+` objects.

There’s just one more step: knowing when to apply the right strategy.

=== Applications and Best Practices

The last stretch on your road to rounding virtuosity is understanding when to apply your newfound knowledge. In this section, you’ll learn some best practices to make sure you round your numbers the right way.

==== Store More and Round Late

When you deal with large sets of data, storage can be an issue. In most relational databases, each column in a table is designed to store a specific data type, and numeric data types are often assigned precision to help conserve memory.

For example, a temperature sensor may report the temperature in a long-running industrial oven every ten seconds accurate to eight decimal places. The readings from this are used to detect abnormal fluctuations in temperature that could indicate the failure of a heating element or some other component. So, there might be a Python script running that compares each incoming reading to the last to check for large fluctuations.

The readings from this sensor are also stored in a SQL database so that the daily average temperature inside the oven can be computed each day at midnight. The manufacturer of the heating element inside the oven recommends replacing the component whenever the daily average temperature drops `+.05+` degrees below normal.

For this calculation, you only need three decimal places of precision. But you know from the incident at the Vancouver Stock Exchange that removing too much precision can drastically affect your calculation.

If you have the space available, you should store the data at full precision. If storage is an issue, a good rule of thumb is to store at least two or three more decimal places of precision than you need for your calculation.

Finally, when you compute the daily average temperature, you should calculate it to the full precision available and round the final answer.

==== Obey Local Currency Regulations

When you order a cup of coffee for $2.40 at the coffee shop, the merchant typically adds a required tax. The amount of that tax depends a lot on where you are geographically, but for the sake of argument, let’s say it’s 6%. The tax to be added comes out to $0.144. Should you round this up to $0.15 or down to $0.14? The answer probably depends on the regulations set forth by the local government!

Situations like this can also arise when you are converting one currency to another. In 1999, the European Commission on Economical and Financial Affairs http://ec.europa.eu/economy_finance/publications/pages/publication1224_en.pdf[codified the use of the “rounding half away from zero” strategy] when converting currencies to the Euro, but other currencies may have adopted different regulations.

Another scenario, https://en.wikipedia.org/wiki/Cash_rounding[“Swedish rounding”], occurs when the minimum unit of currency at the accounting level in a country is smaller than the lowest unit of physical currency. For example, if a cup of coffee costs $2.54 after tax, but there are no 1-cent coins in circulation, what do you do? The buyer won’t have the exact amount, and the merchant can’t make exact change.

How situations like this are handled is typically determined by a country’s government. You can find a list of rounding methods used by various countries on https://en.wikipedia.org/wiki/Cash_rounding[Wikipedia].

If you are designing software for calculating currencies, you should always check the local laws and regulations in your users’ locations.

==== When In Doubt, Round Ties To Even

When you are rounding numbers in large datasets that are used in complex computations, the primary concern is limiting the growth of the error due to rounding.

Of all the methods we’ve discussed in this article, the “rounding half to even” strategy minimizes rounding bias the best. Fortunately, Python, NumPy, and Pandas all default to this strategy, so by using the built-in rounding functions you’re already well protected!

=== Summary

Whew! What a journey this has been!

In this article, you learned that:

* There are various rounding strategies, which you now know how to implement in pure Python.
* Every rounding strategy inherently introduces a rounding bias, and the “rounding half to even” strategy mitigates this bias well, most of the time.
* The way in which computers store floating-point numbers in memory naturally introduces a subtle rounding error, but you learned how to work around this with the `+decimal+` module in Python’s standard library.
* You can round NumPy arrays and Pandas `+Series+` and `+DataFrame+` objects.
* There are best practices for rounding with real-world data.

*__ Take the Quiz:* Test your knowledge with our interactive “Rounding Numbers in Python” quiz. Upon completion you will receive a score so you can track your learning progress over time:

link:/quizzes/python-rounding/[Take the Quiz »]

If you are interested in learning more and digging into the nitty-gritty details of everything we’ve covered, the links below should keep you busy for quite a while.

At the very least, if you’ve enjoyed this article and learned something new from it, pass it on to a friend or team member! Be sure to share your thoughts with us in the comments. We’d love to hear some of your own rounding-related battle stories!

Happy Pythoning!

=== Additional Resources

*Rounding strategies and bias:*

* https://en.wikipedia.org/wiki/Rounding[Rounding], Wikipedia
* https://zipcpu.com/dsp/2017/07/22/rounding.html[Rounding Numbers without Adding a Bias], from https://zipcpu.com[ZipCPU]

*Floating-point and decimal specifications:*

* https://en.wikipedia.org/wiki/IEEE_754[IEEE-754], Wikipedia
* http://speleotrove.com/decimal/decarith.html[IBM’s General Decimal Arithmetic Specification]

*Interesting Reads:*

* http://perso.ens-lyon.fr/jean-michel.muller/goldberg.pdf[What Every Computer Scientist Should Know About Floating-Point Arithmetic], David Goldberg, ACM Computing Surveys, March 1991
* https://docs.python.org/3/tutorial/floatingpoint.html[Floating Point Arithmetic: Issues and Limitations], from https://python.org[python.org]
* http://python-history.blogspot.com/2010/08/why-pythons-integer-division-floors.html[Why Python’s Integer Division Floors], by Guido van Rossum