NumPy配列ndarrayの行と列を入れ替え(転置、次元・軸の入れ替え)

Posted: | Tags: Python, NumPy

NumPy配列ndarrayの行と列を入れ替える(転置する、転置行列を取得する)にはT属性(.T)、ndarrayのメソッドtranspose()、関数numpy.transpose()を使う。

ndarraytranspose()メソッド, numpy.transpose()関数では二次元配列(行列)の転置だけではなく、多次元配列の次元(軸)を任意の順番に入れ替えるという、より一般的な処理が可能。

ここでは以下の内容について説明する。

  • 二次元配列(行列)の転置
    • T属性(.T
    • ndarray.transpose()
    • np.transpose()
  • 一次元配列と行ベクトル、列ベクトル
  • 三次元以上の多次元配列の次元(軸)の入れ替え
    • デフォルトの処理結果
    • transpose()の引数に次元(軸)の順番を指定
    • 活用例: 複数の行列を一括で転置

ndarrayではなく二次元リスト(リストのリスト)やpandas.DataFrameの行と列を入れ替えたい場合は以下の記事を参照。

二次元配列(行列)の転置

T属性(.T)

T属性で元の二次元配列(行列)の転置行列を取得できる。

import numpy as np

a_2d = np.arange(6).reshape(2, 3)
print(a_2d)
# [[0 1 2]
#  [3 4 5]]

a_2d_T = a_2d.T
print(a_2d_T)
# [[0 3]
#  [1 4]
#  [2 5]]

T属性が返すのは元の配列のビュー(参照)であり、いずれかの要素を変更するともう一方の要素も変更される。

2つのndarrayが同じメモリのデータを参照している(片方がもう片方のビューである)かどうかはnp.shares_memory()で確認できる。

print(np.shares_memory(a_2d, a_2d_T))
# True

a_2d_T[0, 1] = 100
print(a_2d_T)
# [[  0 100]
#  [  1   4]
#  [  2   5]]

print(a_2d)
# [[  0   1   2]
#  [100   4   5]]

a_2d[1, 0] = 3
print(a_2d)
# [[0 1 2]
#  [3 4 5]]

print(a_2d_T)
# [[0 3]
#  [1 4]
#  [2 5]]

別々のデータとして処理したい場合はcopy()でコピーを作成する。

a_2d_T_copy = a_2d.T.copy()
print(a_2d_T_copy)
# [[0 3]
#  [1 4]
#  [2 5]]

print(np.shares_memory(a_2d, a_2d_T_copy))
# False

a_2d_T_copy[0, 1] = 100
print(a_2d_T_copy)
# [[  0 100]
#  [  1   4]
#  [  2   5]]

print(a_2d)
# [[0 1 2]
#  [3 4 5]]

ndarray.transpose()

配列ndarrayのメソッドとしてtranspose()がある。Tと同様、ビューが返される。

print(a_2d.transpose())
# [[0 3]
#  [1 4]
#  [2 5]]

print(np.shares_memory(a_2d, a_2d.transpose()))
# True

np.transpose()

numpy.transpose()関数もある。第一引数に元の配列ndarrayを指定する。これもビューが返される。

print(np.transpose(a_2d))
# [[0 3]
#  [1 4]
#  [2 5]]

print(np.shares_memory(a_2d, np.transpose(a_2d)))
# True

一次元配列と行ベクトル、列ベクトル

一次元配列に対してTtranspose()を適用しても、元の配列と等価な配列が返されるのみ。

a_1d = np.arange(3)
print(a_1d)
# [0 1 2]

print(a_1d.T)
# [0 1 2]

print(a_1d.transpose())
# [0 1 2]

print(np.transpose(a_1d))
# [0 1 2]

1つの行のみを持つ行列を行ベクトル、1つの列を持つ行列を列ベクトルと呼ぶが、ndarrayの一次元配列には行・列の区別はない。行のみまたは列のみであることを明確に示すには二次元配列で表す。

ここではreshape()で形状を変換する。

a_row = a_1d.reshape(1, -1)
print(a_row)
# [[0 1 2]]

print(a_row.shape)
# (1, 3)

print(a_row.ndim)
# 2
a_col = a_1d.reshape(-1, 1)
print(a_col)
# [[0]
#  [1]
#  [2]]

print(a_col.shape)
# (3, 1)

print(a_col.ndim)
# 2

上述のように、二次元配列は転置が可能。

print(a_row.T)
# [[0]
#  [1]
#  [2]]

print(a_col.T)
# [[0 1 2]]

dot()matmul(), @演算子による行列の積では一次元配列か二次元配列かで結果が異なるので注意。詳細は以下の記事を参照。

また、配列の連結の場合、vstack()では二次元配列と一次元配列の連結が可能だが、hstack()では二次元配列と一次元配列の連結はできない(エラー)などの違いがある。二次元配列同士の連結はどちらでも可能。

三次元以上の多次元配列の次元(軸)の入れ替え

デフォルトの処理結果

三次元以上の多次元配列に対してもT, transpose()が適用可能だが、行列の転置というイメージとは異なる。

デフォルトでは以下のような結果となる。np.transpose()も同じ結果なので省略。

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

print(a_3d.T)
# [[[ 0 12]
#   [ 4 16]
#   [ 8 20]]
# 
#  [[ 1 13]
#   [ 5 17]
#   [ 9 21]]
# 
#  [[ 2 14]
#   [ 6 18]
#   [10 22]]
# 
#  [[ 3 15]
#   [ 7 19]
#   [11 23]]]

print(a_3d.T.shape)
# (4, 3, 2)

print(a_3d.transpose())
# [[[ 0 12]
#   [ 4 16]
#   [ 8 20]]
# 
#  [[ 1 13]
#   [ 5 17]
#   [ 9 21]]
# 
#  [[ 2 14]
#   [ 6 18]
#   [10 22]]
# 
#  [[ 3 15]
#   [ 7 19]
#   [11 23]]]

print(a_3d.transpose().shape)
# (4, 3, 2)

出力結果を見てもイメージしにくいが、(0次元目, 1次元目, 2次元目)という次元(軸)の順番が反転して(2次元目, 1次元目, 0次元目)という順番になっている。

二次元配列では(0次元目, 1次元目) = (行, 列)だった順番が(1次元目, 0次元目) = (列, 行)という順番に入れ替わったのと同じ。

transpose()の引数に次元(軸)の順番を指定

Tでは次元の順番を指定できないので常に反転した順番になるが、transpose()では引数に任意の順番を指定できる。

以下の例ではデフォルトと同じ反転した順番を指定し、結果が変わりないことを確認する。

ndarrayのメソッドtranspose()では、可変長引数として次元の順番を指定する。タプルでもよい。

print(a_3d.transpose(2, 1, 0))
# [[[ 0 12]
#   [ 4 16]
#   [ 8 20]]
# 
#  [[ 1 13]
#   [ 5 17]
#   [ 9 21]]
# 
#  [[ 2 14]
#   [ 6 18]
#   [10 22]]
# 
#  [[ 3 15]
#   [ 7 19]
#   [11 23]]]

print(a_3d.transpose(2, 1, 0).shape)
# (4, 3, 2)

print(a_3d.transpose((2, 1, 0)).shape)
# (4, 3, 2)

np.transpose()では第二引数にタプルで指定する。可変長引数として指定することはできない。

print(np.transpose(a_3d, (2, 1, 0)))
# [[[ 0 12]
#   [ 4 16]
#   [ 8 20]]
# 
#  [[ 1 13]
#   [ 5 17]
#   [ 9 21]]
# 
#  [[ 2 14]
#   [ 6 18]
#   [10 22]]
# 
#  [[ 3 15]
#   [ 7 19]
#   [11 23]]]

print(np.transpose(a_3d, (2, 1, 0)).shape)
# (4, 3, 2)

# print(np.transpose(a_3d, 2, 1, 0))
# TypeError: transpose() takes from 1 to 2 positional arguments but 4 were given

指定した次元の数が元の配列の次元数と一致していなかったり、存在しない次元を指定するとエラー。

# print(a_3d.transpose(0, 1))
# ValueError: axes don't match array

# print(a_3d.transpose(0, 1, 2, 3))
# ValueError: axes don't match array

# print(a_3d.transpose(0, 1, 3))
# AxisError: axis 3 is out of bounds for array of dimension 3

活用例: 複数の行列を一括で転置

transpose()が便利なのが、例えば三次元配列が二次元配列(行列)のまとまりである場合。

n個の行列のデータが形状(n, 行, 列)の三次元配列として格納されているとすると、以下のようにすべての行列を転置できる。

print(a_3d)
# [[[ 0  1  2  3]
#   [ 4  5  6  7]
#   [ 8  9 10 11]]
# 
#  [[12 13 14 15]
#   [16 17 18 19]
#   [20 21 22 23]]]

print(a_3d.shape)
# (2, 3, 4)

print(a_3d.transpose(0, 2, 1))
# [[[ 0  4  8]
#   [ 1  5  9]
#   [ 2  6 10]
#   [ 3  7 11]]
# 
#  [[12 16 20]
#   [13 17 21]
#   [14 18 22]
#   [15 19 23]]]

print(a_3d.transpose(0, 2, 1).shape)
# (2, 4, 3)

n個の行列のデータが形状(行, 列, n)の三次元配列として格納されている場合は以下のようにすればよい。結果をそのまま出力してもよくわからないが、行列部分のみを確認すると転置されていることがわかる。

print(a_3d.transpose(1, 0, 2))
# [[[ 0  1  2  3]
#   [12 13 14 15]]
# 
#  [[ 4  5  6  7]
#   [16 17 18 19]]
# 
#  [[ 8  9 10 11]
#   [20 21 22 23]]]

print(a_3d.transpose(1, 0, 2).shape)
# (3, 2, 4)

print(a_3d[:, :, 0])
# [[ 0  4  8]
#  [12 16 20]]

print(a_3d.transpose(1, 0, 2)[:, :, 0])
# [[ 0 12]
#  [ 4 16]
#  [ 8 20]]

関連カテゴリー

関連記事