NumPy 配列操作の基礎

NumPyBeginner
オンラインで実践に進む

はじめに

この実験では、NumPy 配列操作の基本的な概念を探求し、特にコピーとビューの違いに焦点を当てます。この違いを理解することは、Python で効率的でバグのない数値コードを作成するために不可欠です。これらの概念を実際に確認するために、Python スクリプトを作成して実行します。

コピーとビューの違い

NumPy では、コピーは独自のデータを持つ完全に新しい配列ですが、ビューは同じデータを参照する新しい方法です。ビューを変更すると元の配列に影響しますが、コピーを変更しても影響しません。

これを実際に見てみましょう。配列を作成し、それからビューとコピーを作成します。その後、両方を変更して元の配列への影響を観察します。

まず、左側のファイルエクスプローラーから main.py ファイルを開きます。次に、その内容を以下のコードに置き換えます。

import numpy as np

## --- Part 1: Modifying a View ---
print("--- Modifying a View ---")
## Create an original array
original_array_view = np.array([1, 2, 3, 4, 5])
print(f"Original array: {original_array_view}")

## Create a view of the array
view_array = original_array_view.view()
## Modify the first element of the view
view_array[0] = 99
print(f"View after modification: {view_array}")
print(f"Original array after modifying the view: {original_array_view}\n")


## --- Part 2: Modifying a Copy ---
print("--- Modifying a Copy ---")
## Create another original array
original_array_copy = np.array([10, 20, 30, 40, 50])
print(f"Original array: {original_array_copy}")

## Create a copy of the array
copy_array = original_array_copy.copy()
## Modify the first element of the copy
copy_array[0] = 999
print(f"Copy after modification: {copy_array}")
print(f"Original array after modifying the copy: {original_array_copy}")

次に、ファイルを保存し、ターミナルから実行して出力を確認します。

python main.py

以下の出力が表示されるはずです。ビューが変更されたときに元の配列が変更された一方で、コピーが変更されたときには変更されなかったことに注目してください。

--- Modifying a View ---
Original array: [1 2 3 4 5]
View after modification: [99  2  3  4  5]
Original array after modifying the view: [99  2  3  4  5]

--- Modifying a Copy ---
Original array: [10 20 30 40 50]
Copy after modification: [999  20  30  40  50]
Original array after modifying the copy: [10 20 30 40 50]

これは、ビューが元のデータにリンクされているのに対し、コピーは完全に独立しているという、根本的な違いを示しています。

配列のスライス - ビューの作成

NumPy で非常に一般的な操作はスライスです。これは、配列から要素の範囲を選択するために使用されます。基本的なスライスは常に元の配列のビューを作成します。これはメモリ効率のための重要な機能ですが、スライスを変更すると元のデータも変更されることに注意する必要があります。

これをテストしてみましょう。main.py の内容をクリアし、以下のコードを追加します。

import numpy as np

## Create an array from 0 to 9
original_array = np.arange(10)
print(f"Original array: {original_array}")

## Create a slice of the array (elements from index 2 to 4)
array_slice = original_array[2:5]
print(f"Slice of the array: {array_slice}")

## Modify the first element of the slice
print("Modifying the first element of the slice to 100...")
array_slice[0] = 100

## Print the original array again to see the change
print(f"Original array after modification: {original_array}")

ファイルを保存し、ターミナルで実行します。

python main.py

出力は以下のようになります。

Original array: [0 1 2 3 4 5 6 7 8 9]
Slice of the array: [2 3 4]
Modifying the first element of the slice to 100...
Original array after modification: [  0   1 100   3   4   5   6   7   8   9]

ご覧のとおり、array_slice[0] を変更すると original_array[2] も変更されました。これは、スライスがコピーではなくビューであったことを確認します。

高度なインデックス作成 - コピーの作成

基本的なスライスはビューを作成しますが、高度なインデックス指定は常にコピーを作成します。高度なインデックス指定では、要素を選択するためにインデックスのリスト、タプル、または別の配列を渡します。選択された要素はメモリ内の連続したブロックにない可能性があるため、NumPy はそれらを保持するために新しい配列(コピー)を作成します。

前のステップと比較してみましょう。main.py をクリアして、このコードを挿入します。

import numpy as np

## Create an array from 0 to 9
original_array = np.arange(10)
print(f"Original array: {original_array}")

## Use advanced indexing to select elements at indices 1, 3, and 5
indexed_array = original_array[[1, 3, 5]]
print(f"Indexed array: {indexed_array}")

## Modify the first element of the new array
print("Modifying the first element of the indexed array to 100...")
indexed_array[0] = 100

## Print the original array again to see if it changed
print(f"Original array after modification: {original_array}")

ファイルを保存してスクリプトを実行します。

python main.py

出力は以下のようになります。

Original array: [0 1 2 3 4 5 6 7 8 9]
Indexed array: [1 3 5]
Modifying the first element of the indexed array to 100...
Original array after modification: [0 1 2 3 4 5 6 7 8 9]

今回は、元の配列は変更されませんでした。indexed_array はコピーであったため、それに加えられた変更は original_array に影響しませんでした。

.base を使用したコピーとビューの識別

操作がビューを返したのか、コピーを返したのかが明らかでない場合があります。NumPy は、それを確認するための信頼できる方法を提供します。それは配列の .base 属性です。

  • 配列がビューの場合、その .base 属性は、それが属する元の配列オブジェクトを指します。
  • 配列がコピーの場合、その .base 属性は None になります。

これを使用して、前のステップでの調査結果を確認しましょう。main.py をクリアして、以下のコードを追加します。

import numpy as np

## Create an original array
original_array = np.arange(10)
print(f"Original array: {original_array}\n")

## Create a view using slicing
view_slice = original_array[2:5]
print(f"Slice (view): {view_slice}")
## Check if the slice is a view of the original array
print(f"Is the slice a view? {view_slice.base is original_array}\n")

## Create a copy using advanced indexing
copy_indexed = original_array[[1, 3, 5]]
print(f"Indexed (copy): {copy_indexed}")
## Check if the indexed array is a copy
print(f"Is the indexed array a copy? {copy_indexed.base is None}")

ファイルを保存し、ターミナルから実行します。

python main.py

以下の出力が得られます。これは、スライスがビューであり、インデックス指定された配列がコピーであることをプログラム的に確認します。

Original array: [0 1 2 3 4 5 6 7 8 9]

Slice (view): [2 3 4]
Is the slice a view? True

Indexed (copy): [1 3 5]
Is the indexed array a copy? True

.base 属性は、デバッグやコードが期待どおりに動作していることを確認するための非常に貴重なツールです。

まとめ

この実験では、NumPy 配列のコピーとビューの重要な違いについて学びました。両方を作成する練習をし、変更が元のデータにどのように影響するかを観察しました。

  • .view() や基本的なスライスのようなメソッドはビューを作成することを学びました。ビューはメモリ効率が良いですが、変更すると意図しない副作用を引き起こす可能性があります。
  • .copy() や高度なインデックス指定のようなメソッドはコピーを作成することを学びました。コピーは元の配列から独立しています。
  • 最後に、.base 属性を使用して、配列がビューなのかコピーなのかを確実に確認する方法を学びました。

これらの概念を習得することは、NumPy を使用して堅牢で効率的な数値アプリケーションを作成するための重要なステップです。