Data yang berhasil dimuat belum berarti data yang kita pahami. Eksplorasi data awal (exploratory data analysis, EDA) adalah tahap mengajukan pertanyaan terhadap data sebelum satu model pun dilatih: bagaimana strukturnya, sebaran nilainya, kualitasnya, dan hubungan antar kolomnya. Kesalahan yang paling sering terjadi adalah menjalankan EDA sebagai ritual grafik — membuat puluhan diagram tanpa pertanyaan, lalu tidak mengambil satu keputusan pun dari hasilnya. Pada chapter ini kita membangun kebiasaan yang berbeda: satu urutan pemeriksaan yang ringkas, setiap langkah menjawab pertanyaan spesifik, dan temuannya langsung menjadi keputusan untuk tahap persiapan data berikutnya.
Pola Pikir EDA: Berbasis Pertanyaan, Bukan Berbasis Grafik #
EDA yang produktif dimulai dari daftar pertanyaan, bukan dari daftar jenis grafik. Pertanyaan dasar yang berlaku untuk hampir semua data tabular hanya ada enam: seperti apa isinya, berapa besar dan bertipe apa strukturnya, bagaimana sebaran angkanya, apa saja isi kolom kategorikal, berapa banyak data yang hilang, dan kolom mana yang saling berhubungan. Setiap fungsi pandas yang kita pakai di chapter ini menjawab tepat satu pertanyaan tersebut. Grafik boleh menyusul untuk pertanyaan yang lebih spesifik — misalnya membandingkan sebaran gaji antar departemen — tetapi keputusan preprocessing seperti imputasi, penanganan pencilan, dan encoding sudah bisa diambil dari pemeriksaan tekstual di atas.
| Fungsi | Pertanyaan yang dijawab |
|---|---|
head() / tail() | Seperti apa tampilan baris awal dan akhir data? |
info() | Berapa baris dan kolom, tipe apa tiap kolom, berapa memori? |
describe() | Bagaimana sebaran kolom numerik (min, kuartil, max)? |
value_counts() | Apa saja isi kolom kategorikal dan seberapa sering muncul? |
nunique() | Berapa banyak nilai berbeda di tiap kolom? |
isna().sum() | Kolom mana yang kehilangan nilai dan berapa banyak? |
corr(numeric_only=True) | Kolom numerik mana yang berhubungan linier? |
Kolom nunique() sering diabaikan padahal sangat menentukan: kolom dengan dua nilai unik adalah biner, puluhan nilai bisa jadi kategorikal, dan ribuan nilai unik pada kolom teks adalah sinyal kardinalitas tinggi yang perlu strategi encoding khusus — materi Chapter Encoding Variabel Kategorikal.
Membaca describe() lewat Kuartil #
Baris 25%, 50%, dan 75% pada describe() adalah kuartil: nilai batas yang membagi data terurut menjadi empat bagian sama banyak. Kuartil tengah (50%) adalah median — nilai tengah yang tidak tergeser oleh nilai ekstrem. Membandingkan mean dengan 50% memberi petunjuk kemiringan sebaran secara gratis: jika rata-rata jauh lebih besar daripada median, sebaran menumpuk di kiri dengan ekor panjang ke kanan — pola khas data pendapatan atau harga. Rentang antar-kuartil (75% dikurangi 25%) juga menjadi dasar aturan deteksi pencilan IQR yang akan kita pakai pada Chapter Menangani Outlier. Satu detail penting pada baris count: angka ini adalah jumlah nilai yang benar-benar tersedia, sehingga count yang lebih kecil dari jumlah baris langsung membocorkan adanya nilai hilang.
Contoh Kode: EDA Sistematis #
Contoh berikut membangun data 60 karyawan secara sintetis — kolom departemen, usia, masa_kerja, dan gaji_juta — lalu menjalankan tujuh pemeriksaan berurutan dari tabel di atas. Tiga nilai masa_kerja sengaja dibuat hilang agar tahap pemeriksaan kualitas benar-benar menemukan sesuatu.
# EDA sistematis pada data karyawan sintetis (60 baris, rng berseed 42).
# Urutan pemeriksaan: cuplikan -> struktur -> ringkasan -> kategori
# -> kualitas -> hubungan antar kolom numerik.
import numpy as np
import pandas as pd
rng = np.random.default_rng(42)
n = 60
departemen = rng.choice(["Keuangan", "Pemasaran", "Teknologi", "SDM"], size=n)
usia = rng.integers(low=22, high=56, size=n)
masa_kerja = rng.integers(low=0, high=15, size=n).astype(float)
gaji_juta = 6.0 + masa_kerja * 0.45 + rng.normal(0, 0.4, size=n)
gaji_juta = np.round(gaji_juta, 3)
# Tanam 3 nilai hilang pada posisi tetap (deterministik, bukan acak).
masa_kerja[[3, 17, 42]] = np.nan
df = pd.DataFrame(
{
"departemen": departemen,
"usia": usia,
"masa_kerja": masa_kerja,
"gaji_juta": gaji_juta,
}
)
print("== 1) Cuplikan data: head() ==")
print(df.head().to_string())
print("\n== 2) Struktur dan tipe: info() ==")
df.info()
print("\n== 3) Ringkasan kolom numerik: describe() ==")
print(df.describe().to_string())
print("\n== 4) Sebaran kolom kategorikal: value_counts() ==")
print(df["departemen"].value_counts().to_string())
print("\n== 5) Banyaknya nilai unik per kolom: nunique() ==")
print(df.nunique().to_string())
print("\n== 6) Kualitas data: isna().sum() ==")
print(df.isna().sum().to_string())
print("\n== 7) Korelasi antar kolom numerik: corr(numeric_only=True) ==")
print(df.corr(numeric_only=True).round(2).to_string())Output yang Dihasilkan:
== 1) Cuplikan data: head() ==
departemen usia masa_kerja gaji_juta
0 Keuangan 53 6.0 8.892
1 SDM 47 10.0 10.679
2 Teknologi 34 9.0 10.316
3 Pemasaran 54 NaN 9.111
4 Pemasaran 35 12.0 11.231
== 2) Struktur dan tipe: info() ==
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 60 entries, 0 to 59
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 departemen 60 non-null object
1 usia 60 non-null int64
2 masa_kerja 57 non-null float64
3 gaji_juta 60 non-null float64
dtypes: float64(2), int64(1), object(1)
memory usage: 2.0+ KB
== 3) Ringkasan kolom numerik: describe() ==
usia masa_kerja gaji_juta
count 60.000000 57.000000 60.000000
mean 39.000000 7.122807 9.084167
std 9.335553 4.092983 1.809263
min 22.000000 0.000000 5.775000
25% 31.000000 4.000000 7.828500
50% 38.500000 8.000000 9.102500
75% 48.000000 10.000000 10.396750
max 54.000000 14.000000 12.636000
== 4) Sebaran kolom kategorikal: value_counts() ==
departemen
SDM 17
Teknologi 16
Keuangan 14
Pemasaran 13
== 5) Banyaknya nilai unik per kolom: nunique() ==
departemen 4
usia 30
masa_kerja 15
gaji_juta 60
== 6) Kualitas data: isna().sum() ==
departemen 0
usia 0
masa_kerja 3
gaji_juta 0
== 7) Korelasi antar kolom numerik: corr(numeric_only=True) ==
usia masa_kerja gaji_juta
usia 1.00 0.06 0.11
masa_kerja 0.06 1.00 0.98
gaji_juta 0.11 0.98 1.00Bedah Kode Baris-per-Baris #
- Baris 8 (
rng = np.random.default_rng(42)): seluruh data sintetis diambil dari satu generator berseed sehingga output contoh ini identik setiap dijalankan. Data 60 baris cukup kecil untuk dibaca penuh, tetapi cukup besar agar kuartil dan korelasi bermakna. - Baris 11–15 (empat kolom):
departemendiambil acak dari empat pilihan,usiadanmasa_kerjadari rentang bilangan bulat, dangaji_jutadibangun mengikuti rumus6.0 + masa_kerja * 0.45plus derau normal — relasi yang sengaja ditanam agar korelasi pada pemeriksaan ketujuh punya jawaban yang bisa diverifikasi. - Baris 18 (
masa_kerja[[3, 17, 42]] = np.nan): tiga baris tetap dijadikan nilai hilang. Posisinya ditulis eksplisit, bukan diacak, supaya contoh tetap deterministik. - Baris 33 (
df.info()): satu-satunya fungsi dalam urutan ini yang tidak perluprint()karenainfo()mencetak langsung. Perhatikan kolomNon-Null Count:masa_kerjahanya 57 dari 60. - Baris 39 (
value_counts()): menghitung frekuensi tiap kategori, terurut dari yang terbanyak — pemeriksaan cepat untuk keseimbangan kategori dan nilai liar sepertiJakrtavsJakarta. - Baris 48 (
corr(numeric_only=True)): argumennumeric_only=Truewajib pada pandas versi 2 ke atas agar kolom teks tidak memicu galat; hasilnya dibulatkan dua desimal agar tabel mudah dibaca.
Temuan dan Keputusan dari Output #
Output di atas mengandung beberapa temuan yang langsung menjadi keputusan. Pertama, isna().sum() menemukan tiga nilai masa_kerja hilang (5% dari baris) — cukup kecil untuk diimputasi, cukup besar untuk dihitung. Kedua, count pada describe() menunjukkan 57 untuk masa_kerja dan 60 untuk kolom lain, memastikan kehilangan data hanya di satu kolom. Ketiga, korelasi gaji_juta terhadap masa_kerja mencapai 0,98 sementara terhadap usia hanya 0,11 — gaji di data ini mengikuti masa kerja, bukan usia, sehingga usia bukan prediktor yang menjanjikan. Keempat, nunique() menunjukkan gaji_juta memiliki 60 nilai unik dari 60 baris — kolom numerik kontinu, bukan kategorikal — sedangkan departemen hanya memiliki 4 nilai yang seimbang antara 13 sampai 17 kemunculan, jadi tidak ada kategori langka yang perlu digabungkan.
Temuan tiga nilai masa_kerja yang hilang membawa kita ke keputusan preprocessing pertama yang sesungguhnya: bagaimana mengisi atau membuang data hilang tanpa membuat model berbohong. Materi tersebut dibahas tuntas pada Chapter Menangani Missing Values.
Referensi Resmi #
- pandas — DataFrame.describe — dokumentasi ringkasan statistik dan kuartil.
- pandas — DataFrame.value_counts — dokumentasi penghitungan frekuensi kategori.
- pandas — DataFrame.corr
— dokumentasi korelasi dan argumen
numeric_only.