NumPy配列ndarrayの対角成分の抽出、対角行列の作成(diag, diagonal)

Posted: | Tags: Python, NumPy

二次元のNumPy配列numpy.ndarrayの対角成分を抽出するにはnumpy.diag()関数を使う。一次元の配列からそれを対角成分とする対角行列を作成することもできる。

numpy.diag()numpy.ndarrayを引数とする関数だが、numpy.ndarrayのメソッドとして対角成分を抽出するnumpy.ndarray.diagonal()も用意されている。

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

  • numpy.diag()で対角成分を抽出
    • 基本的な使い方
    • 開始位置の指定: 引数k
    • 正方行列ではない場合
    • 多次元配列の場合
    • 注意: 戻り値はread-onlyのビュー(v1.14.5の場合)
  • numpy.diag()で対角行列を生成
    • 基本的な使い方
    • 開始位置の指定: 引数k
    • 単位行列の生成
  • diagonal()メソッドの使い方

下三角行列・上三角行列の抽出・生成については以下の記事を参照。

numpy.diag()で対角成分を抽出

基本的な使い方

以下のnumpy.ndarrayを例とする。

import numpy as np

a = np.arange(9).reshape((3, 3))

print(a)
# [[0 1 2]
#  [3 4 5]
#  [6 7 8]]

np.diag()の引数にnumpy.ndarrayを指定すると、対角成分が一次元配列として返される。

print(np.diag(a))
# [0 4 8]

開始位置の指定: 引数k

上の例のようにデフォルトは(0, 0)を開始位置として対角成分を抽出するが、引数kにオフセットを整数で指定できる。

正の整数を指定すると開始位置が右側(上側)に移動。範囲外を指定してもエラーにはならず、空の配列が返される。

print(np.diag(a, k=1))
# [1 5]

print(np.diag(a, k=2))
# [2]

print(np.diag(a, k=3))
# []

負の整数は開始位置が左側(下側)に移動。

print(np.diag(a, k=-1))
# [3 7]

print(np.diag(a, k=-2))
# [6]

print(np.diag(a, k=-3))
# []

正方行列ではない場合

正方行列でないnumpy.ndarraynp.diag()の引数として指定可能。引数kもそのまま使える。

a = np.arange(12).reshape((3, 4))

print(a)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

print(np.diag(a))
# [ 0  5 10]

print(np.diag(a, k=1))
# [ 1  6 11]

print(np.diag(a, k=-1))
# [4 9]

多次元配列の場合

三次元以上の多次元配列を指定するとエラーとなる。

a = np.arange(27).reshape((3, 3, 3))

print(a)
# [[[ 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]]]

# print(np.diag(a))
# ValueError: Input must be 1- or 2-d.

一次元配列の場合は次に示すようにその配列を対角成分とする対角行列を返す。

注意: 戻り値はread-onlyのビュー(v1.14.5の場合)

np.diag()の戻り値がビューかコピーかはバージョンによって異なる。

whether it returns a copy or a view depends on what version of numpy you are using.
numpy.diag — NumPy v1.15 Manual

以下の例はバージョン1.14.5の場合。このバージョンではnp.diag()はread-only(書き換え禁止)のビューとして返される。

a = np.arange(9).reshape((3, 3))

print(a)
# [[0 1 2]
#  [3 4 5]
#  [6 7 8]]

a_diag = np.diag(a)

print(a_diag)
# [0 4 8]

# a_diag[0] = 100
# ValueError: assignment destination is read-only

要素の値を書き換えたい場合はflags.writeable属性をTrueとする。ビューなので元の配列の要素の値も変更される。

a_diag.flags.writeable = True

a_diag[0] = 100

print(a_diag)
# [100   4   8]

print(a)
# [[100   1   2]
#  [  3   4   5]
#  [  6   7   8]]

ビューではなくコピーを取得したい場合はcopy()を使う。この場合、元の配列の要素の値は変更されない。

a_diag_copy = np.diag(a).copy()

print(a_diag_copy)
# [100   4   8]

a_diag_copy[1] = 100

print(a_diag_copy)
# [100 100   8]

print(a)
# [[100   1   2]
#  [  3   4   5]
#  [  6   7   8]]

numpy.diag()で対角行列を生成

基本的な使い方

np.diag()は対角行列の生成にも使える。

引数に一次元配列を指定するとその配列を対角成分とした対角行列が生成される。

import numpy as np

a = np.array([10, 20, 30])

print(a)
# [10 20 30]

print(np.diag(a))
# [[10  0  0]
#  [ 0 20  0]
#  [ 0  0 30]]

引数はnumpy.ndarrayでなくPython組み込みのリストやタプルでもOK。

print(np.diag([100, 200, 300]))
# [[100   0   0]
#  [  0 200   0]
#  [  0   0 300]]

開始位置の指定: 引数k

対角成分の抽出と同様に引数kで開始位置を指定できる。

第一引数の配列の要素がすべて収まる正方行列が返される。

print(np.diag(a, k=1))
# [[ 0 10  0  0]
#  [ 0  0 20  0]
#  [ 0  0  0 30]
#  [ 0  0  0  0]]

print(np.diag(a, k=-2))
# [[ 0  0  0  0  0]
#  [ 0  0  0  0  0]
#  [10  0  0  0  0]
#  [ 0 20  0  0  0]
#  [ 0  0 30  0  0]]

単位行列の生成

単位行列(対角成分がすべて1の対角行列)はnp.diag()を使って生成することもできるが、専用の関数np.identity()が便利。

np.identity()では第一引数にサイズ、第二引数に型dtypeを指定する(デフォルトはNoneで多くの場合はfloatになる)。

print(np.diag([1, 1, 1]))
# [[1 0 0]
#  [0 1 0]
#  [0 0 1]]

print(np.identity(3))
# [[1. 0. 0.]
#  [0. 1. 0.]
#  [0. 0. 1.]]

print(np.identity(3, int))
# [[1 0 0]
#  [0 1 0]
#  [0 0 1]]

np.identity()を使うと配列をone-hot表現に簡単に変換できる。以下の記事を参照。

diagonal()メソッドの使い方

numpy.ndarrayのメソッドとして、対角成分を抽出するdiagonal()もある。

import numpy as np

a = np.arange(9).reshape((3, 3))

print(a)
# [[0 1 2]
#  [3 4 5]
#  [6 7 8]]

print(a.diagonal())
# [0 4 8]

diagonal()では引数offsetdiag()の引数kに相当する。

print(a.diagonal(offset=1))
# [1 5]

print(a.diagonal(offset=3))
# []

print(a.diagonal(offset=-2))
# [6]

正方行列でない場合も同様。

a = np.arange(12).reshape((3, 4))

print(a)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

print(a.diagonal())
# [ 0  5 10]

print(a.diagonal(offset=1))
# [ 1  6 11]

diagonal()メソッドはnp.diag()関数のように対角行列の生成はできない。一次元配列から呼び出すとエラーになるので注意。

a = np.arange(3)

print(a)
# [0 1 2]

# a.diagonal()
# ValueError: diag requires an array of at least two dimensions

関連カテゴリー

関連記事