List Python fleksibel, tetapi fleksibilitas itu dibayar mahal untuk komputasi numerik: setiap elemen adalah objek penuh di heap, dan menjumlahkan satu juta angka berarti satu juta putaran interpreter. NumPy menyelesaikan masalah ini dengan satu struktur data — ndarray — yang menyimpan angka ber-type sama secara berurutan di memori lalu memprosesnya dengan loop C. Chapter ini membangun fondasi seluruh section: setiap operasi pandas dan matplotlib di chapter berikutnya berdiri di atas array yang kita pelajari di sini.
Mengapa NumPy Jauh Lebih Cepat #
Mari kita buktikan dulu klaimnya dengan pengukuran nyata, menjumlahkan sepuluh juta angka dengan loop Python murni versus arr.sum() dari NumPy:
import time
import numpy as np
n = 10_000_000
data_list = list(range(n))
data_arr = np.arange(n)
mulai = time.perf_counter()
total = 0
for nilai in data_list:
total += nilai
selesai = time.perf_counter()
print(f"Loop Python : {selesai - mulai:.3f} detik -> total {total}")
mulai = time.perf_counter()
total_arr = int(data_arr.sum())
selesai = time.perf_counter()
print(f"NumPy sum() : {selesai - mulai:.3f} detik -> total {total_arr}")Output yang Dihasilkan (waktu bergantung pada mesin):
Loop Python : 0.988 detik -> total 49999995000000
NumPy sum() : 0.006 detik -> total 49999995000000Pada pengukuran di atas, arr.sum() lebih dari 150 kali lebih cepat. Mari kita bedah alasannya:
- Baris 4 (
data_list = list(range(n))): list Python menyimpan 10 juta pointer ke 10 juta objekintyang tersebar di heap; setiap penjumlahan harus mengikuti pointer, memeriksa tipe, lalu membuat objek hasil baru. - Baris 5 (
data_arr = np.arange(n)):ndarraymenyimpan 10 juta bilanganint64berurutan dalam satu blok memori, tanpa pointer per elemen. - Baris 9-10 (
for nilai in data_list: total += nilai): setiap putaran diawasi interpreter Python — inilah biaya per elemen yang membuat loop lambat. - Baris 17 (
data_arr.sum()): NumPy menyerahkan seluruh penjumlahan ke satu loop C yang membaca memori berurutan; interpreter Python tidak ikut mengawasi tiap elemen.
Konsekuensi praktisnya dirangkum pada tabel berikut:
| Aspek | list Python | ndarray NumPy |
|---|---|---|
| Isi elemen | objek apa saja, boleh campur | satu dtype saja, seragam |
| Memori | pointer per elemen | blok berurutan, padat |
Operasi a + b | loop manual | vectorized, satu baris |
| Kecepatan numerik | lambat | hingga ratusan kali lebih cepat |
| Cocok untuk | data heterogen, ukuran kecil | data numerik besar |
Membuat Array dan Atribut Dasar #
Fungsi np.array() mengubah list menjadi ndarray. Tiga atribut akan selalu kita tanyakan pada setiap array: dtype (tipe elemen), shape (ukuran per dimensi), dan ndim (jumlah dimensi):
import numpy as np
harga = np.array([150000, 99000, 750000, 250000, 350000])
print(harga)
print("dtype :", harga.dtype)
print("shape :", harga.shape)
print("ndim :", harga.ndim)
print()
matriks = np.array([[10, 20, 30], [40, 50, 60]])
print(matriks)
print("shape :", matriks.shape)
print("ndim :", matriks.ndim)
print()
campuran = np.array([1.5, 2, 3])
print("dtype campuran:", campuran.dtype)Output yang Dihasilkan:
[150000 99000 750000 250000 350000]
dtype : int64
shape : (5,)
ndim : 1
[[10 20 30]
[40 50 60]]
shape : (2, 3)
ndim : 2
dtype campuran: float64Baris np.array([[10, 20, 30], [40, 50, 60]]) membuat array dua dimensi dengan shape (2, 3) — dua baris, tiga kolom — persis seperti matriks. Perhatikan campuran: satu elemen 1.5 membuat seluruh array dinaikkan ke float64, karena seluruh elemen ndarray wajib ber-type sama.
Aritmetika Vektor dan Broadcasting #
Operasi aritmetika diterapkan elemen-per-elemen tanpa loop. Array juga dapat dioperasikan dengan skalar — NumPy meng-broadcast skalar tersebut ke seluruh elemen:
import numpy as np
harga = np.array([150000, 99000, 750000, 250000, 350000])
setelah_diskon = harga * 0.9
print("harga :", harga)
print("setelah_diskon:", setelah_diskon)
print("setelah_diskon.dtype:", setelah_diskon.dtype)
print()
a = np.array([10, 20, 30])
b = np.array([1, 2, 3])
print("a + b :", a + b)
print("a * b :", a * b)
print("a - 5 :", a - 5)Output yang Dihasilkan:
harga : [150000 99000 750000 250000 350000]
setelah_diskon: [135000. 89100. 675000. 225000. 315000.]
setelah_diskon.dtype: float64
a + b : [11 22 33]
a * b : [10 40 90]
a - 5 : [ 5 15 25]Broadcasting juga bekerja pada dua array berbeda bentuk selama dimensinya cocok. Kasus klasiknya: vektor kolom shape (3, 1) dikalikan vektor baris shape (1, 3) menghasilkan matriks shape (3, 3) — setiap kombinasi baris-kolom terisi otomatis:
import numpy as np
stok = np.array([[10], [20], [30]]) # shape (3, 1): vektor kolom
permintaan = np.array([[5, 3, 8]]) # shape (1, 3): vektor baris
print("stok + permintaan:")
print(stok + permintaan)
print()
print("stok * permintaan:")
print(stok * permintaan)Output yang Dihasilkan:
stok + permintaan:
[[15 13 18]
[25 23 28]
[35 33 38]]
stok * permintaan:
[[ 50 30 80]
[100 60 160]
[150 90 240]]Bayangkan hasil stok * permintaan sebagai tabel stok tiga gudang (baris) dikombinasikan dengan permintaan tiga toko (kolom) — broadcasting menghitung seluruh kombinasi tanpa satu pun loop eksplisit.
Indexing, Slicing, dan Boolean Masking #
Pengindeksan array satu dimensi mengikuti kebiasaan list: indeks mulai 0, indeks negatif membaca dari belakang, dan slicing a[m:n] mengambil rentang:
import numpy as np
harga = np.array([150000, 99000, 750000, 250000, 350000])
print("harga[0] :", repr(harga[0]))
print("harga[-1] :", harga[-1])
print("harga[1:3] :", harga[1:3])
print()
penjualan = np.array([[10, 20, 30],
[40, 50, 60],
[70, 80, 90]])
print("penjualan[1, 2] :", penjualan[1, 2])
print("penjualan[:, 0] :", penjualan[:, 0])
print("penjualan[0:2, 1:]:")
print(penjualan[0:2, 1:])Output yang Dihasilkan:
harga[0] : np.int64(150000)
harga[-1] : 350000
harga[1:3] : [ 99000 750000]
penjualan[1, 2] : 60
penjualan[:, 0] : [10 40 70]
penjualan[0:2, 1:]:
[[20 30]
[50 60]]Pada array dua dimensi, penjualan[:, 0] mengambil seluruh baris kolom pertama, dan penjualan[0:2, 1:] mengambil sub-blok dua baris pertama dikolom kedua sampai akhir. Perhatikan juga repr(harga[0]) yang menampilkan np.int64(150000) — sejak NumPy 2.0, elemen tunggal array ditampilkan dengan nama tipenya; fungsi print() biasa tetap menampilkan 150000 bersih.
Fitur pencarian yang paling sering dipakai adalah boolean masking: perbandingan array menghasilkan array True/False, dan array tersebut dapat dipakai sebagai penyaring:
import numpy as np
harga = np.array([150000, 99000, 750000, 250000, 350000])
mahal = harga > 200000
print("mahal :", mahal)
print("harga[mahal] :", harga[mahal])
print()
label = np.where(harga > 200000, "mahal", "terjangkau")
print("label :", label)Output yang Dihasilkan:
mahal : [False False True True True]
harga[mahal] : [750000 250000 350000]
label : ['terjangkau' 'terjangkau' 'mahal' 'mahal' 'mahal']Ekspresi harga > 200000 dievaluasi elemen-per-elemen menjadi array boolean, lalu harga[mahal] hanya mengambil elemen pada posisi True. Fungsi np.where(kondisi, a, b) adalah versi tiga argumen: pilih dari a saat kondisi benar, dari b saat salah — padanan langsung dari ternary expression untuk seluruh array.
Agregasi dan Array Pembangkit #
Fungsi agregasi seperti sum(), mean(), dan std() menerima parameter axis yang menentukan arah perhitungan pada array multi-dimensi:
import numpy as np
penjualan_unit = np.array([[4, 3, 5],
[2, 4, 1],
[6, 2, 3]])
print("penjualan_unit:")
print(penjualan_unit)
print("total semua :", penjualan_unit.sum())
print("total per kolom :", penjualan_unit.sum(axis=0))
print("total per baris :", penjualan_unit.sum(axis=1))
print("rata-rata per baris:", penjualan_unit.mean(axis=1))
print()
print("np.arange(0, 10, 2):", np.arange(0, 10, 2))
print("np.linspace(0, 1, 5):", np.linspace(0, 1, 5))
print("np.zeros(3) :", np.zeros(3))
print("np.ones((2, 2)):")
print(np.ones((2, 2)))Output yang Dihasilkan:
penjualan_unit:
[[4 3 5]
[2 4 1]
[6 2 3]]
total semua : 30
total per kolom : [12 9 9]
total per baris : [12 7 11]
rata-rata per baris: [4. 2.33333333 3.66666667]
np.arange(0, 10, 2): [0 2 4 6 8]
np.linspace(0, 1, 5): [0. 0.25 0.5 0.75 1. ]
np.zeros(3) : [0. 0. 0.]
np.ones((2, 2)):
[[1. 1.]
[1. 1.]]Makna axis mudah diingat lewat hasilnya: axis=0 meruntuhkan baris (hasil per kolom), sedangkan axis=1 meruntuhkan kolom (hasil per baris). Untuk membangun array tanpa menulis elemen manual, NumPy menyediakan np.arange(awal, akhir, langkah) untuk deret berjarak tetap, np.linspace(awal, akhir, jumlah) untuk deret berjumlah tetap, serta np.zeros() dan np.ones() untuk array awal — pola yang akan terus muncul saat kita membuat sumbu grafik dan data sintetis di chapter visualisasi.