NumPyのshape表記の (2, ) って何ですか?


サマリ

  • (2, )
    • np.shapeでndarray(NumPy配列)の形状取得した結果
    • ndarrayが1次元の場合の表記
  • 特徴
    • 2次元目がブランク(空白)になっている
    • (2)のようにすればいいと感じる
  • (2, )となっている理由
    • 分かりやすいから(理由は後述)

はじめに

前提

次元の呼び方(大切なので…)

  • (3,4)は?
    • 「3」と「4」の2つで2次元
    • 行と列の行列なので2次元
    • 次元が2つあるので2次元
  • 一番左数字の呼び方
    • 最初の次元
    • 1次元目(推奨)
      • 「~目」は序数(~番目)になる
    • 第1次元(おすすめではない)
      • 浸透していない
    • 0次元、1次元(おすすめではない)
      • 人によってまちまち
      • インデックス番号の0と第1次元の「第」省略したものがミックスされた?
  • (3,4)の「3」は?
    • 1次元目の要素数で、shape[0]で取得できる
    • shape[0]なので、0次元と言ってしまうのもわかる気がする
  • (3,4)の「4」は?
    • 2次元目の要素数で、shape[1]で取得できる
  • 補足:0次元とは?
    • スカラーのこと
    • ベクトル表記すると「()」(中身なし)
  • 参考

詳細説明

言葉で説明

  • (2, )は1次元
    • 1次元目の要素数は2
      • array([1,2])やarray([10,5])のようなデータ
    • 2次元目はないのでブランク(空白)になっている
    • np.ndimで確認すると1
  • 行列のように2次元データにすることもできる
    • 形状は(1,2)のようになる
    • 1次元目の要素数は1、2次元目の要素数は2
      • array([[1,2]])やarray([[10,5]])のようなデータ
    • np.ndimで確認すると2
  • 中身のデータは同じなので、状況に応じて使いやすい方を使えばいい
  • 以降ではどのような場合にそれぞれのデータが適しているかを説明する

データを見ながら説明

1次元

import numpy as np
a = np.arange(2)
print(a)
print(a.ndim)
print(a.shape)
[0 1]
1
(2,)
  • 次元数は1次元
  • (2, )
    • 1次元目の要素数が2
    • 2次元目の要素数は0(なし)

2次元(行列)

import numpy as np
b = np.arange(2).reshape(1,2)
print(b)
print(b.ndim)
print(b.shape)
[[0 1]]
2
(1, 2)
  • 次元数は2次元
  • (1,2)
    • 1次元目の要素数が1
    • 2次元目の要素数は2
  • ここまでで両者の違いが何となく分かった
  • 以降でそれぞれのよいところや気を付けたいところを説明

(2, )表記もありだなと思える

  • 1次元、2次元、3次元、…と並べてみる
    (2, ) …… 1次元(中の数字は1つ)
    (3,2) …… 2次元(中の数字は2つ)
    (2,1,3) …… 3次元(中の数字は3つ)
    (4,1,2,2) …… 4次元(中の数字は4つ)
  • ここで1次元の(2, )を(1,2)に入れ替えると、違和感があります
    • (2)のような表記にしないことでndarrayだとすぐにわかります
    • (2, )の表記もありだなと思えました?
  • 考え方
    • 上記でも触れたように(2, )でも(1,2)でも中のデータは同じ
    • 以降でどのような場合に適しているかを説明

適しているパターンを探る

(1,2)がよい場合

データ作成

import numpy as np
a = np.array([1,2])
b = np.array([[1,2]])
c = np.array([[1,2], [3,4]])
print(f'[a]shape:{a.shape} ndim:{a.ndim}')
print(f'[b]shape:{b.shape} ndim:{b.ndim}')
print(f'[c]shape:{c.shape} ndim:{c.ndim}')
[a]shape:(2,) ndim:1
[b]shape:(1, 2) ndim:2
[c]shape:(2, 2) ndim:2
  • a
    • 1次元
    • データは1,2の2つ
  • b
    • 2次元
    • データは1,2の2つ
  • c
    • 普通の行列
    • 2次元
    • データは1,2,3,4の4つ

同じように取り出してみると

print(a[0])
print(a[0].shape)
print(b[0])
print(b[0].shape)
print(c[0])
print(c[0].shape)
1
()
[1 2]
(2,)
[1 2]
(2,)
  • a[0]
    • 1
    • スカラー
  • b[0]
    • 1と2
    • 1次元ndarray(NumPy配列)
  • c[0]
    • 1と2
    • 1次元ndarray
  • aとbを比較する
    • 同じように取り出しているが取得できるデータが異なる
    • cの取り出し結果を見ると、行列として処理を作っている場合はaよりbの方が扱いやすそうです

(2, )がよい場合

データ作成

a = np.arange(6).reshape(3,2)
b = np.arange(2)
c = np.arange(2).reshape(1,2)
print(a)
print(a.shape)
print(b)
print(b.shape)
print(c)
print(c.shape)
[[0 1]
 [2 3]
 [4 5]]
(3, 2)
[0 1]
(2,)
[[0 1]]
(1, 2)
  • a
    • 形状(3,2)のndarray(NumPy配列)を作成
  • b
    • 形状(2, )のndarrayを作成
  • c
    • 形状(1,2)のndarrayを作成
  • aに対するb,cのdot積を計算する

np.dot(a,b)

rtn = np.dot(a,b)
print(rtn)
print(rtn.shape)
[1 3 5]
(3,)
  • (3,2)と(2, )のdot積計算
  • (3, )
    • dot積計算の法則の通り間の2がなくなった形状になっている
    • 例えば、(3,2)と(2,4)の計算なら(3,4)になるのと同様に扱える

np.dot(a,c)

rtn = np.dot(a,c)
print(rtn)
print(rtn.shape)
ValueError: shapes (3,2) and (1,2) not aligned: 2 (dim 1) != 1 (dim 0)
  • (3,2)と(1,2)のdot積計算
  • dot積の計算のルールに沿っていないため計算できない
    • 計算しようと思えば、(3,2)と(2,1)にすれば計算できる
    • その場合、計算結果は(3,1)でいい?(1,3)に逆転させるべき?
  • このことから「(2, )」のような表記も素直に受け入れた方がよいことが分かる
    • 実際のコードでは(2, )の方が一般的で、(1,2)の方が珍しいです
  • 参考

まとめ

  • それぞれの特徴を知っておく
    • (2, )の方が(1,2)より一般的
    • 状況に応じて使い分けれるようにしておく
  • もし(1,2)のように変更しているコードがあったなら
    • 何か理由があると考える
    • ロジック理解やバグを見つけるきっかけになる
    • 思わぬところで時間を取られることも減る

補足


Posted by futa