Setiap perhitungan yang kita lakukan pada dua chapter sebelumnya — dot product, gradien, mean, simpangan baku — dieksekusi oleh satu pustaka: NumPy. scikit-learn menerima datanya dalam bentuk ndarray, pandas menyimpan kolomnya di atas ndarray, dan jaringan saraf pada dasarnya hanya rangkaian operasi array berlapis. Penguasaan NumPy karena itu bukan pelengkap, melainkan prasyarat teknis untuk membaca, menulis, dan men-debug kode machine learning. Chapter ini melatih enam keterampilan inti: membuat array, mengirisnya, menyaring dengan mask, menyiarkan operasi (broadcasting), merangkum per sumbu (axis), dan menggantikan loop dengan vektorisasi.

ndarray: Wadah Data Machine Learning #

ndarray adalah kumpulan angka berdimensi bebas yang selalu homogen: satu array hanya menyimpan satu jenis data (dtype), dan bentuknya (shape) menjelaskan susunan baris-kolomnya. Array dua dimensi berbentuk (4, 3) adalah cara NumPy merepresentasikan dataset 4 sampel × 3 fitur — konvensi yang sama dengan scikit-learn. dtype juga menentukan presisi dan memori: int64 untuk bilangan bulat, float64 untuk desimal yang dipakai hampir semua model.

File array-dasar.py memperkenalkan pembuatan array dari list maupun pola (zeros, ones, arange, linspace), membaca atribut bentuk, lalu mengiris array dengan notasi [baris, kolom].

python
# ndarray: pembuatan, bentuk, dtype, dan potongan (slicing).

import numpy as np

np.set_printoptions(precision=2, suppress=True)

# Matriks 2 baris x 3 kolom: cara paling langsung membuat ndarray
a = np.array([[1.0, 2.0, 3.0],
              [4.0, 5.0, 6.0]])
print("Array a:")
print(a)
print("shape :", a.shape)
print("ndim  :", a.ndim)
print("dtype :", a.dtype)

# Pembuatan dengan pola: nol, satu, rentang, dan pembagian rata
print("\nzeros(2, 3):\n", np.zeros((2, 3)))
print("ones(3)         :", np.ones(3))
print("arange(0, 10, 2):", np.arange(0, 10, 2))
print("linspace(0, 1, 5):", np.linspace(0, 1, 5))

# dtype menentukan jenis dan presisi data yang disimpan
bil_bulat = np.array([1, 2, 3])
bil_desimal = np.array([1, 2, 3], dtype=np.float64)
print("\ndtype bawaan :", bil_bulat.dtype)
print("dtype float64:", bil_desimal.dtype)

# Slicing: notasi [baris, kolom] dengan pola start:stop
print("\nBaris ke-0    :", a[0, :])
print("Kolom ke-2    :", a[:, 2])
print("Blok a[0:2, 1:3]:\n", a[0:2, 1:3])
print("Elemen terakhir:", a[-1, -1])

# Vektorisasi: operasi berlaku ke seluruh elemen tanpa loop
print("\na * 2 + 1:\n", a * 2 + 1)

Output yang Dihasilkan:

text
Array a:
[[1. 2. 3.]
 [4. 5. 6.]]
shape : (2, 3)
ndim  : 2
dtype : float64

zeros(2, 3):
 [[0. 0. 0.]
 [0. 0. 0.]]
ones(3)         : [1. 1. 1.]
arange(0, 10, 2): [0 2 4 6 8]
linspace(0, 1, 5): [0.   0.25 0.5  0.75 1.  ]

dtype bawaan : int64
dtype float64: float64

Baris ke-0    : [1. 2. 3.]
Kolom ke-2    : [3. 6.]
Blok a[0:2, 1:3]:
 [[2. 3.]
 [5. 6.]]
Elemen terakhir: 6.0

a * 2 + 1:
 [[ 3.  5.  7.]
 [ 9. 11. 13.]]

Mari kita telaah baris-baris kode yang berperan penting:

  1. Baris 8–14 (a dan atributnya): array dibuat dari list bersarang; shape (2, 3) dan ndim 2 menggambarkan susunannya, dtype float64 karena semua elemen desimal.
  2. Baris 17–20 (pola pembuatan): zeros dan ones menyiapkan wadah kosong (matriks bobot awal, misalnya), arange membuat rentang berlangkah, linspace membagi rentang menjadi jumlah titik yang sama rata.
  3. Baris 23–26 (dtype): list [1, 2, 3] otomatis menjadi int64; parameter dtype=np.float64 memaksanya menjadi desimal — penting sebelum dipakai pembagian atau masuk ke estimator.
  4. Baris 29–32 (slicing): a[0, :] mengambil satu baris (satu sampel), a[:, 2] mengambil satu kolom (satu fitur untuk seluruh sampel) — pola yang dipakai terus-menerus saat menyiapkan data.
  5. Baris 35 (vektorisasi): a * 2 + 1 mengubah keenam elemen sekaligus tanpa loop; kode yang sama pada list Python memicu galat.

Boolean Mask: Menyaring Data #

Pekerjaan sehari-hari machine learning penuh pertanyaan penyaringan: baris mana yang pencilan, sampel mana yang salah prediksi, transaksi mana di atas ambang. NumPy menjawabnya dengan mask — array boolean yang dihasilkan langsung dari perbandingan, lalu dipasang sebagai indeks untuk memilih baris yang True. Operator & (dan), | (atau), dan ~ (tidak) menggabungkan beberapa kondisi; keduanya harus ditulis dengan tanda kurung karena operator pembanding lebih rapat daripada &.

File masking-data.py menyaring data pelanggan sintetis: siapa yang berusia di bawah 35, siapa yang berpengeluaran di atas 2,0 juta rupiah, dan siapa yang memenuhi keduanya.

python
# Boolean mask: menyaring baris data dengan kondisi logika.

import numpy as np

np.set_printoptions(precision=2, suppress=True)

rng = np.random.default_rng(42)

# Data 8 pelanggan: kolom [usia, pengeluaran bulanan (juta rupiah)]
usia = rng.integers(20, 55, size=8)
pengeluaran = rng.uniform(0.5, 5.0, size=8).round(2)
data = np.column_stack([usia, pengeluaran])
print("Data pelanggan:\n", data)

# Mask: array boolean sepanjang jumlah baris
mask_muda = usia < 35
mask_royal = pengeluaran > 2.0
print("\nMask usia < 35        :", mask_muda)
print("Mask pengeluaran > 2.0:", mask_royal)
print("Kombinasi keduanya (&):", mask_muda & mask_royal)

# Seleksi baris: cukup pasang mask sebagai indeks
print("\nPelanggan usia < 35:\n", data[mask_muda])
print("Pelanggan usia < 35 dan pengeluaran > 2.0:\n", data[mask_muda & mask_royal])
print("Jumlah pelanggan royal:", int(mask_royal.sum()))

# np.where: ubah kondisi menjadi label kategori
label = np.where(pengeluaran > 2.0, "royal", "reguler")
print("\nLabel pelanggan:", label)

Output yang Dihasilkan:

text
Data pelanggan:
 [[23.    0.92]
 [47.    4.89]
 [42.    3.93]
 [35.    4.04]
 [35.    1.08]
 [50.    2.53]
 [23.    2.17]
 [44.    4.67]]

Mask usia < 35        : [ True False False False False False  True False]
Mask pengeluaran > 2.0: [False  True  True  True False  True  True  True]
Kombinasi keduanya (&): [False False False False False False  True False]

Pelanggan usia < 35:
 [[23.    0.92]
 [23.    2.17]]
Pelanggan usia < 35 dan pengeluaran > 2.0:
 [[23.    2.17]]
Jumlah pelanggan royal: 6

Label pelanggan: ['reguler' 'royal' 'royal' 'royal' 'reguler' 'royal' 'royal' 'royal']

Bedah kode di atas baris demi baris:

  1. Baris 10–13 (persiapan data): usia dan pengeluaran dibuat generator berseed, lalu np.column_stack menumpuk keduanya sebagai kolom menjadi matriks (8, 2).
  2. Baris 16–20 (pembuatan mask): perbandingan usia < 35 menghasilkan array boolean delapan elemen; mask_muda & mask_royal bernilai True hanya pada baris yang memenuhi keduanya — perhatikan hanya pelanggan berusia 23 dengan pengeluaran 2,17 yang lolos.
  3. Baris 23–25 (seleksi baris): data[mask_muda] mempertahankan baris yang True dan membuang sisanya; hasil kombinasi tinggal satu baris [[23. 2.17]]. mask_royal.sum() menghitung enam nilai True — jumlah pelanggan royal tanpa loop.
  4. Baris 28–29 (np.where): kondisi diubah menjadi label teks per elemen, pola yang berguna untuk membuat kolom kategori dari ambang numerik.

Broadcasting dan Agregasi per Sumbu #

Dua mekanisme membuat NumPy ringkas sekaligus cepat. Pertama, agregasi axis: X.mean(axis=0) merangkum ke bawah sehingga hasilnya satu angka per kolom (statistik tiap fitur), sedangkan axis=1 merangkum ke samping sehingga hasilnya satu angka per baris (ringkasan tiap sampel). Kedua, broadcasting: operasi antara bentuk yang berbeda tetap berjalan selama dimensi yang tidak sama bernilai 1 — vektor (3,) otomatis diperluas ke setiap baris matriks (4, 3) tanpa disalin.

Kombinasi keduanya memungkinkan normalisasi seluruh dataset dalam satu baris. File broadcasting-dan-agregasi.py mendemonstrasikannya: Z = (X - mu) / sigma mengurangi setiap kolom dengan mean-nya sendiri dan membaginya dengan simpangan baku kolom itu — persis definisi StandardScaler.

python
# Broadcasting dan agregasi per sumbu: normalisasi fitur ala StandardScaler.

import numpy as np

np.set_printoptions(precision=2, suppress=True)

# Data 4 rumah x 3 fitur: [luas (m2), kamar, jarak stasiun (km)]
X = np.array([
    [70.0, 3.0, 1.5],
    [120.0, 4.0, 0.8],
    [85.0, 3.0, 2.0],
    [150.0, 5.0, 1.0],
])

# Agregasi: axis menentukan sumbu yang diringkas
print("Mean seluruh elemen    :", f"{X.mean():.3f}")
print("Mean per fitur (axis=0):", X.mean(axis=0))
print("Mean per baris (axis=1):", X.mean(axis=1))
print("Sum per fitur (axis=0) :", X.sum(axis=0))
print("Max per baris (axis=1) :", X.max(axis=1))

# Broadcasting: vektor (3,) dikalikan ke setiap baris matriks (4, 3)
skala = np.array([0.01, 1.0, 10.0])
X_skala = X * skala
print("\nX * skala (broadcasting kolom):\n", X_skala)

# Normalisasi fitur ala StandardScaler: z = (x - mean) / std per kolom
mu = X.mean(axis=0)
sigma = X.std(axis=0)
Z = (X - mu) / sigma
print("\nMean tiap fitur:", mu)
print("Std tiap fitur :", sigma)
print("Z = (X - mu) / sigma:\n", Z)
print("Cek: mean Z per fitur:", Z.mean(axis=0))
print("Cek: std Z per fitur :", Z.std(axis=0))

Output yang Dihasilkan:

text
Mean seluruh elemen    : 37.108
Mean per fitur (axis=0): [106.25   3.75   1.32]
Mean per baris (axis=1): [24.83 41.6  30.   52.  ]
Sum per fitur (axis=0) : [425.   15.    5.3]
Max per baris (axis=1) : [ 70. 120.  85. 150.]

X * skala (broadcasting kolom):
 [[ 0.7   3.   15.  ]
 [ 1.2   4.    8.  ]
 [ 0.85  3.   20.  ]
 [ 1.5   5.   10.  ]]

Mean tiap fitur: [106.25   3.75   1.32]
Std tiap fitur : [31.1   0.83  0.47]
Z = (X - mu) / sigma:
 [[-1.17 -0.9   0.38]
 [ 0.44  0.3  -1.13]
 [-0.68 -0.9   1.45]
 [ 1.41  1.51 -0.7 ]]
Cek: mean Z per fitur: [ 0. -0.  0.]
Cek: std Z per fitur : [1. 1. 1.]

Bedah kode di atas baris demi baris:

  1. Baris 16–20 (agregasi): axis=0 meruntuhkan baris dan meninggalkan tiga angka — mean tiap fitur [106.25, 3.75, 1.32]; axis=1 meruntuhkan kolom dan meninggalkan empat angka — mean tiap rumah.
  2. Baris 23–25 (broadcasting): vektor skala berbentuk (3,) dikalikan ke matriks (4, 3); NumPy memperlakukan vektor itu seolah-olah disalin ke setiap baris, mengubah satuan tiap kolom tanpa loop.
  3. Baris 28–30 (normalisasi): mu dan sigma berbentuk (3,); pengurangan (4, 3) − (3,) menyebar ke setiap baris lewat broadcasting sehingga setiap kolom ternormalisasi dengan statistiknya sendiri.
  4. Baris 34–35 (verifikasi): mean Z per fitur bernilai nol dan simpangan baku bernilai satu — angka -0. pada keluaran adalah nol negatif, artefak pembulatan floating point yang nilainya tetap nol.

Vektorisasi Menggantikan Loop #

Aturan praktis menulis kode NumPy: jika kita menulis for yang mengisi array elemen per elemen, hampir selalu ada versi bervektor yang lebih pendek dan puluhan kali lebih cepat, karena loop Python berjalan di interpreter sedangkan operasi array berjalan di kode C yang terkompilasi. a * 2 + 1, X @ w, np.abs(sampel) <= 1 pada chapter-chapter sebelumnya adalah contoh vektorisasi: satu ekspresi untuk seluruh array. Kebiasaan ini nanti menentukan kualitas feature engineering kita — transformasi yang ditulis vektorisasi tetap cepat saat dataset membesar dari puluhan baris menjadi jutaan.

OperasiContohPeran di machine learning
Pembuatan arraynp.zeros, np.arange, np.arrayMenyiapkan matriks fitur dan bobot
SlicingX[:, 0], X[0:2, 1:3]Memilih fitur, membagi data
Boolean maskX[X[:, 0] > 70]Menyaring baris, deteksi pencilan
Agregasi axisX.mean(axis=0)Statistik per fitur untuk scaling
Broadcasting(X - mu) / sigmaNormalisasi seluruh dataset sekaligus
VektorisasiX @ wPrediksi satu batch tanpa loop

Array NumPy menjadi mesin hitung kita, tetapi data nyata datang dalam bentuk tabel berlabel kolom, baris bernama, dan tipe campuran — dan di situlah pandas bekerja. Kita akan berkenalan dengan DataFrame pada Chapter Pandas untuk Machine Learning.

Referensi Resmi #