Pada chapter-chapter sebelumnya, hasil groupby().agg() selalu menyusut menjadi satu baris per kelompok. Format ringkas itu bagus untuk laporan rekap, tetapi sering kali kita justru membutuhkan angka agregat yang menempel pada setiap baris asli, misalnya menampilkan total kota di samping setiap transaksi. Method transform() pada GroupBy dirancang untuk kebutuhan tersebut: hasil agregasi dikembalikan dengan jumlah baris dan urutan yang sama seperti data asli. Chapter ini membahas cara kerja transform(), penggunaannya untuk menambahkan kolom agregat, dan kapan kita sebaiknya memilih transform() dibanding agg().

Masalah: Angka Agregat yang Tetap Menempel pada Baris #

Bayangkan laporan penjualan berisi tujuh transaksi dari empat kota. Manajer ingin membaca tabel transaksi yang utuh, tetapi setiap baris juga menampilkan total kotanya serta selisih transaksi dari rata-rata kota. Method agg() tidak cocok untuk kebutuhan ini karena hasilnya hanya beberapa baris ringkasan tanpa detail transaksi. Kita membutuhkan operasi yang menghitung per kelompok, namun mengembalikan hasil ke setiap baris asli.

Mekanisme transform() pada GroupBy #

transform() menerima argumen yang sama dengan agg(): string nama fungsi seperti "sum", "mean", atau "max", maupun fungsi Python. GroupBy membagi data per kelompok, menghitung fungsi tersebut pada setiap kelompok, lalu menempatkan hasilnya ke baris-baris anggota kelompok yang bersangkutan. Proses penempatan kembali ini sering disebut broadcast: satu nilai per kelompok disebarkan ke semua baris kelompoknya. Karena itu, Series hasil transform() selalu punya jumlah baris dan index yang sama dengan DataFrame asli, sehingga aman ditempel sebagai kolom baru.

Contoh: Total Kota dan Selisih dari Rata-rata #

Skrip berikut memakai data penjualan empat kota. Kita bandingkan dulu hasil sum() yang menyusut dengan hasil transform("sum") yang tidak menyusut, lalu kita tambahkan dua kolom baru: total_kota dan selisih_rata.

python
import pandas as pd

penjualan = pd.DataFrame({
    "kota": ["Jakarta", "Jakarta", "Bandung", "Bandung", "Surabaya", "Surabaya", "Medan"],
    "kategori": ["Elektronik", "Pakaian", "Elektronik", "Pakaian", "Elektronik", "Pakaian", "Elektronik"],
    "nilai": [120, 80, 90, 70, 110, 60, 50],
})

print(penjualan, "\n")
print("sum() menyusut menjadi satu baris per kelompok:")
print(penjualan.groupby("kota")["nilai"].sum(), "\n")

print('transform("sum") mempertahankan jumlah baris asli:')
print(penjualan.groupby("kota")["nilai"].transform("sum"), "\n")

penjualan["total_kota"] = penjualan.groupby("kota")["nilai"].transform("sum")
penjualan["selisih_rata"] = (
    penjualan["nilai"] - penjualan.groupby("kota")["nilai"].transform("mean")
)
print(penjualan)

Output yang Dihasilkan:

text
       kota    kategori  nilai
0   Jakarta  Elektronik    120
1   Jakarta     Pakaian     80
2   Bandung  Elektronik     90
3   Bandung     Pakaian     70
4  Surabaya  Elektronik    110
5  Surabaya     Pakaian     60
6     Medan  Elektronik     50 

sum() menyusut menjadi satu baris per kelompok:
kota
Bandung     160
Jakarta     200
Medan        50
Surabaya    170
Name: nilai, dtype: int64 

transform("sum") mempertahankan jumlah baris asli:
0    200
1    200
2    160
3    160
4    170
5    170
6     50
Name: nilai, dtype: int64 

       kota    kategori  nilai  total_kota  selisih_rata
0   Jakarta  Elektronik    120         200          20.0
1   Jakarta     Pakaian     80         200         -20.0
2   Bandung  Elektronik     90         160          10.0
3   Bandung     Pakaian     70         160         -10.0
4  Surabaya  Elektronik    110         170          25.0
5  Surabaya     Pakaian     60         170         -25.0
6     Medan  Elektronik     50          50           0.0

Mari kita telaah baris-baris penting pada skrip di atas:

  1. Baris 3-7: DataFrame penjualan dibangun dari tujuh transaksi; kolom kota menjadi kunci kelompok dan kolom nilai menjadi data yang diagregasi.
  2. Baris 10-11: groupby("kota")["nilai"].sum() menghasilkan empat baris, satu per kota. Hasil ini ringkas, tetapi tidak bisa ditempel langsung ke tabel asli karena ukurannya berbeda.
  3. Baris 13-14: transform("sum") menghasilkan Series dengan tujuh baris. Baris 0 dan 1 sama-sama bernilai 200 karena keduanya milik Jakarta, dan seterusnya mengikuti kelompoknya.
  4. Baris 16-19: hasil transform("sum") menjadi kolom total_kota, sedangkan selisih_rata mengurangi nilai tiap transaksi dengan rata-rata kotanya dari transform("mean").

Perhatikan kolom selisih_rata pada output: transaksi Medan bernilai 0.0 karena Medan hanya memiliki satu transaksi, sehingga nilainya sama dengan rata-rata kotanya sendiri. Sementara itu, Surabaya memiliki selisih 25.0 dan -25.0, tanda dua transaksinya sama jauh di atas dan di bawah rata-rata. Pola selisih dari rata-rata seperti ini sering dipakai untuk menandai transaksi yang tidak lazim.

Memilih antara agg() dan transform() #

Kedua method mengerjakan agregasi yang sama, tetapi bentuk hasilnya berbeda tujuan. Tabel berikut merangkum kapan masing-masing lebih pas:

Aspekagg()transform()
Ukuran hasilsatu baris per kelompoksama dengan data asli
Cocok untuklaporan ringkasan per kelompokkolom bantu pada tabel asli
Contoh pertanyaan“berapa total per kota?”“berapa total kota untuk transaksi ini?”
Posisi hasiltabel ringkasan terpisahsejajar dengan index asli

Aturan praktisnya: jika hasil akhir yang diinginkan adalah tabel ringkasan, pakai agg(); jika tabel asli harus tetap utuh dengan tambahan angka per kelompok, pakai transform(). Kedua method ini saling melengkapi dan akan muncul bergantian pada chapter-chapter berikutnya. Referensi lengkapnya ada pada panduan GroupBy , DataFrameGroupBy.transform , dan DataFrame.transform .

Dengan transform(), angka agregat dan data mentah kini hidup berdampingan dalam satu tabel. Kemampuan ini menjadi fondasi teknik lanjutan seperti menghitung persentase per kelompok yang dibahas beberapa chapter lagi. Sebelum sampai ke sana, Chapter berikutnya membahas filter(): menyaring kelompok utuh berdasarkan kondisi agregat, misalnya mempertahankan hanya kota dengan total penjualan tertentu.