🕌

第2回:NumPy配列の追加・挿入・コピー

に公開

はじめに

本記事は「Pythonで数値計算を行うためのNumPy入門」シリーズの第2回です.
このシリーズでは, "NumPyのよく使われる機能を分野ごとに理解すること" を目標としています.

Pythonで数値計算を行う際に欠かせないライブラリ「NumPy」について,基礎から応用まで体系的に学べます.

シリーズ構成予定(全10記事)

  1. NumPyのインストール 〜 arrayの基本
  2. 配列の追加・挿入・コピー(本記事)
  3. 配列の作成(ones / zeros / arange / linspace)(公開後にリンクを追加する予定)
  4. 配列の結合(hstack / vstack)(公開後にリンクを追加する予定)
  5. 条件抽出と検索(where / clip / unique)(公開後にリンクを追加する予定)
  6. 統計処理(mean / std / median ほか)(公開後にリンクを追加する予定)
  7. 線形代数(dot / inv / det / norm)(公開後にリンクを追加する予定)
  8. 乱数生成(rand / randint / normal)(公開後にリンクを追加する予定)
  9. 三角関数と角度変換(sin / arctan2 ほか)(公開後にリンクを追加する予定)
  10. ファイル入出力(savetxt / loadtxt)(公開後にリンクを追加する予定)

私のNumpyライブラリのバージョンは "1.26.4" です.

本記事のゴール

本記事では, 配列操作の基本となる「追加」「挿入」「コピー」 を学んでいきます.

今回扱う関数は以下の3つです.

関数 内容 備考
np.append() 配列の末尾に追加 配列の末尾にデータが追加
np.insert() 任意の位置に挿入 挿入位置を任意で設定
np.copy() 配列のコピー 実体コピーと参照コピーに注意

実体コピーと参照コピーの違いを理解しないと,想定通りに動かなくなりますので,しっかりと理解しましょう.

NumPyライブラリの読み込み

NumPyを使うためには,まずライブラリの読み込みから始めます.

import numpy as np

numpyライブラリを "np" という名前として,扱えるようにライブラリを読み込んでいます.
以降では "np" という名前を使っていきます.

np.append() (配列の末尾にデータを追加)

配列の末尾にデータを追加する np.append() 関数に関して説明する.

# 配列の初期化
arr = np.array([10, 20, 30])
print(arr)
# 出力:[10, 20, 30]

# 配列の末尾にデータを追加
new_arr = np.append(arr, 40)
print(new_arr)
# 出力:[10, 20, 30, 40]
print(arr)
# 出力:[10, 20, 30]

元の配列 arr には,データが追加されない.new_arrarr の末尾にデータが追加された配列が保存される.
arrnew_arr は異なる配列となる.

np.insert() (配列の任意位置にデータを挿入)

配列の任意位置にデータを挿入する np.insert() 関数に関して説明する.

# 配列の初期化
arr = np.array([10, 20, 30])
print(arr)
# 出力:[10, 20, 30]

# 配列の位置1に15を挿入
new_arr = np.insert(arr, 1, 15)  # 位置1に15を挿入
print(new_arr)
# 出力:[10, 15, 20, 30]

配列のインデックスは "0" スタートです.
配列にデータを挿入すると挿入位置以降のデータが1つずつ後ろに移動します.

np.copy() (配列のコピー)

配列をコピーする np.copy() 関数に関して説明する.

# 配列の初期化
arr = np.array([10, 20, 30])
print(arr)
# 出力:[10, 20, 30]

# 配列のコピー
arr_copy = arr.copy()
# 要素0を999へ更新
arr_copy[0] = 999

print(arr)
# 出力:[10, 20, 30]
print(arr_copy)
# 出力:[999, 20, 30]

np.copy() を使用すると,別の配列として,定義されます.arrarr_copy は同じデータ内容だが,異なる配列となります.
要するに,arrarr_copy はメモリ内で異なるアドレス上に同じデータがコピーされます.
詳細は後ほど説明します.

次に,np.copy() を使用せずに,= によるコピーを実行します.

# "=" による配列のコピー
arr_view = arr
arr_view[0] = 999

print(arr)
# 出力:[999, 20, 30]
print(arr_view)
# 出力:[999, 20, 30]

**= は「コピー」ではなく「参照コピー」**になるため,arrarr_view の両方とも値が変更されます.
要するに,arrarr_view はメモリ内で同じアドレス上を参照しているので,片方の変数を修正すると両方の変数に影響が生じます.
次に,実体コピー(np.copy())と参照コピー(=) の違いを説明します.

実体コピーと参照コピー

実体コピー(np.copy())と参照コピー(=)の違いを説明します.

前提条件としては,下記の図の通りとなります.
変数名 arr を定義している前提で話を進めます.

はじめに,実体コピー(np.copy())の処理を説明します.
内容は下図の通りとなります.

変数 arr_view は変数 arr と異なるアドレス上に同じデータを保持しています.要するに arr_viewarr は異なるアドレスを参照しています.
異なるアドレスを参照しているため,arr_view を変更しても arr は変わらないです.また,arr を変更しても arr_view は変わらないです.

次に,参照コピー('=')の処理を説明します.
内容は下図の通りとなります.

変数 arr_view は変数 arr のアドレスを保存しています.要するに arr_viewarr は同じアドレスを参照しています.
同じアドレスを参照しているため,arr_view を変更すると arr も変更されます.また,arr を変更すると arr_view も変更されます.

実体コピーと参照コピーを下表にまとめます.

コピー方法 コピー先のアドレスとコピー元のアドレス 内容
実体コピー('np.copy()') 異なるアドレス コピー先のデータをコピー
参照コピー('=') 同じアドレス コピー先のアドレスをコピー

実体コピーと参照コピーの違いを理解しないと,想定外の挙動となるので,注意しましょう.
次に,どうやって実体コピーか参照コピーかどうかを確認する方法を説明します.

実体コピーと参照コピーの判別方法

Pythonにて,実体コピー(np.copy())と参照コピー(=)を判別する方法を説明します.
Pythonが提供している標準関数である id() を使用して,変数が保存されているアドレスを取得して,実体コピーなのか参照コピーなのかを判別していきます.
ソースコードの実装例を説明します.

# 配列の初期化
arr = np.array([10, 20, 30])

# 配列のコピー
arr_copy = arr.copy()

# "=" による配列のコピー
arr_view = arr

# 各変数が保存されているアドレスを表示
print(id(arr))
# 出力:4380430704
print(id(arr_copy))
# 出力:4380977040
print(id(arr_view))
# 出力:4380430704

注意:id()の結果は各々で異なります.また,他にソフトウェアを起動していたりすると,毎回異なる結果となります.表示している値は私のPCで実行した時の結果となります.

注目して欲しい箇所は,id(arr)id(arr_view) の結果が一致することと,id(arr)id(arr_copy) の結果が不一致なことです.
id(arr)id(arr_view) の結果が一致するので,= は参照コピーとなっていることが判別できます.
id(arr)id(arr_copy) の結果が不一致なので,np.copy() は実体コピーとなっていることが判別できます.

実体コピーか参照コピーかを判別するには,id() の結果を見ましょう.

まとめ

NumPyライブラリの append / insert / copy を比較します.

| 操作 | 関数 | よく使う場面 |
| 値を末尾に追加 | np.append() | データの拡張 |
| 中間に挿入 | np.insert() | 条件挿入 |
| 配列のコピー | np.copy() | 後で加工する時 |

次に,実体コピーと参照コピーを比較します.

コピー方法 コピー先のアドレスとコピー元のアドレス 内容
実体コピー('np.copy()') 異なるアドレス コピー先のデータをコピー
参照コピー('=') 同じアドレス コピー先のアドレスをコピー

配列操作を理解することで,データ加工や分析がスムーズになります.
実体コピーや参照コピーを理解することで,不具合を未然に防ぐことができます.

次回予告

第3回:「配列の作成(ones / zeros / arange / linspace)」

  • np.ones() (配列を1で初期化)
  • np.zeros() (配列を0で初期化)
  • arange() (初期値から目標値まで一定値で増加する配列の作成)
  • linspace() (初期値と目標値を等間隔にN分割した配列の作成)

そんなときに使う「配列生成の基本」を説明します.

参考文献

本記事を作成するに当たって参考にしたサイトをまとめました.

Discussion