NumPy配列ndarrayの符号(正負)を取得・判定・置換

Modified: | Tags: Python, NumPy

NumPy配列numpy.ndarrayの符号(正負、プラス・マイナス)に関する処理について、以下の内容を説明する。

  • NumPy配列ndarrayの符号を取得: np.sign()
    • 基本的な使い方
    • 負のゼロや無限大inf、欠損値nanの場合
    • 複素数の場合
  • NumPy配列ndarrayの符号を判定: np.signbit()
    • 基本的な使い方
    • 比較演算子で判定
    • 符号ごとに要素数をカウント
    • 負のゼロや無限大inf、欠損値nanの場合
    • 複素数の場合
  • NumPy配列ndarrayの符号を別の配列のものに置換: np.copysign()
    • 基本的な使い方
    • ブロードキャスト
    • 負のゼロや無限大inf、欠損値nanの場合
    • 複素数の場合

NumPyを使わずにsign()copysign()の処理を行いたい場合は以下の記事を参照。

NumPy配列ndarrayの符号を取得: np.sign()

numpy.sign()関数でnumpy.ndarrayの符号を取得できる。

基本的な使い方

numpy.sign()の引数にnumpy.ndarrayを指定する。負の値は-1、正の値は100となるnumpy.ndarrayが返される。

import numpy as np

a = np.array([-100, -10, 0, 10, 100])
print(a)
# [-100  -10    0   10  100]

print(np.sign(a))
# [-1 -1  0  1  1]

print(type(np.sign(a)))
# <class 'numpy.ndarray'>

print(np.sign(a).dtype)
# int64

データ型dtypeは元のnumpy.ndarrayと同じ。浮動小数点数floatの場合はfloat

a_float = np.array([-1.23, 0.0, 1.23])
print(a_float)
# [-1.23  0.    1.23]

print(np.sign(a_float))
# [-1.  0.  1.]

print(np.sign(a_float).dtype)
# float64

スカラー値に対してはスカラー値を返す。この場合も元の型と同じ型となる。

print(np.sign(100))
# 1

print(type(np.sign(100)))
# <class 'numpy.int64'>

print(np.sign(-1.23))
# -1.0

print(type(np.sign(-1.23)))
# <class 'numpy.float64'>

負のゼロや無限大inf、欠損値nanの場合

浮動小数点数floatでは負のゼロ(= -0.0)を表現できる。整数intのゼロには正負はない。

numpy.sign()は正負いずれの0.0に対しても0.0を返す。

無限大infはその符号、欠損値nannanとなる。np.infnp.nanは浮動小数点数floatなので返り値もfloat

a_special = np.array([0.0, -0.0, np.inf, -np.inf, np.nan])
print(a_special)
# [  0.  -0.  inf -inf  nan]

print(np.sign(a_special))
# [ 0.  0.  1. -1. nan]

print(np.sign(a_special).dtype)
# float64

複素数の場合

複素数の場合、実部が0でなければ実部の符号、実部が0の場合は虚部の符号が返される。返り値はすべて虚部が0の複素数。0, nanも実部が0, nanで虚部が0の複素数となる。

a_complex = np.array([[10 + 10j, -10 + 10j], [10 - 10j, -10 - 10j], [10, -10], [10j, -10j], [0, np.nan], [0j, np.nan * 1j]])
print(a_complex)
# [[ 10.+10.j -10.+10.j]
#  [ 10.-10.j -10.-10.j]
#  [ 10. +0.j -10. +0.j]
#  [  0.+10.j  -0.-10.j]
#  [  0. +0.j  nan +0.j]
#  [  0. +0.j  nan+nanj]]

print(np.sign(a_complex))
# [[ 1.+0.j -1.+0.j]
#  [ 1.+0.j -1.+0.j]
#  [ 1.+0.j -1.+0.j]
#  [ 1.+0.j -1.+0.j]
#  [ 0.+0.j nan+0.j]
#  [ 0.+0.j nan+0.j]]

実部、虚部それぞれの符号を取得したい場合はreal, imag属性を使う。

print(a_complex.real)
# [[ 10. -10.]
#  [ 10. -10.]
#  [ 10. -10.]
#  [  0.  -0.]
#  [  0.  nan]
#  [  0.  nan]]

print(np.sign(a_complex.real))
# [[ 1. -1.]
#  [ 1. -1.]
#  [ 1. -1.]
#  [ 0.  0.]
#  [ 0. nan]
#  [ 0. nan]]

print(a_complex.imag)
# [[ 10.  10.]
#  [-10. -10.]
#  [  0.   0.]
#  [ 10. -10.]
#  [  0.   0.]
#  [  0.  nan]]

print(np.sign(a_complex.imag))
# [[ 1.  1.]
#  [-1. -1.]
#  [ 0.  0.]
#  [ 1. -1.]
#  [ 0.  0.]
#  [ 0. nan]]

NumPy配列ndarrayの符号を判定: np.signbit()

numpy.signbit()関数でnumpy.ndarrayの符号を判定できる。名前の通り、符号ビット(sign bit)をブール値で返す。負の値がTrue0および正の値がFalseとなる。

後述のように、比較演算子を使っても同様の処理が可能。

基本的な使い方

numpy.signbit()の引数にnumpy.ndarrayを指定する。負の値がTrue0および正の値がFalseとなるnumpy.ndarrayが返される。

a = np.array([-100, -10, 0, 10, 100])
print(a)
# [-100  -10    0   10  100]

print(np.signbit(a))
# [ True  True False False False]

print(type(np.signbit(a)))
# <class 'numpy.ndarray'>

print(np.signbit(a).dtype)
# bool

スカラー値に対してはスカラー値を返す。

print(np.signbit(-100))
# True

比較演算子で判定

同様の処理は比較演算子を使ってもできる。

print(a == 0)
# [False False  True False False]

print(a > 0)
# [False False False  True  True]

print(a >= 0)
# [False False  True  True  True]

print(a < 0)
# [ True  True False False False]

print(a <= 0)
# [ True  True  True False False]

符号ごとに要素数をカウント

numpy.count_nonzero()関数の引数にboolを要素とするnumpy.ndarrayを指定するとTrueの数をカウントできる。sum()でもよい。

したがって、numpy.signbit()の結果をnumpy.count_nonzero()の引数に指定すると、True、すなわち、負の値の数をカウントできる。

print(np.count_nonzero(np.signbit(a)))
# 2

~bool値の各要素の否定も可能。False、すなわち、0および正の値の数をカウントできる。

print(~np.signbit(a))
# [False False  True  True  True]

print(np.count_nonzero(~np.signbit(a)))
# 3

比較演算子を使っても同様の処理が可能。

print(np.count_nonzero(a == 0))
# 1

print(np.count_nonzero(a < 0))
# 2

print(np.count_nonzero(a > 0))
# 2

負のゼロや無限大inf、欠損値nanの場合

numpy.signbit()では、浮動小数点数floatのゼロおよび無限大infはその符号を元に判定され、欠損値nanFalseとなる。

a_special = np.array([0.0, -0.0, np.inf, -np.inf, np.nan])
print(a_special)
# [  0.  -0.  inf -inf  nan]

print(np.signbit(a_special))
# [False  True False  True False]

0との比較演算の結果は以下の通り。

print(a_special == 0)
# [ True  True False False False]

print(a_special < 0)
# [False False False  True False]
# 
# /usr/local/lib/python3.7/site-packages/ipykernel_launcher.py:1: RuntimeWarning: invalid value encountered in less
#   """Entry point for launching an IPython kernel.

print(a_special > 0)
# [False False  True False False]
# 
# /usr/local/lib/python3.7/site-packages/ipykernel_launcher.py:1: RuntimeWarning: invalid value encountered in greater
#   """Entry point for launching an IPython kernel.

正負いずれの00と等価とみなされる。また、欠損値nanはすべての比較演算に対してFalseとなる。

環境によっては、上の例のように、要素数が2個以上で欠損値nanを含むnumpy.ndarrayの比較演算に対して警告が出る場合がある。エラーではないので処理は中断されない。

複素数の場合

複素数を要素とするnumpy.ndarraynumpy.signbit()の対象外。エラーとなる。

a_complex = np.array([3 + 4j, -3 - 4j])
print(a_complex)
# [ 3.+4.j -3.-4.j]

# print(np.signbit(a_complex))
# TypeError: ufunc 'signbit' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''

np.abs()で絶対値、real, imag属性で実部・虚部を取得できる。それらに対する処理はもちろん可能。

print(np.abs(a_complex))
# [5. 5.]

print(a_complex.real)
# [ 3. -3.]

print(a_complex.imag)
# [ 4. -4.]

print(np.signbit(a_complex.real))
# [False  True]

print(a_complex.real < 0)
# [False  True]

NumPy配列ndarrayの符号を別の配列のものに置換: np.copysign()

numpy.copysign()で、あるnumpy.ndarrayの符号を別のnumpy.ndarrayのものに置き換えることができる。

基本的な使い方

第一引数、第二引数にそれぞれnumpy.ndarrayを指定する。

第一引数の符号が第二引数の符号に置き換えられる。引数のデータ型によらず、返り値のデータ型は常に浮動小数点数float

a = np.arange(12).reshape(3, 4)
print(a)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

b = np.arange(-5, 7).reshape(3, 4)
print(b)
# [[-5 -4 -3 -2]
#  [-1  0  1  2]
#  [ 3  4  5  6]]

a_copysign = np.copysign(a, b)
print(a_copysign)
# [[-0. -1. -2. -3.]
#  [-4.  5.  6.  7.]
#  [ 8.  9. 10. 11.]]

print(a_copysign.dtype)
# float64

スカラー値でもOK。この場合もfloatが返される。

print(np.copysign(10, -5))
# -10.0

print(type(np.copysign(10, -5)))
# <class 'numpy.float64'>

ブロードキャスト

形状shapeが異なるnumpy.ndarray同士の演算では、可能な場合はブロードキャストによって形状が揃えられる。

print(a)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

b_small = np.array([-100, -100, 100, 100])
print(b_small)
# [-100 -100  100  100]

print(a + b_small)
# [[-100  -99  102  103]
#  [ -96  -95  106  107]
#  [ -92  -91  110  111]]

numpy.copysign()においてもブロードキャストが行われる。

print(np.copysign(a, b_small))
# [[-0. -1.  2.  3.]
#  [-4. -5.  6.  7.]
#  [-8. -9. 10. 11.]]

ブロードキャストできない場合はエラーとなる。

b_mismatch = np.array([-100, -100, 100])
print(b_mismatch)
# [-100 -100  100]

# print(np.copysign(a, b_mismatch))
# ValueError: operands could not be broadcast together with shapes (3,4) (3,) 

numpy.copysign()の第二引数にはスカラー値も指定できる。

print(np.copysign(b, -10))
# [[-5. -4. -3. -2.]
#  [-1. -0. -1. -2.]
#  [-3. -4. -5. -6.]]

第二引数にスカラー値を指定した場合は全てに要素がその符号に揃えられるが、絶対値を返すnp.abs()を使って同じように要素全体の符号を揃えることもできる。numpy.copysign()の返り値はfloatだが、np.abs()を使うとそれぞれのデータ型に応じた型となる。

print(np.abs(b) * -1)
# [[-5 -4 -3 -2]
#  [-1  0 -1 -2]
#  [-3 -4 -5 -6]]

print(np.abs(b) * -1.0)
# [[-5. -4. -3. -2.]
#  [-1. -0. -1. -2.]
#  [-3. -4. -5. -6.]]

負の0や無限大inf、欠損値nanの場合

浮動小数点数floatのゼロや無限大infは符号を持つのでそのほかの値と同じように扱われる。

第一引数がnanである場合は第二引数によらずnanのまま。

a_special = np.array([0.0, -0.0, np.inf, -np.inf, np.nan])
print(a_special)
# [  0.  -0.  inf -inf  nan]

print(np.copysign(a_special, 1))
# [ 0.  0. inf inf nan]

print(np.copysign(a_special, -1))
# [ -0.  -0. -inf -inf  nan]

第二引数がnanである場合は正となる。

print(np.copysign([10, 10, 10, 10, 10], a_special))
# [ 10. -10.  10. -10.  10.]

print(np.copysign([-10, -10, -10, -10, -10], a_special))
# [ 10. -10.  10. -10.  10.]

第二引数に整数int0を指定した場合は正として扱われる。

print(np.copysign(10, 0))
# 10.0

numpy.copysign()は常に浮動小数点数floatを返すので、第一引数が整数の0でも第二引数に応じて正負いずれかのゼロ(0.0または-0.0)となる。

print(np.copysign(0, 10))
# 0.0

print(np.copysign(0, -10))
# -0.0

複素数の場合

numpy.copysign()は複素数には対応していない。第一引数に指定しても第二引数に指定してもエラーとなる。

a_complex = np.array([10 + 10j, -10 + 10j])
print(a_complex)
# [ 10.+10.j -10.+10.j]

# print(np.copysign(a_complex, 1))
# TypeError: ufunc 'copysign' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''

# print(np.copysign([1, 1], a_complex))
# TypeError: ufunc 'copysign' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''

関連カテゴリー

関連記事