Setelah kategori terbentuk dan terurut, pertanyaan analisis pertama biasanya berbunyi: berapa banyak baris di tiap kategori? pandas menjawabnya dengan value_counts() untuk distribusi satu kolom dan groupby() untuk agregasi per kategori. Pada dtype category keduanya punya perilaku khusus yang tidak dimiliki kolom teks biasa: kategori tanpa baris bisa ikut tampil dengan hitungan nol, dan urutan kategori terurut dihormati. Chapter ini menghitung distribusi kota pengiriman dan ukuran produk, lalu menutupnya dengan agregasi omzet per kota.

Distribusi dengan value_counts() #

python
import pandas as pd

kota = pd.Series([
    "Jakarta", "Bandung", "Jakarta", "Surabaya", "Jakarta", "Bandung",
]).astype("category")
print(kota.cat.categories.tolist())
print(kota.value_counts().to_dict())

lengkap = kota.cat.add_categories(["Medan"])
print(lengkap.value_counts().to_dict())

tingkat = pd.Series(["M", "S", "M", "L", "XL", "M"]).astype(
    pd.api.types.CategoricalDtype(categories=["S", "M", "L", "XL"], ordered=True)
)
print(tingkat.value_counts().to_dict())
print(tingkat.value_counts(normalize=True).round(2).to_dict())

pesanan = pd.DataFrame({
    "kota": pd.Categorical(
        ["Jakarta", "Bandung", "Jakarta", "Medan"],
        categories=["Bandung", "Jakarta", "Medan", "Surabaya"],
    ),
    "omzet": [500000, 350000, 700000, 250000],
})
print(pesanan.groupby("kota")["omzet"].sum())
print(pesanan.groupby("kota", observed=False)["omzet"].sum())
print(pesanan.groupby("kota", observed=False)["omzet"].size())

Output yang Dihasilkan:

text
['Bandung', 'Jakarta', 'Surabaya']
{'Jakarta': 3, 'Bandung': 2, 'Surabaya': 1}
{'Jakarta': 3, 'Bandung': 2, 'Surabaya': 1, 'Medan': 0}
{'M': 3, 'S': 1, 'L': 1, 'XL': 1}
{'M': 0.5, 'S': 0.17, 'L': 0.17, 'XL': 0.17}
kota
Bandung     350000
Jakarta    1200000
Medan       250000
Name: omzet, dtype: int64
kota
Bandung      350000
Jakarta     1200000
Medan        250000
Surabaya          0
Name: omzet, dtype: int64
kota
Bandung     1
Jakarta     2
Medan       1
Surabaya    0
Name: omzet, dtype: int64

Mari kita telaah bagian penting skrip di atas:

  1. Baris 3-7: kolom kota hasil astype("category") berisi tiga kategori; value_counts() menyusunnya dari hitungan terbanyak, {'Jakarta': 3, 'Bandung': 2, 'Surabaya': 1}.
  2. Baris 9-10: setelah add_categories(["Medan"]), value_counts() ikut menampilkan Medan dengan hitungan 0 — perilaku yang tidak ada pada kolom teks biasa, karena kategori memang bagian dari definisi kolom.
  3. Baris 12-14: kolom tingkat berkategori terurut S, M, L, XL dari chapter sebelumnya.
  4. Baris 15-16: value_counts() pada kategori terurut mengikuti urutan kategori — {'M': 3, 'S': 1, 'L': 1, 'XL': 1} — dan normalize=True mengubah hitungan menjadi proporsi, M menempati 0.5.

Dua urutan penyajian berbeda pada skrip ini patut dibedakan: kolom tanpa urutan disajikan dari frekuensi terbanyak, sedangkan kolom terurut disajikan mengikuti urutan kategori meski hitungannya tidak menurun. Bila perlu mengurutkan ulang hasilnya, sort_index() pada hasil value_counts() mengikuti urutan kategori yang sama.

value_counts() pada kolom kategori juga memahami argumen dropna. Bawaannya True, sehingga baris NaN tidak ikut dihitung; dengan dropna=False, baris kosong tampil sebagai salah satu entri — cara cepat mengaudit kelengkapan sebelum distribusi dipakai dalam laporan. Kombinasi normalize=True dan dropna=False menghasilkan proporsi yang dihitung dari seluruh baris, bukan hanya baris terisi.

Agregasi per Kategori dengan groupby #

Baris 18-24 menyiapkan tabel omzet dengan kolom kota yang dibuat langsung dari pd.Categorical dan diberi empat kategori, termasuk Surabaya yang tidak memiliki baris data. Baris 25 memanggil groupby("kota")["omzet"].sum() dengan parameter bawaan; hasilnya hanya tiga kota, karena groupby() pada pandas 3.0 berperilaku observed=True secara bawaan — kategori tanpa data tidak muncul. Baris 26-27 mengulang agregasi dengan observed=False, dan Surabaya tampil dengan omzet 0 serta hitungan size() bernilai 0.

Hitungan size() dengan observed=False memperlihatkan kelompok kosong secara langsung — Surabaya bernilai 0 menandakan tidak ada pesanan, bukan lupa dimasukkan ke laporan. Pola audit ini berguna saat daftar kategori berasal dari master data: selisih antara kategori master dan kategori yang muncul di data menunjukkan wilayah tanpa transaksi atau status yang belum pernah terpakai. Untuk laporan yang menuntut persentase tiap kategori terhadap total, jumlahkan omzet dengan observed=False lalu bagi hasilnya dengan total keseluruhan.

ArgumenKategori tanpa barisDipakai saat
observed=True (bawaan 3.0)tidak munculanalisis data yang ada saja
observed=Falsetampil dengan nilai nollaporan wajib memuat semua kategori

Dokumentasi resmi yang relevan: pandas.Series.value_counts , pandas.Series.groupby , dan User Guide: Categorical data .

Distribusi dan agregasi berjalan benar karena kategori hanya menyimpan kode integer plus daftar kecil kategori. Efek samping struktur itu — penghematan memori pada data besar — menjadi tema penutup section ini.