ねこすたっと

ねこの気持ちと統計について悩む筆者の備忘録的ページ。

チュートリアル拾い読み (2):NumPy公式チュートリアル (2/5) [Python]

Rもたいして使いこなせてないのにPythonの勉強を始めてみました。色々と素晴らしいチュートリアルはありそうですが、目移りしてしまうので公式チュートリアルを拾い読みしていきます。

今回も前回に続き、NumPy公式チュートリアルの "NumPy: the absolute basics for beginners"を読んでいきます。

前回:

necostat.hatenablog.jp

配列の要素へのアクセス

配列を構成する各値を項目(item)と呼ぶべきか、要素(element)と呼ぶべきか迷います。 Pythonの配列に関してはどちらでも良さそうですが、数学では「行列の要素」という言い方を習ったので、以後は要素に統一しようと思います。

インデックス(index)を使ったアクセス

インデックスとは「添字」のことで、配列の要素の「番地」を表す数字です。それぞれの軸(次元)の何番目の箱に収められたデータなのかを表しています。

まずは例に使う2次元配列を作ります。

a = np.array([[1, 2, 3, 4], 
              [5, 6, 7, 8], 
              [9, 10, 11, 12]])

この配列の第1次元の1番目, 第2次元の4番目(つまり「4」)をインデックスを使って抽出するコードは次のようになります。

print(a[0, 3])  # 出力: 4

Pythonでは順番を数えるときに0から始めることに注意です(いまだに慣れません...)。

また、マイナスを使えば後ろから数えた順序で指定することもできます。例えば、第1次元の最後で第2次元の後ろから2番目(つまり「11」)を指定するコードは次のようになります。

print(a[-1, -2])  # 出力: 11

一部の次元でインデックスを指定しない場合は:を使います。

print(a[1, :]) # 出力: [5 6 7 8]

スライシング(slicing)による部分抽出

スライシングとは範囲を指定して配列の一部を抽出する操作です。ここでも:を使います。

例えば、

a = np.array([1, 2, 3, 4, 5, 6])

という配列の2番目から4番目(つまり「2, 3, 4」)を抽出する場合は、

print(a[1:4])  # 出力: [2 3 4]

とします。

注意点の1つ目は、先程と同じく0から数え始めることです。 2つ目は、開始側は含むが、終了側は含まないことです。1:4とスライシングした場合、「2番目から開始し、5番目は含まない(4番目まで)」ということになります。

論理式を使った部分抽出

条件を満たした要素だけを抽出することもできます。

例えば、a<4を満たす要素だけ抽出したければ、

a = np.array([1, 2, 3, 4, 5, 6])
print(a[a<4])  # 出力: [1 2 3]

各要素について条件式に対する真偽が[ ]内で指定されることで要素を抽出しているので、別に保存された条件を使って抽出することもできます。

例えば、

cond = (a<4)

とすると、condにはそれぞれの要素に対するTrue/Falseが保持されています。

print(cond) # [ True  True  True False False False]

これを使って配列の部分抽出をすると、

a[cond]  # 出力: array([1, 2, 3])

となります。

Pythonでは真偽(boolean値)はTrue, Falseと最初だけ大文字で書きます。

要素の置換(というよりコピーの話)

配列aの一部をスライシングして、bという名前で保存します。

a = np.array([[1,2,3,4],
              [6,7,8,9]])
b1 = a[0,1:3]
print(b1)  # 出力: [2 3]

予想どおりの出力です。

次に配列b1の1番目の要素を99に置き換えます。

b1[0] = 99
print(b1)  # 出力: [99  3]

これも予想どおりの結果です。

ここで元の配列aを見てみます。

print(a)
# 出力: 
[[ 1 99  3  4]
 [ 6  7  8  9]]

なんと、b1だけ置換したつもりが、元の配列の要素も置換されてしまっていますね。

スライシングで作られた配列は、新しいデータとして作成されているのではなく、既存のデータを参照しているだけなのです。これを「浅いコピー(shallow copy)」と呼びます。新しいデータを作成するわけではないので、メモリの節約になります。

新しくコピーを作って、元のオブジェクトとは別で扱いたい場合は、.copy( )メソッドを使います。これを「深いコピー(deep copy)

深いコピーで作成した配列の置換をしてみます。配列b2の(0,1)を99に置換しました。

a = np.array([[1,2,3,4],
              [6,7,8,9]])
b2 = a.copy( )
b2[0,1] = 99
print(b2)
# 出力: 
[[ 1 99  3  4]
 [ 6  7  8  9]]

配列b2の(0,1)は99に置換されています。

print(a)
# 出力: 
[[1 2 3 4]
 [6 7 8 9]]

元の配列aは変更されていません。

図にしてみました。

おわりに

今回学んだこと:

  • インデックスを使ったアクセスでは、0から始まることに注意。
  • スライシングでは「開始は含む、末尾は含まない」に注意。
  • 浅いコピー(= 単なる参照)と深いコピー(= 複製)がある

チュートリアルは読んだけど取り上げなかったもの:

  • np.vstack( ):配列を縦方向(第1次元)に結合する関数。
  • np.hstack( ):配列を横方向(第2次元)に結合する関数。
  • np.hsplit( ):配列を水平方向(第2次元)に等分する関数。
  • .view( ):配列を参照するためのメソッド。shallow copyを作成。

次回:

necostat.hatenablog.jp