Data teks dari formulir, berkas ekspor, atau input pengguna hampir selalu kotor: ada spasi terlalu banyak, huruf kapital yang tidak konsisten, dan penulisan kode yang beragam. Membersihkannya sel demi sel dengan loop Python melelahkan dan rentan salah. NumPy menyediakan jalan yang lebih rapi: merangkai ufunc string dari np.strings menjadi satu alur pembersihan (pipeline) yang berjalan vectorized pada seluruh array.

Chapter ini adalah studi kasus penutup section String dan Karakter. Kita memakai fungsi strip, lower, replace, dan startswith yang telah dibahas pada Chapter np.strings Dasar dan Chapter Pencarian dan Penggantian String. Latar belakang dtype string <U ada pada Chapter Tipe Data String dan Object.

Studi Kasus Data Pelanggan Kotor #

Misalkan kita menerima array catatan pelanggan dari empat sumber berbeda. Setiap baris memuat ID pesanan atau nama pelanggan, tetapi penulisannya berantakan:

python
import numpy as np

catatan = np.array([
    "  ID-1001  Budi  ",
    "  Siti  Aminah  ",
    "id-1002  Dewi",
    "  Agus  Setiawan ",
])
np.set_printoptions(linewidth=200)
print(repr(catatan))

Output yang Dihasilkan:

text
array(['  ID-1001  Budi  ', '  Siti  Aminah  ', 'id-1002  Dewi', '  Agus  Setiawan '], dtype='<U17')

Masalah yang terlihat: spasi ganda di tengah teks, spasi sisa di kedua ujung, dan kapitalisasi ID- yang tidak konsisten (ID- vs id-). Perhatikan juga dtype <U17: lebar tetap 17 karakter, mengikuti elemen terpanjang.

Pipeline Pembersihan Vectorized #

Alur pembersihannya tiga langkah: strip membuang spasi di kedua ujung, lower menyeragamkan huruf kecil, lalu replace menekan spasi ganda menjadi satu:

python
import numpy as np

catatan = np.array([
    "  ID-1001  Budi  ",
    "  Siti  Aminah  ",
    "id-1002  Dewi",
    "  Agus  Setiawan ",
])
bersih = np.strings.strip(catatan)
print(bersih)
bersih = np.strings.lower(bersih)
bersih = np.strings.replace(bersih, "  ", " ")
print(bersih)

Output yang Dihasilkan:

text
['ID-1001  Budi' 'Siti  Aminah' 'id-1002  Dewi' 'Agus  Setiawan']
['id-1001 budi' 'siti aminah' 'id-1002 dewi' 'agus setiawan']
  1. Baris 9 (bersih = np.strings.strip(catatan)): spasi di awal dan akhir setiap elemen dibuang; spasi ganda di tengah tetap ada.
  2. Baris 11-12: hasil strip ditampung di variabel bersih, lalu dinormalisasi lower, dan replace(" ", " ") mengganti setiap pasangan spasi dengan satu spasi.
  3. Seluruh langkah menghasilkan array baru; array catatan asli tidak berubah.

Semua operasi di atas adalah ufunc, sehingga array 40 ribu baris maupun 4 baris diperlakukan sama tanpa satu pun loop Python.

Memisahkan Pesanan dengan Mask Boolean #

Setelah bersih, tugas berikutnya memisahkan baris yang merupakan ID pesanan dari baris nama pelanggan. Karena teks sudah dinormalisasi menjadi huruf kecil, satu uji startswith cukup:

python
import numpy as np

catatan = np.array([
    "  ID-1001  Budi  ",
    "  Siti  Aminah  ",
    "id-1002  Dewi",
    "  Agus  Setiawan ",
])
bersih = np.strings.replace(np.strings.lower(np.strings.strip(catatan)), "  ", " ")
mask_id = np.strings.startswith(bersih, "id-")
print(mask_id)
print(bersih[mask_id])
print(bersih[~mask_id])

Output yang Dihasilkan:

text
[ True False  True False]
['id-1001 budi' 'id-1002 dewi']
['siti aminah' 'agus setiawan']

Baris pertama menampilkan mask bool; baris kedua memilih baris yang berawalan id- dengan boolean indexing, dan baris ketiga memakai ~mask_id untuk mengambil sisanya. Teknik pemilihan ini dibahas mendalam pada Chapter Boolean Indexing, sedangkan alternatif pemilihan berbasis kondisi ada pada Chapter Memilih Elemen dengan where.

Dua Jebakan yang Sering Muncul #

Jebakan pertama: spasi sisa memengaruhi pencocokan. Pembersihan dengan strip harus selesai sebelum uji startswith, endswith, atau perbandingan kesamaan dijalankan. Satu spasi di akhir string membuat dua teks yang terlihat sama dianggap berbeda:

python
import numpy as np

kode = np.array(["ID-101", "ID-101 "])
print(np.strings.equal(kode, "ID-101"))
print(np.unique(kode))
print(np.unique(np.strings.strip(kode)))

Output yang Dihasilkan:

text
[ True False]
['ID-101' 'ID-101 ']
['ID-101']

Elemen kedua tidak lolos perbandingan karena spasi sisa, dan np.unique menganggap keduanya berbeda. Setelah strip, keduanya menjadi satu nilai unik. Kondisi serupa berlaku pada Chapter Pencarian dan Penggantian String saat menguji awalan dan akhiran.

Jebakan kedua: lebar tetap dtype <U. Array string memiliki lebar tetap sejak dibuat. Menugaskan teks yang lebih panjang ke elemen array membuat NumPy memotongnya tanpa peringatan:

python
import numpy as np

kota = np.array(["Bandung"], dtype="<U8")
kota[0] = "Jakarta Raya"
print(repr(kota), kota.dtype)

Output yang Dihasilkan:

text
array(['Jakarta '], dtype='<U8') <U8

Teks "Jakarta Raya" dipotong menjadi "Jakarta " karena lebar array hanya 8 karakter — dan hasilnya bahkan menyisakan spasi di akhir, persis jebakan pertama di atas. Bila hasil pembersihan bisa lebih panjang dari sumbernya, buat array hasil dengan lebar yang cukup, atau gunakan StringDType yang mendukung panjang variabel sebagaimana dibahas pada Chapter StringDType dan np.dtypes. Dengan dua jebakan ini terkendali, alur pembersihan vectorized di atas siap dipakai pada dataset teks yang jauh lebih besar.