Chapter Dasar Pandas sudah membuat kita nyaman memakai DataFrame hasil read_csv(). Akan tetapi, nyaman memakai belum berarti paham: begitu mulai menyaring baris, menambah kolom, dan membaca sel tunggal, kebingungan klasik muncul — kapan memakai [], loc, atau iloc, dan mengapa penugasan kadang diam-diam gagal. Chapter ini membedah anatomi DataFrame sampai ke tuasnya, lalu menutup satu per satu titik kebingungan tersebut.

Membangun DataFrame dari Nol #

Dua cara membangun DataFrame paling umum: dictionary of lists (kolom demi kolom) dan list of dictionaries (baris demi baris). Keduanya menghasilkan DataFrame identik:

python
import pandas as pd

data_kolom = {
    "produk": ["Kaos Polos", "Kemeja Flanel", "Sepatu Sneakers"],
    "harga": [95000, 185000, 450000],
    "stok": [40, 12, 8],
}
df_kolom = pd.DataFrame(data_kolom)
print(df_kolom)
print()

data_baris = [
    {"produk": "Kaos Polos", "harga": 95000, "stok": 40},
    {"produk": "Kemeja Flanel", "harga": 185000, "stok": 12},
    {"produk": "Sepatu Sneakers", "harga": 450000, "stok": 8},
]
df_baris = pd.DataFrame(data_baris)
print(df_baris)

Output yang Dihasilkan:

text
            produk   harga  stok
0       Kaos Polos   95000    40
1    Kemeja Flanel  185000    12
2  Sepatu Sneakers  450000     8

            produk   harga  stok
0       Kaos Polos   95000    40
1    Kemeja Flanel  185000    12
2  Sepatu Sneakers  450000     8

Pilih berdasarkan arah datanya: data hasil loop atau hasil API biasanya terkumpul sebagai dictionary per baris (pakai list of dicts), sedangkan data yang disusun per kolom lebih alami sebagai dictionary of lists.

Index: Alamat Setiap Baris #

Angka 0, 1, 2 di tepi kiri kedua DataFrame di atas adalah index — RangeIndex otomatis yang berperan sebagai alamat baris, terlihat juga pada info() di chapter sebelumnya. Alamat ini bisa diganti label yang lebih bermakna lewat set_index():

python
df_indeks = df_kolom.set_index("produk")
print(df_indeks)
print()
print(df_indeks.loc["Kemeja Flanel", "harga"])

Output yang Dihasilkan:

text
                  harga  stok
produk                       
Kaos Polos        95000    40
Kemeja Flanel    185000    12
Sepatu Sneakers  450000     8

185000

Setelah set_index("produk"), nama produk menjadi alamat baris, dan df_indeks.loc["Kemeja Flanel", "harga"] membaca sel dengan dua label — persis seperti mencari nilai pada tabel dua dimensi berjudul.

loc vs iloc vs [] #

Inilah sumber kebingungan nomor satu pemula pandas. Tiga pintu akses tersebut punya peran berbeda:

AksesMembaca berdasarkanRentang slicingKegunaan utama
df["harga"]nama kolomtidak berlakumemilih kolom
df.loc[baris, kolom]label index dan kolomujung kanan ikutmembaca/menulis sel terlabel
df.iloc[baris, kolom]posisi integer mulai 0ujung kanan dipotongmembaca/menulis sel berurutan

Ketiganya bekerja pada DataFrame yang sama:

python
df = pd.DataFrame({
    "produk": ["Kaos Polos", "Kemeja Flanel", "Sepatu Sneakers", "Jaket Bomber"],
    "kategori": ["Fashion", "Fashion", "Fashion", "Fashion"],
    "harga": [95000, 185000, 450000, 520000],
    "stok": [40, 12, 8, 0],
})
print(df)
print()
print("df['harga']:")
print(df["harga"])
print()
print("df.loc[1, 'harga'] :", df.loc[1, "harga"])
print("df.iloc[1, 2]      :", df.iloc[1, 2])
print()
print("df.loc[:2, ['produk', 'harga']]:")
print(df.loc[:2, ["produk", "harga"]])
print()
print("df.iloc[:2, [0, 3]]:")
print(df.iloc[:2, [0, 3]])

Output yang Dihasilkan:

text
            produk kategori   harga  stok
0       Kaos Polos  Fashion   95000    40
1    Kemeja Flanel  Fashion  185000    12
2  Sepatu Sneakers  Fashion  450000     8
3     Jaket Bomber  Fashion  520000     0

df['harga']:
0     95000
1    185000
2    450000
3    520000
Name: harga, dtype: int64

df.loc[1, 'harga'] : 185000
df.iloc[1, 2]      : 185000

df.loc[:2, ['produk', 'harga']]:
            produk   harga
0       Kaos Polos   95000
1    Kemeja Flanel  185000
2  Sepatu Sneakers  450000

df.iloc[:2, [0, 3]]:
          produk  stok
0     Kaos Polos    40
1  Kemeja Flanel    12

Perhatikan hasil dua slicing terakhir: df.loc[:2, ...] mengembalikan tiga baris karena label 2 ikut, sedangkan df.iloc[:2, ...] mengembalikan dua baris karena posisi 2 dipotong — persis perilaku slicing list Python. Pada DataFrame dengan RangeIndex, label dan posisi kebetulan sama, sehingga keduanya menunjuk baris yang sama di contoh pertama; begitu index diganti label (set_index), keduanya langsung berbeda makna.

Menyaring Baris dan Mengubah Kolom #

Penyaringan baris memakai boolean expression langsung di dalam kurung siku. Setelah tersaring, kolom baru ditambahkan dengan penugasan biasa, kolom dibuang dengan drop(), dan nama dirapikan dengan rename():

python
df = pd.DataFrame({
    "produk": ["Kaos Polos", "Kemeja Flanel", "Sepatu Sneakers", "Jaket Bomber"],
    "harga": [95000, 185000, 450000, 520000],
    "stok": [40, 12, 8, 0],
})

print(df[df["stok"] < 10])
print()
df["nilai_stok"] = df["harga"] * df["stok"]
print(df)

Output yang Dihasilkan:

text
            produk   harga  stok
2  Sepatu Sneakers  450000     8
3     Jaket Bomber  520000     0

            produk   harga  stok  nilai_stok
0       Kaos Polos   95000    40     3800000
1    Kemeja Flanel  185000    12     2220000
2  Sepatu Sneakers  450000     8     3600000
3     Jaket Bomber  520000     0           0

Ekspresi df["stok"] < 10 menghasilkan Series boolean; df[...] dengan Series boolean menyaring baris yang bernilai True. Kolom nilai_stok dihitung vektorial — harga kali stok untuk seluruh baris sekaligus, persis pola array NumPy dari chapter Dasar NumPy. Lanjut ke operasi bentuk tabel:

python
df = df.drop(columns=["nilai_stok"])
df = df.rename(columns={"produk": "nama_produk"})
df = df.set_index("nama_produk")
print(df)

Output yang Dihasilkan:

text
                  harga  stok
nama_produk                  
Kaos Polos        95000    40
Kemeja Flanel    185000    12
Sepatu Sneakers  450000     8
Jaket Bomber     520000     0

Metode drop(), rename(), dan set_index() semuanya mengembalikan DataFrame baru; penugasan df = df.... itulah yang membuat perubahan menetap.

Jebakan Chained Indexing #

Satu jebakan wajib dipecahkan sebelum chapter pembersihan. Perhatikan kode yang tampak wajar berikut, yang mencoba menyetok habis produk dengan stok di bawah enam:

python
import pandas as pd

df = pd.DataFrame({
    "produk": ["Kaos Polos", "Kemeja Flanel", "Jaket Bomber"],
    "stok": [10, 5, 8],
})

print("Stok sebelum         :", df["stok"].tolist())
df[df["stok"] < 6]["stok"] = 0
print("Stok sesudah chained :", df["stok"].tolist())

df.loc[df["stok"] < 6, "stok"] = 0
print("Stok sesudah .loc    :", df["stok"].tolist())

Output yang Dihasilkan:

text
Stok sebelum         : [10, 5, 8]
Stok sesudah chained : [10, 5, 8]
Stok sesudah .loc    : [10, 0, 8]

Baris df[df["stok"] < 6]["stok"] = 0 gagal diam-diam: stok Kemeja Flanel tetap 5. Mekanismenya: df[df["stok"] < 6] lebih dulu membuat DataFrame baru, lalu ["stok"] = 0 menulis pada salinan tersebut — DataFrame asli tidak pernah disentuh. pandas bahkan menampilkan SettingWithCopyWarning di terminal sebagai peringatan:

text
SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

Baris df.loc[df["stok"] < 6, "stok"] = 0 melakukan penyaringan dan penugasan dalam satu langkah pada objek yang sama, sehingga perubahan menetap. Inilah mengapa konvensi resmi pandas merekomendasikan satu operasi .loc tunggal setiap kali kita membaca dan menulis sekaligus.