Pada chapter pertama kita melihat gejala aneh: kolom yang tampak berisi angka ternyata bertipe object, sehingga penjumlahannya merekatkan teks alih-alih menjumlahkan. Tipe yang salah adalah masalah paling diam-diam di data cleaning — operasi bisa gagal dengan galat yang jelas, tetapi bisa juga “berhasil” dengan hasil yang salah sepenuhnya. Pada chapter ini kita memperbaiki tiga keluarga kasus: angka berkala yang tersimpan sebagai teks, konversi yang menabrak nilai rusak, dan kolom label yang lebih tepat diperlakukan sebagai kategori.

Tipe Data yang Dipakai pandas #

Sebelum memperbaiki, kita perlu tipe apa yang tersedia. pandas menyimpan setiap kolom dengan satu tipe; keenam tipe berikut mencakup hampir semua kebutuhan data tabular.

TipeMenyimpanContoh kolom toko
int64Bilangan bulatjumlah cangkir terjual
float64Desimal — satu-satunya tipe angka yang bisa memuat NaNpersen diskon
boolTrue/Falsepenanda pelanggan member
objectTeks atau campuranmenu, pelanggan
categoryLabel berulang dari daftar terbatasmenu, porsi
datetime64[ns]Tanggal dan waktutanggal transaksi

Kolom angka bertipe object hampir selalu tanda masalah — kecuali memang sengaja, seperti nomor telepon yang tidak akan dihitung. Sebaliknya, dua tipe terakhir sering tidak terpakai padahal cocok: category menghemat memori untuk kolom berulang, dan datetime64 membuka seluruh pintu analisis waktu yang kita bahas pada chapter Menangani Format Tanggal .

Ketika Angka Tersimpan Sebagai Teks #

Daftar menu di bawah diekspor dari aplikasi kasir lama dengan dua kebiasaan penulisan Indonesia: harga dengan titik ribuan (24.000) dan persen diskon dengan koma desimal (12,5). Keduanya membuat kolom terbaca sebagai teks, dan penjumlahan teks menghasilkan bencana yang senyap.

python
import pandas as pd

menu = pd.DataFrame({
    "menu": ["kopi susu", "matcha latte", "teh tawar", "americano"],
    "harga": ["24.000", "28.000", "20.000", "22.000"],
    "diskon": ["12,5", "0", "5,5", "10"],
})

print(menu)
print()
print(menu.dtypes)
print()
print("Hasil sum() kolom harga:", menu["harga"].sum())

Output yang Dihasilkan:

text
           menu   harga diskon
0     kopi susu  24.000   12,5
1  matcha latte  28.000      0
2     teh tawar  20.000    5,5
3     americano  22.000     10

menu      object
harga     object
diskon    object
dtype: object

Hasil sum() kolom harga: 24.00028.00020.00022.000

Mari kita telaah hasilnya:

  1. Baris 9-11 (print(menu), dtypes): tabelnya tampak sehat, tetapi ketiga kolom bertipe object — harga dan diskon tersimpan sebagai teks, bukan angka.
  2. Baris 13 (menu["harga"].sum()): penjumlahan tidak gagal dan tidak memperingatkan; teks-teks justru direkatkan menjadi 24.00028.00020.00022.000. Inilah bahaya tipe salah yang paling sukses menyamar: operasi berjalan, hasilnya tak bermakna.

Konversi Langsung dengan astype dan Jebakannya #

Metode astype mengubah tipe kolom sekaligus — cara tercepat bila isinya bersih. Tetapi pada data kita, konversi langsung menabrak dua jebakan berbeda: satu yang berbunyi keras, satu yang tidak.

python
import pandas as pd

menu = pd.DataFrame({
    "menu": ["kopi susu", "matcha latte", "teh tawar", "americano"],
    "harga": ["24.000", "28.000", "20.000", "22.000"],
    "diskon": ["12,5", "0", "5,5", "10"],
})

try:
    menu["diskon"].astype(float)
except ValueError as galat:
    print("astype(float) pada kolom diskon gagal:", galat)
print()
hasil = menu["harga"].astype(float)
print("astype(float) pada kolom harga tidak mengeluarkan galat:")
print(hasil)

Output yang Dihasilkan:

text
astype(float) pada kolom diskon gagal: could not convert string to float: '12,5'

astype(float) pada kolom harga tidak mengeluarkan galat:
0    24.0
1    28.0
2    20.0
3    22.0
Name: harga, dtype: float64

Dua kelakuan yang berlawanan itu layak dibedah:

  1. Baris 8-11 (astype(float) pada diskon): teks 12,5 ditolak keras dengan ValueError — Python tidak mengenal koma sebagai penanda desimal. Galat yang berbunyi keras itu sebenarnya membantu: kita langsung tahu ada masalah.
  2. Baris 13-15 (astype(float) pada harga): konversi berhasil tanpa keluhan — dan di situlah jebakannya. Titik pada 24.000 ditafsirkan sebagai penanda desimal, sehingga harga 24.000 rupiah berubah menjadi 24,0 rupiah. Nilainya melenceng seribu kali lipat tanpa satu pun peringatan.

Bersihkan Dulu, Konversi Kemudian #

Karena astype menuntut isi yang sudah bersih, urutan kerjanya jelas: rapikan formatnya dengan metode teks lebih dulu, baru konversi. Titik ribuan dibuang dengan str.replace; koma desimal diganti titik. Setelah itu pd.to_numeric mengubah kolom menjadi angka murni.

python
import pandas as pd

menu = pd.DataFrame({
    "menu": ["kopi susu", "matcha latte", "teh tawar", "americano"],
    "harga": ["24.000", "28.000", "20.000", "22.000"],
    "diskon": ["12,5", "0", "5,5", "10"],
})

menu["harga"] = pd.to_numeric(menu["harga"].str.replace(".", "", regex=False))
menu["diskon"] = pd.to_numeric(menu["diskon"].str.replace(",", ".", regex=False))

print(menu)
print()
print(menu.dtypes)
print()
print("Total harga:", menu["harga"].sum())

Output yang Dihasilkan:

text
           menu  harga  diskon
0     kopi susu  24000    12.5
1  matcha latte  28000     0.0
2     teh tawar  20000     5.5
3     americano  22000    10.0

menu       object
harga       int64
diskon    float64
dtype: object

Total harga: 94000

Mari kita telaah dua baris yang melakukan seluruh pekerjaan:

  1. Baris 9 (harga): str.replace(".", "", regex=False) membuang semua titik sehingga 24.000 menjadi "24000"; pd.to_numeric lalu menghasilkan bilangan bulat 24000 bertipe int64. Pemeriksaan silangnya mudah: total harga 94.000 harus sama dengan 24+28+20+22 ribu — dan memang demikian.
  2. Baris 10 (diskon): koma diganti titik ("12,5" menjadi "12.5"), lalu to_numeric menghasilkan desimal float64. Tipe ini memang tepat karena diskon bisa berupa pecahan.
  3. Urutan berpikirnya: kenali format -> rapikan dengan operasi teks -> konversi -> verifikasi hasil. Bila berkas aslinya masih ada, kita bahkan bisa melewati langkah manual ini: pd.read_csv(..., thousands=".", decimal=",") menanganinya sejak pembacaan, seperti dijelaskan pada dokumentasi pandas.read_csv .

Menjinakkan Nilai Gagal dengan errors=“coerce” #

Data nyata tidak sepenurut daftar menu kita. Kolom stok berikut memuat catatan manusia: kata habis dan tanda -. Konversi bawaan menolaknya keras; errors="coerce" mengubah nilai yang gagal menjadi NaN sehingga proses berlanjut — dengan catatan kita wajib mengaudit nilai apa saja yang gagal.

python
import pandas as pd

stok = pd.DataFrame({
    "menu": ["kopi susu", "matcha latte", "teh tawar", "americano", "es kopi"],
    "stok": ["40", "habis", "35", "-", "38"],
})

try:
    pd.to_numeric(stok["stok"])
except ValueError as galat:
    print("Tanpa errors='coerce':", galat)
print()
stok["stok"] = pd.to_numeric(stok["stok"], errors="coerce")
print(stok)
print()
print("Nilai yang gagal dikonversi:", stok["stok"].isna().sum())
print("Barisnya:", stok.loc[stok["stok"].isna(), "menu"].tolist())

Output yang Dihasilkan:

text
Tanpa errors='coerce': Unable to parse string "habis" at position 1

           menu  stok
0     kopi susu  40.0
1  matcha latte   NaN
2     teh tawar  35.0
3     americano   NaN
4       es kopi  38.0

Nilai yang gagal dikonversi: 2
Barisnya: ['matcha latte', 'americano']

Mari kita telaah alurnya:

  1. Baris 8-11 (try/except): konversi bawaan berhenti di nilai habis dengan ValueError. Pada data ribuan baris, satu nilai rusak menghentikan seluruh proses — kadang memang diinginkan, kadang tidak.
  2. Baris 13 (errors="coerce"): nilai yang gagal dikonversi menjadi NaN — habis dan - berubah menjadi sel kosong, dan kolom akhirnya bertipe angka (float64, karena ada NaN).
  3. Baris 16-17 (audit): errors="coerce" tidak melaporkan apa pun secara otomatis, jadi kita hitung sendiri: dua nilai gagal, pada baris matcha latte dan americano. Tanpa audit ini, coerce hanya memindahkan masalah dari “galat yang terlihat” ke “kekosongan yang tersembunyi” — yang kebetulan sudah kita pelajari cara menanganinya pada chapter Menangani Missing Values .

Kolom Kategori dan Urutannya #

Kolom label yang berulang — nama menu, porsi, kota — menempati pilihan tipe terakhir: category. Alih-alih menyimpan teks yang sama berulang kali, pandas menyimpan daftar label unik plus kode angka di tiap baris; hasilnya lebih hemat memori dan agregasi menjadi lebih cepat. Bonusnya, kategori bisa diberi urutan: kecil < sedang < besar bukan sekadar teks, melainkan tingkatan yang bisa diurutkan dan dibandingkan.

python
import pandas as pd

penjualan = pd.DataFrame({
    "menu": ["kopi susu", "teh tawar", "kopi susu", "americano",
             "kopi susu", "teh tawar", "americano", "matcha latte"],
    "porsi": ["sedang", "besar", "kecil", "sedang",
              "besar", "sedang", "kecil", "sedang"],
})

penjualan["menu"] = penjualan["menu"].astype("category")
print(penjualan["menu"].dtype)
print("Kategori:", penjualan["menu"].cat.categories.tolist())
print()

urutan = ["kecil", "sedang", "besar"]
penjualan["porsi"] = pd.Categorical(penjualan["porsi"], categories=urutan, ordered=True)
print("Porsi terkecil:", penjualan["porsi"].min())
print("Porsi terbesar:", penjualan["porsi"].max())
print()
print(penjualan.sort_values("porsi"))

Output yang Dihasilkan:

text
category
Kategori: ['americano', 'kopi susu', 'matcha latte', 'teh tawar']

Porsi terkecil: kecil
Porsi terbesar: besar

           menu   porsi
2     kopi susu   kecil
6     americano   kecil
0     kopi susu  sedang
3     americano  sedang
5     teh tawar  sedang
7  matcha latte  sedang
1     teh tawar   besar
4     kopi susu   besar

Mari kita telaah hasilnya:

  1. Baris 12 (astype("category")): kolom menu berubah tipe; cat.categories memperlihatkan empat label uniknya — daftar ini yang bisa kita audit: satu nilai asing akan langsung terlihat sebagai kategori tambahan.
  2. Baris 16-17 (pd.Categorical(..., ordered=True)): kolom porsi didefinisikan dengan urutan eksplisit kecil < sedang < besar. Setelah itu min() dan max() bermakna tingkatan, bukan urutan alfabet.
  3. Baris 21 (sort_values("porsi")): pengurutan mengikuti urutan kategori — seluruh baris kecil mendahului sedang, lalu besar. Pada kolom teks biasa, pengurutan mengikuti alfabet dan besar akan mendahului kecil.

Rangkuman #

  • Tipe kolom menentukan operasi yang sah; kolom angka bertipe object adalah masalah yang menyamar, karena penjumlahannya merekatkan teks tanpa galat.
  • astype menuntut isi bersih: koma desimal ditolak keras (ValueError), sedangkan titik ribuan terlewat sebagai desimal dan mengubah nilai secara diam-diam.
  • Pola kerjanya: rapikan format dengan str.replace -> konversi dengan pd.to_numeric -> verifikasi hasil; pd.read_csv(thousands=".", decimal=",") menangkapnya lebih awal.
  • errors="coerce" mengubah nilai gagal menjadi NaN; selalu audit nilai mana saja yang gagal dengan isna().
  • category menghemat memori untuk label berulang, dan ordered=True memberi makna urutan pada tingkatan seperti porsi.

Tipe sudah benar, tetapi perhatikan kolom menu dan pelanggan pada data kita: spasi tersembunyi, kapital yang tak seragam, dan nilai gabungan masih membuat analisis bocor. Operasi teks pandas menjadi peralatan utama pada chapter berikutnya, Membersihkan Teks .