Scientific Computing Tools For Python — Numpy
NumPy は Pythonプログラミング言語の拡張モジュールであり、大規模な多次元配列や行列のサポート、これらを操作するための大規模な高水準の数学関数ライブラリを提供する。(via Wikipedia)
これまで知識があいまいだったNumPyについて、もう一度おさらいしたいと思います。NumPyはSciPyと併せて科学技術計算でよく利用されています。また、高速に行列演算ができるのでOpenCV(コンピュータビジョンライブラリ)でもNumPyを利用したPythonインタフェースが提供されるようになりました。
OpenCVのPythonバインディングについては去年のエントリーでも取り上げていますので参考までに。
* さくらVPSにOpenCVをインストールしてPythonから使う
[2017/04/29 更新]
- CentOS 7.0, Python 3.6, NumPy 1.12で動作確認
- Record Arrays, Automatic Reshaping, 乱数生成, Linear Algebra(線形代数)について新規説明を追加
環境
- CentOS 7.0 (x86_64)
- Python 3.6.0
- NumPy 1.12.0
NumPyの日本語ドキュメントは豊富にありますが、自分で手を動かしながらここに整理しようと思います。
- NumPy配列(numpy.ndarray)とは
- numpy.ndarrayの属性(attributes)
- データ型について
- 配列の生成
- Record Arrays
- 配列形状の変更
- Automatic Reshaping
- Indexing
- Fancy Indexing
- インデックスの検索
- 配列に対する操作/演算
- 配列の結合/分割、軸操作
- 配列のソート
- 配列要素に対する演算
- Broadcasting
- 配列の走査
- 乱数生成
- 統計関数
- 線形代数
- ファイル入出力
NumPy配列(numpy.ndarray)とは
An ndarray is a (usually fixed-size) multidimensional container of items of the same type and size.
numpy.ndarray は多次元配列を扱うクラスです。基本的に以下の制限があります。
* 配列長は固定 (固定長配列)
* 配列の各次元の要素数は同じ
(※ ただし、制限付きで配列の形状変更は可能。説明は後述)
実体はC言語の配列になっています。制限がある分、Pythonのリスト(list)型と比較して大規模な配列を扱う際の処理効率が良くなっています。また、これ以降 “配列” と表記したときは基本的に numpy.ndarray を指すこととします。
* import
1 2 |
## import時に np というエイリアスを付けるのが慣例のようです。 import numpy as np |
numpy.ndarrayの属性(attributes)
numpy.ndarray の主な属性については以下の通り。効率良く処理するためにndarrayのデータはメモリの連続領域上に保持されていますが、これらの属性を参照するとデータがメモリ上にどうレイアウトされているかを調べることができます。
An instance of class ndarray consists of a contiguous one-dimensional segment of computer memory
* Internal memory layout of an ndarray
ndarray.flags | 配列データのメモリレイアウト情報 (numpy.flagsobj) |
ndarray.ndim | 配列の次元数 |
ndarray.size | 配列の要素数 |
ndarray.shape | 各次元の要素数 |
ndarray.itemsize | 1要素のバイト数 |
ndarray.strides | 各次元で次の要素に移動する際に必要なバイト数 |
ndarray.nbytes | 配列全体のバイト数 |
ndarray.dtype | 配列要素のデータ型 (numpy.dtype) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
## 二次元配列 (行列) >>> a = np.array([[1,2,3],[4,5,6],[7,8,9],[10,11,12]]) >>> a array([[ 1, 2, 3], [ 4, 5, 6], [ 7, 8, 9], [10, 11, 12]]) >>> a.flags C_CONTIGUOUS : True ## データがメモリ上に連続しているか(C配列型) F_CONTIGUOUS : False ## 同上(Fortran配列型) OWNDATA : True ## 自分のデータかどうか、ビュー(後述)の場合はFalse WRITEABLE : True ## データ変更可能か ALIGNED : True ## データ型がアラインされているか UPDATEIFCOPY : False ## Trueには変更できないので特に気にしなくて良い >>> a.ndim ## 次元数 2 >>> a.size ## 要素数 12 >>> a.shape ## 各次元の要素数 (行数, 列数) (4, 3) >>> a.itemsize ## 1要素のバイト数 8 >>> a.strides ## 24バイトで次の行、8バイトで次の列 (24, 8) >>> a.nbytes ## 配列全体のバイト数 (size*itemsize) 96 >>> a.dtype ## 要素のデータ型(後述) dtype('int64') |
stridesは、メモリ上でa[0,0]とa[0,1]は8バイト離れており、a[0,0]とa[1,0]は24バイト離れていることを示します。データの並びがFortran型の場合は、strides は (8, 32) となります。一次元の場合はC型もFortran型も関係ありません。
データ型について
配列要素のデータ型はnumpy.dtypeというオブジェクトで扱われています。指定できる型については大きく分けて、論理型、符号付き整数型、符号無し整数型、浮動小数点型、複素数型の5つ。あとはデータ型のビット数別にそれぞれ指定できます。詳細は公式サイトを参照してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
## numpy.array() でdtypeオプションを指定しなければ自動的に設定される ## ここでは64ビット環境なので64ビットの型で生成される >>> a = np.array([1,2,3,4,5]) >>> a.dtype dtype('int64') ## 64ビット符号付き整数 >>> a.itemsize 8 >>> a = np.array([1.0,2.0,3.0,4.0,5.0]) >>> a.dtype dtype('float64') ## 64ビット浮動小数点数 >>> a.itemsize 8 ## numpy.array() でdtypeオプションを指定 ## 32ビット符号付き整数 >>> a = np.array([1,2,3], dtype=np.int32) >>> a.dtype dtype('int32') >>> a.itemsize 4 ## 8ビット符号無し整数 (画像処理ではよく使われる) >>> a = np.array([1,2,3], dtype=np.uint8) >>> a.dtype dtype('uint8') >>> a.itemsize 1 |
また、文字列表現でも型指定が可能です。
1 2 3 4 5 6 7 8 9 10 11 |
'b' boolean 'i' (signed) integer 'u' unsigned integer 'f' floating-point 'c' complex-floating point 'm' timedelta 'M' datetime 'O' (Python) objects 'S' 'a' (byte-)string 'U' Unicode 'V' raw data (void) |
1 2 3 4 |
>>> dt = np.dtype('i4') # 32-bit signed integer >>> dt = np.dtype('f8') # 64-bit floating-point number >>> dt = np.dtype('c16') # 128-bit complex floating-point number >>> dt = np.dtype('a25') # 25-character string |
データ型のキャストも可能です。ndarray.astype() を使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
>>> a = np.array([1, 2, 3, 4, 5])) >>> a.dtype dtype('int64') ## int64型からint32型にキャスト >>> a.astype(np.int32) array([1, 2, 3, 4, 5], dtype=int32) >>> a = np.array([1.2, 2.4, 3.6, 4.8, 5.0]) >>> a.dtype dtype('float64') ## float64からint64型にキャスト、小数点以下は切り捨て >>> a.astype(np.int64) array([1, 2, 3, 4, 5]) |
ダウンキャストでも例外は発生しません。また、ndarray.astype() は新しい配列を生成して返すので注意してください。
配列の生成
まずは配列の生成方法から。生成方法はたくさんありますが、ここでは基本的でよく使われる方法を中心に紹介します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
>>> import numpy as np ## numpy.array() で生成、引数にはリスト(またはタプル)を渡す >>> np.array([1,4,3,2,5]) array([1, 4, 3, 2, 5]) ## 二次元配列もOK >>> np.array([[3,1,2], [6,4,5]]) array([[3, 1, 2], [6, 4, 5]]) ## numpy.zeros() で生成、全要素の値は0 >>> np.zeros(5) array([ 0., 0., 0., 0., 0.]) >>> np.zeros([2,3]) array([[ 0., 0., 0.], [ 0., 0., 0.]]) ## numpy.ones() で生成、全要素の値は1 >>> np.ones(5) array([ 1., 1., 1., 1., 1.]) >>> np.ones([2,3]) array([[ 1., 1., 1.], [ 1., 1., 1.]]) ## numpy.identity() で生成、単位行列 (正方行列なので引数は1つ) >>> np.identity(3) array([[ 1., 0., 0.], [ 0., 1., 0.], [ 0., 0., 1.]]) ## numpy.eye() で生成、identity() と似ているが列数指定ができる >>> np.eye(3, 2) array([[ 1., 0.], [ 0., 1.], [ 0., 0.]]) ## numpy.arange() で生成、組み込みの range() と同じ要領 >>> np.arange(10) array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) ## (始点,終点,増分)を指定 >>> np.arange(1.0, 2.0, 0.2) array([ 1. , 1.2, 1.4, 1.6, 1.8]) ## numpy.linspace() で生成、arange() と似ているが要素数を指定できる >>> np.linspace(1, 4, 6) array([ 1. , 1.6, 2.2, 2.8, 3.4, 4. ]) ## numpy.logspace() で生成、値は対数スケール(常用対数)で並べられる >>> np.logspace(2, 3, 4) array([ 100. , 215.443469 , 464.15888336, 1000. ]) ## 底(base)を2に指定 >>> np.logspace(2, 4 ,4, base=2) array([ 4. , 6.34960421, 10.0793684 , 16. ]) ## numpy.tile() で生成、要素を繰り返した配列を返す >>> np.tile([0,1,2,3,4], 2) array([0, 1, 2, 3, 4, 0, 1, 2, 3, 4]) ## numpy.meshgrid() で生成、縦横に等間隔な格子状配列 >>> a, b = np.meshgrid([1,2,3], [4,5,6,7]) >>> a array([[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]) >>> b array([[4, 4, 4], [5, 5, 5], [6, 6, 6], [7, 7, 7]]) ## numpy.tri() で生成、三角行列 >>> np.tri(3) array([[ 1., 0., 0.], [ 1., 1., 0.], [ 1., 1., 1.]]) ## numpy.diag() で生成、入力配列から対角要素を抜き出した配列を返す >>> a = np.array([[0,1,2], [3,4,5], [6,7,8]]) >>> a array([[0, 1, 2], [3, 4, 5], [6, 7, 8]]) >>> np.diag(a) array([0, 4, 8]) ## numpy.empty() で生成、領域の確保のみで初期化はされない >>> np.empty(5) array([ 1.06452759e-312, 1.06452759e-312, 1.00000000e+000, 1.00000000e+000, 2.37151510e-322]) ## ndarray.copy() で配列のディープコピー >>> a = np.array([1,2,3]) >>> b = a.copy() ## numpy.random モジュールの利用 ## numpy.random.randint() で整数乱数値を要素とした配列を生成 ## 生成する疑似乱数の範囲(最小値、最大値)、要素数を指定 >>> np.random.randint(0,100,10) array([54, 68, 19, 57, 23, 27, 36, 99, 53, 70]) |
numpy.random モジュールによる疑似乱数生成については後半で詳しく紹介します。
Record Arrays
冒頭で配列内要素の型は全て同じと書きましたが、各要素個別に型指定することで配列内に複数の型の値を入れることができます。また、各要素を数値インデックスでなく属性で参照することもできます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# numpy.rec モジュールを利用する >>> a = np.rec.array([(1, 2.0, "python"),(3, 4.0, "numpy")], dtype=[("foo", "i4"),("bar", "f4"),("baz", "S10")]) >>> a rec.array([(1, 2., b'python'), (3, 4., b'numpy')], dtype=[('foo', '<i4'), ('bar', '<f4'), ('baz', 'S10')]) # 数値インデックスによる要素の参照 >>> a[0] (1, 2.0, b'python') >>> a[1] (3, 4.0, b'numpy') >>> a[0][0] 1 # 属性による参照 >>> a[0].foo, a[0].bar, a[0].baz (1, 2.0, b'python') # 値の変更 >>> a[0].foo = 100 >>> a[0].foo 100 # 32ビット整数型("i4")に文字列の代入はできない >>> a[0].foo = "new value" Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/python-3.6.0/lib/python3.6/site-packages/numpy/core/records.py", line 261, in __setattr__ return self.setfield(val, *res[:2]) ValueError: invalid literal for int() with base 10: 'new value' |
このように配列内要素を構造化することができます。初期化が少し面倒ですね。
配列形状の変更
ndarrayオブジェクトは制限付きで配列形状の変更が可能です。
※ ビュー(View:参照)
NumPyの一部の操作で返されるオブジェクトはビュー(View)と呼ばれ、元データの参照となります。なので、ビュー内の値を変更すると元データも変更されるので注意してください。ディープコピーが必要な場合は前述した ndarray.copy()
を利用しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
## 一次元配列 >>> a = np.arange(10) >> a array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) ## ndarray.reshape() で配列形状の変更 ## 二次元配列に変更、ビューを返す >>> b = a.reshape((2,5)) >>> b array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]) ## ビューの値を変更 >>> b[0] = 100 >>> b array([[100, 100, 100, 100, 100], [ 5, 6, 7, 8, 9]]) ## 元データも変更される >>> a array([100, 100, 100, 100, 100, 5, 6, 7, 8, 9]) ## 要素数が異なるとエラーとなる >>> a.reshape((2,3)) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: total size of new array must be unchanged ## ndarray.resize() で配列形状の変更(in-place) >>> a = np.arange(10) ## 要素数はそのままで二次元配列に変更、ビューは返さない >>> a.resize((2,5)) ## 元データの形状が変更される >>> a array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]) ## 生成後、一度でも変数を参照していると要素数(配列長)の変更は不可となる >>> a.resize(11) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: cannot resize an array references or is referenced by another array in this way. Use the resize function ## ただし refcheck オプションを False にすると参照チェックは行われない >>> a.resize(11, refcheck=False) >>> a array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]) ## ndarray.flatten() で一次元配列に変更、ビューではなくコピーを返す >>> a = np.arange(10).reshape((2,5)) >>> a array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]) >>> b = a.flatten() >>> b array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) |
返される配列がビューかコピーになるかはきちんと把握しておいた方がいいです。それと ndarray.reshape() はよく使うのでこれも覚えておくといいかと思います。
Automatic Reshaping
配列形状変更の際に、要素数の指定を一部省略することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
>>> a = np.arange(10) >>> a array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> a.shape (10,) ## ndarray.reshape() ではViewを返すが、 ## shape 属性に形状(次元数)を指定すると元配列の形状が変更される >>> a.shape = 2, 5 >>> a.shape (2, 5) >>> a array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]) ## Automatic Reshaping >>> a = np.arange(30) >>> a array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]) >>> a.shape (30,) >>> a.shape = 2, -1, 3 ## -1 を指定した次元の要素数は自動で設定される >>> a.shape (2, 5, 3) >>> a array([[[ 0, 1, 2], [ 3, 4, 5], [ 6, 7, 8], [ 9, 10, 11], [12, 13, 14]], [[15, 16, 17], [18, 19, 20], [21, 22, 23], [24, 25, 26], [27, 28, 29]]]) |
これは便利ですね。配列の分割操作等もスマートに書けそうです。
Indexing
配列要素の参照/代入、配列のスライスなどについて紹介します。基本的にPythonのリスト(list)とよく似た操作方法ですが、一部拡張構文があるので確認しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
## 二次元配列 (行列) >>> a = np.array([[0,1,2], [3,4,5], [6,7,8]]) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) ## 要素の参照/代入 (リストと同じ) >>> a[2] array([7, 8, 9]) >>> a[1][2] 6 >>> a[1][2] = 1 >>> a[1][2] 1 ## 範囲外を指定するとIndexError >>> a[3] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: index out of bounds ## カンマ区切りで参照/代入 (拡張構文) ## a[行,列] >>> a[1,2] 6 ## インデックスに負数を指定すると末尾から数える >>> a[-1,-2] 8 >>> a[1,2] = 10 >>> a[1,2] 10 ## スライス (記法はリストと同じ、返り値はビュー) ## a[start:end:step] >>> a = np.arange(10) >>> a array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> a[1:8:2] array([1, 3, 5, 7]) >>> a[3:] ## a[3:10:1] array([3, 4, 5, 6, 7, 8, 9]) >>> a[:8] ## a[0:8:1] array([0, 1, 2, 3, 4, 5, 6, 7]) >>> a[::-2] ## a[-1:-10:-2] array([9, 7, 5, 3, 1]) ## 組み合わせてもOK >>> a = np.arange(25).reshape((5,5)) >>> a array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19], [20, 21, 22, 23, 24]]) >>> a[:,3] = 10 ## a[0:5:1,3] = 10 (複数要素を一度に更新) >>> a array([[ 0, 1, 2, 10, 4], [ 5, 6, 7, 10, 9], [10, 11, 12, 10, 14], [15, 16, 17, 10, 19], [20, 21, 22, 10, 24]]) >>> a[:2,:3] = 100 ## a[0:2:1,0:3:1] = 100 >>> a array([[100, 100, 100, 10, 4], [100, 100, 100, 10, 9], [ 10, 11, 12, 10, 14], [ 15, 16, 17, 10, 19], [ 20, 21, 22, 10, 24]]) >>> a[2::2,::2] ## a[2:5:2,0:5:2] (省略すると逆に読みづらいケース) array([[10, 12, 14], [20, 22, 24]]) |
Fancy Indexing
要素の位置や条件などでマスクした特殊なインデックス指定も可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
>>> a = np.arange(15,0,-1) >>> a array([15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]) ## 整数配列でインデックス指定、コピーを返す >>> a[[0,2,4,8]] array([15, 13, 11, 7]) ## bool型配列でインデックス指定、コピーを返す >>> n = a>10 >>> n array([ True, True, True, True, True, False, False, False, False, False, False, False, False, False, False], dtype=bool) >>> a[n] array([15, 14, 13, 12, 11]) ## インデックスに直接条件を指定してもOK >>> a[a>10] array([15, 14, 13, 12, 11]) ## 複数の条件がある場合はビット演算子を使う >>> a[(4<a) & (a<10)] array([9, 8, 7, 6, 5]) ## 値の代入もできる >>> a[a<8] = 0 >>> a array([15, 14, 13, 12, 11, 10, 9, 8, 0, 0, 0, 0, 0, 0, 0]) |
インデックスの検索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
## numpy.argmax() で最大値要素の内で最も小さいインデックスを返す >>> a = np.tile(np.arange(3),3) >>> a array([0, 1, 2, 0, 1, 2, 0, 1, 2]) >>> np.argmax(a) 2 ## numpy.argmin() で最小値要素の内で最も小さいインデックスを返す >>> np.argmin(a) 0 ## numpy.nonzero() で非ゼロ要素のインデックス配列を返す >>> a = np.eye(3) >>> a array([[ 1., 0., 0.], [ 0., 1., 0.], [ 0., 0., 1.]]) >>> np.nonzero(a) ## 二次元配列なので二つの配列が返される (array([0, 1, 2]), array([0, 1, 2])) ## numpy.where() で条件に合致した要素のインデックス配列を返す >>> a = np.arange(15).reshape((3,5)) >>> a array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]) >>> np.where(a%2==0) (array([0, 0, 0, 1, 1, 2, 2, 2]), array([0, 2, 4, 1, 3, 0, 2, 4])) ## numpy.select() で複数条件検索、インデックスに値をセット ## 第一引数: 条件の配列(bool配列) ## 第二引数: 条件に合致した要素のインデックスにセットする値の配列 >>> a = np.arange(10) >>> np.select([a<3, a>5], [a, a**2]) array([ 0, 1, 2, 0, 0, 0, 36, 49, 64, 81]) |
ndarrayの内部構造はCの配列と似ていてもインタフェースはさすが高級言語ですね。 ただ、配列のスライス処理でNumPy固有の記法を使いすぎると読みづらくなるような気もするので、複数人で開発するときは節度ある使い方をした方が良さそうです。
配列に対する操作/演算
前述の配列形状の変更以外にも様々な操作が可能です。ここでは紹介しきれないのでほんの一部だけ載せます。
配列の結合/分割、軸操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
>>> a = np.arange(9).reshape((3,3)) >>> a array([[0, 1, 2], [3, 4, 5], [6, 7, 8]]) >>> b = np.arange(8,-1,-1).reshape((3,3)) >>> b array([[8, 7, 6], [5, 4, 3], [2, 1, 0]]) ## numpy.dstack() で二次元配列を結合して三次元配列にする >>> np.dstack((a,b)) array([[[0, 8], [1, 7], [2, 6]], [[3, 5], [4, 4], [5, 3]], [[6, 2], [7, 1], [8, 0]]]) ## numpy.hstack() で列方向に結合 >>> np.hstack((a,b)) array([[0, 1, 2, 8, 7, 6], [3, 4, 5, 5, 4, 3], [6, 7, 8, 2, 1, 0]]) ## numpy.vstack() で行方向に結合 >>> np.vstack((a,b)) array([[0, 1, 2], [3, 4, 5], [6, 7, 8], [8, 7, 6], [5, 4, 3], [2, 1, 0]]) ## numpy.dsplit() で三次元配列を分割 >>> a = np.arange(16).reshape(2,2,4) >>> a array([[[ 0, 1, 2, 3], [ 4, 5, 6, 7]], [[ 8, 9, 10, 11], [12, 13, 14, 15]]]) >>> np.dsplit(a,2) [array([[[ 0, 1], [ 4, 5]], [[ 8, 9], [12, 13]]]), array([[[ 2, 3], [ 6, 7]], [[10, 11], [14, 15]]])] ## numpy.hsplit() で列方向に分割 >>> a = np.arange(16).reshape(4,4) >>> a array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11], [12, 13, 14, 15]]) >>> np.hsplit(a,2) [array([[ 0, 1], [ 4, 5], [ 8, 9], [12, 13]]), array([[ 2, 3], [ 6, 7], [10, 11], [14, 15]])] ## numpy.vsplit() で行方向に分割 >>> np.vsplit(a,2) [array([[0, 1, 2, 3], [4, 5, 6, 7]]), array([[ 8, 9, 10, 11], [12, 13, 14, 15]])] ## numpy.transpose() で配列を転置 >>> a = np.array([[1, 2], [3, 4]]) >>> a array([[1, 2], [3, 4]]) >>> np.transpose(a) array([[1, 3], [2, 4]]) ## ndarray.T でも良い >>> a.T array([[1, 3], [2, 4]]) ## numpy.swapaxes() で軸の交換 >>> a = np.array([[1,2,3]]) >>> np.swapaxes(a, 0, 1) array([[1], [2], [3]]) |
配列のソート
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
## numpy.sort() で配列をソートしてコピーを返す >>> a = np.random.randint(0,100,10) >>> a array([75, 24, 74, 49, 93, 18, 19, 85, 73, 90]) >>> np.sort(a) array([18, 19, 24, 49, 73, 74, 75, 85, 90, 93]) ## ndarray.sort() で配列を破壊的(in-place)にソートする >>> a.sort() >>> a array([18, 19, 24, 49, 73, 74, 75, 85, 90, 93]) ## 多次元配列の場合 >> a = np.array([[1,4,2],[9,6,8],[5,3,7]]) >>> a array([[1, 4, 2], [9, 6, 8], [5, 3, 7]]) >>> np.sort(a) array([[1, 2, 4], [6, 8, 9], [3, 5, 7]]) ## 軸を指定する (ここでは列でソート) >>> np.sort(a, axis=0) array([[1, 3, 2], [5, 4, 7], [9, 6, 8]]) ## 一次元配列にしてソート >>> np.sort(a, axis=None) array([1, 2, 3, 4, 5, 6, 7, 8, 9]) |
配列要素に対する演算
様々な配列要素に対する(element-wise)演算機能が提供されています。また、それらの機能の多くは各演算子を用いて利用することも可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
>>> a = np.array([1,2,3]) >>> b = np.array([4,5,6]) ## 加算 >>> a + b ## np.add(a,b) array([5, 7, 9]) ## 減算 >>> b - a ## np.subtract(b,a) array([3, 3, 3]) ## 乗算 >>> a * b ## np.multiply(a,b) array([ 4, 10, 18]) ## 除算 >>> b / a ## np.divide(b,a) array([4, 2, 2]) ## 剰余 >>> a % b ## np.mod(a,b) array([1, 2, 3]) ## 冪乗 >>> a ** b ## np.power(a,b) array([ 1, 32, 729]) ## 符号反転 >>> -a ## np.negative(a) array([-1, -2, -3]) |
Broadcasting
前述の配列要素に対する各演算は同じサイズ/形状の配列同士で行っていましたが、NumPyではサイズ/形状の異なる配列同士の演算も可能で、これをブロードキャストを呼んでいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
>>> a = np.array([1,2,3,4,5]) >>> b = np.array([10]) >>> a * b ## (1,5) * (1,1) = (1,5) array([10, 20, 30, 40, 50] >>> a = np.array([0,10,20,30]).reshape((4,1)) >>> a array([[ 0], [10], [20], [30]]) >>> b = np.array([0,1,2]) >>> a + b ## (4,1) + (1,3) = (4,3) array([[ 0, 1, 2], [10, 11, 12], [20, 21, 22], [30, 31, 32]]) ## ブロードキャストできない例 >>> b = np.array([[1,2],[3,4]]) >>> a + b Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: operands could not be broadcast together with shapes (4,1) (2,2) |
2つめの例は面白いですね。ブロードキャスト機能は上手く使いこなせばコード量を大きく減らすことができそうです。
配列の走査
配列の走査はリスト(list)と同様に for in 構文を使って行うことができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
>>> a = np.arange(9).reshape((3,3)) >>> a array([[0, 1, 2], [3, 4, 5], [6, 7, 8]]) ## リスト(list)と同様に for in で走査できる ## ここでは行毎に走査 >>> for row in a: ... print row ... [0 1 2] [3 4 5] [6 7 8] ## numpy.ndenumerate() で多次元配列の走査 ## enumerate() と同様にインデックスも取得できる >>> for i, value in np.ndenumerate(a): ... print i, value ... (0, 0) 0 (0, 1) 1 (0, 2) 2 (1, 0) 3 (1, 1) 4 (1, 2) 5 (2, 0) 6 (2, 1) 7 (2, 2) 8 ## ndarray.flat で一次元配列にして走査 >>> for value in a.flat: ... print value ... 0 1 2 3 4 5 6 7 8 |
乱数生成
Mersenne Twisterアルゴリズムによる疑似乱数生成器が利用できます。numpy.random モジュールはPython標準の random モジュールよりも大規模データを扱う際に効率的に処理されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
## numpy.random モジュールを利用 ## rand()で[0, 1)の範囲の連続一様分布に従う乱数を生成 >>> np.random.rand() ## 単一の値 0.41166714072036725 >>> np.random.rand(5) array([ 0.1641323 , 0.61112414, 0.20739344, 0.19875631, 0.52371858]) ## 複数の値を配列で返す >>> np.random.rand(2,4) ## 配列形状の指定も可能 array([[ 0.72604791, 0.07460053, 0.95383173, 0.1478788 ], [ 0.29854117, 0.99342098, 0.71737757, 0.37551895]]) ## randn()で標準正規分布 N(0,1)に従う乱数を生成 >>> np.random.randn() 0.451880620719888 >>> np.random.randn(3,3) array([[-0.26029199, 2.91935795, -0.09902817], [ 0.34429428, 1.2341639 , 1.85230476], [ 0.57515358, 0.13601623, -0.26629502]]) >>> sigma * np.random.randn() + mu ## N(μ,σ^2)に従う乱数を作りたい場合 ## randint()で [low, high)の範囲の離散一様分布に従う整数値の乱数を生成 >>> np.random.randint(1, 10) 7 ## random_sample()で[0.0, 1.0)の範囲の連続一様分布に従う実数値の乱数を生成 >>> np.random.random_sample(3) array([ 0.40238323, 0.56226536, 0.94932419]) ## エイリアスとなる関数が複数存在する >>> np.random.random(3) array([ 0.59852672, 0.49758833, 0.77002109]) >>> np.random.ranf(3) array([ 0.59416564, 0.95570407, 0.87782414]) >>> np.random.sample(3) array([ 0.34746661, 0.06948941, 0.79589021]) ## random.choice()で指定配列内からN個ランダムサンプリングする >>> a = np.arange(10) >>> a array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> np.random.choice(a, 3) array([4, 7, 1]) ## 第一引数に整数値Nを与えるとarange(N)で生成した配列を対象とする >>> np.random.choice(5,3) ## randint(0,5,3)と同じ array([1, 3, 4]) ## random.bytes()でランダムなバイト列を生成する >>> np.random.bytes(10) b'\xce\x8e\xb2\x19n\xd6\xe6$1\xbd' |
同じ機能に複数の別名関数(エイリアス)があるのはPythonicじゃないと思います。何らかの確率分布に従う乱数が欲しい場合は SciPy の scipy.stats モジュールを使った方がわかりやすいかもしれません。
統計関数
NumPyでは基礎的な統計処理用の関数群が提供されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
>>> a = np.random.randint(0,500,20) >>> a array([260, 253, 185, 240, 252, 375, 63, 413, 293, 431, 207, 230, 288, 462, 270, 477, 451, 58, 408, 492]) >>> np.amax(a) ## 最大値 492 >>> np.amin(a) ## 最小値 58 >>> np.ptp(a) ## 値の範囲(最大値-最小値) 434 >>> np.mean(a) ## 算術平均 305.39999999999998 >>> np.median(a) ## 中央値 279.0 >>> np.std(a) ## 標準偏差 125.73519793597973 >>> np.var(a) ## 分散 15809.34 >>> b = np.random.randint(0,500,20) >>> b array([313, 117, 167, 353, 468, 289, 177, 196, 20, 70, 235, 280, 480, 125, 195, 271, 253, 55, 49, 354]) >>> np.corrcoef(a,b) ## 相関係数 array([[ 1. , 0.05950681], [ 0.05950681, 1. ]]) >>> c = np.random.randint(0,10,20) >>> c array([3, 8, 7, 9, 1, 8, 4, 0, 8, 3, 9, 4, 2, 1, 4, 3, 0, 4, 8, 4]) >>> np.histogram(c) ## ヒストグラム (array([2, 2, 1, 3, 5, 0, 0, 1, 4, 2]), array([ 0. , 0.9, 1.8, 2.7, 3.6, 4.5, 5.4, 6.3, 7.2, 8.1, 9. ])) |
より高度な統計処理を行いたい場合は SciPy を利用することになると思います。
線形代数
基礎的な線形代数関連の関数も備えています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
## numpy.linalg モジュールを利用する >>> a = np.array([[1.0, 2.0], [3.0, 4.0]]) >>> a array([[ 1., 2.], [ 3., 4.]]) >>> np.linalg.det(a) ## 行列式 (determinant) -2.0000000000000004 >>> np.linalg.norm(a) ## ノルム (norm) 5.4772255750516612 >>> np.linalg.inv(a) ## 逆行列 (inverse matrix) array([[-2. , 1. ], [ 1.5, -0.5]]) >>> y = np.array([[5.0],[7.0]]) >>> y array([[ 5.], [ 7.]]) ## 連立方程式を解く(逆行列計算) ## np.linalg.inv(a).dot(y) と同じ >>> np.linalg.solve(a, y) array([[-3.], [ 4.]]) |
逆行列を計算してからベクトルとの積を取るよりも、linalg.solve() で計算した方が楽だし計算も速いとのことです。
ファイル入出力
配列データのファイル入出力もできます。実験作業などではよくお世話になる機能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
>>> a = np.array([[1,3,2], [4,6,5]]) >>> a array([[1, 3, 2], [4, 6, 5]]) ## numpy.savetxt() で配列データをASCII形式でファイルに書き出す >>> np.savetxt('data.txt', a) ## デリミタはオプションで自由に指定できる (TSV形式) >>> np.savetxt('data.txt', a, delimiter='\t') ## ASCII形式なのでファイルの中身は人間にも読める $ cat data.txt 1.000000000000000000e+00 3.000000000000000000e+00 2.000000000000000000e+00 4.000000000000000000e+00 6.000000000000000000e+00 5.000000000000000000e+00 ## numpy.loadtxt() でテキストファイルから配列データを読み込む >>> np.loadtxt('data.txt', delimiter='\t') array([[ 1., 3., 2.], [ 4., 6., 5.]]) ## numpy.save() で配列データをバイナリ形式でファイルに書き出す ## 出力ファイルの拡張子は .npy となる >>> np.save('data', a) ## numpy.load() でバイナリファイルから配列データを読み込む >>> np.load('data.npy') array([[1, 3, 2], [4, 6, 5]]) ## numpy.savez() で複数の配列データをバイナリ形式でファイルに書き出す >>> x, y = np.meshgrid([1,2,3], [4,5,6,7]) >>> x array([[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]) >>> y array([[4, 4, 4], [5, 5, 5], [6, 6, 6], [7, 7, 7]]) ## 出力ファイルの拡張子は .npz となる >>> np.savez('data', x=x, y=y) ## .npy, .npzファイル共に numpy.load() で読み込み可能 >>> npz = np.load('data.npz') >>> npz.files ['y', 'x'] >>> npz['x'] array([[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]) >>> npz['y'] array([[4, 4, 4], [5, 5, 5], [6, 6, 6], [7, 7, 7]]) |
巨大なデータを保存するときは、バイナリ形式で保存した方がファイルサイズが小さくなるので良いかと。
今回はNumPyの基礎について整理してみました。僕自身はNumPyを単体で利用することはほとんどないのですが、OpenCVのPythonバインディングを利用するときによくお世話になっています。NumPyは機能が豊富なため、必要に応じてその都度公式リファレンスを参照しながら使っていく必要がありそうです。
* 参考
NumPy Reference
科学技術計算のために Python を始めよう
機械学習の Python との出会い
ありがとうございます。
とても参考になりました。
参考になりました
最後の例にxとaのtypoがあります
ご指摘ありがとうございます。修正致しました。