Pythonで数値を丸める方法

Pythonで数値を丸める方法

それはビッグデータの時代であり、日々、ますます多くの企業が情報に基づいた意思決定を行うためにデータを活用しようとしています。 Python’s rising popularity in the data science realmで証明されているように、多くの企業がPythonの強力なデータサイエンスエコシステムを利用してデータを分析しています。

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

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

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

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

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

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

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

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

__ Take the Quiz:インタラクティブな「Pythonの四捨五入」クイズで知識をテストします。 完了すると、学習の進捗状況を経時的に追跡できるようにスコアを受け取ります。

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

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

Pythonの組み込みround()関数

Pythonには、nndigitsの2つの数値引数を取り、ndigitsに丸められた数値nを返す組み込みのround()関数があります。 ndigits引数のデフォルトはゼロであるため、これを省略すると、数値は整数に丸められます。 ご覧のとおり、round()は期待どおりに機能しない可能性があります。

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

最初にnの小数点をp桁でシフトし、nに10ᵖを掛けて(10を%に上げて)、数値npの小数点以下の桁数に丸めます。 (t5)s。

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

最後に、mを10ᵖで割って、小数点をpの桁に戻します。

それは簡単なアルゴリズムです! たとえば、最も近い整数に丸められた数値2.53です。 小数点以下第1位に四捨五入された1.64の数は1.6です。

次に、インタプリタセッションを開き、Pythonの組み込みround()関数を使用して、2.5を最も近い整数に丸めます。

>>>

>>> round(2.5)
2

あえぎ!

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

>>>

>>> round(1.5)
2

したがって、round()1.52に切り上げ、2.52に切り下げます!

Pythonバグトラッカーで問題を提起する前に、round(2.5)2を返すことになっていることを確認しておきます。 round()がそのように動作するのには十分な理由があります。

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

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

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

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

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

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

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

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

>>>

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

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

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

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

>>>

>>> actual_value, truncated_value = 100, 100

次に、1,000,000秒(約11.5日)シミュレーションを実行します。 毎秒、randomモジュールのuniform()関数を使用して-0.050.05の間にランダムな値を生成し、actualtruncatedを更新します。 s:

>>>

>>> 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

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

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

ループの実行後にactual_value変数を調べるとわかるように、失われたのは約$ 3.55だけです。 ただし、truncated_valueを見ていたとしたら、ほとんどすべてのお金を失ったと思っていたでしょう。

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

Pythonのランダム性について詳しくは、Real PythonのGenerating Random Data in Python (Guide)をご覧ください。

round()が期待どおりに動作しないことを今のところ無視して、シミュレーションを再実行してみましょう。 今回はround()を使用して、各ステップで小数点以下3桁に丸め、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年代初頭に、Vancouver Stock Exchangeの値を記録するように設計されたシステムが、全体のインデックス値を四捨五入する代わりに小数点以下3桁に切り捨てたときにかなりの騒動を引き起こしました。 丸め誤差にはswayed electionsがあり、結果としてloss of lifeになります。

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

メソッドのメナジェリー

plethora of rounding strategiesがあり、それぞれに長所と短所があります。 このセクションでは、最も一般的な手法のいくつかと、それらがデータに与える影響について学習します。

切り捨て

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

切り捨て 結果

12.345

十の場所

10

12.345

ワンズプレイス

12

12.345

10分の1位

12.3

12.345

100分の1の場所

12.34

How Much Impact Can Rounding Have?セクションのtruncate()関数でこれを実装する1つの方法をすでに見てきました。 この関数では、次の方法で入力番号が小数点以下3桁に切り捨てられました。

  • 数値に1000を掛けて、小数点を3桁右にシフトします

  • その新しい数の整数部分をint()で取る

  • 1000で除算して、小数点以下を3桁左にシフトします。

このプロセスを一般化するには、1000を数値10ᵖ(10pの累乗)に置き換えます。ここで、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つ目の丸め戦略は、「切り上げ」と呼ばれます。この戦略は、常に指定された桁数に数値を切り上げます。 次の表に、この戦略の概要を示します。

切り上げ 結果

12.345

十の場所

20

12.345

ワンズプレイス

13

12.345

10分の1位

12.4

12.345

100分の1の場所

12.35

Pythonで「切り上げ」戦略を実装するには、mathモジュールのceil()関数を使用します。

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

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

数学では、ceiling functionと呼ばれる特別な関数が、すべての数値をその上限にマップします。 天井関数が整数を受け入れることができるように、整数の天井は整数自体になるように定義されています。 したがって、数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の小数点をn10 ** decimalsを掛けて、正しい桁数だけ右にシフトします。 この新しい値は、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.52に切り上げられる場合、-1.5-2に切り上げられる必要があります。

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

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

切り捨て

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

切り捨て 結果

12.345

十の場所

10

12.345

ワンズプレイス

12

12.345

10分の1位

12.3

12.345

100分の1の場所

12.34

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

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

幸運なことに、mathモジュールには、入力のフロアを返す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()関数isはゼロを中心に対称です。 これは、小数点を右にシフトした後、truncate()が残りの桁を切り落とすためです。 初期値が正の場合、これは数値を切り捨てることになります。 負の数は切り上げられます。 したがって、truncate(1.5)1を返し、truncate(-1.5)-1を返します。

対称性の概念は、丸めがデータセット内の数値データにどのように影響するかを説明するrounding biasの概念を導入します。

値は常に正の無限大の方向に切り上げられるため、「切り上げ」戦略にはround towards positive infinity biasがあります。 同様に、「切り捨て」戦略にはround towards negative infinity biasがあります。

「切り捨て」戦略は、正の値では負の無限大バイアスへのラウンド、負の値では正の無限大へのラウンドを示します。 この動作をする丸め関数は、一般にround towards zero biasを持つと言われています。

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

>>>

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

statistics.mean()関数を使用して、dataの値の平均値を計算してみましょう。

>>>

>>> import statistics

>>> statistics.mean(data)
-1.1083333333333332

次に、list comprehensionround_up()round_down()、およびtruncate()のそれぞれを適用して、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であり、実際の平均に最も近い値です。

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

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

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

たとえば、1.231.28の数値を小数点以下第1位に四捨五入するように求められた場合、おそらく1.21.3ですばやく応答します。 truncate()round_up()、およびround_down()関数は、このようなことは何もしません。

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

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

切り上げ

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

半分を切り上げる 結果

13.825

十の場所

10

13.825

ワンズプレイス

14

13.825

10分の1位

13.8

13.825

100分の1の場所

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.225100を掛けることです。 これが期待どおりに機能することを確認しましょう。

>>>

>>> -1.225 * 100
-122.50000000000001

まあ...それは間違っています! しかし、round_half_up(-1.225, 2)が-1.23を返す理由は説明されています。 round_half_up()アルゴリズムを段階的に続行し、REPLの_を利用して、各ステップで出力された最後の値を呼び出します。

>>>

>>> _ + 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にバグはありますか?

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

>>>

>>> 0.1 + 0.1 + 0.1
0.30000000000000004

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

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

たとえば、10進数0.1の10進数表現は有限ですが、2進数表現は無限です。 分数1/3は、無限に繰り返される小数0.333...として小数でしか表現できないのと同様に、分数1/10は、無限に繰り返される小数0.0001100110011...として2進数でのみ表現できます。

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

Pythonのドキュメントには、Floating Point Arithmetic: Issues and Limitationsというセクションがあり、0.1という数字について次のように述べています。

ほとんどのマシンでは、Pythonが0.1に格納されているバイナリ近似の真の10進値を出力する場合、次のように表示する必要があります。

>>>

>>> 0.1
0.1000000000000000055511151231257827021181583404541015625

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

>>>

>>> 1 / 10
0.1

印刷された結果は1/10の正確な値のように見えますが、実際に格納されている値は最も近い表現可能な2進小数であることに注意してください。 (Source

浮動小数点演算に関するより詳細な論文については、David Goldbergの記事What Every Computer Scientist Should Know About Floating-Point Arithmeticをチェックしてください。これは、元々ジャーナルACM Computing Surveys、Vol。 23、いいえ 1991年3月1日。

Pythonが-1.225 * 100-122.50000000000001であると言っているという事実は、浮動小数点表現エラーのアーティファクトです。 「わかりましたが、これを修正する方法はありますか?」と自問するかもしれません。自問するより良い質問は、「これを修正するためにneedですか?」です。

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

Pythonの標準のfloatクラスでアプリケーションに十分であると判断した場合は、浮動小数点表現エラーによるround_half_up()のエラーが発生する可能性はありません。

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

切り捨て

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

半分に丸める 結果

13.825

十の場所

10

13.825

ワンズプレイス

14

13.825

10分の1位

13.8

13.825

100分の1の場所

13.82

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

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()の両方にバイアスin generalはありません。 ただし、多くの関係を持つデータを丸めると、バイアスが生じます。 極端な例として、次の数字のリストを検討してください。

>>>

>>> 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つの方法は、常にゼロからタイを丸めることです。 次の表に、この仕組みを示します。

ゼロから半分までのラウンド 結果

15.25

十の場所

20

15.25

ワンズプレイス

15

15.25

10分の1位

15.3

-15.25

十の場所

-20

-15.25

ワンズプレイス

-15

-15.25

10分の1位

-15.3

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

  1. nが正で、d >= 5の場合、切り上げます

  2. nが正で、d < 5の場合は、切り捨てます

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

  4. nが負で、d < 5の場合は、切り上げます

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

数値ndecimalsの値が与えられた場合、round_half_up()round_half_down()を使用してPythonでこれを実装できます。

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

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

Pythonの組み込みabs()関数を使用して最初にnの絶対値を取得する場合は、round_half_up()を使用して数値を丸めることができます。 次に、丸めた数値にnと同じ符号を付けるだけです。 これを行う1つの方法は、math.copysign()関数を使用することです。

math.copysign()は2つの数値abを取り、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()では、nの絶対値はround_half_up()を使用して小数点以下decimalsに丸められ、この結果は変数rounded_absに割り当てられます。 次に、nの元の符号がmath.copysign()を使用して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の数値の平均値は、dataの各数値をround_half_away_from_zero()で小数点以下1桁に丸めると、ほぼ正確に保持されます。

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

正と負の関係の数が大幅に異なる状況をどのように処理しますか? この質問への答えは、この記事の冒頭で私たちをだましていた関数、Pythonの組み込みround()関数に完全に循環します。

半分から偶数への丸め

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

半分から均等に 結果

15.255

十の場所

20

15.255

ワンズプレイス

15

15.255

10分の1位

15.3

15.255

100分の1の場所

15.26

「半分を均等にする戦略」は、Pythonの組み込みround()関数で使用される戦略であり、default rounding rule in the IEEE-754 standardです。 この戦略は、切り捨てまたは切り上げられるデータセットの同点の確率が等しいという仮定の下で機能します。 実際には、これが通常のケースです。

これで、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()関数にはバイアスがほとんどありませんが、完全ではありません。 たとえば、データセットのタイの大部分が切り捨てではなく偶数に切り上げられた場合、丸めバイアスが導入される可能性があります。 バイアスを「半分から偶数に丸める」do existよりもさらに効果的に軽減する戦略ですが、ややあいまいで、極端な状況でのみ必要です。

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

>>>

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

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

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

Decimalクラス

Pythonのdecimalモジュールは、Pythonを初めて使用する場合は気付かない可能性のある、言語の「バッテリーに含まれる」機能の1つです。 decimalモジュールの基本原則は、ドキュメントに記載されています。

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

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

  • Exact decimal representation0.1actually0.1であり、0.1 + 0.1 + 0.1 - 0.3は予想どおり0を返します。

  • Preservation of significant digits1.202.50を追加すると、結果は3.70になり、重要性を示すために末尾のゼロが維持されます。

  • User-alterable precisiondecimalモジュールのデフォルトの精度は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')

Note:浮動小数点数から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モジュールの正確な小数表現のおかげで、Decimalクラスでこの問題が発生することはありません。

>>>

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

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

>>>

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

精度を変更するには、decimal.getcontext()を呼び出し、.prec属性を設定します。 関数呼び出しで属性を設定するのが奇妙に見える場合は、.getcontext()decimalで使用されるデフォルトのパラメーターを含む現在の内部コンテキストを表す特別なContextオブジェクトを返すため、これを行うことができます。モジュール。

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

デフォルトの丸め戦略を変更するには、decimal.getcontect().roundingプロパティをいくつかのflagsのいずれかに設定します。 次の表は、これらのフラグと、それらが実装する丸め戦略をまとめたものです。

Flag 丸め戦略

decimal.ROUND_CEILING

切り上げ

decimal.ROUND_FLOOR

切り捨て

decimal.ROUND_DOWN

切り捨て

decimal.ROUND_UP

ゼロからの丸め

decimal.ROUND_HALF_UP

ゼロから半分に丸める

decimal.ROUND_HALF_DOWN

半分をゼロに丸める

decimal.ROUND_HALF_EVEN

半分を偶数に丸めます

decimal.ROUND_05UP

切り上げとゼロへの切り上げ

最初に気付くのは、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_DOWNROUND_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_up()とは異なり、round_half_away_from_zero()と同じように機能することに注意してください。

ゼロに向かって丸めることによって関係を断ち切る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.40または5で終わらないため、そのままにしておきます。 一方、1.51は、小数点以下第2位でゼロに向かって丸められるため、1.5という数値になります。 これは5で終わるため、小数点以下第1位はゼロから1.6に丸められます。

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

Decimalの詳細については、PythonドキュメントのQuick-start Tutorialを確認してください。

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

NumPy配列の丸め

data scienceおよび科学計算のドメインでは、データをNumPy arrayとして保存することがよくあります。 NumPyの最も強力な機能の1つは、vectorization and broadcastingを使用して、一度に1つの要素ではなく、配列全体に一度に操作を適用することです。

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

>>>

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

>>> data = np.random.randn(3, 4)
>>> data

まず、出力を簡単に再現できるように、np.randomモジュールをシードします。 次に、浮動小数点数の3×4 NumPy配列がnp.random.randn()で作成されます。

Note:環境にNumPyがまだない場合は、上記のコードをREPLに入力する前にpip3 install numpyを実行する必要があります。 Anacondaを使用してPythonをインストールした場合は、すでに設定されています。

これまでにNumPyを使用したことがない場合は、ここReal PythonのBradSolomonのLook Ma, No For-Loops: Array Programming With NumPyGetting Into Shapeセクションで簡単な紹介を得ることができます。

NumPyのrandomモジュールの詳細については、BradのGenerating Random Data in Python (Guide)PRNG’s for Arraysセクションを確認してください。

data配列のすべての値を丸めるには、np.around()関数の引数としてdataを渡すことができます。 必要な小数点以下の桁数は、decimalsキーワード引数で設定されます。 Pythonの組み込みのround()関数と同じように、四捨五入の戦略が使用されます。

たとえば、次のように、dataのすべての値を小数点以下3桁に丸めます。

>>>

>>> np.around(data, decimals=3)

np.around()` is at the mercy of floating-point representation error, just like `+round()` is.

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

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

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

>>>

>>> np.ceil(data)

ねえ、私たちは新しい番号を発見しました! 負のゼロ!

実際、IEEE-754標準では、正と負の両方のゼロを実装する必要があります。 このようなものにはどのような用途がありますか? ウィキペディアは答えを知っています:

非公式には、ゼロに丸められた負の値に「−0」という表記を使用できます。 この表記は、負の符号が重要な場合に役立ちます。たとえば、摂氏温度を表にした場合、マイナス記号は氷点下を意味します。 (Source

すべての値を最も近い整数に切り捨てるには、np.floor()を使用します。

>>>

>>> np.floor(data)

np.trunc()を使用して、各値を整数コンポーネントに切り捨てることもできます。

>>>

>>> np.trunc(data)

最後に、「半分を偶数に丸める」戦略を使用して最も近い整数に丸めるには、np.rint()を使用します。

>>>

>>> np.rint(data)

前に説明した丸め戦略の多くがここにないことに気付いたかもしれません。 ほとんどの場合、必要なのは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のvectorized operationsのおかげで、これは期待どおりに機能します。

>>>

>>> round_half_up(data, decimals=2)

NumPyの丸めマスターになったので、Pythonの他のデータサイエンスの大物であるPandasライブラリを見てみましょう。

パンダの丸めSeriesおよびDataFrame

Pandasライブラリは、Pythonで作業するデータサイエンティストやデータアナリストの定番となっています。 RealPython自身のJoeWyndhamの言葉によると:

Pandasは、特にExcelやVBAよりも強力なものを探していたためにPythonを利用した場合、データサイエンスと分析のゲームチェンジャーです。 (Source

Note:続行する前に、環境にまだ持っていない場合はpip3 install pandasにする必要があります。 NumPyの場合と同様に、Anacondaを使用してPythonをインストールした場合は、準備ができているはずです。

2つの主要なPandasデータ構造は、非常に大まかに言えばExcelスプレッドシートのように機能するDataFrameと、スプレッドシートの列と考えることができるSeriesです。 SeriesオブジェクトとDataFrameオブジェクトはどちらも、Series.round()メソッドとDataFrame.round()メソッドを使用して効率的に丸めることができます。

>>>

>>> import pandas as pd

>>> # Re-seed np.random if you closed your REPL since the last example
>>> 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
          A         B         C
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

DataFrame.round()メソッドは、ディクショナリまたはSeriesを受け入れて、列ごとに異なる精度を指定することもできます。 たとえば、次の例は、dfの最初の列を小数点以下1桁に、2番目を小数点以下2桁に、3番目を小数点以下3桁に丸める方法を示しています。

>>>

>>> # Specify column-by-column precision with a dictionary
>>> df.round({"A": 1, "B": 2, "C": 3})
     A     B      C
0 -0.9 -1.14 -0.542
1 -0.5  0.21  0.213
2  1.3 -0.81 -3.303

>>> # Specify column-by-column precision with a Series
>>> decimals = pd.Series([1, 2, 3], index=["A", "B", "C"])
>>> df.round(decimals)
     A     B      C
0 -0.9 -1.14 -0.542
1 -0.5  0.21  0.213
2  1.3 -0.81 -3.303

より丸めの柔軟性が必要な場合は、NumPyのfloor()ceil()、およびrint()関数をPandasSeriesおよびDataFrameオブジェクトに適用できます。

>>>

>>> 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

前のセクションで変更されたround_half_up()関数は、ここでも機能します。

>>>

>>> 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

おめでとうございます。丸めの習得に向けて順調に進んでいます。 タコスの組み合わせよりも、数値を丸める方法がたくさんあることがわかりました。 (まあ…多分そうではありません!)純粋なPythonで多数の丸め戦略を実装でき、NumPy配列とPandasのSeriesおよびDataFrameオブジェクトの丸めに関するスキルを磨きました。

もう1つのステップがあります。それは、適切な戦略をいつ適用するかを知ることです。

アプリケーションとベストプラクティス

妙技を丸める道の最後の部分は、新しく見つけた知識をいつ適用するかを理解することです。 このセクションでは、数値を正しい方法で丸めることを確認するためのいくつかのベストプラクティスを学習します。

より多くのストアとラウンドレイト

大量のデータセットを処理する場合、ストレージが問題になる可能性があります。 ほとんどのリレーショナルデータベースでは、テーブルの各列は特定のデータ型を格納するように設計されており、数値データ型にはメモリを節約するために精度が割り当てられることがよくあります。

たとえば、温度センサーは、長時間稼働する工業用オーブンの温度を10秒ごとに小数点以下8桁まで正確に報告する場合があります。 これからの読み取り値は、発熱体またはその他のコンポーネントの故障を示す可能性のある温度の異常な変動を検出するために使用されます。 そのため、各受信読み取り値を最後の読み取り値と比較して大きな変動をチェックするPythonスクリプトが実行されている可能性があります。

このセンサーからの読み取り値もSQLデータベースに保存されるため、オーブン内の1日の平均温度を毎日深夜に計算できます。 オーブン内の発熱体の製造元は、毎日の平均気温が通常より.05度下がったときにコンポーネントを交換することを推奨しています。

この計算では、小数点以下3桁の精度しか必要ありません。 しかし、バンクーバー証券取引所での事件から、精度を下げすぎると計算に大きな影響を与える可能性があることがわかります。

使用可能なスペースがある場合は、データを完全な精度で保存する必要があります。 ストレージが問題になる場合は、経験則として、計算に必要な精度よりも小数点以下2桁または3桁以上を格納することをお勧めします。

最後に、1日の平均気温を計算するときは、利用可能な最高の精度で計算し、最終的な答えを丸める必要があります。

現地通貨の規制に従う

コーヒーショップでコーヒーを2.40ドルで注文すると、通常、販売者は必要な税金を追加します。 その税額は地理的にどこにいるかによって大きく異なりますが、議論のために、6%だとしましょう。 追加される税金は$ 0.144になります。 これを$ 0.15に切り上げるか、$ 0.14に切り下げる必要がありますか? 答えはおそらく地方自治体によって定められた規制に依存します!

このような状況は、ある通貨を別の通貨に変換するときにも発生する可能性があります。 1999年に、通貨をユーロに変換する際の欧州経済財務委員会codified the use of the “rounding half away from zero” strategyですが、他の通貨では異なる規制が採用されている可能性があります。

別のシナリオである“Swedish rounding”は、国の会計レベルでの最小通貨単位が物理通貨の最小単位よりも小さい場合に発生します。 たとえば、コーヒー1杯の税引き後の価格が2.54ドルであるのに、1セント硬貨が流通していない場合、どうしますか。 購入者は正確な金額を把握できず、販売者は正確な変更を行うことができません。

このような状況がどのように処理されるかは、通常、国の政府によって決定されます。 さまざまな国で使用されている丸め方法のリストは、Wikipediaにあります。

通貨を計算するためのソフトウェアを設計している場合は、ユーザーの場所にある地域の法律や規制を常に確認する必要があります。

疑わしいときは、丸い結びつき

複雑な計算で使用される大規模なデータセットの数値を丸める場合、主な懸念事項は、丸めによるエラーの増加を制限することです。

この記事で説明したすべての方法の中で、「半分から偶数に丸める」戦略は、丸めバイアスを最小限に抑えます。 幸い、Python、NumPy、Pandasはすべてデフォルトでこの戦略を採用しているため、組み込みの丸め関数を使用することで、すでに十分に保護されています。

概要

こんにちは! これはなんという旅だ​​ったのでしょう。

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

  • さまざまな丸め戦略があり、純粋なPythonで実装する方法がわかりました。

  • すべての丸め戦略は本質的に丸めバイアスを導入し、「半分から偶数に丸める」戦略はほとんどの場合、このバイアスを十分に軽減します。

  • コンピュータが浮動小数点数をメモリに格納する方法では、当然、微妙な丸め誤差が発生しますが、Pythonの標準ライブラリのdecimalモジュールを使用してこれを回避する方法を学びました。

  • NumPy配列とパンダのSeriesおよびDataFrameオブジェクトを丸めることができます。

  • 実際のデータで丸めるためのベストプラクティスがあります。

__ Take the Quiz:インタラクティブな「Pythonの四捨五入」クイズで知識をテストします。 完了すると、学習の進捗状況を経時的に追跡できるようにスコアを受け取ります。

詳細を学び、私たちがカバーしたすべての詳細を掘り下げることに興味がある場合は、以下のリンクでかなり長い間忙しくなります。

少なくとも、この記事を楽しんで、そこから何か新しいことを学んだ場合は、それを友人やチームメンバーに渡してください。 コメントであなたの考えを私たちと共有してください。 あなた自身の丸め関連のバトルストーリーのいくつかを聞いてみたいです!

ハッピーパイソン!

追加のリソース

丸め戦略とバイアス:

浮動小数点および小数の仕様:

興味深い読み物: