配列の次元数や大きさの操作

ブロードキャストを紹介する前に, NumPy 配列の基礎 で紹介した,NumPy の配列クラス np.ndarray の属性 ndimshape を操作する方法を紹介します.

ndim は,配列の次元数を表す属性で,ベクトルでは 1 に,配列では 2 になります. shape は,スカラーや,タプルによって配列の各次元の大きさを表す属性です. 例えば,大きさが 5 のベクトルはスカラー 5 によって, \(2 \times 3\) の行列はタプル (2, 3) となります.

次元数を操作する必要がある例として配列の転置の例を紹介します. 転置した配列を得るには,属性 T か,メソッド transpose() を用います. 2次元の配列である行列を転置してみましょう:

In [10]: a = np.array([[1, 3], [2, 1]])
In [11]: a
Out[11]:
array([[1, 3],
       [2, 1]])
In [12]: a.T
Out[12]:
array([[1, 2],
       [3, 1]])
In [13]: a.transpose()
Out[13]:
array([[1, 2],
       [3, 1]])

今度は,1次元配列であるベクトルを転置してみます:

In [14]: b = np.array([10, 20])
In [15]: b
Out[15]: array([10, 20])
In [16]: b.T
Out[16]: array([10, 20])

転置しても,縦ベクトルになることはありません.属性 T やメソッド transpose() は,次元数 ndim が 1 以下であれば,元と同じ配列を返します.

np.newaxis による操作

縦ベクトルを得るには次元数や大きさを,転置する前に操作しておく必要があります. それには,定数 np.newaxis を使います [1] [2]np.newaxis は,添え字指定の表記の中に用います. 元の配列の大きさを維持する次元には : を指定し,新たに大きさが 1 の次元を追加するところには np.newaxis を指定します.

In [17]: b
Out[17]: array([10, 20])
In [18]: b.ndim
Out[18]: 1
In [19]: b.shape
Out[19]: (2,)

In [20]: c = b[:, np.newaxis]
In [21]: c
Out[21]:
array([[10],
       [20]])
In [22]: c.ndim
Out[22]: 2
In [23]: c.shape
Out[23]: (2, 1)

In [24]: d = b[np.newaxis, :]
In [25]: d
Out[25]: array([[10, 20]])
In [26]: d.ndim
Out[26]: 2
In [27]: d.shape
Out[27]: (1, 2)

この例で,元の bndim は 1 で,その大きさは 2 です. 20行目では,第0次元 [3] は元のベクトルをコピーし,第1次元には大きさ 1 の新たな次元を追加しています. その結果, cshape(2, 1) となり, \(2 \times 1\) 行列,すなわち縦ベクトルになっています. 24行目では,第0次元の方に新たな次元を追加し,第1次元は元ベクトルをコピーしており,その結果,配列 dshape(1, 2) となります. これは, \(1 \times 2\) 行列,すなわち横ベクトルとなっています.

これら縦ベクトル c と横ベクトル d はそれぞれ2次元の配列,すなわち行列なので,次のように転置することができます.

In [28]: c.T
Out[28]: array([[10, 20]])
In [29]: d.T
Out[29]:
array([[10],
       [20]])

転置により,縦ベクトル c は横ベクトルに,横ベクトル d は縦ベクトルになっています.

np.newaxis は,2次元以上の配列にも適用できます.

In [30]: e = np.array([[1, 2, 3], [2, 4, 6]])
In [31]: e.shape
Out[31]: (2, 3)
In [32]: e[np.newaxis, :, :].shape
Out[32]: (1, 2, 3)
In [33]: e[:, np.newaxis, :].shape
Out[33]: (2, 1, 3)
In [34]: e[:, :, np.newaxis].shape
Out[34]: (2, 3, 1)

np.newaxis の挿入位置に応じて,大きさ1の新しい次元が shape に加わっていることが分かります. また,同時に2個以上の新しい次元を追加することも可能です.

In [35]: e[np.newaxis, :, np.newaxis, :].shape
Out[35]: (1, 2, 1, 3)

reshpe() による操作

ブロードキャストとは関連がありませんが, shape を変更する他の方法として np.ndarrayreshape() メソッドと,関数 np.reshape() をここで紹介しておきます.

np.reshape(a, newshape)

Gives a new shape to an array without changing its data.

この関数は,配列 a 全体の要素数はそのままで,その shapenewshape で指定したものに変更するものです. 同様の働きをする reshape() メソッドもあります.

In [35]: np.arange(6)
Out[35]: array([0, 1, 2, 3, 4, 5])
In [36]: np.reshape(np.arange(6), (2, 3))
Out[36]:
array([[0, 1, 2],
       [3, 4, 5]])
In [37]: np.arange(6).reshape((3, 2))
Out[37]:
array([[0, 1],
       [2, 3],
       [4, 5]])

この例では,6個の要素を含む shape(6,) の配列を,それぞれ np.reshape() 関数で (2, 3) に, reshape() メソッドで (3, 2)shape を変更しています. ただし, np.reshape() 関数や, reshape() メソッドでは,配列の総要素数を変えるような変更は指定できません.

In [38]: np.arange(6).reshape((3, 3))
ValueError: total size of new array must be unchanged

この例では,総要素数が6個の配列を,総要素数が9個の shape (3, 3) を指定したためエラーとなっています.

配列の総要素数が不明の場合は,大きさが不明な次元で -1 を指定すると適切な値が自動的に設定されます.

In [39]: np.arange(6).reshape((2, -1))
Out[39]:
array([[0, 1, 2],
       [3, 4, 5]])

この例では,次元1に -1 を指定すると,全体の要素数が 6 で,次元0の大きさに 2 なので,自動的に次元1の大きさが 3 に設定されています.

この記法は, shape(1, -1)(-1, 1) を指定すると,それぞれ2次元の横ベクトルや縦ベクトルを簡便に作ることができます.

In [40]: np.arange(6).reshape((1, -1))
Out[40]: array([[0, 1, 2, 3, 4, 5]])
In [41]: np.arange(6).reshape((-1, 1))
Out[41]:
array([[0],
       [1],
       [2],
       [3],
       [4],
       [5]])

注釈

[1]np.newaxis の実体は None であり, np.newaxis の代わりに None と書いても全く同じ動作をします. ここでは,記述の意味を明確にするために, np.newaxis を用います.
[2]他にも np.expand_dims()np.atleast_3d() などの関数を使う方法もありますが,最も自由度の高い np.newaxis を用いる方法を紹介します.
[3]shape で示されるタプルの一番左側から第 0 次元,第 1 次元,… となります.