numpyの多次元配列の「軸を入れ換える」ということについての学習

ラベル:

ネットを検索しただけでは理解できなかったので、Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理を購入して読んでいますが、p106のnumpy.ndarray.transposeの使い方がよくわからなかったので「次元」と「軸」について調べてみました。

numpy.ndarray.transposeは軸を入れ換える


この本の106ページの「4.1.7 転置行列、行と列の入れ換え」の例。

まず4要素の配列を2要素もつ配列を2要素持つ配列(2x2x4の多次元配列)をnumpy.reshapeで生成します。
import numpy as np
arr=np.arange(16).reshape((2,2,4))
arr
arr.transpose((1,0,2))
これで軸の順序が入れ替わって以下のようになります。
numpy.ndarray.transposeを読んでも何がどうなってこうなるのかがよくわからなかったのでさらに調べてみました。

「次元」とか「軸」とか、は何か?


まず「次元」と「軸」が何のことかわからなかったので、調べ始めたのですが回答のページを見つける前にいじくり回しているうちに答えがわかったのでわかったことを書いておきます。

「次元」とは多次元配列の次元のことです。

つまり4要素の配列を2要素もつ配列を2要素持つ配列、2x2x4の多次元配列、の次元は3になります。

4要素の配列を2要素もつ配列、2x4の多次元配列の次元は2になります。

配列の形状の指定方法はnumpy.reshapeの引数のタプルと同じですので、次元はnumpy.reshapeの引数のタプルの要素数と一致します。

2x2x4の3次元配列は(2, 2, 4)のタプルで表現できます。

「軸」とはnumpy.reshapeの引数のタプルでいうと、第0要素が第0軸、第1要素が第1軸、、、第n要素が第n軸、ということになります。

(2, 2, 4)という形状の3次元配列を表すタプルの要素は各軸の配列の長さを表すことになります。

つまり(2, 2, 4)の3次元配列の場合、第0軸の配列の長さは2、第1軸の配列の長さは2、第2軸の配列の長さは4、になります。

この各軸の配列の長さは、形状の異なる配列の間の算術計算をするとき(ブロードキャスト)に重要になります。

軸を入れ替えるということはどういうことか?


(2, 2, 4)の3次元配列Aの各要素をその位置のタプルの添字で表現すると次のようになります。
$$A=\begin{bmatrix}\begin{bmatrix}
\left[{a}_{(1,1,1)},\;{a}_{(1,1,2)},\;{a}_{(1,1,3)},\;{a}_{(1,1,4)}\right]\\
\left[{a}_{(1,2,1)},\;{a}_{(1,2,2)},\;{a}_{(1,2,3)},\;{a}_{(1,2,4)}\right]
\end{bmatrix}\\\begin{bmatrix}
\left[{a}_{(2,1,1)},\;{a}_{(2,1,2)},\;{a}_{(2,1,3)},\;{a}_{(2,1,4)}\right]\\
\left[{a}_{(2,2,1)},\;{a}_{(2,2,2)},\;{a}_{(2,2,3)},\;{a}_{(2,2,4)}\right]
\end{bmatrix}\end{bmatrix}$$
2x2x4の多次元配列なので$$${a}_{(1,1,1)}$$$から始まって$$${a}_{(2,2,4)}$$$までの要素があります。

この3次元配列の第0軸と第1軸を入れ換える、というのは位置を表すタプルの要素の0番目と1番目を入れ換えることになります。

入れ換えたあとの3次元配列Bの要素をbで表すと$$${b}_{(j,i,k)}={a}_{(i,j,k)}$$$という関係が成り立ちます。

(2, 2, 4)の配列では第0軸と第1軸の各配列の長さは同じ2なのでAとBの形状は変わりませんので入れ換えたあとの配列Bは以下のようになります。
$$B=\begin{bmatrix}\begin{bmatrix}
\left[{b}_{(1,1,1)},\;{b}_{(1,1,2)},\;{b}_{(1,1,3)},\;{b}_{(1,1,4)}\right]\\
\left[{b}_{(1,2,1)},\;{b}_{(1,2,2)},\;{b}_{(1,2,3)},\;{b}_{(1,2,4)}\right]
\end{bmatrix}\\\begin{bmatrix}
\left[{b}_{(2,1,1)},\;{b}_{(2,1,2)},\;{b}_{(2,1,3)},\;{b}_{(2,1,4)}\right]\\
\left[{b}_{(2,2,1)},\;{b}_{(2,2,2)},\;{b}_{(2,2,3)},\;{b}_{(2,2,4)}\right]
\end{bmatrix}\end{bmatrix}$$
$$${b}_{(j,i,k)}={a}_{(i,j,k)}$$$なのでBをAの要素で表現すると第0軸と第1軸が入れ替わっていることがわかります。
$$B=\begin{bmatrix}\begin{bmatrix}
\left[{a}_{(1,1,1)},\;{a}_{(1,1,2)},\;{a}_{(1,1,3)},\;{a}_{(1,1,4)}\right]\\
\left[{a}_{(2,1,1)},\;{a}_{(2,1,2)},\;{a}_{(2,1,3)},\;{a}_{(2,1,4)}\right]
\end{bmatrix}\\\begin{bmatrix}
\left[{a}_{(1,2,1)},\;{a}_{(1,2,2)},\;{a}_{(1,2,3)},\;{a}_{(1,2,4)}\right] \\
\left[{a}_{(2,2,1)},\;{a}_{(2,2,2)},\;{a}_{(2,2,3)},\;{a}_{(2,2,4)}\right]
\end{bmatrix}\end{bmatrix}$$
今度はAの第0軸と第2軸を入れ換えたCを作ってみます。

Aの(2,2,4)の配列が(4,2,2)の形状の配列になるので配列の見た目が変わります。
$$C=\begin{bmatrix}\begin{bmatrix}
\left[{c}_{(1,1,1)},\;{c}_{(1,1,2)}\right]\\
\left[{c}_{(1,2,1)},\;{c}_{(1,2,2)}\right]
\end{bmatrix}\\\begin{bmatrix}
\left[{c}_{(2,1,1)},\;{c}_{(2,1,2)}\right]\\
\left[{c}_{(2,2,1)},\;{c}_{(2,2,2)}\right]
\end{bmatrix}\\\begin{bmatrix}
\left[{c}_{(3,1,1)},\;{c}_{(3,1,2)}\right]\\
\left[{c}_{(3,2,1)},\;{c}_{(3,2,2)}\right]
\end{bmatrix}\\\begin{bmatrix}
\left[{c}_{(4,1,1)},\;{c}_{(4,1,2)}\right]\\
\left[{c}_{(4,2,1)},\;{c}_{(4,2,2)}\right]
\end{bmatrix}\end{bmatrix}$$
$$${c}_{(k,j,i)}={a}_{(i,j,k)}$$$という関係が成り立つのでCをAの要素で表現すると次のようになります。
$$C=\begin{bmatrix}\begin{bmatrix}
\left[{a}_{(1,1,1)},\;{a}_{(2,1,1)}\right]\\
\left[{a}_{(1,2,1)},\;{a}_{(2,2,1)}\right]
\end{bmatrix}\\\begin{bmatrix}
\left[{a}_{(1,1,2)},\;{a}_{(2,1,2)}\right]\\
\left[{a}_{(1,2,2)},\;{a}_{(2,2,2)}\right]
\end{bmatrix}\\\begin{bmatrix}
\left[{a}_{(1,1,3)},\;{a}_{(2,1,3)}\right]\\
\left[{a}_{(1,2,3)},\;{a}_{(2,2,3)}\right]
\end{bmatrix}\\\begin{bmatrix}
\left[{a}_{(1,1,4)},\;{a}_{(2,1,4)}\right]\\
\left[{a}_{(1,2,4)},\;{a}_{(2,2,4)}\right]
\end{bmatrix}\end{bmatrix}$$
numpy.ndarray.transposeは引数で軸の順序をタプルで指定するとこの変化をやってくれます。

具体的に配列にデータをいれてやってみると以下のようになります。

(2,2,4)の3次元配列Aの作成。

In [47]:
import numpy as np
A=np.arange(16).reshape((2,2,4))
A
Out[47]:
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7]],

       [[ 8,  9, 10, 11],
        [12, 13, 14, 15]]])

Aの第0軸と第1軸を入れ替えたBを作成。

In [45]:
B=A.transpose((1,0,2))
B
Out[45]:
array([[[ 0,  1,  2,  3],
        [ 8,  9, 10, 11]],

       [[ 4,  5,  6,  7],
        [12, 13, 14, 15]]])

Aの第0軸と第2軸を入れ替えたCを作成。

In [46]:
C=A.transpose((2,1,0))
C
Out[46]:
array([[[ 0,  8],
        [ 4, 12]],

       [[ 1,  9],
        [ 5, 13]],

       [[ 2, 10],
        [ 6, 14]],

       [[ 3, 11],
        [ 7, 15]]])
学生のころ数学で習った「行列」と「多次元配列」を混同していて理解する妨げになりました。

行列を(多次元配列でいうところの)2次元からn次元まで拡張したものが多次元配列で、行列 - Wikipediaに書いてあるような行列で成り立つ計算規則が多次元配列で成り立つのかと思いこんでしまいましたがそうではないようです。

そもそも「次元」の意味が行列と多次元配列とでは異なります。

ジュンク堂書店にいって数学書のコーナーで多次元配列について書いた簡単な数学書がないかさがしてみましたが、行列について書いた線形代数の本はありましたが、多次元配列の解説はみつけられませんでした。

参考にしたサイト


Array manipulation routines — NumPy v1.9 Manual
Numpyの配列操作するルチン一覧。

行列 - Wikipedia
「行列」と「多次元配列」は性質が違うようです。

次の関連記事:pandasのパネルのお勉強

PR

1 件のコメント:

  1. 私も同様の問題で悩んでまして,本ページは分かりやすくとても参考になりました.

    返信削除