note.nkmk.me

NumPyのデータ型dtype一覧とastypeによる変換(キャスト)

Date: 2018-03-11 / tags: Python, NumPy

NumPy配列ndarrayはデータ型dtypeを保持しており、np.array()ndarrayオブジェクトを生成する際に指定したり、astype()メソッドで変更したりすることができる。

基本的には一つのndarrayオブジェクトに対して一つのdtypeが設定されていて、すべての要素が同じデータ型となる。

一つのndarrayで複数のデータ型を扱うためのStructured Data(構造化データ)という仕組みも用意されているが、ここでは触れない。複数のデータ型を含む配列(数値の列と文字列の列を含む二次元配列など)を処理するにはpandasのほうが便利。

ここでは、

  • NumPyの主要なデータ型dtype一覧
  • 文字列の文字数についての注意
  • object型: Pythonオブジェクトへのポインターを格納
  • astype()によるデータ型dtypeの変換(キャスト)
  • astype()で変換(キャスト)する際の注意点

について、サンプルコードとともに説明する。

pandasのデータ型dtypeastype()については以下の記事を参照。

スポンサーリンク

NumPyの主要なデータ型dtype一覧

主要なデータ型dtypeは以下の通り。

データ型dtype 型コード 説明
int8 i1 符号あり8ビット整数型
int16 i2 符号あり16ビット整数型
int32 i4 符号あり32ビット整数型
int64 i8 符号あり64ビット整数型
uint8 u1 符号なし8ビット整数型
uint16 u2 符号なし16ビット整数型
uint32 u4 符号なし32ビット整数型
uint64 u8 符号なし64ビット整数型
float16 f2 半精度浮動小数点型(符号部1ビット、指数部5ビット、仮数部10ビット)
float32 f4 単精度浮動小数点型(符号部1ビット、指数部8ビット、仮数部23ビット)
float64 f8 倍精度浮動小数点型(符号部1ビット、指数部11ビット、仮数部52ビット)
float128 f16 四倍精度浮動小数点型(符号部1ビット、指数部15ビット、仮数部112ビット)
complex64 c8 複素数(実部・虚部がそれぞれfloat32
complex128 c16 複素数(実部・虚部がそれぞれfloat64
complex256 c32 複素数(実部・虚部がそれぞれfloat128
bool ? ブール型(True or False
unicode U Unicode文字列
object O Pythonオブジェクト型

データ型名の末尾の数字はbitで表し、型コード末尾の数字はbyteで表す。同じ型でも値が違うので注意。

また、bool型の型コード?は不明という意味ではなく文字通り?が割り当てられている。

各種メソッドの引数でデータ型dtypeを指定するとき、例えばint64型の場合は、

  • np.int64
  • 文字列'int64'
  • 型コードの文字列'i8'

のいずれでもOK。

import numpy as np

a = np.array([1, 2, 3], dtype=np.int64)
print(a.dtype)
# int64

a = np.array([1, 2, 3], dtype='int64')
print(a.dtype)
# int64

a = np.array([1, 2, 3], dtype='i8')
print(a.dtype)
# int64

ビット精度の数値を省略してintfloat, strのようなPythonの型で指定することもできる。

この場合、等価なdtypeに自動的に変換されるが、どのdtypeに変換されるかは環境(Python2かPython3か、32ビットか64ビットかなど)によって異なる。

以下の例はPython3、64ビット環境のもの。uintというPythonの型はないが便宜上まとめて挙げておく。

Pythonの型 等価なdtypeの例
int int64
float float64
str unicode
uint uint64

引数で指定する場合はintでも文字列'int'でもOK。Pythonの型ではないuintは文字列'uint'のみ可。

print(int is np.int)
# True

a = np.array([1, 2, 3], dtype=int)
print(a.dtype)
# int64

a = np.array([1, 2, 3], dtype='int')
print(a.dtype)
# int64

文字列の文字数についての注意

strunicodeなどで文字列として指定する場合のdtype<U1のようになる。

a_str = np.array([1, 2, 3], dtype=str)
print(a_str)
print(a_str.dtype)
# ['1' '2' '3']
# <U1

先頭の<, >は、それぞれリトルエンディアン、ビッグエンディアンを表している。ほとんどの場合、特に気にする必要はない。

末尾の数字は文字数を表し、この例のようにコンストラクタでdtypestrunicodeと指定した場合、要素の中で最大の文字数となる。

それぞれの要素に対してこの文字数分のメモリしか確保されていないので、それ以上の文字数の文字列は保持できず切り捨てられる。あらかじめ十分な文字数の型を指定しておけばOK。

a_str[0] = 'abcde'
print(a_str)
# ['a' '2' '3']

a_str10 = np.array([1, 2, 3], dtype='U10')
print(a_str10.dtype)
# <U10

a_str10[0] = 'abcde'
print(a_str10)
# ['abcde' '2' '3']

object型: Pythonオブジェクトへのポインターを格納

object型は特殊なデータ型で、Pythonオブジェクトへのポインターを格納する。

各要素のデータの実体はそれぞれメモリ領域を確保するので、一つの配列ndarray内に複数の型のデータ(へのポインタ)をもつことができる。

a_object = np.array([1, 0.1, 'one'], dtype=object)
print(a_object)
print(a_object.dtype)
# [1 0.1 'one']
# object

print(type(a_object[0]))
print(type(a_object[1]))
print(type(a_object[2]))
# <class 'int'>
# <class 'float'>
# <class 'str'>

文字数の変更も可能。

a_object[2] = 'oneONE'
print(a_object)
# [1 0.1 'oneONE']

なお、このような複数の型を持つ配列はPython標準のリストlist型でも実現できる。

listとNumPy配列ndarrayは演算子に対する振る舞いが異なり、ndarrayの場合は各要素に対する演算が簡単にできるが、あえてNumPyでこのようなデータを作成して処理するメリットは少ないかもしれない。

l = [1, 0.1, 'oneONE']
print(type(l[0]))
print(type(l[1]))
print(type(l[2]))
# <class 'int'>
# <class 'float'>
# <class 'str'>

print(a_object * 2)
# [2 0.2 'oneONEoneONE']

print(l * 2)
# [1, 0.1, 'oneONE', 1, 0.1, 'oneONE']

astype()によるデータ型dtypeの変換(キャスト)

NumPy配列ndarrayのメソッドastype()でデータ型dtypeを変換(キャスト)することができる。

dtypeが変更された新たなndarrayが生成され、もとのndarrayは変化しない。

import numpy as np

a = np.array([1, 2, 3])
print(a)
print(a.dtype)
# [1 2 3]
# int64

a_float = a.astype(np.float32)
print(a_float)
print(a_float.dtype)
# [1. 2. 3.]
# float32

print(a)
print(a.dtype)
# [1 2 3]
# int64

上述のように、dtypeは様々な書き方で指定できる。

a_float = a.astype(float)
print(a_float)
print(a_float.dtype)
# [1. 2. 3.]
# float64

a_str = a.astype('str')
print(a_str)
print(a_str.dtype)
# ['1' '2' '3']
# <U21

a_int = a.astype('int32')
print(a_int)
print(a_int.dtype)
# [1 2 3]
# int32

astype()で変換(キャスト)する際の注意点

floatからintへのキャスト

floatからintへキャストする場合は小数点以下切り捨てとなる。負の値は0への丸め。

_a = np.arange(50).reshape((5, 10)) / 10 - 2
print(_a)
print(_a.dtype)
# [[-2.  -1.9 -1.8 -1.7 -1.6 -1.5 -1.4 -1.3 -1.2 -1.1]
#  [-1.  -0.9 -0.8 -0.7 -0.6 -0.5 -0.4 -0.3 -0.2 -0.1]
#  [ 0.   0.1  0.2  0.3  0.4  0.5  0.6  0.7  0.8  0.9]
#  [ 1.   1.1  1.2  1.3  1.4  1.5  1.6  1.7  1.8  1.9]
#  [ 2.   2.1  2.2  2.3  2.4  2.5  2.6  2.7  2.8  2.9]]
# float64

_a_int = _a.astype('int64')
print(_a_int)
print(_a_int.dtype)
# [[-2 -1 -1 -1 -1 -1 -1 -1 -1 -1]
#  [-1  0  0  0  0  0  0  0  0  0]
#  [ 0  0  0  0  0  0  0  0  0  0]
#  [ 1  1  1  1  1  1  1  1  1  1]
#  [ 2  2  2  2  2  2  2  2  2  2]]
# int64

np.round()を使うと偶数への丸めになる。一般的な四捨五入ではなく0.51ではなく0に丸められたりする。

Python標準のround()と同じ仕様。

print(np.round(_a).astype(int))
# [[-2 -2 -2 -2 -2 -2 -1 -1 -1 -1]
#  [-1 -1 -1 -1 -1  0  0  0  0  0]
#  [ 0  0  0  0  0  0  1  1  1  1]
#  [ 1  1  1  1  1  2  2  2  2  2]
#  [ 2  2  2  2  2  2  3  3  3  3]]

以下のような関数を指定すると0.51となり、四捨五入を実現できる。

my_round_int = lambda x: np.round((x * 2 + 1) // 2)

print(my_round_int(_a).astype(int))
# [[-2 -2 -2 -2 -2 -1 -1 -1 -1 -1]
#  [-1 -1 -1 -1 -1  0  0  0  0  0]
#  [ 0  0  0  0  0  1  1  1  1  1]
#  [ 1  1  1  1  1  2  2  2  2  2]
#  [ 2  2  2  2  2  3  3  3  3  3]]

上の関数は負の値-0.50となる。-0.5-1としたい場合は以下のような関数にする。

def my_round(x, digit=0):
    p = 10 ** digit
    s = np.copysign(1, x)
    return (s * x * p * 2 + 1) // 2 / p * s

print(my_round(_a).astype(int))
# [[-2 -2 -2 -2 -2 -2 -1 -1 -1 -1]
#  [-1 -1 -1 -1 -1 -1  0  0  0  0]
#  [ 0  0  0  0  0  1  1  1  1  1]
#  [ 1  1  1  1  1  2  2  2  2  2]
#  [ 2  2  2  2  2  3  3  3  3  3]]
スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事