Pekerjaan data science hampir selalu mengikuti alur yang sama: memuat data mentah, memeriksa kualitasnya, membersihkan bagian yang bermasalah, lalu meringkas hasilnya menjadi angka yang siap dipakai untuk mengambil keputusan. NumPy berada di lapisan perhitungan dari seluruh alur tersebut — pandas untuk tabel berlabel maupun scikit-learn untuk model sama-sama menjalankan komputasinya di atas ndarray.

Agar pola itu terlihat utuh, kita akan mengolah data penjualan toko yang sengaja dibuat tidak rapi: sebagian kolom harga dibiarkan kosong. Seluruh alur kita kerjakan hanya dengan NumPy, sehingga setiap langkah terlihat jelas tanpa lapisan pustaka lain.

Alur mini yang kita bangun pada bab ini:

  1. Memuat file CSV dengan np.genfromtxt.
  2. Mendeteksi dan membuang baris yang mengandung nan.
  3. Menghitung pendapatan per kategori dengan np.unique dan np.add.at.
  4. Meringkas hasilnya dengan statistik deskriptif.

Memuat Data Penjualan dari CSV #

Data mentah kita berupa file CSV berisi tiga kolom: kode kategori, harga (dalam ribuan rupiah), dan jumlah terjual. Dua baris menyisakan kolom harga kosong, dan np.genfromtxt mengganti sel kosong itu menjadi nan:

python
import numpy as np

isi = """kategori,harga,jumlah
1,15.0,3
2,,2
1,12.0,5
3,45.0,1
2,8.0,4
3,,2
1,13.5,6
2,9.5,3
3,50.0,4
1,11.0,2
"""
with open("penjualan.csv", "w") as f:
    f.write(isi)

data = np.genfromtxt("penjualan.csv", delimiter=",", skip_header=1)
print(data.shape)
print(data)

Output yang Dihasilkan:

text
(10, 3)
[[ 1.  15.   3. ]
 [ 2.   nan  2. ]
 [ 1.  12.   5. ]
 [ 3.  45.   1. ]
 [ 2.   8.   4. ]
 [ 3.   nan  2. ]
 [ 1.  13.5  6. ]
 [ 2.   9.5  3. ]
 [ 3.  50.   4. ]
 [ 1.  11.   2. ]]

Mari kita bedah:

  1. Baris 3-13 (isi = """..."""): string multibaris menyusun isi CSV lengkap dengan baris judulnya; dua catatan penjualan sengaja tidak memiliki harga.
  2. Baris 15-16 (open(...) / f.write(isi)): script membuat file penjualan.csv di folder kerja lalu menuliskan seluruh isinya, meniru data yang biasanya diunduh dari sistem kasir.
  3. Baris 18 (np.genfromtxt(...)): delimiter="," memisahkan kolom dan skip_header=1 melewatkan baris judul. Karena kolom harga memuat sel kosong, seluruh array dipromosikan ke float64.
  4. Baris 19-20: hasilnya array berbentuk (10, 3); sel kosong tampil sebagai nan, bukan nol — penting agar transaksi tanpa harga tidak terbaca sebagai penjualan gratis.

Membersihkan Baris yang Tidak Valid #

Seperti dibahas pada Chapter NaN dan Infinity, satu nan cukup untuk membuat hasil agregasi ikut menjadi nan. Pola pembersihan yang umum: buat mask dengan np.isnan, gabungkan antar kolom dengan | atau .any(axis=1), lalu gunakan boolean indexing untuk mengambil baris yang valid:

python
import numpy as np

data = np.array([
    [1., 15.0, 3.],
    [2., np.nan, 2.],
    [1., 12.0, 5.],
    [3., 45.0, 1.],
    [2., 8.0, 4.],
    [3., np.nan, 2.],
    [1., 13.5, 6.],
    [2., 9.5, 3.],
    [3., 50.0, 4.],
    [1., 11.0, 2.],
])
baris_valid = ~np.isnan(data).any(axis=1)
bersih = data[baris_valid]
print("mask  :", baris_valid.astype(int))
print("baris :", bersih.shape[0], "dari", data.shape[0])
print(bersih)

Output yang Dihasilkan:

text
mask  : [1 0 1 1 1 0 1 1 1 1]
baris : 8 dari 10
[[ 1.  15.   3. ]
 [ 1.  12.   5. ]
 [ 3.  45.   1. ]
 [ 2.   8.   4. ]
 [ 1.  13.5  6. ]
 [ 2.   9.5  3. ]
 [ 3.  50.   4. ]
 [ 1.  11.   2. ]]

Penjelasan tiap langkah:

  1. Baris 15 (baris_valid = ~np.isnan(data).any(axis=1)): np.isnan menghasilkan mask per sel; .any(axis=1) merangkumnya per baris — True berarti baris itu memuat setidaknya satu nan; operator ~ membalik mask sehingga True kini menandai baris yang lengkap.
  2. Baris 16 (bersih = data[baris_valid]): boolean indexing mengambil baris bernilai True saja; 10 baris menyusut menjadi 8 baris yang bersih.
  3. Baris 17-19: mask tampil sebagai 0/1 per baris, diikuti hitungan dan isi data yang tersisa.

Pola ini identik dengan yang dipakai pandas saat memanggil dropna() — selisihnya hanya pada label kolom. Varian lain, misalnya mengisi nan dengan np.nanmedian alih-alih membuang baris, dibahas pada Chapter NaN-aware Aggregation.

Menghitung Pendapatan per Kategori #

Pertanyaan bisnis berikutnya: berapa pendapatan tiap kategori? NumPy tidak punya groupby, tetapi kombinasi np.unique dan np.add.at menjalankan logika yang sama: kategori diubah menjadi nomor grup, lalu setiap pendapatan diakumulasikan ke slot grupnya:

python
import numpy as np

bersih = np.array([
    [1., 15.0, 3.],
    [1., 12.0, 5.],
    [2., 8.0, 4.],
    [2., 9.5, 3.],
    [3., 45.0, 1.],
    [3., 50.0, 4.],
    [1., 13.5, 6.],
    [1., 11.0, 2.],
])
pendapatan = bersih[:, 1] * bersih[:, 2]
kategori = bersih[:, 0].astype(int)
unik, indeks, frek = np.unique(kategori, return_inverse=True, return_counts=True)
total = np.zeros(unik.size)
np.add.at(total, indeks, pendapatan)
print("kategori   :", unik)
print("transaksi  :", frek)
print("pendapatan :", total)

Output yang Dihasilkan:

text
kategori   : [1 2 3]
transaksi  : [4 2 2]
pendapatan : [208.   60.5 245. ]

Mari kita bedah:

  1. Baris 13 (bersih[:, 1] * bersih[:, 2]): vectorization mengalikan kolom harga dengan kolom jumlah, menghasilkan pendapatan per transaksi (dalam ribuan rupiah).
  2. Baris 14 (astype(int)): kode kategori dikonversi ke integer agar bisa dipakai sebagai indeks grup.
  3. Baris 15 (np.unique(..., return_inverse=True, return_counts=True)): unik berisi kode kategori yang telah diurutkan, indeks memetakan setiap baris ke nomor grupnya, dan frek menghitung jumlah transaksi per grup.
  4. Baris 16-17 (np.add.at): np.add.at(total, indeks, pendapatan) menambahkan tiap pendapatan ke slot total sesuai grupnya — padanan GROUP BY kategori SUM(...).

Hasilnya: kategori 1 menyumbang 208 ribu dari 4 transaksi, kategori 2 sebanyak 60,5 ribu, dan kategori 3 menjadi penyumbang terbesar (245 ribu) meskipun hanya dari 2 transaksi.

Statistik Deskriptif Pendapatan #

Tahap terakhir alur: meringkas delapan nilai pendapatan menjadi ukuran yang mudah dibaca. Perhatikan bagaimana mean dan median memberi cerita yang berbeda:

python
import numpy as np

pendapatan = np.array([45., 60., 32., 28.5, 45., 200., 81., 22.])
print("n       :", pendapatan.size)
print("mean    :", np.mean(pendapatan))
print("median  :", np.median(pendapatan))
print("std     :", np.round(np.std(pendapatan), 2))
print("min-max :", np.min(pendapatan), "-", np.max(pendapatan))
print("kuartil :", np.percentile(pendapatan, [25, 75]))

Output yang Dihasilkan:

text
n       : 8
mean    : 64.1875
median  : 45.0
std     : 54.28
min-max : 22.0 - 200.0
kuartil : [31.125 65.25 ]
UkuranNilaiInterpretasi
mean64.19rata-rata tertarik ke atas oleh satu transaksi 200 ribu
median45.0separuh transaksi bernilai di bawah 45 ribu — gambaran yang lebih mewakili
std54.28sebaran pendapatan lebar dibanding rata-ratanya
kuartil 25-7531.1 - 65.2550% transaksi berada di rentang ini

Posisi NumPy dalam Alur Data Science #

Setiap langkah di atas punya padanan langsung di pustaka yang lebih tinggi. Memahami versi arraynya membuat perilaku pustaka tersebut mudah diprediksi:

TahapVersi NumPySaat pindah ke pustaka lain
Memuat CSVnp.genfromtxtkolom teks + label → pandas (Membaca dan Menulis CSV)
Pembersihanmask np.isnan + boolean indexingdropna()/fillna() berlabel → pandas
Agregasi grupnp.unique + np.add.atgroupby() multi-kolom → pandas
Statistik ringkasmean, median, percentileuji statistik formal → SciPy
Pemodelantidak disediakanscikit-learn, dibahas bab berikutnya

Pola yang kita bangun di bab ini — muat, bersih, agregasi, ringkas — juga merupakan kerangka bab-bab berikutnya: bab selanjutnya menambahkan lapisan model di atas array yang sama.