-Python- Numpyの基本的事項

Numpyの配列型は ndarray というクラスです.配列はリストのように要素を並べたものですが,全ての要素の型は同じでなければなりません.

以下にNumpy配列の操作例を示します.

>>> import numpy as np
>>> a = np.array([2, 3, 5, 7, 8])
>>> a[0]    # first element
2
>>> a[1:3]  # Elements of index 1-2
array([3, 5])
>>> a[2:-1] # Elements of index 0 to 5-1
array([5, 7])
>>> b = np.arange(5)   # Creating an array with the arange function
>>> b
array([0, 1, 2, 3, 4])
>>> c = np.arange(1, 3, 0.2) # Specify step as 0.2
>>> c
array([1. , 1.2, 1.4, 1.6, 1.8, 2. , 2.2, 2.4, 2.6, 2.8])
>>> a.dtype # Securing type
dtype('int64')
>>> c.dtype
dtype('float64')
>>> d = np.array([1, 2, 3], dtype = np.float64) # Specify type at creation
>>> d
array([1., 2., 3.])
>>> d.dtype
dtype('float64')
>>> e = np.arange(5.) # Specify floating point number as argument
>>> e
array([0., 1., 2., 3., 4.])
>>> e.dtype
dtype('float64')
>>> 
 
上記の例では,最初にnp.array を使って変数aに配列を代入しています.引数にはリストが与えられています.このようにリストによって初期化が可能です.
a[0]のようにインデックスを使ったアクセスが可能です.また,a[1:3]のようなスライシングが可能です(リストと同じ).
np.arangeにより連続値を要素に持つ配列を生成できます.np.arangeaはrangeと使い方は似ていますが,戻り値がndarray型である点が異なります.また,上記の例での c への代入のように,np.arange はステップ値を浮動小数点数型にすることも可能です(rangeでは浮動小数点数は指定できません).
データ属性 dtype によって,配列の要素の型を表示しています.配列は全要素が同じ型である必要があり,その型は dtype で確認することができます.a のdtypeは int64 となっていますが,これは64ビットで表される整数という意味です.c のdtype はfloat64 になっていますが,これは64ビットで表される浮動小数点数という意味です.int64 とfloat64 以外にもNumpyには組み込みの数値型があります.
配列aのように,整数値のみからなるリストを元に配列を作ると要素の型は int64になりますが,dのように名前付き引数 dtype を指定して配列を定義すると,明示的に指定することもできます.
dtypeを使う代わりに引数を浮動小数点数にするという方法もあります(例えば,1ではなく1.と指定します).
配列はベクトルや行列を表現するものとして扱うことが多いので,要素の方は浮動小数点数にするという方が都合が良い場合が多いです.

2次元配列
インデックスを2つ持つような配列を2次元配列と呼びます.
以下に,2次元配列の操作例を示します.
>>> import numpy as np
>>> a = np.array([[2, 3, 4], [5, 6, 7]], dtype = np.float64)
>>> a
array([[2., 3., 4.],
       [5., 6., 7.]])
>>> a[0, 1]
3.0
>>> a[:, 1]
array([3., 6.])
>>> a[1, :]
array([5., 6., 7.])
>>> a[0, 2:]
array([4.])
>>> a[0, :2]
array([2., 3.])
>>> 
上記の例では,リストのリストを使って2次元配列を初期化しています.2次元配列の要素には,行列と同様に2つのインデックスでアクセスします.数字の行列の添字は1から始まることが多いのですが,配列のインデックスは0から始まるので注意が必要です.
a[0, 1] は第0行,第1列の要素を参照します.a[:, 1]というのはスライシングの一種で,第一列の要素全てを取り出し,結果を1次元配列として返します.a[1, :]は第1行を取り出します.a[0, 2:]は第0行の第2列以降を取り出します.a[0, :2]は第0行の第2列より前を取り出します.これらの例のように,2次元配列においては,1つ目のインデックスと2つ目のインデックスについてそれぞれ別にスライシングを指定することができます.スライシングの指定の仕方はリストと同様です.
 
>>> v = np.array([2., 3., 4.])
>>> a = np.array([[1., 2.], [3., 4.]])
>>> 
上記のように v と a を設定するとv は1次元配列と呼ばれ,aは2次元配列と呼ばれます.

配列のデータ属性
>>> 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.size
15
>>> b = np.arange(4.)
>>> b.shape
(4,)
>>> b.ndim
1
>>> b.size
4
>>> 
arange(15.) は0. から14. の整数で構成される1次元配列を作りますが,それに対して,reshape (3, 5) は,配列の形状を3行5列に変形します.ここでいう形状とは,行列でいうところのサイズにあたるものです.
配列のデータ属性 shape を見ることで,形状を確認できます.ndim は配列としての次元を意味していて,aは2次元配列なので,a.ndim は2になります.a.size はaの全体での要素数です.次にbに1次元配列を設定するとshapeは要素数1つだけのタプルになり,ndimは1になります.sizeはshape中の要素と同じになります.


reshapeメソッドと形状の変更
reshapeについては,一部の値を指定せずに自動的に形状を変更することもできます.

>>> a = np.arange(16.)
>>> c = a.reshape(4, -1)
>>> c
array([[ 0.,  1.,  2.,  3.],
       [ 4.,  5.,  6.,  7.],
       [ 8.,  9., 10., 11.],
       [12., 13., 14., 15.]])
>>> c.ravel()
array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10., 11., 12.,
       13., 14., 15.])
>>> c.reshape(-1)
array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10., 11., 12.,
       13., 14., 15.])
>>> b = np.arange(4.)
>>> b.reshape(-1, 1)
array([[0.],
       [1.],
       [2.],
       [3.]])
>>> b[:, np.newaxis]
array([[0.],
       [1.],
       [2.],
       [3.]])
>>> b[:, None]
array([[0.],
       [1.],
       [2.],
       [3.]])
>>> b.reshape(1, -1)
>>> b[np.newaxis, :]
>>> 
a では要素数16の配列aを用意しています..reshape(4, -1)により行列を4に指定して形状を変更しています.
引数が -1 になっているインデックスは,その部分は自動で設定するようにという命令になります.a のサイズは16なので,行数を4にすると列数は自動的に4列に設定されます.配列を1次元に変換することはよくあり,ravel という特別なメソッドが用意されています.メソッド .ravel() を呼ぶことは .reshape(-1) と同じ結果になります.
 
bにはサイズ4の1次元配列を代入しています.続いて,b.reshape(-1, 1)によって行数自動,列数1の2次元配列に変換しています.ここでは(4, 1)の2次元配列が作られています.b[:, np.newaxis] としても同様の結果になります.np.newaxis はNoneのエイリアス(言い換え)です.
b.reshape(1, -1)では行数を1として,列数自動で変換します.今度の形状は(1, 4)になります.次の[np.newaxis, :]でも同じ結果が得られます.
ここでの注意点は,サイズ4の1次元配列,形状が(4, 1)の2次元配列,(1, 4)の2次元配列は全て異なるものであるということです.
        array([0., 1., 2., 3.])        # 1次元配列
        array([[0. ],                     # 形状(4, 1)の2次元配列
                   [1. ] , 
                   [2. ], 
                   [3. ]])
        array(0., 1., 2., 3. )    # 形状が(1, 4)の2次元配列
1次元配列は一組の角括弧で表されますが,2次元配列では角括弧は必ず2重になっています.数学ではベクトルと列数1の行列は同一視しまスガ,Numpyでは1次元配列と2次元配列は別物として扱われます.
 
配列の操作
>>> a = np.zeros*1
>>> a
array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])
>>> b = np.ones*2
>>> b
array([[1., 1., 1.],
       [1., 1., 1.]])
>>> c = np.empty*3
>>> c
array([[2.96439388e-323, 1.90979621e-313, 2.23445797e-314,
        6.36598737e-311, 3.18299369e-313],
       [3.10503618e+231, 3.10503618e+231, 3.10503618e+231,
        3.10503618e+231, 2.68679214e+154]])
>>> d = np.linspace(0, 1, 10)
>>> d
array([0.        , 0.11111111, 0.22222222, 0.33333333, 0.44444444,
       0.55555556, 0.66666667, 0.77777778, 0.88888889, 1.        ])
>>> 
zeros は,形状を指定して全ての要素が0であるような配列を用意します.零ベクトルや零行列を作る際に便利です.
onesは,全ての要素が1であるような配列を用意します.
emptyは与えられた形状の行列を用意しますが,その中身の値は何も保証されていません.初期値はどうなるのかわかりません.あとで,確実に全ての要素を設定するような(余計な初期化をせずに配列を用意しても問題にならない)場合には便利です.
linspaceは第1引数と第2引数で表される区間を等間隔で等分した結果を返します.返ってくる配列のサイズは第3引数で指定した数です.区間の両端が含まれるように等分するので,第3引数をnとすると区間はn-1等分されるので注意が必要です.上記の例では[0, 1]区間を9等分しています.結果には0と1が含まれるので,全部で10個の要素から構成される配列になります.
 
行列の連結
行列の連結例を以下に示します.
>>> a = np.array([[0, 1, 2], [3, 4, 5]])
>>> a
array([[0, 1, 2],
       [3, 4, 5]])
>>> b = np.array([[6, 7, 8], [9, 10, 11]])
>>> b
array([[ 6,  7,  8],
       [ 9, 10, 11]])
>>> np.r_[a, b]
array([[ 0,  1,  2],
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11]])
>>> np.c_[a, b]
array([[ 0,  1,  2,  6,  7,  8],
       [ 3,  4,  5,  9, 10, 11]])
>>> c = np.arange(3)
>>> c
array([0, 1, 2])
>>> d = np.arange(3, 6)
>>> d
array([3, 4, 5])
>>> np.r_[c, d]
array([0, 1, 2, 3, 4, 5])
>>> np.c_[c, d]
array([[0, 3],
       [1, 4],
       [2, 5]])
>>> np.r_[a, c]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/anaconda3/lib/python3.5/site-packages/numpy/lib/index_tricks.py", line 335, in __getitem__
    res = self.concatenate(tuple(objs), axis=axis)
ValueError: all the input arrays must have same number of dimensions
>>> np.r_[a, c.reshape(1, -1)]
array([[0, 1, 2],
       [3, 4, 5],
       [0, 1, 2]])
>>> 
2つの行列を縦に連結するには r_ を使います.r_ は関数ではなくクラスであり,角括弧の中に連結したい行列を並べるという特殊な使い方をします.横方向に並べるには c_ を使い,呼び出し時に角括弧を使うのも r_ と同様です.r_ を使うときは適用する行列の列数が一致する必要があります.c_ を使うときは行数が一致する必要があります.
2次元配列のときは縦方向と横方向の連結の違いはわかりやすいのですが,1次元配列のときは注意が必要です.数学に置き換えると,1次元配列は縦ベクトルとみなせば良いことになります.2つの1次元ベクトルにr_を使うと縦ベクトルを縦に繋げるので,縦に長い行列になり,それを1次元配列として表現するので,2つの配列を横につなげた形になります.
一方で2つの1次元配列にc_を適用すると,2つの縦ベクトルを横に繋げるので,列数2の行列になります.また,2次元配列の下に1次元配列を新しい行として追加したいときは単純にr_を使うとうまくいきません.上記の例ではr_[a, c]はエラーになります.これはcが縦ベクトルと見なされているからです.c のreshapeメソッドで形状(1, 3)に変換してからr_を呼び出せば連結することができます.
>>> c = c.reshape(1, 3)
>>> c
array(0, 1, 2)
>>> np.r_[a, c]
array([[0, 1, 2],
       [3, 4, 5],
       [0, 1, 2]])
>>> 
 

*1:3, 4

*2:2, 3

*3:2, 5