NumPyの多次元配列ndarrayには、データ型を表現するdtype
というプロパティがあります。このdtype
を指定することで、要素のデータ型を指定して確保するメモリ量を調節することができます。
本記事では、dtype
に焦点を当てながらNumPyで指定できるdtypeの種類と指定方法、そして内部でdtypeが活用される仕組みについて解説していきます。
dtypeが必要な理由
NumPyは基本的には、大量のデータ操作を高速に実行できるように内部ではCで実装されています。Python自体はそれほど高速な言語ではないため、行列演算の操作やデータの扱いはCから行われます。
つまり、正しくNumPy配列のデータ型を指定することでPythonからでもメモリ効率と実行効率の良いコードを実装することができます。
データ型
まずは、NumPyで使うことのできるデータ型についてまとめます。NumPyのデータ型を型で分類すると整数のint
、浮動小数のfloat
、複素数のcomplex
、符号なしの整数のuint
、真偽値のbool
になります。さらに、型ごとに1要素あたりに確保するデータサイズをビットで指定することができます。
データ型ごとに分類すると以下の通りになります。
int (符号付きの整数)
データ型 | 概要 |
---|---|
int8 | 8ビットの符号付き整数 |
int16 | 16ビットの符号付き整数 |
int32 | 32ビットの符号付き整数 |
int64 | 64ビットの符号付き整数 |
uint (符号なしの整数)
データ型 | 概要 |
---|---|
uint8 | 8ビットの符号なし整数 |
uint16 | 16ビットの符号なし整数 |
uint32 | 2ビットの符号なし整数 |
uint64 | 64ビットの符号なし整数 |
float (浮動小数点数) [1]
データ型 | 概要 |
---|---|
float16 | 16ビットの浮動小数点数 |
float32 | 32ビットの浮動小数点数 |
float64 | 64ビットの浮動小数点数 |
float128 | 128ビットの浮動小数点数 |
bool –真偽値–
データ型 | 概要 |
---|---|
bool | TrueかFalseで表される、真偽値。データ量としては8ビット |
まずは、それぞれのデータ型でどのような表記がされるのかを確かめてみましょう。NumPyのndarrayのdtypeは、arr.dtype
のようにして知ることができます。
In [1]: import numpy as np
In [2]: a = np.array([0, 1, 2]) # まずは何も指定しない状態で配列を生成。
In [3]: a.dtype # データ型を確かめる。
Out[3]: dtype('int64')
In [4]: b = np.array([0, 1, 2], dtype = 'int32') # ビット数を下げてみる。
In [5]: b.dtype
Out[5]: dtype('int32')
In [6]: b
Out[6]: array([0, 1, 2], dtype=int32)
In [7]: c = np.array([0, 1, 2], dtype = 'float') # floatやintのデフォルトのビット数は64。
In [8]: c # cの要素の表記のされかたが'int'の時と異なっていて、小数点がついている。
Out[8]: array([ 0., 1., 2.])
In [9]: d = np.array([3e50, 4e35], dtype = 'int64') # 桁数を非常に大きくしてみると、'int'では表示しきれなくなってエラーが返ってくる。
---------------------------------------------------------------------------
(エラーメッセージが表示される)
OverflowError: Python int too large to convert to C long
In [10]: d = np.array([3e50, 4e35], dtype = 'float64') # floatにすれば問題なく配列を生成できる。
In [11]: e = np.array([3.5, 4.2, -4.3], dtype = 'int') # 小数の形で表した配列も、データ型を'int'に設定すると整数部分だけが残る。
In [12]: e
Out[12]: array([ 3, 4, -4])
中身の要素をint
やfloat
の値を指定しても、データ型がbool
だった場合は、勝手にbool値へとキャストされた要素が入ります。
In [13]: f = np.array([0, 3, 0, -1], dtype = 'bool') # bool値は0ならばFalse、0以外ならばTrueとなる。
In [14]: f
Out[14]: array([False, True, False, True], dtype=bool)
原則として、配列が生成された後でデータ型の変換はおすすめしません。
データ型で指定できるビット数は変換される前のデータ型のビット列を何ビットごとに区切って読み取るか、という風に読み替えることもでき、ビット数を変換してしまうとそもそも返ってくる数字列が異なることがあります。
同様に、データ型を変換させてもデータの読み取り方を変えているだけなので、データ型を元に戻すと元の配列を再現することができます。サンプルコードを見ながらそれを確かめていきましょう。
In [15]: g = np.array([0., 1., 2.], dtype = 'int64')
In [16]: g
Out[16]: array([0, 1, 2])
In [17]: g.dtype = 'int32' # データ型を'int32'に変換。
In [18]: g
Out[18]: array([0, 0, 1, 0, 2, 0], dtype=int32)
In [19]: g.dtype = 'float64' # floatに変換してみる。
In [20]: g
Out[20]: array([ 0.00000000e+000, 4.94065646e-324, 9.88131292e-324])
In [21]: g.dtype = 'float32' # ビット数を変える。
In [22]: g
Out[22]:
array([ 0.00000000e+00, 0.00000000e+00, 1.40129846e-45,
0.00000000e+00, 2.80259693e-45, 0.00000000e+00], dtype=float32)
In [23]: g.dtype = 'int64' # データ型を元に戻すと値も元に戻る。
In [24]: g
Out[24]: array([0, 1, 2])
これらのビット数の違いを確かめます。8ビット(bit) = 1バイト(byte)です。
In [27]: h = np.random.randint(10, size = 100, dtype = 'int8') # 0~9までの整数を100個生成。データ型はint8
In [28]: i = np.random.randint(10, size = 100, dtype = 'int64') # こちらはビット数を64に設定。
In [29]: h.nbytes # バイト数を見てみる。
Out[29]: 100
In [30]: i.nbytes # hの8倍になっている。
Out[30]: 800