Satu kolom kunci sering tidak cukup untuk mengidentifikasi baris data. Laporan penjualan tiga cabang, misalnya, baru bermakna ketika kunci kota, bulan, dan kategori dibaca bersamaan; menyimpan ketiganya sebagai kolom biasa membuat seleksi dan agregasi terasa bertele-tele. pandas menjawab kebutuhan itu dengan MultiIndex, yaitu index bertingkat (hierarchical index) yang menempelkan beberapa level label pada satu axis. Pada Chapter ini kita membuat MultiIndex lewat empat cara berbeda, membaca struktur repr-nya, dan melakukan akses pertama dengan .loc.

Empat Cara Membuat MultiIndex #

Script berikut membangun dataset penjualan 27 baris (3 kota × 3 bulan × 3 kategori), lalu mendemonstrasikan empat cara pembuatan index bertingkat. Satuan omzet adalah juta rupiah; nilainya mengikuti rumus dasar kategori + 30 × nomor urut kota + 12 × (bulan − 1) agar setiap angka mudah dicek ulang.

python
import pandas as pd

kota_list = ["Jakarta", "Bandung", "Surabaya"]
bulan_list = [1, 2, 3]
kategori_list = ["Elektronik", "Pakaian", "Makanan"]
dasar = {"Elektronik": 420, "Pakaian": 260, "Makanan": 150}

baris = []
for kota in kota_list:
    for bulan in bulan_list:
        for kategori in kategori_list:
            omzet = dasar[kategori] + 30 * kota_list.index(kota) + 12 * (bulan - 1)
            baris.append((kota, bulan, kategori, omzet))

penjualan = pd.DataFrame(baris, columns=["kota", "bulan", "kategori", "omzet"])
print(penjualan.head(4))
print("jumlah baris:", len(penjualan))
print()

# set_index memindahkan kolom menjadi level index bertingkat
multi = penjualan.set_index(["kota", "bulan", "kategori"])
print("tipe index:", type(multi.index).__name__)
print(multi.head(5))
print()

print("nama level   :", list(multi.index.names))
print("jumlah level :", multi.index.nlevels)
print("ukuran level :", multi.index.levshape)
print()

# from_tuples: index dari pasangan kunci eksplisit
indeks = pd.MultiIndex.from_tuples(
    [("Jakarta", 1), ("Jakarta", 2), ("Bandung", 1), ("Bandung", 2)],
    names=["kota", "bulan"],
)
omzet_kecil = pd.Series([420, 432, 450, 462], index=indeks, name="omzet")
print(omzet_kecil)
print()

# from_product: semua kombinasi dua list
penuh = pd.MultiIndex.from_product([["Bandung", "Jakarta"], [1, 2]],
                                   names=["kota", "bulan"])
print(list(penuh))
print()

# from_frame: ambil kombinasi dari kolom DataFrame
kunci = penjualan[["kota", "bulan"]].drop_duplicates().head(3)
print(list(pd.MultiIndex.from_frame(kunci).names))
print()

# akses parsial: satu label level terluar
print(multi.loc["Jakarta"].head(3))
print()

# akses penuh: tuple kunci menghasilkan satu baris
print(multi.loc[("Jakarta", 1, "Elektronik")])
print()

# set_index mempertahankan urutan baris; sort_index() yang mengurutkan
acak = penjualan.sample(frac=1.0, random_state=7).set_index(["kota", "bulan"])
print("terurut leksikografis?", acak.index.is_monotonic_increasing)
print(acak.sort_index().head(4))

Output yang Dihasilkan:

text
      kota  bulan    kategori  omzet
0  Jakarta      1  Elektronik    420
1  Jakarta      1     Pakaian    260
2  Jakarta      1     Makanan    150
3  Jakarta      2  Elektronik    432
jumlah baris: 27

tipe index: MultiIndex
                          omzet
kota    bulan kategori         
Jakarta 1     Elektronik    420
              Pakaian       260
              Makanan       150
        2     Elektronik    432
              Pakaian       272

nama level   : ['kota', 'bulan', 'kategori']
jumlah level : 3
ukuran level : (3, 3, 3)

kota     bulan
Jakarta  1        420
         2        432
Bandung  1        450
         2        462
Name: omzet, dtype: int64

[('Bandung', 1), ('Bandung', 2), ('Jakarta', 1), ('Jakarta', 2)]

['kota', 'bulan']

                  omzet
bulan kategori         
1     Elektronik    420
      Pakaian       260
      Makanan       150

omzet    420
Name: (Jakarta, 1, Elektronik), dtype: int64

terurut leksikografis? False
                 kategori  omzet
kota    bulan                   
Bandung 1      Elektronik    450
        1         Makanan    180
        1         Pakaian    290
        2         Pakaian    302

Mari kita bedah bagian pentingnya:

  1. Baris 1-17 (dataset): perulangan tiga tingkat menghasilkan 27 baris dengan satu baris per kombinasi kota, bulan, kategori; DataFrame penjualan masih memakai index angka default.
  2. Baris 21 (set_index): memberikan list kolom ["kota", "bulan", "kategori"] ke set_index() memindahkan ketiganya menjadi level index; atribut type(multi.index).__name__ mengonfirmasi hasilnya bertipe MultiIndex.
  3. Baris 26-28 (metadata): names mencantumkan nama setiap level, nlevels menghitung jumlah level (3), dan levshape menampilkan banyaknya label unik per level, yaitu (3, 3, 3).
  4. Baris 32-48 (from_tuples, from_product, from_frame): from_tuples() memakai pasangan kunci eksplisit, from_product() menghitung hasil kali kartesian dua list sehingga ("Bandung", 1) muncul sebelum ("Jakarta", 1), dan from_frame() menurunkan index dari kolom DataFrame yang sudah ada.

Membaca Repr Index Bertingkat #

Perhatikan tampilan multi.head(5) pada output. Label level terluar (Jakarta) hanya dicetak sekali, lalu bagian kosong di bawahnya berarti “sama dengan baris di atasnya”; setelan cetak seperti ini membuat tabel panjang tetap ringkas. Jika sebuah Series ber-MultiIndex dicetak, mekanisme penyingkat yang sama berlaku pada label kuncinya. Struktur ini persis seperti hasil groupby(["kota", "kategori"]) yang sudah kita pelajari pada section GroupBy dan Agregasi — bedanya, di sini kita membangun indexnya secara sengaja, bukan sebagai efek samping agregasi.

Akses Pertama dengan .loc #

Dua pola akses pada akhir script menjadi fondasi chapter berikutnya. multi.loc["Jakarta"] menyebut satu label di level terluar saja sehingga pandas mengembalikan DataFrame berisi sembilan baris Jakarta dengan dua level index tersisa; output menampilkan tiga baris pertamanya lewat .head(3). multi.loc[("Jakarta", 1, "Elektronik")] menyebut ketiga level sekaligus lewat tuple, dan hasilnya satu baris tunggal dengan nilai omzet sebesar 420. Aturan bacaannya konsisten: urutan elemen tuple mengikuti urutan level dari luar ke dalam.

set_index() Tidak Mengurutkan Data #

Baris terakhir script menunjukkan perilaku yang sering mengejutkan: set_index() mempertahankan urutan baris semula. Dataset kita diacak dengan sample(frac=1.0, random_state=7) sebelum set_index(), dan is_monotonic_increasing melaporkan False karena baris tidak lagi terurut. Method sort_index() yang baru mengurutkan baris berdasarkan nilai label semua level. Urutan ini belum terasa penting sekarang, tetapi menjadi syarat mutlak saat kita mulai memakai irisan (slice) pada MultiIndex di Chapter Advanced Selection.

Dengan index bertingkat di tangan, langkah berikutnya adalah bernavigasi: mengambil irisan satu level tanpa memengaruhi level lain, menukar posisi dua level, atau membuang level yang tidak diperlukan. Alat-alat tersebut — xs(), swaplevel(), droplevel(), dan pasangan stack()/unstack() — kita bahas tuntas pada Chapter berikutnya. Dokumentasi resmi untuk chapter ini ada pada user guide MultiIndex / Advanced Indexing , pandas.MultiIndex , dan pandas.DataFrame.set_index .