Bayangkan kita mengambil sepuluh baris pertama dari data penjualan untuk diperiksa, lalu mengubah satu nilai di hasil itu — dan beberapa saat kemudian menyadari data aslinya ikut berubah. Kejadian seperti ini adalah salah satu jebakan paling umum bagi pengguna NumPy baru. Penyebabnya ada pada perbedaan view dan copy.
Sebuah view adalah array baru yang berbagi buffer memori dengan array aslinya; perubahan pada salah satunya otomatis terlihat di satunya. Sebuah copy memiliki buffer sendiri yang terpisah. NumPy sengaja mengembalikan view bila memungkinkan demi efisiensi memori dan kecepatan, sehingga kita wajib tahu operasi mana yang berbagi memori dan mana yang tidak.
Aturan Dasar View dan Copy #
Aturannya ringkas dan bisa dihafal:
- Single indexing dan basic slicing (contoh
a[0],a[0:2],a[:, 1]) menghasilkan view. - Fancy indexing dan boolean indexing (contoh
a[[0, 2]],a[a > 5]) menghasilkan copy. .copy()menghasilkan copy secara eksplisit kapan pun kita memanggilnya.reshape,ravel, dan.Tmenghasilkan view bila memungkinkan;flattenselalu menghasilkan copy.
| Operasi | Jenis hasil | Berbagi memori dengan aslinya? |
|---|---|---|
a[i], a[i, j] (single indexing) | view | Ya |
a[0:2], a[:, 1] (basic slicing) | view | Ya |
a.reshape(...), a.ravel() | view bila memungkinkan | Ya |
a.T | view | Ya |
a[[0, 2]] (fancy indexing) | copy | Tidak |
a[a > 5] (boolean indexing) | copy | Tidak |
a.flatten() | copy | Tidak |
a.copy() | copy | Tidak |
Dua bab sebelumnya telah menyinggung aturan ini secara sepintas; kini kita buktikan dengan eksekusi nyata.
Membuktikan View dengan Pola Ubah dan Amati #
Cara paling meyakinkan menguji sifat sebuah array adalah pola ubah-dan-amati: ubah satu nilai di hasil seleksi, lalu periksa array aslinya.
import numpy as np
penjualan = np.array([
[120, 85, 200, 65],
[90, 150, 110, 40],
[175, 60, 95, 130],
])
b = penjualan[0:2]
b[0, 0] = 999
print(penjualan)
print(b.base is penjualan)Output yang Dihasilkan:
[[999 85 200 65]
[ 90 150 110 40]
[175 60 95 130]]
TrueYang terjadi di belakang layar:
- Baris 8 (
b = penjualan[0:2]): basic slicing tidak menyalin satu byte pun. NumPy hanya membuat objek array baru yang menunjuk ke buffer milikpenjualan, dengan metadata bentuk(2, 4). - Baris 9 (
b[0, 0] = 999): penulisan dilakukan ke buffer yang sama, sehingga elemen pertamapenjualanikut berubah. - Baris 10 (
print(penjualan)): buktinya tampak jelas —120di posisi pertama telah menjadi999, padahal yang kita ubah adalahb. - Baris 11 (
b.base is penjualan): atributbasemilik sebuah view menunjuk ke array pemilik buffer. HasilnyaTrue, tanda bahwabhanyalah jendela menuju data milikpenjualan. Pada hasil copy,basebernilaiNone.
Fancy Indexing Menghasilkan Copy #
Kebalikannya berlaku untuk fancy indexing: hasilnya selalu copy, sehingga modifikasi pada hasil tidak pernah menyentuh data asli. Untuk mendapat efek yang sama dari sebuah slice, kita memanggil .copy() secara eksplisit.
import numpy as np
penjualan = np.array([
[120, 85, 200, 65],
[90, 150, 110, 40],
[175, 60, 95, 130],
])
c = penjualan[[0]]
c[0, 0] = 777
print(c)
print(penjualan[0, 0])
d = penjualan[0].copy()
d[0] = 555
print(penjualan[0, 0])
print(c.base)Output yang Dihasilkan:
[[777 85 200 65]]
120
120
NoneMari kita periksa:
- Baris 8 (
c = penjualan[[0]]): fancy indexing menyalin baris pertama ke buffer baru. Variabelcberdiri sendiri, danbase-nyaNone. - Baris 9-10 (
c[0, 0] = 777laluprint): perubahan hanya terjadi padac;penjualan[0, 0]tetap120. Berbeda dengan blok sebelumnya, data asli tidak tersentuh. - Baris 12 (
d = penjualan[0].copy()):penjualan[0]saja adalah view, tetapi.copy()memutus hubungannya.dkini memiliki buffer sendiri. - Baris 13-14 (
d[0] = 555laluprint): mengubahdtidak memengaruhipenjualan— persis perilaku yang kita inginkan saat data asli harus dilindungi.
reshape, T, ravel, dan flatten #
Keluarga operasi pengubahan bentuk mengikuti kaidah yang sama: view bila memungkinkan, copy bila tidak. Kita uji semuanya sekaligus dengan base.
import numpy as np
a = np.arange(6)
b = a.reshape(2, 3)
print(b.base is a)
print(b.T.base is a)
print(b.ravel().base is a)
print(b.flatten().base)
print(b.T.ravel().base)
b.T[0, 0] = 99
print(a)Output yang Dihasilkan:
True
True
True
None
None
[99 1 2 3 4 5]Penjelasan tiap pemeriksaan:
- Baris 4 (
b = a.reshape(2, 3)): reshape biasanya hanya membaca ulang metadata tanpa memindahkan data, sehinggab.base is abernilaiTrue. - Baris 5 (
b.T.base is a): transpose adalah view; tidak ada elemen yang dipindah, hanya urutan pembacaan sumbu yang ditukar. Karena itu menulis lewat transpose ikut mengubah data asli — terbukti dari dua baris terakhir, ketikab.T[0, 0] = 99mengubaha[0]menjadi99. - Baris 6 (
b.ravel().base is a): pada array yang kontigu,ravelmengembalikan view berbentuk satu dimensi. - Baris 7 (
b.flatten().base): hasilnyaNone—flattenselalu menyalin, apa pun kondisinya. - Baris 8 (
b.T.ravel().base): setelah transpose, susunan data tidak lagi kontigu untuk urutan yang dimintaravel, sehingga NumPy terpaksa menyalin;base-nyaNone. Inilah kasus langka ketikaravelmenghasilkan copy.
Satu tabel dan tiga eksperimen di atas cukup untuk memeriksa hampir semua operasi seleksi yang kita tulis. Saat membaca kode orang lain, cari dua hal: pola slicing yang menjadi view, dan pola fancy/boolean yang menjadi copy — lalu tanyakan apakah data asli seharusnya berubah atau tidak.