naoya_t@hatenablog

いわゆるチラシノウラであります

私訳「暫定的 NumPy チュートリアル」

# 原文:http://www.scipy.org/Tentative_NumPy_Tutorial

このチュートリアルを読む前に、Pythonについてちょっとは知っているべきだ。記憶をリフレッシュしたいと思うなら、Pythonチュートリアルを見てくるがいい。
このチュートリアルに出てくる例を試したいなら、あなたのPCに少なくとも

はインストールされているべきで、他に入ってると便利なのは:

  • ipython は拡張されたインタラクティブPythonシェルで、NumPyの機能を探検するのにとても便利
  • matplotlib があると図表の描画が可能になる
  • SciPy はNumPyの上で動く科学計算ルーチンを沢山用意してくれる

基礎

NumPy の主要なオブジェクトは、同じ型(普通は数)の要素のみから成り、正の整数のタプルで添字付けされた、均質なテーブル(というか多次元配列)である。
NumPyにおいて、次元は「軸」と呼ばれる。
軸の数は「ランク」である。

  • 3次元空間にある点の座標 [1,2,1] はランク1の配列 ←軸が1つ。軸の長さは3。
  • 次の配列はランク2(二次元)。最初の次元(軸)は長さ2で、2つ目の次元は長さ3。
[[ 1., 0., 0.],
 [ 0., 1., 2.]]

NumPyの配列クラスは ndarray と呼ばれる。これはarrayという別名でも知られるが、1次元しか扱えない上に機能も乏しい標準Pythonライブラリの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, etc.)を追加している。

ndarray.itemsize

配列の各要素のバイト数。例えば、float64型の要素から成る配列の場合、itemsizeは8 (=64/8) で、complex32型のものであれば itemsizeは4 (=32/8) である。これはndarray.dtype.itemsizeと等価である。

ndarray.data

配列の実際の要素を収容しているバッファである。配列の要素には添字機能を用いてアクセスできるので、普通はこの属性を用いる必要はないだろう。

>>> from numpy  import *
>>> a = arange(10).reshape(2,5)
>>> a
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])
>>> a.shape
(2, 5)
>>> a.ndim
2
>>> a.dtype.name
'int32'
>>> a.itemsize
4
>>> a.size
10
>>> type(a)
numpy.ndarray
>>> b = numpy.array([6, 7, 8])
>>> b
array([6, 7, 8])
>>> type(b)
numpy.ndarray

配列を作成する

配列を作成する方法は何通りかある。
例えば、普通のPythonのリストやタプルからarray関数で作成する方法。出来上がる配列の型はシークエンスの要素の型から推論される。

>>> from numpy  import *
>>> a = array( [2,3,4] )
>>> a
array([2, 3, 4])
>>> a.dtype
dtype('int32')
>>> b = array([1.2, 3.5, 5.1])
>>> b.dtype
dtype('float64')

array関数の引数に数値のリストを渡すところを複数の数値をそのまま引数に渡してエラーになるというのはよくある。

>>> a = array(1,2,3,4)    # 間違っている

>>> a = array([1,2,3,4])  # 正しい

arrayはシークエンスのシークエンスを2次元配列に、シークエンスのシークエンスのシークエンスを3次元配列に、といったように変換してくれる。

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

配列作成時に、型を明示的に指定することも可能だ:

>>> c = 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である。

>>> zeros( (3,4) )
array([[0.,  0.,  0.,  0.],
       [0.,  0.,  0.,  0.],
       [0.,  0.,  0.,  0.]])
>>> ones( (2,3,4), dtype=int16 )                # dtype を指定することも可能
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)
>>> empty( (2,3) )
array([[  3.73603959e-262,   6.02658058e-154,   6.55490914e-260],
       [  5.30498948e-313,   3.14673309e-307,   1.00000000e+000]])

数値のシークエンスを作成するためにNumPyは、リストの代わりに配列を返す、rangeに似た関数を用意している。

>>> arange( 10, 30, 5 )
array([10, 15, 20, 25])
>>> arange( 0, 2, 0.3 )                 # 浮動小数点数も引数にできる
array([ 0. ,  0.3,  0.6,  0.9,  1.2,  1.5,  1.8])

arangeが浮動小数点数を引数として用いられる場合、浮動小数点の精度が有限であるため、一般に、要素数を先に知ることはできない。このため、必要な要素数をステップの代わりに引数として渡せる関数linspaceを使ったほうが大抵良い:

>>> linspace( 0, 2, 9 )                 # 0と2の間で9つの数値
array([ 0.  ,  0.25,  0.5 ,  0.75,  1.  ,  1.25,  1.5 ,  1.75,  2.  ])
>>> x = linspace( 0, 2*pi, 100 )        # 沢山の点において関数を評価する際に便利
>>> f = sin(x)
関連項目

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

配列を印字する

配列を印字する際、NumPyはネストされたリストと似たような表示をするが、次のようなレイアウトになる:

  • 最後の軸は左から右へ印字される
  • 最後から二番目の軸は上から下へ印字される
  • 残りは上から下へ、空行で区切られて表示される

1次元の配列は行のように、2次元の配列は行列のように、3次元の配列は行列のリストのように表示される。

>>> a = arange(6)                         # 1次元配列
>>> print a
[0 1 2 3 4 5]
>>>
>>> b = arange(12).reshape(4,3)           # 2次元配列
>>> print b
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]
>>>
>>> c = arange(24).reshape(2,3,4)         # 3次元配列
>>> 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 arange(10000)
[   0    1    2 ..., 9997 9998 9999]
>>>
>>> print 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 を用いて印字オプションを変更することができる。

>>> set_printoptions(threshold='nan')

基本的な操作

配列に対する算術演算子は要素毎に適用され、演算結果が入った新たな配列が作成される。

>>> a = array( [20,30,40,50] )
>>> b = 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*sin(a)
array([ 9.12945251, -9.88031624,  7.4511316 , -2.62374854])
>>> a<35
array([True, True, False, False], dtype=bool)

多くの行列言語とは異なり、NumPyの配列においては乗算演算子 * は要素単位の演算を行う。行列の積は dot 関数を用いるか、行列オブジェクトを作成することで計算できる。(本チュートリアルの行列のセクションを参照のこと)。

>>> A = array( [[1,1],
...             [0,1]] )
>>> B = array( [[2,0],
...             [3,4]] )
>>> A*B                         # 要素毎の積
array([[2, 0],
       [0, 4]])
>>> dot(A,B)                    # 行列積
array([[5, 4],
       [3, 4]])
  1. = や *= のような演算は、新たな配列を作成せず、既に存在する配列の内容をその場で書き換える。
>>> a = ones((2,3), dtype=int)
>>> b = random.random((2,3))
>>> a *= 3
>>> a
array([[3, 3, 3],
       [3, 3, 3]])
>>> b += a
>>> b
array([[ 3.69092703,  3.8324276 ,  3.0114541 ],
       [ 3.18679111,  3.3039349 ,  3.37600289]])
>>> a += b                                  # b は整数型に変換される
>>> a
array([[6, 6, 6],
       [6, 6, 6]])

型が異なる配列同士を演算する際には、演算結果の型はより一般的もしくはより精度の高い側に対応する。(アップキャスティングとして知られる振舞い)

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

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

>>> a = random.random((2,3))
>>> a
array([[ 0.6903007 ,  0.39168346,  0.16524769],
       [ 0.48819875,  0.77188505,  0.94792155]])
>>> a.sum()
3.4552372100521485
>>> a.min()
0.16524768654743593
>>> a.max()
0.9479215542670073

デフォルトで、これらの演算は配列に対し、配列のshapeに関わらず、数値のリストであるかのように適用される。しかし、軸パラメータを指定することで、配列の特定の軸に沿って演算を適用することが可能である:

>>> b = arange(12).reshape(3,4)
>>> b
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>
>>> b.sum(axis=0)                            # 各列の総和
array([12, 15, 18, 21])
>>>
>>> b.min(axis=1)                            # 各行の最小値
array([0, 4, 8])
>>>
>>> b.cumsum(axis=1)                         # 各行に沿った累積和
array([[ 0,  1,  3,  6],
       [ 4,  9, 15, 22],
       [ 8, 17, 27, 38]])

ユニバーサル関数

NumPyはsin, cos, exp のようなおなじみの数学関数も用意している。NumPyでは、これらは「ユニバーサル関数(ufunc)」と呼ばれる。NumPyの中では、これらの関数は配列の要素毎に演算を行い、出力として新しい配列を生成する。

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

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

添字アクセス、スライス、イテレート

1次元配列は、リストPythonの他のシークエンスとほぼ同様、添字でアクセスしたり、スライスしたり、イテレートしたりすることができる。

>>> a = 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    # a[0:6:2] = -1000 と等価; 最初から位置6の手前まで、要素1つおきに-1000にセット
>>> a
array([-1000,     1, -1000,    27, -1000,   125,   216,   343,   512,   729])
>>> a[ : :-1]                                 # 反転された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

多次元配列は軸ごとに添字を1つ持つことができる。これらの添字はカンマ区切りのタプルで与えられる:

>>> def f(x,y):
...         return 10*x+y
...
>>> b = 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]                       # bの各行の2列目
array([ 1, 11, 21, 31, 41])
>>> b[ : ,1]                        # 前の例と等価
array([ 1, 11, 21, 31, 41])
>>> b[1:3, : ]                      # bの2行目3行目の各列
array([[10, 11, 12, 13],
       [20, 21, 22, 23]])

与えられた添字の数が軸数より少ない場合、足りない添字は完全なスライスと見なされる:

>>> b[-1]                                  # 最後の行。b[-1,:] と等価。
array([40, 41, 42, 43])

b[i] のブラケットの中の式は、i の後に残りの軸を表現するのに必要な分の : が続いているものとして扱われる。NumPyではドットを用いてこれを b[i,...] と各いてもよい。
ドット (...) は完全な添字タプルを生成するために必要な分のコロンを表す。例えば、x がランク5の配列(即ち、軸が5つ)であるとするなら、

  • x[1,2,...] は x[1,2,:,:,:] と等価
  • x[...,3] は x[:,:,:,:,3] と等価
  • x[4,...,5,:] は x[4,:,:,5,:] と等価

である。

>>> c = array( [ [[  0,  1,  2],               # 3次元配列(2つ積まれた2次元配列)
...               [ 10, 12, 13]],
...
...              [[100,101,102],
...               [110,112,113]] ] )
>>> c.shape
(2, 2, 3)
>>> c[1,...]                                   # c[1,:,:] あるいは 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
関連項目

[], ..., newaxis, ndenumerate, indices, index exp

形状の操作

配列の形状を変更する

配列には、各軸沿いの要素数によって与えられた形状(shape)がある:

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

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

>>> a.ravel() # 配列を解きほぐして(ravel)平らにする
array([ 7.,  5.,  9.,  3.,  7.,  2.,  7.,  8.,  6.,  8.,  3.,  2.])
>>> a.shape = (6, 2)
>>> a.transpose()
array([[ 7.,  9.,  7.,  7.,  6.,  3.],
       [ 5.,  3.,  2.,  8.,  8.,  2.]])

ravel() の結果として得られる配列に含まれる要素の順序は普通に "C-style"、即ち、一番右の添字が「最初に変わる」ので、a[0,0]の次に来る要素はa[0,1]である。他の形状に再整形されても、配列は再び "C-style" として扱われる。Numpyは普通ravel()が引数をコピーしなくてもいいようにこの順序で格納して配列を作成するが、その配列が他の配列をスライスして作られたものであったり、一般的でないオプションで作られたものである場合、コピーが必要になるかもしれない。関数 ravel(), reshape() はオプション引数を指定することで、FORTRAN形式の配列を用いるようにもできる。その場合、一番左の添字が最初に変わる。
reshape関数はその引数を修正された形状で返す一方で、resizeメソッドは配列そのものを修正する:

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

再整形操作において寸法が-1で与えられた場合、他の寸法は自動的に算出される:

>>> a.reshape(3,-1)
array([[ 7.,  5.,  9.,  3.],
       [ 7.,  2.,  7.,  8.],
       [ 6.,  8.,  3.,  2.]])
参考

shape example, reshape example, resize example, ravel example

異なる配列を一緒に積む

いくつかの配列を、異なる軸に沿って一緒に積むことができる:

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

column_stack関数は1次元配列を列として積み上げて2次元配列を作る。1次元配列の場合に限れば、これはvstackと等価である:

>>> column_stack((a,b))   # 2次元配列で
array([[ 1.,  1.,  3.,  3.],
       [ 5.,  8.,  6.,  0.]])
>>> a=array([4.,2.])
>>> b=array([2.,8.])
>>> a[:,newaxis]  # こうすると2次元の列ベクトルが得られる
array([[ 4.],
       [ 2.]])
>>> column_stack((a[:,newaxis],b[:,newaxis]))
array([[ 4.,  2.],
       [ 2.,  8.]])
>>> vstack((a[:,newaxis],b[:,newaxis])) # vstackの振舞いは異なる
array([[ 4.],
       [ 2.],
       [ 2.],
       [ 8.]])

一方、関数 row_stack は、1次元配列を行として積み上げて2次元配列を作る。2次元を超える配列では、hstack は2番目の軸に沿って積み、vstack は1番目の軸に沿って積み、concatenate はどの軸に沿って結合が起こるべきかを示す数をオプション引数に与えることができる。

複雑なケースにおいては、ある軸に沿って配列を作る際に r_ と c_ が便利である。これらは範囲を表すリテラル (":") を使うことができる:

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

配列を引数に使われる時、r_ と c_ は vstack と hstack のデフォルトの振舞いに似ているが、どの軸に沿って結合するかをオプション引数で与えることができる。

参考

hstack example, vstack exammple, column_stack example, row_stack example, concatenate example, c_ example, r_ example

1つの配列をより小さな複数の配列に分割する

hsplitを用いれば、同じ形状の配列をいくつ返すか、あるいはどの列で分割が起こるべきかを指定して、配列を水平軸沿いに分割できる:

>>> a = floor(10*random.random((2,12)))
>>> a
array([[ 8.,  8.,  3.,  9.,  0.,  4.,  3.,  0.,  0.,  6.,  4.,  4.],
       [ 0.,  3.,  2.,  9.,  6.,  0.,  4.,  5.,  7.,  5.,  1.,  4.]])
>>> hsplit(a,3)   # aを3つに分割
[array([[ 8.,  8.,  3.,  9.],
       [ 0.,  3.,  2.,  9.]]), array([[ 0.,  4.,  3.,  0.],
       [ 6.,  0.,  4.,  5.]]), array([[ 0.,  6.,  4.,  4.],
       [ 7.,  5.,  1.,  4.]])]
>>> hsplit(a,(3,4))   # aを3列目と4列目で分割
[array([[ 8.,  8.,  3.],
       [ 0.,  3.,  2.]]), array([[ 9.],
       [ 9.]]), array([[ 0.,  4.,  3.,  0.,  0.,  6.,  4.,  4.],
       [ 6.,  0.,  4.,  5.,  7.,  5.,  1.,  4.]])]

vsplitは垂直軸沿いに分割し、array split はどの軸沿いに分割するかを指定できる。

コピーとビュー

配列を演算や操作において、そのデータは新たな配列にコピーされる時もあればそうでない時もある。これは時として初心者にとって混乱の元となる。3つのケースがある:

1. 全くコピーを行わない

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

>>> a = arange(12)
>>> b = a            # 新しいオブジェクトは生成されない
>>> b is a           # a と b は同一のndarrayオブジェクトを指す2つの名前である
True
>>> b.shape = 3,4    # aの形状を変える
>>> a.shape
(3, 4)

Python は書き換え可能なオブジェクトを参照渡しするため、関数コールはコピーを行わない。

>>> def f(x):
...     print id(x)
...
>>> id(a)                           # id はあるオブジェクトのユニークな識別子である
148293216
>>> f(a)
148293216

2. ビュー、または浅いコピー

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

>>> c = a.view()
>>> c is a
False
>>> c.base is a                        # c は a が所有しているデータのビューである
True
>>> c.flags.owndata
False
>>>
>>> c.shape = 2,6                      # a の形状は変更されない
>>> a.shape
(3, 4)
>>> c[0,4] = 1234                      # a のデータは変更される
>>> a
array([[   0,    1,    2,    3],
       [1234,    5,    6,    7],
       [   8,    9,   10,   11]])

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

>>> s = a[ : , 1:3]     # わかりやすくする為に空白を入れてあるだけで、"s = a[:,1:3]" と書いてもよい
>>> s[:] = 10           # s[:] はsのビューである。s=10 と s[:]=10 の違いに注意。
>>> a
array([[   0,   10,   10,    3],
       [1234,   10,   10,    7],
       [   8,   10,   10,   11]])

3. 深いコピー

copyメソッドは、配列とそのデータの完全なコピーを作る。

>>> d = a.copy()                          # 新たに作成されたデータを持つ新たな配列オブジェクト
>>> d is a
False
>>> d.base is a                           # d は a と何も共有していない
False
>>> d[0,0] = 9999
>>> a
array([[   0,   10,   10,    3],
       [1234,   10,   10,    7],
       [   8,   10,   10,   11]])

関数とメソッドの概要

NumPyの関数およびメソッドのカテゴリ別一覧である。用例が見られるように、関数名をNumpy Example Listにリンクしてある。(※この訳ではリンクしていない)

配列作成

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

変換

astype, atleast 1d, atleast 2d, atleast 3d, mat

操作

array split, column stack, concatenate, diagonal, dsplit, dstack, hsplit, hstack, item, newaxis, ravel, repeat, reshape, resize, squeeze, swapaxes, take, transpose, vsplit, vstack

問合せ

all, any, nonzero, where

整列

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

演算

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

基本的統計

cov, mean, std, var

基本的線形代数

cross, dot, outer, svd, vdot

中級編

Broadcasting ルール

broadcastingは、ユニバーサル関数が、正確に同一の形状を持つわけではない入力を意味のある形で扱えるようにしてくれる。
broadcastingの第1のルールは、もし全ての入力配列が同じ次元数でないなら、より小さい配列の形状に対し次元数が同じになるまで先頭に "1" を繰り返し追加するというもの。
broadcastingの第2のルールは、特定の次元に沿ったサイズが1の配列が、その次元において最大の形状をもつ配列と同じサイズを持っているかのように振る舞うことを保証するというもの。"broadcast"配列にとって、配列の要素の値はその次元に沿って同じであると仮定される。
broadcastingルールを適用した後、全ての配列のサイズは合致していなければならない。詳しくはこのドキュメントに記載されている。

変わった添字と、添字付け技法

NumPyは、Pythonの普通のシークエンスよりも多くの添字付け機能を用意している。整数やスライスによる添字付けに加え、これまでに見てきたように、整数配列や真偽値配列による配列の添字付けも可能である。

添字配列による添字付け

>>> a = arange(12)**2                          # 最初の12個の平方数
>>> i = array( [ 1,1,3,8,5 ] )                 # 添字の配列
>>> a[i]                                       # 配列 a の(複数の)位置 i にある要素たち
array([ 1,  1,  9, 64, 25])
>>>
>>> j = array( [ [ 3, 4], [ 9, 7 ] ] )         # 添字の2次元配列
>>> a[j]                                       # jと同じ形状
array([[ 9, 16],
       [81, 49]])

添字付けされた配列aが多次元配列のとき、添字の単一配列はaの最初の次元を参照する。以下の例では、ラベル画像をパレットを用いてカラー画像に変換することでこの振舞いを例示する。

>>> palette = array( [ [0,0,0],                # 黒
...                    [255,0,0],              # 赤
...                    [0,255,0],              # 緑
...                    [0,0,255],              # 青
...                    [255,255,255] ] )       # 白
>>> image = array( [ [ 0, 1, 2, 0 ],           # 各値が、パレット内の1つの色に対応する
...                  [ 0, 3, 4, 0 ]  ] )
>>> palette[image]                            # (2,4,3) のカラー画像
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]]])

2次元以上の添字を与えることも可能である。各次元の添字配列は同じ形状でなければならない。

>>> a = arange(12).reshape(3,4)
>>> a
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>> i = array( [ [0,1],                        # aの1次元目の添字
...              [1,2] ] )
>>> j = array( [ [2,1],                        # 2次元目の添字
...              [3,3] ] )
>>>
>>> a[i,j]                                     # i と j は同じ形状であること
array([[ 2,  5],
       [ 7, 11]])
>>>
>>> a[i,2]
array([[ 2,  6],
       [ 6, 10]])
>>>
>>> a[:,j]                                     # 即ち a[ : , j]
array([[[ 2,  1],
        [ 3,  3]],
       [[ 6,  5],
        [ 7,  7]],
       [[10,  9],
        [11, 11]]])

普通は、i と j をシークエンス(というかリスト)に入れた上で、リストで添字付けを行う。

>>> l = [i,j]
>>> a[l]                                       # a[i,j] と等価
array([[ 2,  5],
       [ 7, 11]])

しかし、i と j を配列に入れて同じことができるわけではない。なぜなら、この配列はaの1次元目を添字付けするものとして解釈されるからである。

>>> s = array( [i,j] )
>>> a[s]                                       # これは求めているものではない
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)]                                # a[i,j] に同じ
array([[ 2,  5],
       [ 7, 11]])

配列での添字付けのもう1つの一般的な使い方は、時間に依存した連続データの中からの最大値の検索である:

>>> time = linspace(20, 145, 5)                 # タイムスケール
>>> data = sin(arange(20)).reshape(5,4)         # 4つの時間依存な連続データ
>>> 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)                   # 各連続データで最大値をとる添字
>>> ind
array([2, 0, 3, 1])
>>>
>>> time_max = time[ ind]                       # 最大値に対応する時刻
>>>
>>> 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 ])
>>>
>>> all(data_max == data.max(axis=0))
True

代入対象を表すために配列で添字付けすることも可能である:

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

しかし、添字リストに繰り返しが含まれる場合、代入は複数回行われ、最後の値が残る:

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

これは十分納得がいくものであるが、Pythonの += 構造を使いたい場合、期待している動作をしないかもしれないので注意せよ:

>>> a = 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 = arange(12).reshape(3,4)
>>> b = a > 4
>>> b                                          # b は a の形状の真偽値
array([[False, False, False, False],
       [False, True, True, True],
       [True, True, True, True]], dtype=bool)
>>> a[b]                                       # 選択された要素から成る1次元配列
array([ 5,  6,  7,  8,  9, 10, 11])

この性質は、代入の際にとても便利である:

>>> a[b] = 0                                   # 'a' の4より上の全要素が0になる
>>> a
array([[0, 1, 2, 3],
       [4, 0, 0, 0],
       [0, 0, 0, 0]])

Mandelbrot集合の例を見れば、Mandelbrot集合の画像の生成に真偽値添字付けをどのように用いているか分かるだろう。
真偽値による添字付けの2つ目の方法は、もっと整数での添字付けに似ている:配列の各次元について、1次元の真偽値配列を与え、欲しいスライスを選択する。

>>> a = arange(12).reshape(3,4)
>>> b1 = array([False,True,True])             # 1次元目の選択
>>> b2 = array([True,False,True,False])       # 2次元目の選択
>>>
>>> a[b1,:]                                   # 行を選択
array([[ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>
>>> a[b1]                                     # 同様
array([[ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>
>>> a[:,b2]                                   # 列を選択
array([[ 0,  2],
       [ 4,  6],
       [ 8, 10]])
>>>
>>> a[b1,b2]                                  # 変わったことをしようと
array([ 4, 10])

1次元真偽値配列の長さは スライスしたい次元(あるいは軸)の長さと合致していなければならないことに注意。前の例では、b1はランク1で長さが3の配列(a に含まれる行数)であり、b2(長さ4)はaの第2ランク(列)を添字付けするのにふさわしい。

ix_() 関数

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

>>> a = array([2,3,4,5])
>>> b = array([8,5,4])
>>> c = array([5,4,6,8,3])
>>> ax,bx,cx = 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 = ix_(*vectors)
    r = ufct.identity
    for v in vs:
        r = ufct(r,v)
    return r

そしてこんな風に使う:

>>> ufunc_reduce(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]]])

reduceのこのバージョンを通常の ufunc.reduce と比較した場合の利点は、出力サイズ×ベクトル数の引数配列の作成を回避するためにBroadcasting Rulesを用いている事にある。

文字列による添字付け

RecordArrays参照

線形代数

執筆中(らしい。原文によると)。基本的線形代数がここに入る。

簡単な配列演算

詳細はnumpyフォルダのlinalg.pyを参照のこと。

>>> from numpy import *
>>> from numpy.linalg import *

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

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

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

>>> u = eye(2) # 2x2単位行列; "eye" は "I" を表している
>>> u
array([[ 1.,  0.],
       [ 0.,  1.]])
>>> j = array([[0.0, -1.0], [1.0, 0.0]])

>>> dot (j, j) # 行列積
array([[-1.,  0.],
       [ 0., -1.]])

>>> trace(u)  # トレース
2.0

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

>>> 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]]))
引数:
    平方行列

返り値
    全固有値。各々の多重度に従って繰り返される

    正規化された(単位「長」)固有ベクトル。カラム "v[:,i]" が固有値 "w[i]" に対応する固有ベクトルになっている。

行列クラス

ここで行列クラスを簡単に紹介する。

>>> A = matrix('1.0 2.0; 3.0 4.0')
>>> A
[[ 1.  2.]
 [ 3.  4.]]
>>> type(A)  # クラスを定義しているファイル
<class 'numpy.matrixlib.defmatrix.matrix'>

>>> A.T  # 転置
[[ 1.  3.]
 [ 2.  4.]]

>>> X = matrix('5.0 7.0')
>>> Y = X.T
>>> Y
[[5.]
 [7.]]

>>> print A*Y  # 行列の乗算
[[19.]
 [43.]]

>>> print A.I  # 逆行列
[[-2.   1. ]
 [ 1.5 -0.5]]

>>> solve(A, Y)  # 線形方程式を解く
matrix([[-3.],
        [ 4.]])

添字アクセス:行列と2次元配列の比較

NumPyの配列と行列の間にはいくつかの重要な差異があることに注意。NumPyは、N次元配列オブジェクトとユニバーサル関数オブジェクトという2種類の基本的なオブジェクトを用意している。その他のオブジェクトはこれら2つの上に構築されている。特に、行列はNumPyのarrayオブジェクトを継承した2次元配列オブジェクトである。配列も行列も、添字は以下の1つもしくは複数の適切な組み合わせで構成されていなければならない:整数スカラー値、省略記号、整数または真偽値のリスト、整数または真偽値のタプル、整数または真偽値の1次元配列。行列を行列の添字として用いることもできるが、普通は配列、リスト、あるいは他の形で事が足りる。Pythonでもそうであるように、添字は0ベースである。伝統的に我々は2次元配列や行列を行と列の長方形の配列として表す。軸0に沿った動きは行を横切り、軸1に沿った動きは列を横切る。

配列と行列を作ってスライスしてみよう。

>>> A = arange(12)
>>> A
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
>>> A.shape = (3,4)
>>> M = mat(A.copy())
>>> print type(A),"  ",type(M)
<type 'numpy.ndarray'>    <class 'numpy.core.defmatrix.matrix'>
>>> print A
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
>>> print M
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]

ここで、いくつか単純なスライスを取ってみよう。基本的なスライシングにはスライスオブジェクトまたは整数の組を用いる。例えば、A[:]とM[:]の評価はPythonの添字付けに似たものに見えるのだが、重要なのはNumPy配列のスライシングはデータをコピーしないという点である。スライシングは同じデータに対し新しいビューを提供する。

>>> print A[:]; print A[:].shape
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
(3, 4)
>>> print M[:]; print M[:].shape
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
(3, 4)

ここはPythonの添字付けと異なるのだが、複数の軸に沿って同時に添字付けする場合にカンマ区切りの添字が使える。

>>> print A[:,1]; print A[:,1].shape
[1 5 9]
(3,)
>>> print M[:,1]; print M[:,1].shape
[[1]
 [5]
 [9]]
(3, 1)

最後の2つの結果の違いに気づいただろうか。二次元配列に対しコロンを1つ使うと1次元配列ができる一方、行列に対してだと2次元行列ができるのである。行列のスライスは常に行列を生み出す。例えばスライス M[2,:] はshape (1,4) の行列になるが、対照的に、配列のスライスは常に可能な限り一番低い次元の配列になる。例えばCが3次元配列だとすると、C[...,1] は2次元配列に、C[1,:,1] は1次元配列になる。今後、対応する行列スライスが同一である場合には配列スライスの結果のみを示すことにする。
配列の1番目と3番目の列が欲しいとしよう。リストを使ってスライスするのが1つの方法である:

>>> A[:,[1,3]]
array([[ 1,  3],
       [ 5,  7],
       [ 9, 11]])

ちょっと込み入った方法になるが、take() メソッドを使うというのもある:

>>> A[:,].take([1,3],axis=1)
array([[ 1,  3],
       [ 5,  7],
       [ 9, 11]])

最初の行を飛ばしたいならこうだ:

>>> A[1:,].take([1,3],axis=1)
array([[ 5,  7],
       [ 9, 11]])

あるいは単にA[1:,[1,3]]としてもよいだろう。上のをスライスするもう1つの方法は、外積の利用だ:

>>> A[ix_((1,2),(1,3))]
array([[ 5,  7],
       [ 9, 11]])

読者の便宜のため、元の配列をもう一度お見せしよう:

>>> print A
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]

さてここでもうちょっと込み入った事をしてみよう。1行目が1より大きい全ての列を保持したいとしよう。まずは真偽値の添字を作る方法:

>>> A[0,:]>1
array([False, False, True, True], dtype=bool)
>>> A[:,A[0,:]>1]
array([[ 2,  3],
       [ 6,  7],
       [10, 11]])

欲しかったのはまさにこれだ!しかし行列を添字付けするのはあまり便利ではない。

>>> M[0,:]>1
matrix([[False, False, True, True]], dtype=bool)
>>> M[:,M[0,:]>1]
matrix([[2, 3]])

問題としているのはもちろん、行列スライスをスライスすることで行列が生成されてしまう点である。しかし行列には便利な'A'属性があって、この値はその行列の配列表現になっているので、代わりにこうするだけで良い:

>>> M[:,M.A[0,:]>1]
matrix([[ 2,  3],
        [ 6,  7],
        [10, 11]])

行列を条件によって2つの方向にスライスしたいのであれば、戦略をわずかに補正しなければならない。

>>> A[A[:,0]>2,A[0,:]>1]
array([ 6, 11])
>>> M[M.A[:,0]>2,M.A[0,:]>1]
matrix([[ 6, 11]])

の代わりに、外積 'ix_' を用いる:

>>> A[numpy.ix_(A[:,0]>2,A[0,:]>1)]
array([[ 6,  7],
       [10, 11]])
>>> M[numpy.ix_(M.A[:,0]>2,M.A[0,:]>1)]
matrix([[ 6,  7],
        [10, 11]])

ちょっとしたコツ

ここで、ちょっとした便利な技法を伝授しよう。

「自動」再整形

配列の寸法を変更する際に、サイズを1つ省略すると自動的に推論してくれる:

>>> a = arange(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]]])

ベクトルの積み重ね

サイズの揃った行ベクトルのリストから二次元配列を構築するにはどうするか?MATLABではこれはとても簡単だ:xとyの2つが同じ長さのベクトルなら、m=[x;y]とするだけで良い。NumPyでは関数 column_stack, dstack, hstack, vstackで同じことができる。関数の選択は積み重ねが行われる次元に依存する。例えば、

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

3次元以上の場合、これらの関数の背後のロジックは奇妙なものになりうる。MatlabユーザのためのNumPyを見て、新たな発見があればそこに追加してほしい。

ヒストグラム

NumPyのhistogram関数を配列に適用すると、2つのベクトル:配列のヒストグラムと、瓶(bins)のベクトル、が返ってくる。 注意:matplotlibにもヒストグラムを構築する関数がある(Matlabと同様 hist と呼ばれる)が、NumPyのものとは異なる。主な差異は、pylab.hist はヒストグラムを自動的に描画するが、numpy.histogram はデータを生成するところまでしか行わない点である。

import numpy
import pylab
# 分散0.5^2, 平均2で正規偏差10000件のベクトルを構築
mu, sigma = 2, 0.5
v = numpy.random.normal(mu,sigma,10000)
# Plot a normalized histogram with 50 bins
pylab.hist(v, bins=50, normed=1)       # matplotlib版 (plot)
pylab.show()
# numpyでヒストグラムを計算して描画
(n, bins) = numpy.histogram(v, bins=50, normed=True)  # NumPy 版 (no plot)
pylab.plot(.5*(bins[1:]+bins[:-1]), n)
pylab.show()

f:id:to33k:20111227192214p:plain
f:id:to33k:20111227192222p:plain

参考文献