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には、n
とndigits
の2つの数値引数を取り、ndigits
に丸められた数値n
を返す組み込みのround()
関数があります。 ndigits
引数のデフォルトはゼロであるため、これを省略すると、数値は整数に丸められます。 ご覧のとおり、round()
は期待どおりに機能しない可能性があります。
ほとんどの人が数字を丸める方法は次のようになります。
最初に
n
の小数点をp
桁でシフトし、n
に10ᵖを掛けて(10を%に上げて)、数値n
をp
の小数点以下の桁数に丸めます。 (t5)s。次に、
m
の小数点以下第1位の数字d
を確認します。d
が5未満の場合、m
を最も近い整数に切り捨てます。 それ以外の場合は、m
を切り上げます。最後に、
m
を10ᵖで割って、小数点をp
の桁に戻します。
それは簡単なアルゴリズムです! たとえば、最も近い整数に丸められた数値2.5
は3
です。 小数点以下第1位に四捨五入された1.64
の数は1.6
です。
次に、インタプリタセッションを開き、Pythonの組み込み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位以降をすべて切り捨てることにします。 専門用語の丸めでは、これはtruncatingと呼ばれ、小数点以下第3位までの数値です。 ここではいくつかのエラーが予想されますが、小数点以下3桁を維持することで、このエラーは重大なものにはなりません。 右?
Pythonを使用して実験を実行するには、数値を小数点以下3桁に切り捨てるtruncate()
関数を作成することから始めましょう。
>>>
>>> def truncate(n):
... return int(n * 1000) / 1000
truncate()
関数は、最初に数値n
の小数点を3桁右にシフトし、n
に1000
を掛けることによって機能します。 この新しい数値の整数部分は、int()
で取得されます。 最後に、n
を1000
で除算することにより、小数点が3桁左にシフトされます。
次に、シミュレーションの初期パラメーターを定義します。 2つの変数が必要です。1つはシミュレーションが完了した後の実際の株価を追跡するため、もう1つは各ステップで小数点以下3桁に切り捨てた後の株価の値です。
これらの変数を100
に初期化することから始めます。
>>>
>>> actual_value, truncated_value = 100, 100
次に、1,000,000秒(約11.5日)シミュレーションを実行します。 毎秒、random
モジュールのuniform()
関数を使用して-0.05
と0.05
の間にランダムな値を生成し、actual
とtruncated
を更新します。 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
シミュレーションの要点は、0
と999,999
の間の数値のrange(1000000)
をループするfor
ループで行われます。 各ステップでrange()
から取得した値は、変数_
に格納されます。これは、ループ内では実際にはこの値を必要としないため、ここで使用します。
ループの各ステップで、-0.05
と0.05
の間の新しい乱数が、random.randn()
を使用して生成され、変数randn
に割り当てられます。 投資の新しい値は、randn
をactual_value
に加算することによって計算され、切り捨てられた合計は、randn
をtruncated_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ᵖ(10
をpの累乗)に置き換えます。ここで、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 |
ceil()
関数の名前は、「天井」という用語から取得されます。これは、数学で、指定された数値以上の最も近い整数を表すために使用されます。
整数ではない数値はすべて、2つの連続する整数の間にあります。 たとえば、数値1.2
は、1
と2
の間の間隔にあります。 「天井」は、間隔の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
の小数点をn
に10 ** 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.5
が2
に切り上げられる場合、-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(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 comprehensionのround_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.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
に関して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.225
に100
を掛けることです。 これが期待どおりに機能することを確認しましょう。
>>>
>>> -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つのケースがあります。
-
n
が正で、d >= 5
の場合、切り上げます -
n
が正で、d < 5
の場合は、切り捨てます -
n
が負で、d >= 5
の場合は、切り捨てます -
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の組み込みabs()
関数を使用して最初にn
の絶対値を取得する場合は、round_half_up()
を使用して数値を丸めることができます。 次に、丸めた数値にn
と同じ符号を付けるだけです。 これを行う1つの方法は、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()
では、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 representation:
0.1
はactually0.1
であり、0.1 + 0.1 + 0.1 - 0.3
は予想どおり0
を返します。 -
Preservation of significant digits:
1.20
と2.50
を追加すると、結果は3.70
になり、重要性を示すために末尾のゼロが維持されます。 -
User-alterable precision:
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')
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.23
と2.32
を追加しましょう。
>>>
>>> decimal.getcontext().prec = 2
>>> Decimal("1.23") + Decimal("2.32")
Decimal('3.6')
精度を変更するには、decimal.getcontext()
を呼び出し、.prec
属性を設定します。 関数呼び出しで属性を設定するのが奇妙に見える場合は、.getcontext()
がdecimal
で使用されるデフォルトのパラメーターを含む現在の内部コンテキストを表す特別なContext
オブジェクトを返すため、これを行うことができます。モジュール。
1.23
と2.32
の正確な値は3.55
です。 精度が2桁になり、丸め戦略がデフォルトの「半分から偶数に丸める」に設定されているため、値3.55
は自動的に3.6
に丸められます。
デフォルトの丸め戦略を変更するには、decimal.getcontect().rounding
プロパティをいくつかのflagsのいずれかに設定します。 次の表は、これらのフラグと、それらが実装する丸め戦略をまとめたものです。
Flag | 丸め戦略 |
---|---|
|
切り上げ |
|
切り捨て |
|
切り捨て |
|
ゼロからの丸め |
|
ゼロから半分に丸める |
|
半分をゼロに丸める |
|
半分を偶数に丸めます |
|
切り上げとゼロへの切り上げ |
最初に気付くのは、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_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.4
は0
または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 NumPyのGetting 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の四捨五入」クイズで知識をテストします。 完了すると、学習の進捗状況を経時的に追跡できるようにスコアを受け取ります。
詳細を学び、私たちがカバーしたすべての詳細を掘り下げることに興味がある場合は、以下のリンクでかなり長い間忙しくなります。
少なくとも、この記事を楽しんで、そこから何か新しいことを学んだ場合は、それを友人やチームメンバーに渡してください。 コメントであなたの考えを私たちと共有してください。 あなた自身の丸め関連のバトルストーリーのいくつかを聞いてみたいです!
ハッピーパイソン!
追加のリソース
丸め戦略とバイアス:
浮動小数点および小数の仕様:
-
IEEE-754、ウィキペディア
興味深い読み物:
-
What Every Computer Scientist Should Know About Floating-Point Arithmetic、David Goldberg、ACM Computing Surveys、1991年3月
-
Floating Point Arithmetic: Issues and Limitations、python.orgから
-
Why Python’s Integer Division Floors、Guido vanRossum作