IT技術者のAIに関する雑記

AI関連のMicrosoft技術やデータサイエンスの話をメインに。

私訳:NumPy Quickstart tutorial

Pythonの数学計算ライブラリであるNumPyは深層学習を含めた機械学習に必須のライブラリですが、日本語情報があまり多くないため、Quickstart tutorialを翻訳してみました。

あくまで私訳ですので、原文と合わせてみて頂ければと思います。

原文:

https://docs.scipy.org/doc/numpy-dev/user/quickstart.html

前提

このチュートリアルを読む前に、Pythonについてある程度知っておいたほうがよいです。記憶を蘇らせたいのであれば、Python tutorial を参照してください。

このチュートリアルのサンプルを実行したい場合は、自分のコンピューターにソフトウエアをインストールしておく必要があります。手順については、http://scipy.org/install.htmlを参照してください。

基礎

NumPy の主要なオブジェクトは、同次の多次元配列です。これは(通常は数字の)要素のテーブルであり、全て同じ型、正の整数のタプルでインデックス付けされています。NumPyでは、次元は軸、軸の数はランクと呼ばれます。

例えば、三次元空間における点の座標「[1, 2, 1]」は、軸が1つであるためランク1の配列になります。軸の長さは3になります。以下の図の例では、配列はランク2 (2次元) となります。最初の次元 (軸) の長さは2、2つ目の次元の長さは3になります。

[[ 1., 0., 0.],
 [ 0., 1., 2.]] 

NumPyの配列クラスは「ndarray」と呼ばれています。これは「array」というエイリアスとしても知られています。標準のPythonライブラリの「array.array」クラスと同じではないということに注意してください。「array.array」クラスは一次元配列しか扱えませんし、機能も不足しています。「ndarray」クラスの重要な属性を以下に挙げておきます:

ndarray.ndim

配列の軸 (次元) 数です。Pythonの世界では、次元数はランクと呼ばれます。

ndarray.shape

配列に含まれる次元です。これは各次元の配列の大きさを表す整数のタプルになります。n行m列の行列の場合「shape」は「(n,m)」となります。「shape」タプルの長さはランクもしくは次元数である「ndim」の値となります。

ndarray.size

配列に含まれる要素の総数です。これは「shape」の要素の積と等しくなります。

ndarray.dtype

配列に含まれる要素の型を記述するオブジェクトです。dtypeは標準Pythonの型を用いて作成もしくは指定することができます。それに加えて、NumPyはnumpy.int32, numpy.int16, numpy.float64等の独自の型を提供しています。

ndarray.itemsize

配列に含まれる各要素のバイト単位の大きさです。例えば、「float64」型の要素からなる配列では、「itemsize」は8 (=64/8) となり、「complex32」型では4 (=32/8) となります。これはndarray.dtype.itemsizeと同値になります

ndarray.data

配列の実際の要素を収容しているバッファです。配列の要素にはインデックス機能を用いてアクセスするので、通常この属性を使う必要はないでしょう。

>>> import numpy as np
>>> a = np.arange(15).reshape(3, 5)
>>> a
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])
>>> a.shape
(3, 5)
>>> a.ndim
2
>>> a.dtype.name
'int64'
>>> a.itemsize
8
>>> a.size
15
>>> type(a)
<type 'numpy.ndarray'>
>>> b = np.array([6, 7, 8])
>>> b
array([6, 7, 8])
>>> type(b)
<type 'numpy.ndarray'>

配列の作成

配列を作成するには、何通りかの方法があります。

例えば、通常のPythonのリストや「array」関数を使用たタプルから配列を作成することができます。作成された配列の型は、シーケンスの要素の型から推定されます。

>>> import numpy as np
>>> a = np.array([2,3,4])
>>> a
array([2, 3, 4])
>>> a.dtype
dtype('int64')
>>> b = np.array([1.2, 3.5, 5.1])
>>> b.dtype
dtype('float64')

「array」関数を呼ぶときに、引数として一つの数値のリストを渡すのでなく複数の数値の引数を渡してエラーになるというのは、よくある間違いです。

>>> a = np.array(1,2,3,4)    # WRONG
>>> a = np.array([1,2,3,4])  # RIGHT

「array」は、シーケンスのシーケンスを2次元配列に、シーケンスのシーケンスのシーケンスを3次元配列に、といった形で変換します。

>>> b = np.array([(1.5,2,3), (4,5,6)])
>>> b
array([[ 1.5,  2. ,  3. ],
       [ 4. ,  5. ,  6. ]])

 配列の作成時に、明示的に型を指定することもできます:

>>> c = np.array( [ [1,2], [3,4] ], dtype=complex )
>>> c
array([[ 1.+0.j,  2.+0.j],
       [ 3.+0.j,  4.+0.j]])

配列の要素が最初は分からず、大きさだけが分かっているということはよくあることです。そのためNumPyは、場所取り用の初期値を入れて配列を作成する関数を提供しています。これにより、コストのかかる演算である配列の拡張の必要性を最小限に抑えられます。

「zeros」関数はすべて0の値を持つ配列を、「ones」関数はすべて1の値を持つ配列を、「empty」はメモリ状態に依存したランダムな初期値を持つ配列を作成します。デフォルトでは、作成された配列のdtypeは「float64」になります。

>>> np.zeros( (3,4) )
array([[ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.]])
>>> np.ones( (2,3,4), dtype=np.int16 )                # dtype can also be specified
array([[[ 1, 1, 1, 1],
        [ 1, 1, 1, 1],
        [ 1, 1, 1, 1]],
       [[ 1, 1, 1, 1],
        [ 1, 1, 1, 1],
        [ 1, 1, 1, 1]]], dtype=int16)
>>> np.empty( (2,3) )                                 # uninitialized, output may vary
array([[  3.73603959e-262,   6.02658058e-154,   6.55490914e-260],
       [  5.30498948e-313,   3.14673309e-307,   1.00000000e+000]])

数値のシーケンスを作成するために、NumPyは、「range」に似た、リストの代わりに配列を返す関数を提供しています。

>>> np.arange( 10, 30, 5 )
array([10, 15, 20, 25])
>>> np.arange( 0, 2, 0.3 )                 # it accepts float arguments
array([ 0. ,  0.3,  0.6,  0.9,  1.2,  1.5,  1.8])

「arange」が浮動小数点数を引数として使用する場合、浮動小数の精度が有限であるために、一般的には得られる要素数を予測することはできません。このため、stepの代わりに必要な要素数を引数として渡せる「linespace」関数を使うほうが通常望ましいです:

>>> from numpy import pi
>>> np.linspace( 0, 2, 9 )                 # 9 numbers from 0 to 2
array([ 0.  ,  0.25,  0.5 ,  0.75,  1.  ,  1.25,  1.5 ,  1.75,  2.  ])
>>> x = np.linspace( 0, 2*pi, 100 )        # useful to evaluate function at lots of points
>>> f = np.sin(x)
関連項目

array, zeros, zeros_like, ones, ones_like, empty, empty_like, arange, linspace, numpy.random.rand, numpy.random.randn, fromfunction,fromfile 

配列の表示

配列を表示する時、NumPyはネストされたリストと同じような方法で表示しますが、以下のレイアウトになります:

  • 最後の軸は左から右へ表示されます。
  • 最後から2番目の軸は、上から下へ表示されます。
  • その他の軸も上から下へ表示されますが、各スライスは空行で区切られます。

したがって、1次元配列は行のように、2次元配列は行列のように、3次元配列は行列のリストのように表示されます。

>>> a = np.arange(6)                         # 1d array
>>> print(a)
[0 1 2 3 4 5]
>>>
>>> b = np.arange(12).reshape(4,3)           # 2d array
>>> print(b)
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]
>>>
>>> c = np.arange(24).reshape(2,3,4)         # 3d array
>>> print(c)
[[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]
 [[12 13 14 15]
  [16 17 18 19]
  [20 21 22 23]]]

「reshape」の詳細については、「形状の操作」の項目を参照してください。

配列が大きすぎて表示できない場合には、NumPyは自動的に配列の中心部分を省略して端部分のみ表示します。

>>> print(np.arange(10000))
[   0    1    2 ..., 9997 9998 9999]
>>>
>>> print(np.arange(10000).reshape(100,100))
[[   0    1    2 ...,   97   98   99]
 [ 100  101  102 ...,  197  198  199]
 [ 200  201  202 ...,  297  298  299]
 ...,
 [9700 9701 9702 ..., 9797 9798 9799]
 [9800 9801 9802 ..., 9897 9898 9899]
 [9900 9901 9902 ..., 9997 9998 9999]]

この動作を無効にしてNumPyに配列全体を表示させたい場合には、「set_printoptions」を使用して表示オプションを変更することができます。

>>> np.set_printoptions(threshold='nan')

基本的な演算

配列に対する算術演算は要素ごとに適用されます。新しい配列が作成され、演算結果が入力されます。

>>> a = np.array( [20,30,40,50] )
>>> b = np.arange( 4 )
>>> b
array([0, 1, 2, 3])
>>> c = a-b
>>> c
array([20, 29, 38, 47])
>>> b**2
array([0, 1, 4, 9])
>>> 10*np.sin(a)
array([ 9.12945251, -9.88031624,  7.4511316 , -2.62374854])
>>> a<35
array([ True, True, False, False], dtype=bool)

多くの行列言語と異なり、NumPyでは乗算演算子「*」は要素ごとの演算を行います。行列の積は「dot」関数もしくはメソッドを使用して実行することができます:

>>> A = np.array( [[1,1],
...             [0,1]] )
>>> B = np.array( [[2,0],
...             [3,4]] )
>>> A*B                         # elementwise product
array([[2, 0],
       [0, 4]])
>>> A.dot(B)                    # matrix product
array([[5, 4],
       [3, 4]])
>>> np.dot(A, B)                # another matrix product
array([[5, 4],
       [3, 4]])

「+=」や「*=」のようないくつかの演算では、新しい行列を作成せずに、既存の配列を変更します。

>>> a = np.ones((2,3), dtype=int)
>>> b = np.random.random((2,3))
>>> a *= 3
>>> a
array([[3, 3, 3],
       [3, 3, 3]])
>>> b += a
>>> b
array([[ 3.417022  ,  3.72032449,  3.00011437],
       [ 3.30233257,  3.14675589,  3.09233859]])
>>> a += b                  # b is not automatically converted to integer type
Traceback (most recent call last):
  ...
TypeError: Cannot cast ufunc add output from dtype('float64') to dtype('int64') with casting rule 'same_kind'

 型が異なる行列に対して演算する場合、演算結果の配列の型は、より一般度の高い型もしくはより精度の高い型(アップキャストとして知られている動作)になります。

>>> a = np.ones(3, dtype=np.int32)
>>> b = np.linspace(0,pi,3)
>>> b.dtype.name
'float64'
>>> c = a+b
>>> c
array([ 1.        ,  2.57079633,  4.14159265])
>>> c.dtype.name
'float64'
>>> d = np.exp(c*1j)
>>> d
array([ 0.54030231+0.84147098j, -0.84147098+0.54030231j,
       -0.54030231-0.84147098j])
>>> d.dtype.name
'complex128'

配列に含まれる全要素の合計計算のような単項演算子の多くは、「ndarray」クラスのメソッドとして実装されています。

>>> a = np.random.random((2,3))
>>> a
array([[ 0.18626021,  0.34556073,  0.39676747],
       [ 0.53881673,  0.41919451,  0.6852195 ]])
>>> a.sum()
2.5718191614547998
>>> a.min()
0.1862602113776709
>>> a.max()
0.6852195003967595

これらの演算子は、デフォルトでは配列の形状にかかわらず、数値のリストのように配列に適用されます。しかし、「axis」パラメータを指定することで、指定された配列の軸に沿って演算を実施することができます:

>>> b = np.arange(12).reshape(3,4)
>>> b
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>
>>> b.sum(axis=0)                            # sum of each column
array([12, 15, 18, 21])
>>>
>>> b.min(axis=1)                            # min of each row
array([0, 4, 8])
>>>
>>> b.cumsum(axis=1)                         # cumulative sum along each row
array([[ 0,  1,  3,  6],
       [ 4,  9, 15, 22],
       [ 8, 17, 27, 38]])

ユニバーサル関数

NumPyは、sin、cos、expのようなよく知られている数学関数も提供しています。NumPyでは、これらは「ユニバーサル関数(ufunc)」と呼ばれています。Numpyでは、これらの関数は配列の要素ごとに演算を行い、出力として新規の行列が作成されます。

>>> B = np.arange(3)
>>> B
array([0, 1, 2])
>>> np.exp(B)
array([ 1.        ,  2.71828183,  7.3890561 ])
>>> np.sqrt(B)
array([ 0.        ,  1.        ,  1.41421356])
>>> C = np.array([2., -1., 4.])
>>> np.add(B, C)
array([ 2.,  0.,  6.])
関連項目

all, any, apply_along_axis, argmax, argmin, argsort, average, bincount, ceil, clip, conj, corrcoef, cov, cross, cumprod, cumsum, diff,dot, floor, inner, inv, lexsort, max, maximum, mean, median, min, minimum, nonzero, outer, prod, re, round, sort, std, sum, trace,transpose, var, vdot, vectorize, where

インデックス付け、スライス、イテレート

一次元配列に対しては、リストや他のPythonのシーケンスと同じように、インデックス付けやスライス、イテレートを行うことができます。

>>> a = np.arange(10)**3
>>> a
array([  0,   1,   8,  27,  64, 125, 216, 343, 512, 729])
>>> a[2]
8
>>> a[2:5]
array([ 8, 27, 64])
>>> a[:6:2] = -1000    # equivalent to a[0:6:2] = -1000; from start to position 6, exclusive, set every 2nd element to -1000
>>> a
array([-1000,     1, -1000,    27, -1000,   125,   216,   343,   512,   729])
>>> a[ : :-1]                                 # reversed a
array([  729,   512,   343,   216,   125, -1000,    27, -1000,     1, -1000])
>>> for i in a:
...     print(i**(1/3.))
...
nan
1.0
nan
3.0
nan
5.0
6.0
7.0
8.0
9.0

多次元配列は、軸ごとに一つのインデックスを持つことができます。これらのインデックスは、カンマ区切りのタプルで与えられます:

>>> def f(x,y):
...     return 10*x+y
...
>>> b = np.fromfunction(f,(5,4),dtype=int)
>>> b
array([[ 0,  1,  2,  3],
       [10, 11, 12, 13],
       [20, 21, 22, 23],
       [30, 31, 32, 33],
       [40, 41, 42, 43]])
>>> b[2,3]
23
>>> b[0:5, 1]                       # each row in the second column of b
array([ 1, 11, 21, 31, 41])
>>> b[ : ,1]                        # equivalent to the previous example
array([ 1, 11, 21, 31, 41])
>>> b[1:3, : ]                      # each column in the second and third row of b
array([[10, 11, 12, 13],
       [20, 21, 22, 23]])

軸数より少ないインデックスが与えられた場合には、不足分のインデックスは完全なスライス「:」と見なされます。

>>> b[-1]                                  # the last row. Equivalent to b[-1,:]
array([40, 41, 42, 43])

「b[i]」の括弧の中の式は、残りの軸を表現するために必要な分の「:」で埋められているものとして扱われます。NumPyでは、これを「b[i,...]」のようにドットを用いて記述することもできます。

ドット「...」は、完全にインデックス付けされたタプルを生成するのに必要な分のコロンを表しています。例えば、「x」がランク5の配列(つまり5軸)の場合、以下の配列は等価になります。

  • x[1,2,...]はx[1,2,:,:,:]
  • x[...,3]はx[:,:,:,:,3]
  • x[4,...,5,:]はx[4,:,:,5,:]
>>> c = np.array( [[[  0,  1,  2],               # a 3D array (two stacked 2D arrays)
...                 [ 10, 12, 13]],
...                [[100,101,102],
...                 [110,112,113]]])
>>> c.shape
(2, 2, 3)
>>> c[1,...]                                   # same as c[1,:,:] or c[1]
array([[100, 101, 102],
       [110, 112, 113]])
>>> c[...,2]                                   # same as c[:,:,2]
array([[  2,  13],
       [102, 113]])

多次元配列に対するイテレートは、最初の軸に対して行われます:

>>> for row in b:
...     print(row)
...
[0 1 2 3]
[10 11 12 13]
[20 21 22 23]
[30 31 32 33]
[40 41 42 43]

一方で、配列の各要素に対して演算を行いたい場合には、配列の全要素に対して適用されるイテレータとなる「flat」属性を使用することができます:

>>> for element in b.flat:
...     print(element)
...
0
1
2
3
10
11
12
13
20
21
22
23
30
31
32
33
40
41
42
43
関連項目

Indexing, Indexing (reference), newaxis, ndenumerate, indices

形状の操作

配列の形状の変更

配列は、各軸に沿った要素数によって与えられる形状を持ちます:

>>> a = np.floor(10*np.random.random((3,4)))
>>> a
array([[ 2.,  8.,  0.,  6.],
       [ 4.,  5.,  1.,  1.],
       [ 8.,  9.,  3.,  6.]])
>>> a.shape
(3, 4)

配列の形状は、様々なコマンドで変更することができます:

>>> a.ravel()  # returns the array, flattened
array([ 2.,  8.,  0.,  6.,  4.,  5.,  1.,  1.,  8.,  9.,  3.,  6.])
>>> a.reshape(6,2)  # returns the array with a modified shape
array([[ 2.,  8.],
       [ 0.,  6.],
       [ 4.,  5.],
       [ 1.,  1.],
       [ 8.,  9.],
       [ 3.,  6.]])
>>> a.T  # returns the array, transposed
array([[ 2.,  4.,  8.],
       [ 8.,  5.,  9.],
       [ 0.,  1.,  3.],
       [ 6.,  1.,  6.]])
>>> a.T.shape
(4, 3)
>>> a.shape
(3, 4)

ravel()の結果として得られる配列の要素の順番は、通常は”Cスタイル”つまり一番右のインデックスが”最初の変わる”ので、a[0,0]の次の要素がa[0,1]になります。配列が別の形状に再整形されても、配列は"Cスタイル"として扱われます。NumPyは、通常はこの順番で格納される配列を作成するため、ravel()は普通は引数をコピーする必要がありませんが、配列が別の配列をスライスして作成されたり、一般的ではないオプションを使って作成された場合には、引数をコピーする必要があることがあります。ravel()とreshape() 関数 は、オプションの引数を使うことでFORTRANスタイルの配列、つまり一番左のインデックスが最初に変わるように指定することもできます。

reshape関数は、変更後の形状で引数を返す一方で、ndarray.resizeメソッドは配列そのものを変更します:

>>> a
array([[ 2.,  8.,  0.,  6.],
       [ 4.,  5.,  1.,  1.],
       [ 8.,  9.,  3.,  6.]])
>>> a.resize((2,6))
>>> a
array([[ 2.,  8.,  0.,  6.,  4.,  5.],
       [ 1.,  1.,  8.,  9.,  3.,  6.]])

再整形の演算時に次元が-1で与えられた場合、他の次元は自動的に計算されます。

>>> a.reshape(3,-1)
array([[ 2.,  8.,  0.,  6.],
       [ 4.,  5.,  1.,  1.],
       [ 8.,  9.,  3.,  6.]])
関連項目

ndarray.shape, reshape, resize, ravel

配列の積み上げ

複数の配列を、さまざまな軸に沿って積み上げることができます:

>>> a = np.floor(10*np.random.random((2,2)))
>>> a
array([[ 8.,  8.],
       [ 0.,  0.]])
>>> b = np.floor(10*np.random.random((2,2)))
>>> b
array([[ 1.,  8.],
       [ 0.,  4.]])
>>> np.vstack((a,b))
array([[ 8.,  8.],
       [ 0.,  0.],
       [ 1.,  8.],
       [ 0.,  4.]])
>>> np.hstack((a,b))
array([[ 8.,  8.,  1.,  8.],
       [ 0.,  0.,  0.,  4.]])

column_stack関数は、1次元配列を2次元配列に積み上げます。1次元配列の場合は、vstackと等価になります:

>>> from numpy import newaxis
>>> np.column_stack((a,b))   # With 2D arrays
array([[ 8.,  8.,  1.,  8.],
       [ 0.,  0.,  0.,  4.]])
>>> a = np.array([4.,2.])
>>> b = np.array([2.,8.])
>>> a[:,newaxis]  # This allows to have a 2D columns vector
array([[ 4.],
       [ 2.]])
>>> np.column_stack((a[:,newaxis],b[:,newaxis]))
array([[ 4.,  2.],
       [ 2.,  8.]])
>>> np.vstack((a[:,newaxis],b[:,newaxis])) # The behavior of vstack is different
array([[ 4.],
       [ 2.],
       [ 2.],
       [ 8.]])

2次元以上の配列の場合には、hstackは2つ目の軸に沿って積み上げ、vstackは最初の軸に沿って積み上げます。concatenateは連結する軸の番号をオプションの引数として指定することができます。

注釈

複雑な場合においては、ある軸に沿って数字を積み上げることで配列を作成するr_とc_が役に立ちます。これは、範囲を表すリテラルである「:」を使うことができます:

>>> np.r_[1:4,0,4]
array([1, 2, 3, 0, 4])

配列を引数として使う場合、r_とc_はvstackとhstackのデフォルトの動作に似てますが、連結する軸の番号をオプションの引数として指定することができます。

関連項目

hstack, vstack, column_stack, concatenate, c_, r_

配列を複数の小さな配列に分割

hsplitを使用すると、同じ形状の配列を返す数を指定することで、もしくは分割させる後ろの列を指定することで、配列を水平軸に沿って分割することができます:

>>> a = np.floor(10*np.random.random((2,12)))
>>> a
array([[ 9.,  5.,  6.,  3.,  6.,  8.,  0.,  7.,  9.,  7.,  2.,  7.],
       [ 1.,  4.,  9.,  2.,  2.,  1.,  0.,  6.,  2.,  2.,  4.,  0.]])
>>> np.hsplit(a,3)   # Split a into 3
[array([[ 9.,  5.,  6.,  3.],
       [ 1.,  4.,  9.,  2.]]), array([[ 6.,  8.,  0.,  7.],
       [ 2.,  1.,  0.,  6.]]), array([[ 9.,  7.,  2.,  7.],
       [ 2.,  2.,  4.,  0.]])]
>>> np.hsplit(a,(3,4))   # Split a after the third and the fourth column
[array([[ 9.,  5.,  6.],
       [ 1.,  4.,  9.]]), array([[ 3.],
       [ 2.]]), array([[ 6.,  8.,  0.,  7.,  9.,  7.,  2.,  7.],
       [ 2.,  1.,  0.,  6.,  2.,  2.,  4.,  0.]])]

vsplitは垂直軸に沿って分割し、array_splitはどの軸に沿って分割するかを指定することができます。

コピーとビュー

配列の演算や操作時には、データを新しい配列にコピーする場合もありますし、コピーしない場合もあります。これは時として初心者を混乱させる元となります。これには3つのケースがあります:

コピー無し

単純な代入では、配列オブジェクトもデータもコピーされません。

>>> a = np.arange(12)
>>> b = a            # no new object is created
>>> b is a           # a and b are two names for the same ndarray object
True
>>> b.shape = 3,4    # changes the shape of a
>>> a.shape
(3, 4)

Pythonは変更可能なオブジェクトを参照渡しするため、関数呼び出しではコピーは行いません。

>>> def f(x):
...     print(id(x))
...
>>> id(a)                           # id is a unique identifier of an object
148293216
>>> f(a)
148293216

ビューとシャローコピー

異なる配列オブジェクトが、同じデータを共有することができます。「view」メソッドは、同じデータを参照する新しい配列オブジェクトを作成します。

>>> c = a.view()
>>> c is a
False
>>> c.base is a                        # c is a view of the data owned by a
True
>>> c.flags.owndata
False
>>>
>>> c.shape = 2,6                      # a's shape doesn't change
>>> a.shape
(3, 4)
>>> c[0,4] = 1234                      # a's data changes
>>> a
array([[   0,    1,    2,    3],
       [1234,    5,    6,    7],
       [   8,    9,   10,   11]])

配列をスライスすると、ビューが返されます:

>>> s = a[ : , 1:3]     # spaces added for clarity; could also be written "s = a[:,1:3]"
>>> s[:] = 10           # s[:] is a view of s. Note the difference between s=10 and s[:]=10
>>> a
array([[   0,   10,   10,    3],
       [1234,   10,   10,    7],
       [   8,   10,   10,   11]])

ディープコピー

「copy」メソッドは、配列とそのデータの完全なコピーを作ります。

>>> d = a.copy()                          # a new array object with new data is created
>>> d is a
False
>>> d.base is a                           # d doesn't share anything with a
False
>>> d[0,0] = 9999
>>> a
array([[   0,   10,   10,    3],
       [1234,   10,   10,    7],
       [   8,   10,   10,   11]])

関数とメソッド概要

ここでは、役に立つNumPyの関数とメソッドをカテゴリ順にリストします。完全なリストはhttps://docs.scipy.org/doc/numpy-dev/reference/routines.html#routinesを参照してください。

Array Creation

arange, array, copy, empty, empty_like, eye, fromfile, fromfunction, identity, linspace, logspace, mgrid, ogrid, ones, ones_like, r,zeros, zeros_like

Conversions

ndarray.astype, atleast_1d, atleast_2d, atleast_3d, mat

Manipulations

array_split, column_stack, concatenate, diagonal, dsplit, dstack, hsplit, hstack, ndarray.item, newaxis, ravel, repeat, reshape,resize, squeeze, swapaxes, take, transpose, vsplit, vstack

Questions

all, any, nonzero, where

Ordering

argmax, argmin, argsort, max, min, ptp, searchsorted, sort

Operations

choose, compress, cumprod, cumsum, inner, ndarray.fill, imag, prod, put, putmask, real, sum

Basic Statistics

cov, mean, std, var

Basic Linear Algebra

cross, dot, outer, linalg.svd, vdot

応用

ブロードキャストルール

ブロードキャストにより、ユニバーサル関数は、正確には同じ形状ではない入力でも意味のある方法で扱うことができます。

ブロードキャストの1つ目のルールは、すべての入力は配列が同じ次元数を持たない場合、すべての配列が同じ次元数を持つまで、配列の先頭に「1」を穴埋めするものです。

ブロードキャストの2つ目のルールでは、特定の次元に沿った大きさが1である配列は、その次元に沿って最大の形状を持つ配列の大きさを持つものとして振舞うことを保証します。配列の要素の値は、「ブロードキャスト」配列の次元に沿って同じ値であるとみなされます。

ブロードキャストルールが適用されると、すべての配列の大きさは一致することになります。詳細は https://docs.scipy.org/doc/numpy-dev/user/basics.broadcasting.htmlに記載されています。

役に立つインデックス付けとインデックスのコツ

NumPyは、通常のPythonシーケンスよりも多くのインデックス機能を提供しています。整数やスライスによるインデックス付けに加えて、これまでに見てきたように、整数配列やブーリアン配列でも配列にインデックス付けすることができます。

インデックス配列によるインデックス付け

>>> a = np.arange(12)**2                       # the first 12 square numbers
>>> i = np.array( [ 1,1,3,8,5 ] )              # an array of indices
>>> a[i]                                       # the elements of a at the positions i
array([ 1,  1,  9, 64, 25])
>>>
>>> j = np.array( [ [ 3, 4], [ 9, 7 ] ] )      # a bidimensional array of indices
>>> a[j]                                       # the same shape as j
array([[ 9, 16],
       [81, 49]])

インデックス配列「a」が多次元配列の時、単一配列のインデックスは「a」の最初の次元を参照します。以下の例は、ラベルの画像をパレットを用いてカラー画像に変換することでこの振る舞いを表します。

>>> palette = np.array( [ [0,0,0],                # black
...                       [255,0,0],              # red
...                       [0,255,0],              # green
...                       [0,0,255],              # blue
...                       [255,255,255] ] )       # white
>>> image = np.array( [ [ 0, 1, 2, 0 ],           # each value corresponds to a color in the palette
...                     [ 0, 3, 4, 0 ]  ] )
>>> palette[image]                            # the (2,4,3) color image
array([[[  0,   0,   0],
        [255,   0,   0],
        [  0, 255,   0],
        [  0,   0,   0]],
       [[  0,   0,   0],
        [  0,   0, 255],
        [255, 255, 255],
        [  0,   0,   0]]])

1次元以上の配列に対してインデックスを与えることもできます。各次元に対するインデックス配列は、同じ形状である必要があります。

>>> a = np.arange(12).reshape(3,4)
>>> a
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>> i = np.array( [ [0,1],                        # indices for the first dim of a
...                 [1,2] ] )
>>> j = np.array( [ [2,1],                        # indices for the second dim
...                 [3,3] ] )
>>>
>>> a[i,j]                                     # i and j must have equal shape
array([[ 2,  5],
       [ 7, 11]])
>>>
>>> a[i,2]
array([[ 2,  6],
       [ 6, 10]])
>>>
>>> a[:,j]                                     # i.e., a[ : , j]
array([[[ 2,  1],
        [ 3,  3]],
       [[ 6,  5],
        [ 7,  7]],
       [[10,  9],
        [11, 11]]])

通常は、「i」と「j」をシーケンス (つまりリスト) に入れて、リストでインデックス付けを行います。

>>> l = [i,j]
>>> a[l]                                       # equivalent to a[i,j]
array([[ 2,  5],
       [ 7, 11]])

しかし、「i」と「j」を配列に入れると、同じことはできません。なぜならこの配列は、「a」の最初の次元をインデックス付けするものとして解釈されるからです。

>>> s = np.array( [i,j] )
>>> a[s]                                       # not what we want
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
IndexError: index (3) out of range (0<=index<=2) in dimension 0
>>>
>>> a[tuple(s)]                                # same as a[i,j]
array([[ 2,  5],
       [ 7, 11]])

配列でインデックス付けする他のよくある使い方は、時間依存連続データの最大値検索です。

>>> time = np.linspace(20, 145, 5)                 # time scale
>>> data = np.sin(np.arange(20)).reshape(5,4)      # 4 time-dependent series
>>> time
array([  20.  ,   51.25,   82.5 ,  113.75,  145.  ])
>>> data
array([[ 0.        ,  0.84147098,  0.90929743,  0.14112001],
       [-0.7568025 , -0.95892427, -0.2794155 ,  0.6569866 ],
       [ 0.98935825,  0.41211849, -0.54402111, -0.99999021],
       [-0.53657292,  0.42016704,  0.99060736,  0.65028784],
       [-0.28790332, -0.96139749, -0.75098725,  0.14987721]])
>>>
>>> ind = data.argmax(axis=0)                   # index of the maxima for each series
>>> ind
array([2, 0, 3, 1])
>>>
>>> time_max = time[ ind]                       # times corresponding to the maxima
>>>
>>> data_max = data[ind, xrange(data.shape[1])] # => data[ind[0],0], data[ind[1],1]...
>>>
>>> time_max
array([  82.5 ,   20.  ,  113.75,   51.25])
>>> data_max
array([ 0.98935825,  0.84147098,  0.99060736,  0.6569866 ])
>>>
>>> np.all(data_max == data.max(axis=0))
True

代入する対象として、配列でインデックス付けすることにも使用できます。

>>> a = np.arange(5)
>>> a
array([0, 1, 2, 3, 4])
>>> a[[1,3,4]] = 0
>>> a
array([0, 0, 2, 0, 0])

一方で、インデックスのリストに繰り返しが含まれている場合、代入は複数回行われ、最後の値が残ります:

>>> a = np.arange(5)
>>> a[[0,0,2]]=[1,2,3]
>>> a
array([2, 1, 3, 3, 4])

これは十分に合理的なのですが、Pythonの「+=」構造を使いたいのであれば、期待している動作とは異なる場合があるので注意してください:

>>> a = np.arange(5)
>>> a[[0,0,2]]+=1
>>> a
array([1, 1, 3, 3, 4])

インデックスのリストに0が2回現れていますが、0番目の要素は1度しか増分されていません。これは、Pythonが「a+=1」を「a=a+1」と等価であるとしているためです。

ブール型配列によるインデックス付け

(整数の) インデックス配列で配列をインデックス付けする場合には、使用したいインデックスのリストを用意しています。ブール型インデックスを使う場合には、アプローチは異なります;使用したい配列の要素と使用したくない配列の要素を、明示的に選択します。

ブール型インデックス付けを検討する最もよくある方法は、元の配列と同じ形状を持つブール型配列を使うことです:

>>> a = np.arange(12).reshape(3,4)
>>> b = a > 4
>>> b                                          # b is a boolean with a's shape
array([[False, False, False, False],
       [False,  True,  True,  True],
       [ True,  True,  True,  True]], dtype=bool)
>>> a[b]                                       # 1d array with the selected elements
array([ 5,  6,  7,  8,  9, 10, 11])

この性質は、代入のときに非常に便利です:

>>> a[b] = 0                                   # All elements of 'a' higher than 4 become 0
>>> a
array([[0, 1, 2, 3],
       [4, 0, 0, 0],
       [0, 0, 0, 0]])

以下の例を見ると、「マンデルブロ集合」のイメージを生成するためにブール型インデックス付けをどのように使うかがわかります:

>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> def mandelbrot( h,w, maxit=20 ):
...     """Returns an image of the Mandelbrot fractal of size (h,w)."""
...     y,x = np.ogrid[ -1.4:1.4:h*1j, -2:0.8:w*1j ]
...     c = x+y*1j
...     z = c
...     divtime = maxit + np.zeros(z.shape, dtype=int)
...
...     for i in range(maxit):
...         z = z**2 + c
...         diverge = z*np.conj(z) > 2**2            # who is diverging
...         div_now = diverge & (divtime==maxit)  # who is diverging now
...         divtime[div_now] = i                  # note when
...         z[diverge] = 2                        # avoid diverging too much
...
...     return divtime
>>> plt.imshow(mandelbrot(400,400))
>>> plt.show()

f:id:masakykato:20170202200535p:plain

ブール型インデックス付けを使う2つ目の方法は、整数型インデックス付けにより似ています;配列の各次元に対して、欲しいスライスを選択して1次元のブール型配列を得ます。

>>> a = np.arange(12).reshape(3,4)
>>> b1 = np.array([False,True,True])             # first dim selection
>>> b2 = np.array([True,False,True,False])       # second dim selection
>>>
>>> a[b1,:]                                   # selecting rows
array([[ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>
>>> a[b1]                                     # same thing
array([[ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>
>>> a[:,b2]                                   # selecting columns
array([[ 0,  2],
       [ 4,  6],
       [ 8, 10]])
>>>
>>> a[b1,b2]                                  # a weird thing to do
array([ 4, 10])

1次元ブール型配列の長さはスライスしたい次元(もしくは軸)の長さと一致している必要があることに注意してください。前の例では、「b1」は長さが3 (「a」の行数) のランク1配列で、「b2」(長さが4) は「a」の2番目のランク(列)のインデックスに一致しています。

ix_()関数

「ix_」関数は、異なるベクトルを結合して各n-upletの結果を得るために使用することができます。例えば、ベクトルa、b、cから取得したすべての3つ組の値に対してa+b*cを計算したいのであれば:

>>> a = np.array([2,3,4,5])
>>> b = np.array([8,5,4])
>>> c = np.array([5,4,6,8,3])
>>> ax,bx,cx = np.ix_(a,b,c)
>>> ax
array([2,
       3,
       4,
       5])
>>> bx
array([[[8],
        [5],
        [4]]])
>>> cx
array([5, 4, 6, 8, 3])
>>> ax.shape, bx.shape, cx.shape
((4, 1, 1), (1, 3, 1), (1, 1, 5))
>>> result = ax+bx*cx
>>> result
array([[[42, 34, 50, 66, 26],
        [27, 22, 32, 42, 17],
        [22, 18, 26, 34, 14]],
       [[43, 35, 51, 67, 27],
        [28, 23, 33, 43, 18],
        [23, 19, 27, 35, 15]],
       [[44, 36, 52, 68, 28],
        [29, 24, 34, 44, 19],
        [24, 20, 28, 36, 16]],
       [[45, 37, 53, 69, 29],
        [30, 25, 35, 45, 20],
        [25, 21, 29, 37, 17]]])
>>> result[3,2,4]
17
>>> a[3]+b[2]*c[4]
17

以下のようにreduceを実装することもできます:

>>> def ufunc_reduce(ufct, *vectors):
...    vs = np.ix_(*vectors)
...    r = ufct.identity
...    for v in vs:
...        r = ufct(r,v)
...    return r

そして、このように使用します:

>>> ufunc_reduce(np.add,a,b,c)
array([[[15, 14, 16, 18, 13],
        [12, 11, 13, 15, 10],
        [11, 10, 12, 14,  9]],
       [[16, 15, 17, 19, 14],
        [13, 12, 14, 16, 11],
        [12, 11, 13, 15, 10]],
       [[17, 16, 18, 20, 15],
        [14, 13, 15, 17, 12],
        [13, 12, 14, 16, 11]],
       [[18, 17, 19, 21, 16],
        [15, 14, 16, 18, 13],
        [14, 13, 15, 17, 12]]])

 

標準のufunc.reduceと比べたこのバージョンのreduceの利点は、出力xベクトル数の大きさの引数配列の作成を避けるために、ブロードキャストルールを使用するところにあります。

文字列によるインデックス付け

Recordarraysを参照してください。(参照先不明)

線形代数

執筆中です。基本的な線形代数がここに入ります。

簡単な配列演算

詳細はnumpyフォルダにあるlinalg.pyを参照してください。

>>> import numpy as np
>>> a = np.array([[1.0, 2.0], [3.0, 4.0]])
>>> print(a)
[[ 1.  2.]
 [ 3.  4.]]

>>> a.transpose()
array([[ 1.,  3.],
       [ 2.,  4.]])

>>> np.linalg.inv(a)
array([[-2. ,  1. ],
       [ 1.5, -0.5]])

>>> u = np.eye(2) # unit 2x2 matrix; "eye" represents "I"
>>> u
array([[ 1.,  0.],
       [ 0.,  1.]])
>>> j = np.array([[0.0, -1.0], [1.0, 0.0]])

>>> np.dot (j, j) # matrix product
array([[-1.,  0.],
       [ 0., -1.]])

>>> np.trace(u)  # trace
2.0

>>> y = np.array([[5.], [7.]])
>>> np.linalg.solve(a, y)
array([[-3.],
       [ 4.]])

>>> np.linalg.eig(j)
(array([ 0.+1.j,  0.-1.j]), array([[ 0.70710678+0.j        ,  0.70710678-0.j        ],
       [ 0.00000000-0.70710678j,  0.00000000+0.70710678j]]))
Parameters:
    square matrix
Returns
    The eigenvalues, each repeated according to its multiplicity.
    The normalized (unit "length") eigenvectors, such that the
    column ``v[:,i]`` is the eigenvector corresponding to the
    eigenvalue ``w[i]`` .

ヒントとコツ

ここでは、ちょっとした役立つTipsを提供します。

「自動的な」再整形

配列の次元を変更する場合、配列のサイズを省略し、自動的に推定させることができます。

>>> a = np.arange(30)
>>> a.shape = 2,-1,3  # -1 means "whatever is needed"
>>> 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]]])

ベクトルの積み上げ

同じサイズを持つ行ベクトルのリストから、どのように2次元配列を作成するのでしょうか。MATLABでは、これは非常に簡単です:「x」と「y」が同じ長さを持つ2つのベクトルの場合、「m=[x;y]」とするだけです。NumPyでは、 積み上げを実行する次元に従って「column_stack」や「dstack」、「hstack」、「vstack」といった関数で実施します。例えば:

x = np.arange(0,10,2)                     # x=([0,2,4,6,8])
y = np.arange(5)                          # y=([0,1,2,3,4])
m = np.vstack([x,y])                      # m=([[0,2,4,6,8],
                                          #     [0,1,2,3,4]])
xy = np.hstack([x,y])                     # xy =([0,2,4,6,8,0,1,2,3,4])

2次元以上でのこれらの関数のロジックは、奇妙ではあります。

関連項目

Numpy for Matlab users

ヒストグラム

配列に適用されるNumPyの「histgram」関数は、ベクトルのペア、つまり配列のヒストグラムとビンのベクトルを返します。

注意:matplotlibにもヒストグラムを生成する関数 (Matlabでは「hist」と呼ばれます) がありますが、NumPyとは異なります。主な違いは、「pylab.hist」は自動的にヒストグラムを描画し、「numpy.histgram」はデータを生成するのみであることです。

>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> # Build a vector of 10000 normal deviates with variance 0.5^2 and mean 2
>>> mu, sigma = 2, 0.5
>>> v = np.random.normal(mu,sigma,10000)
>>> # Plot a normalized histogram with 50 bins
>>> plt.hist(v, bins=50, normed=1)       # matplotlib version (plot)
>>> plt.show()

f:id:masakykato:20170202201220p:plain

>>> # Compute the histogram with numpy and then plot it
>>> (n, bins) = np.histogram(v, bins=50, normed=True)  # NumPy version (no plot)
>>> plt.plot(.5*(bins[1:]+bins[:-1]), n)
>>> plt.show()

f:id:masakykato:20170202201251p:plain

参考情報