【NumPy】「NumPy配列(ndarray)のスライス」と「Python組み込みのlistのスライス」の違い
のスライス」と「Python組み込みのlistのスライス」の違い-1.jpg)
「NumPy 配列(ndarray)のスライス」と「Python 組み込みの list のスライス」の違いについて解説します。
目次
「NumPy 配列(ndarray)のスライス」と
「Python 組み込みの list のスライス」の違い
NumPy の配列(ndarray)は、データ分析で非常によく使用されるデータ型です。ndarray は、Python のリストのスライス同様に一部を取り出すことができます。しかし、NumPy の ndarray と 組み込みの list は、スライスの意味が異なります。
結論をまず言ってしまうと「NumPy の配列(ndarray)のスライスはビュー」であるため、スライスした値を変更すると元の配列のデータも更新されます。
一方で、「Python 組み込みの list のスライスはコピー」であるため、スライスした値を変更しても元のデータは変更されません。
この記事では、この違いについて例を用いながら紹介します。
NumPy 配列(ndarray)のスライスはビュー
NumPy 配列(ndarray)をスライスで取得した場合にはビューとなり、取得したデータを変更すると元のデータも変更されます。
NumPy は、科学技術計算や画像処理、機械学習等で、巨大な行列、高次元行列、膨大な件数のデータといった大規模データを扱います。そのため、スライスのたびにコピーが発生するとメモリも時間も浪費し、パフォーマンスに大きく影響します。そのため、ビューで問題ない状況であればビューにするという考え方の設計となっています。
1 次元配列と 2 次元配列の簡単な例で見てみましょう。
【1次元配列で確認】
import numpy as np
# ===== 1次元配列で確認
data_1d = np.arange(10)
print(f'元のデータ: {data_1d}')
# スライスで一部のデータを取得する
tmp_data_1d = data_1d[3:8]
print(f'スライスデータ: {tmp_data_1d}')
# スライスのデータを変更する
print('\n=== スライスしたデータを変更')
tmp_data_1d[:] = 0
print(f'スライスデータ: {tmp_data_1d}')
# 元のデータを確認する
print(f'元のデータ: {data_1d}')【実行結果】 元のデータ: [0 1 2 3 4 5 6 7 8 9] スライスデータ: [3 4 5 6 7] === スライスしたデータを変更 スライスデータ: [0 0 0 0 0] 元のデータ: [0 1 2 0 0 0 0 0 8 9]
【2次元配列で確認】
import numpy as np
# ===== 2次元配列で確認
data_2d = np.arange(25).reshape((5, 5))
print(f'元のデータ: \n{data_2d}')
# スライスで一部のデータを取得する
tmp_data_2d = data_2d[1:4, 1:4]
print(f'スライスデータ: \n{tmp_data_2d}')
# スライスのデータを変更する
print('\n=== スライスしたデータを変更')
tmp_data_2d[:, :] = 0
print(f'スライスデータ: \n{tmp_data_2d}')
# 元のデータを確認する
print(f'元のデータ: \n{data_2d}')【実行結果】 元のデータ: [[ 0 1 2 3 4] [ 5 6 7 8 9] [10 11 12 13 14] [15 16 17 18 19] [20 21 22 23 24]] スライスデータ: [[ 6 7 8] [11 12 13] [16 17 18]] === スライスしたデータを変更 スライスデータ: [[0 0 0] [0 0 0] [0 0 0]] 元のデータ: [[ 0 1 2 3 4] [ 5 0 0 0 9] [10 0 0 0 14] [15 0 0 0 19] [20 21 22 23 24]]
例では、tmp_data_1d や tmp_data_2d にスライスのデータを保持しています。その値をすべて 0 にした後に、元データである data_1d や data_2d を参照すると、スライスした部分の数字がすべて 0 に変わっていることが分かります。
2 次元までで確認しましたが、3 次元以上の n 次元配列となっても同じです。
Python 組み込みの list のスライスはコピー
Python のリストの場合はスライスはコピーとなります。1 次元の例だけ確認してみましょう。2次元以降の多次元配列でも考え方は同じです。
data_1d = [i for i in range(10)]
print(f'元のデータ: {data_1d}')
# スライスで一部のデータを取得する
tmp_data_1d = data_1d[3:8]
print(f'スライスデータ: {tmp_data_1d}')
# スライスのデータを変更する
print('\n=== スライスしたデータを変更')
for i, _ in enumerate(tmp_data_1d):
tmp_data_1d[i] = 0
print(f'スライスデータ: {tmp_data_1d}')
# 元のデータを確認する
print(f'元のデータ: {data_1d}')【実行結果】 元のデータ: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] スライスデータ: [3, 4, 5, 6, 7] === スライスしたデータを変更 スライスデータ: [0, 0, 0, 0, 0] 元のデータ: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Python のリストの場合、スライスで取得したデータの要素を変更しても、元データの値が変わっていません。つまり、スライスではコピーとなっていることが分かります。
NumPy 配列(ndarray)のスライスを
コピーにする場合
NumPy 配列(ndarray)のスライスをビューではなく明示的にコピーして使用したい場合には、copy メソッドを使用します。
import numpy as np
data = np.arange(10)
print(f'data: {data}')
# データをコピーする
data_copy = data[3:8].copy()
print(f'data_copy: {data_copy}')
# コピーしたデータを変更する
print('\nコピーしたデータを変更する')
data_copy[:] = 0
print(f'data_copy: {data_copy}')
# もとのデータを確認する
print(f'data: {data}')【実行結果】 data: [0 1 2 3 4 5 6 7 8 9] data_copy: [3 4 5 6 7] コピーしたデータを変更する data_copy: [0 0 0 0 0] data: [0 1 2 3 4 5 6 7 8 9]
例の data[3:8].copy() のようにスライスしたデータで copy メソッドを実行することで、スライスのコピーを作成することができます。コピーしたスライスの値を全て 0 に変更していますが、元データには変更がされていないことが分かります。
NumPy 配列のスライスで元データを変更したくない場合は、copy メソッドでコピーしてから操作するというように覚えておきましょう。
まとめ
「NumPy 配列(ndarray)のスライス」と「Python 組み込みの list のスライス」の違いについて解説しました。
これらの違いは以下のようになります。
- NumPy 配列(
ndarray)のスライス:ビュー - Python 組み込みの
listのスライス:コピー
この記事では、上記を例を使って確認してみました。NumPy 配列(ndarray)のスライスをコピーして使用したい場合には、明示的に copy メソッドを使用する必要があるので覚えておきましょう。


の要素を参照する方法.jpg)
の作成方法-_array-zeros-ones-full-arange-random-randint-randn-normal-linspace-eye-empty_.jpg)
をソートする方法-_-sort-argsort-_-1.jpg)
の形状を変更する方法-_-reshape-_.jpg)

の結合方法-_-concatenate-vstack-hstack-_.jpg)