NumPy♪axis指定を易しく図解(axis = -1とは)

NumPyの関数max等ではaxisで方向を指定しますが、axisの番号と方向の対応が分かりにくく、axisを使うたびに調べ直していませんか。また、計算後の配列の次元数や各次元の大きさについて、モヤモヤしている人も多いのではないでしょうか。

そこで、NumPy(Python)のaxis指定を易しく図解し、axis = 0, 1, -1の違いを説明したいと思います。

なお、この記事では「import numpy as np」が実行されたものとして、図中や解説で「numpy」を「np」と表現しています。

1.NumPyの基本事項のおさらい

コード01では、3~6行目でNumPy配列の「次元数」「各次元の長さ」「全要素数」「0番目の次元の長さ」を出力します。また、9行目では「0番目の次元の要素番号が0」「1番目の次元の要素番号が0, 1, 2」の要素を出力します。

解説で使用する用語をはっきりするためにも、「次元数」「各次元の長さ」「全要素数」「0番目の次元の長さ」の用語はサンプルコードと照らし合わせながら用語の使い方をはっきりさせておいてください。

参考記事:Python♪用語集:NumPyの配列に関する日本語表現

#コード01
x = np.array([[1, 2, 3], [4 ,5 ,6]])
print(x.ndim)  #次元数
print(x.shape) #形状(各次元の長さ)
print(x.size)  #全要素数
print(len(x))  #0番目の次元の長さ
print(x)
print(x[0, 0])
print(x[0, 0:3])
#出力01
2
(2, 3)
6
[[1 2 3]
 [4 5 6]]
1
[1 2 3]

コード02では、axisを使用する関数(メソッド)の例です。maxは最大値、sumは合計値を求める関数であり、axisで何番目の次元を計算するか指定します。これらの関数はaxis以外にも様々な引数をとりますが、この記事ではaxisのみ解説します。

#コード02
import numpy as np

x = np.array([[1, 2, 3], [4 ,5 ,6]])
print(np.max(x, axis = 0))
print(np.sum(x, axis = 0))
print(x.sum(axis = 0))
#出力02
[4 5 6]
[5 7 9]
[5 7 9]

2.2次元配列のaxis

それでは、次元数(ndim)が2、つまり2次元配列のaxisの使い方から説明します。axisの日本語は「軸」です。

下図の例では、x[0, 0:3] = [1 2 3]であり、x[1, 0:3] = [4 5 6]です。つまり、0番目の次元の要素番号(下図ではaxis0)を変化させることで、図の縦方向の位置を指定することができます。

一方、x[axis0, axis1]のaxis1を変化させることで、図の横方向の位置を指定することができます。

2次元配列とaxis

(1) axis = 0

これを意識して、np.max(x, axis = 0)の計算をしてみます。axis = 0とは、axis0の要素番号を変えたときに変化する内容同士を比較します。下図では青い点線の中を比較し、一番大きい値を抽出します。この時、計算結果の配列の次元数は計算前よりも1つ減ることに注意してください。

なお、下の図で配列の形状がどのように変化するかを理解しようとするのはやめましょう。特に3次元以上では混乱します。

2次元配列のaxis0

配列の形状変化については、下図で確認すると明快になります。x[2, 3]は、0番目の次元の大きさが2、1番目の次元の大きさが3であることを示し、axis = 0を指定して計算すると、0番目の次元が消滅し、全要素数3の1次元配列x = [4 5 6] となります。

つまり、axisで指定した次元の要素を使って計算し、指定した次元は消えてしまいます。

いかがですか、axis = 0, axis = 1については、このルールで全て説明できますので残りの解説は復習のつもりで読んでください。

どの次元がへるのかaxis0

(2) axis = 1

axis = 1の場合も図示します。axis = 0の場合と同様に配列の次元数が1つ減ります。

2次元配列のaxis1

配列の形状変化は下図で確認してください。

どの次元がへるのかaxis1

(3) axis = -1

2次元以上の配列ではaxis = -1はaxis = 1の場合と同じ結果になります。なお、1次元の場合は同じ結果になりませんので後述します。

(4) サンプルコード

以下、サンプルコードです。

#コード03
import numpy as np

x = np.array([[1, 2, 3], [4 ,5 ,6]])
print(x.shape)
print('x:')
print(x)
print('axis = 0:')
print(np.max(x, axis=0))
print('axis = 1:')
print(np.max(x, axis=1))
print('axis = -1:')
print(np.max(x, axis=-1))
#出力03
(2, 3)
x:
[[1 2 3]
 [4 5 6]]
axis = 0:
[4 5 6]
axis = 1:
[3 6]
axis = -1:
[3 6]

3.1次元配列のaxis

次に次元数が1、つまり1次元配列のaxisの使い方を説明します。注意すべき点は2次元配列では横方向がaxis1だったのに、1次元配列では横方向がaxis0になっている点です。これがモヤモヤを生む原因です。

1次元配列では文字通り次元数が1なので、軸(axis)は1つしかありません。axis = 0の次はないのです。この点を注意すれば1次元配列も2次元配列と同様の考え方で理解できます。

1次元配列とaxis

なお、1次元配列のときだけ、axis = -1とaxis = 1の結果が異なることに注意しましょう。

(1) axis = 0

1次元配列のaxis = 0は、どの方向がaxis = 0なのか理解できていれば難しくありません。

1次元配列のaxis0

なお、計算前は次元数1、全要素数3の配列でしたが、計算後は次元数が1つ減りますので次元数が0になり、ただの値(スカラー)になってしまいます。

どの次元が減るのかaxis0_1次元配列

(2) axis = 1

1次元配列のaxis = 1は注意が必要です。そもそも、axis = 1が存在しないのでエラーになります。エラーコードは以下の通りです。2次元配列のときのイメージを引きずると1行3列のように考えてしまいますが、ただのベクトルなのでaxis = 0しか存在しません。

AxisError: axis 1 is out of bounds for array of dimension 1
1次元配列のaxis1

以下の図の方が、axis = 1が存在しないことが分かりやすいかもしれません。

どの次元が減るのかaxis1_1次元配列

(3) axis = -1

axis = -1は1次元配列のときはaxis = 0の結果と同じになり、2次元以上の配列ではaxis = 1の結果と同じになります。

つまり、1次元配列のときaxis = 1ではエラーになりますが、axis = -1ではエラーになりません。

(4) サンプルコード

コード04は1次元配列の場合のサンプルコードです。10行目ではaxis = 1を指定しているためエラーになります。

#コード04
import numpy as np

x = np.array([1, 2, 3])
print('x:')
print(x)
print('axis=0:')
print(np.max(x, axis=0))
#print('axis=1:')
#print(np.max(x, axis=1))   #エラー(AxisError:)
print('axis=-1:')
print(np.max(x, axis=-1))
#出力04
x:
[1 2 3]
axis=0:
3
axis=-1:
3

4.3次元配列のaxis

3次元配列は特に難しくありません。図によりaxis = 0, 1, 2がどの方向を示すのかを確認してください。

図では次元数によってaxis = 0の示す位置が変化して分かりにくいかもしれません。しかし、x[0, 0:2, 0:3]は図の手前のブロック、x[1, 0:2, 0:3]は図の置くのブロックであることを意識すればaxis = 0がどの方向を示しているのかがわかると思います。

また、配列x[axis0, axis1, axis2]において、各次元の左から順番に0, 1, 2と番号がふられていることが分かります。

3次元配列とaxis

(1) 計算結果の比較(axis = 0, 1, 2)

axsis =0, 1, 2のときの計算結果の違いを図で比較します。まずは、どのような結果になるか図と比較しながら確認してください。どの部分の要素を使って計算しているのかが分かると思います。

ただ、この図から配列の形状変化までを想定するのは難しいです。

(a) axis = 0

3次元配列のaxis0

(b) axis = 1

3次元配列のaxis1

(c) axis = 2

3次元配列のaxis2

(2) 配列の形状変化(axis = 0, 1, 2)

配列の形状変化は配列xの形状x[2, 2, 3]から考えれば明快です。

(a) axis = 0

配列xは、0番目の次元の大きさは2、1番目の次元の大きさ2、2番目の次元の大きさは3の配列ですが、np.max(x, axis =0)の計算結果では、指定したaxis = 0の次元が消え、0番目の次元の大きさが2、1番目の次元の大きさが3となります。

どの次元が減るのかaxis0_3次元配列

(b) axis = 1

axis = 1でも同様に指定したaxis = 1の次元が消えてしまいます。

どの次元が減るのかaxis1_3次元配列

(c) axis = 2

axis = 2でも同様に指定したaxis = 2の次元が消えてしまいます。

どの次元が減るのかaxis2_3次元配列

(3) axis = -1

axis = -1の場合は、1次元配列のときはaxis = 0と同じ、2次元以上の配列ではaxis = 1と同じ計算結果になりますから、3次元配列ではaxis = 1と同じ結果になります。

(4) サンプルコード

コード05はサンプルコードです。

#コード05
import numpy as np

x = np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])
print('x:')
print(x)
print('axis=0:')
print(np.max(x, axis=0))
print('axis=1:')
print(np.max(x, axis=1))
print('axis=2:')
print(np.max(x, axis=2))
#出力05
x:
[[[ 1  2  3]
  [ 4  5  6]]

 [[ 7  8  9]
  [10 11 12]]]
axis=0:
[[ 7  8  9]
 [10 11 12]]
axis=1:
[[ 4  5  6]
 [10 11 12]]
axis=2:
[[ 3  6]
 [ 9 12]]

5.まとめ

一見、複雑そうに見えるaxis指定ですが、ルールを整理するとシンプルです。

  • axisの番号は、何番目の次元であるかを示します。(例:x[axis0, axis1, axis2]) したがって、要素を平面的(立体的)に並べた図だけで理解しようとすると混乱します。
  • 出力結果は必ず次元数が1つ減り、axisで指定した次元が消えます。
  • 1次元配列では配列の次元数が1なのでaxis = 1はエラーになります。
  • axis = -1は1次元配列のときはaxis = 0の結果と同じになり、2次元以上の配列ではaxis = 1の結果と同じになります。

私が実際に購入した教材のご紹介

以下、私が実際に購入したPythonの教材をまとめてみました。 Pythonを学習する上で、少しでもお役に立つことができればうれしいです。

Python♪私が購入したPythonの書籍のレビュー
UdemyのPythonの動画講座を書籍を買う感覚で購入してみた

その他

Twitterへのリンクです。SNSもはじめました♪

以下、私が光回線を導入した時の記事一覧です。
 (1) 2020年「光回線は値段で選ぶ」では後悔する ←宅内工事の状況も説明しています。
 (2) NURO光の開通までWiFiルーターを格安レンタルできる
 (3) NURO光の屋外工事の状況をご紹介。その日に開通!
 (4) 光回線開通!実測するとNURO光はやっぱり速かった
 (5) ネット上のNURO光紹介特典は個人情報がもれないの?