一覧へ

配列ビューとシャローコピー — 変更の伝播に注意

NumPyでスライスがビューを返す理由、変更の伝播が発生する条件、copy()でそれを防ぐ方法を学びます。

中級
|
10
|
検証済み (2026-07)
配列ビューシャローコピーディープコピー変更の伝播NumPyメモリ
進捗0/17 (0%)

配列ビューとシャローコピー ― 修正の伝播に注意

このトピックを終えると

NumPyにおけるビュー(view)とコピー(copy)の違いを理解し、スライシングがビューを返す理由を説明でき、意図しない修正の伝播を防ぐことができるようになります。


驚くべき動作

python
import numpy as np
original = np.array([1, 2, 3, 4, 5])
sliced = original[1:4]
sliced[0] = 99
print(sliced) # [99 3 4]
print(original) # [ 1 99 3 4 5] ← 元の配列も変わる!

slicedのみを変更したのに、originalも変わりました。これはバグではなく、設計によるものです。NumPyのスライシングはデータをコピーせず、**同じメモリを共有するビュー(view)**を返します。


ビュー(View)とは

ビューは、同じデータを別の視点から見るものです。

text
メモリ:  [1] [2] [3] [4] [5]
          ↑   ↑   ↑   ↑   ↑
original: [0] [1] [2] [3] [4]

sliced = original[1:4]
          ↑   ↑   ↑
sliced:  [0] [1] [2]   ← 同じメモリを指す!

データがコピーされないため:

  • メモリ節約: 1GBの配列のスライスでも追加のメモリは不要
  • 高速: コピーにかかる時間が0
  • 修正の伝播: ビューを変更すると、元の配列も変わる(注意!)

ビューが作成される場合

python
arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
# 1. スライシング → ビュー
v1 = arr[2:5] # ビュー
v1.base is arr # True (arrのビューである)
# 2. reshape → ビュー(可能な場合)
v2 = arr.reshape(3, 3) # ビュー
v2.base is arr # True
# 3. 転置 → ビュー
mat = np.array([[1, 2], [3, 4]])
v3 = mat.T # ビュー
v3.base is mat # True
# 4. dtypeの変換(同じサイズ) → ビュー
v4 = arr.view(np.int64) # ビュー

ビューかどうかを確認する方法

python
arr = np.array([1, 2, 3, 4, 5])
sliced = arr[1:4]
copied = arr[1:4].copy()
print(sliced.base is arr) # True — ビュー
print(copied.base is None) # True — 独立したコピー(baseなし)

baseNoneの場合、それはコピーであり、そうでなければ別の配列のビューです。


コピーが作成される場合

python
arr = np.array([1, 2, 3, 4, 5])
# 1. ファンシーインデックス → コピー
c1 = arr[[0, 2, 4]] # コピー!
c1[0] = 99
print(arr) # [1 2 3 4 5] — 元の配列は変更されない
# 2. ブールインデックス → コピー
c2 = arr[arr > 3] # コピー!
c2[0] = 99
print(arr) # [1 2 3 4 5] — 元の配列は変更されない
# 3. 明示的なコピー
c3 = arr.copy() # コピー
c3[0] = 99
print(arr) # [1 2 3 4 5] — 元の配列は変更されない
演算結果
arr[2:5] (スライシング)ビュー
arr[[0,2,4]] (ファンシーインデックス)コピー
arr[arr > 3] (ブールインデックス)コピー
arr.reshape(...)ビュー (可能な場合)
arr.copy()コピー
arr.flatten()コピー
arr.ravel()ビュー (可能な場合)

pandasとの違い

python
import pandas as pd
df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
# pandasでのスライシング
subset = df[df["A"] > 1]
subset["B"] = 99
# SettingWithCopyWarningが発生する可能性あり!

pandasは、ビュー/コピーの動作が状況によって異なるため、予測が難しい場合があります。そのため、SettingWithCopyWarningが存在します。安全な方法:

python
# 確実なコピー
subset = df[df["A"] > 1].copy()
subset["B"] = 99 # 元の配列は変更されない、警告なし
# 元の配列の変更が目的の場合
df.loc[df["A"] > 1, "B"] = 99 # 直接変更

実践でよくある間違い

関数内で元の配列を修正

python
def normalize(data):
# Bad: dataがビューの場合、元の配列が修正される
data -= data.mean()
return data
original = np.array([10.0, 20.0, 30.0])
result = normalize(original[0:3]) # スライシング = ビュー!
print(original) # [−10. 0. 10.] ← 元の配列が変更される!
# Good: コピーから作業
def normalize_safe(data):
result = data.copy()
result -= result.mean()
return result

大容量データでのビューの活用

python
# 10GBのデータの一部のみを分析
huge_data = np.memmap("data.bin", dtype=np.float64, shape=(1_000_000_000,))
# ビュー:追加のメモリは不要
chunk = huge_data[1000:2000] # ビュー — メモリコピーなし
# コピー:メモリの割り当て
chunk_copy = huge_data[1000:2000].copy() # コピー — 8KB割り当て

大容量データでは、ビューを意図的に活用してメモリを節約します。修正が必要な場合にのみ、copy()を呼び出します。


Pythonリストとの比較

Pythonリストのスライシングは、NumPyとは異なり、常にコピーを返します。

python
# Pythonリスト:スライシング = 常にシャローコピー
py_list = [1, 2, 3, 4, 5]
sliced = py_list[1:4]
sliced[0] = 99
print(py_list) # [1, 2, 3, 4, 5] — 元の配列は変更されない!
# NumPy:スライシング = ビュー(共有)
np_arr = np.array([1, 2, 3, 4, 5])
sliced = np_arr[1:4]
sliced[0] = 99
print(np_arr) # [ 1 99 3 4 5] — 元の配列が変更される!

この違いを理解していないと、NumPyに移行した際に深刻なバグが発生する可能性があります。Pythonリストでは安全だったコードが、NumPyでは元の配列を破壊する可能性があります。

シャローコピー vs ディープコピー

python
import copy
nested = [[1, 2], [3, 4]]
# シャローコピー:外部リストのみコピー、内部リストは共有
shallow = copy.copy(nested)
shallow[0][0] = 99
print(nested) # [[99, 2], [3, 4]] — 内部リストが変更される!
# ディープコピー:すべてのネストされたオブジェクトまでコピー
nested = [[1, 2], [3, 4]]
deep = copy.deepcopy(nested)
deep[0][0] = 99
print(nested) # [[1, 2], [3, 4]] — 元の配列は変更されない

NumPyの.copy()は、データ全体をコピーするため、ディープコピーと同じ効果があります。NumPy配列は内部に他のPythonオブジェクトを含まない(純粋な数値データであるため)、シャロー/ディープコピーの区別は意味がなく、.copy()だけで十分です。


判断フローチャート

text
配列演算の結果が必要か?
├── 元の配列を変更してもよいか?
│   ├── Yes → ビューを使用(スライシングのまま)
│   └── No  → .copy()を呼び出す
└── メモリが十分か?
    ├── Yes → .copy()で安全に
    └── No  → ビューを使用、変更に注意

重要なまとめ

概念まとめ
ビュー(View)同じメモリを共有。変更すると元の配列に伝播
コピー(Copy)独立したメモリ。変更しても元の配列は変更されない
スライシングビューを返す(NumPy)
ファンシー/ブールインデックスコピーを返す
.baseNoneであればコピー、そうでなければビュー
.copy()明示的なディープコピー

NumPyのビューは、パフォーマンス最適化のための設計です。数GBのデータをコピーせずに扱えるようにしますが、「変更すると元の配列も変わる」という副作用を伴います。ルールは単純です:スライシング = ビュー、ファンシー/ブール = コピー。 確信がない場合は.copy()

pandas 2.0以降、Copy-on-Write(CoW)モードが導入され、スライシングの結果を変更すると自動的にコピーが行われます。pd.options.mode.copy_on_write = Trueで有効にでき、今後のデフォルトの動作になる予定です。ただし、NumPyは依然として明示的なビュー/コピーモデルに従うため、このトピックのルールを確実に理解する必要があります。

💬 質問・コメント

0件のコメント

ログインせずに投稿できます。ゲスト投稿は投稿者自身で編集・削除できません。

0/2000

読み込み中...