Agregasi mengecilkan tabel menjadi satu baris per grup, sedangkan method transform mengembalikan hasil per grup tanpa mengubah jumlah baris. Topik ini membahas perbedaan bentuk keluaran keduanya, pemanfaatan transform untuk menandai anomali lokal pada log sensor, dan perhitungan skor-z per grup. Setelah membaca, statistik level grup dapat ditempelkan kembali ke setiap baris asal dengan aman.
transform mempertahankan jumlah baris#
Method transform menerapkan satu fungsi pada setiap pecahan lalu menempelkan hasilnya kembali ke posisi baris asal, sehingga keluarannya selalu sepanjang data asal. Nilai yang dihasilkan satu grup sama untuk semua anggotanya, misalnya rata-rata suhu Lab A diulang pada seluruh 36 barisnya. Perilaku ini kebalikan dari agg yang menyusutkan tabel menjadi satu baris per grup.
import numpy as np
import pandas as pd
rng = np.random.default_rng(42)
n = 96
df = pd.DataFrame({
"waktu": pd.date_range("2026-01-05 07:00", periods=n, freq="15min"),
"ruang": rng.choice(["Lab A", "Lab B", "Ruang Arsip"], n, p=[0.4, 0.35, 0.25]),
"suhu_c": np.round(rng.normal(24, 1.5, n), 1),
"lembap_pct": np.round(rng.normal(60, 5, n), 1),
"status_sensor": rng.choice(["normal", "kalibrasi", "rusak"], n, p=[0.85, 0.10, 0.05]),
})
df.to_csv("sensor_log.csv", index=False) # dataset bersama untuk seluruh modul
df = pd.read_csv("sensor_log.csv", parse_dates=["waktu"])
df["suhu_rata_ruang"] = df.groupby("ruang")["suhu_c"].transform("mean")
print(df[["waktu", "ruang", "suhu_c", "suhu_rata_ruang"]].head(4))
print("panjang hasil transform:", len(df.groupby("ruang")["suhu_c"].transform("mean"))) waktu ruang suhu_c suhu_rata_ruang
0 2026-01-05 07:00:00 Ruang Arsip 21.5 24.259091
1 2026-01-05 07:15:00 Lab B 21.8 23.807895
2 2026-01-05 07:30:00 Ruang Arsip 22.0 24.259091
3 2026-01-05 07:45:00 Lab B 22.5 23.807895
panjang hasil transform: 96Kolom baru suhu_rata_ruang berisi rata-rata ruang masing-masing baris, dan baris Ruang Arsip memuat 24.259091 pada baris 0 dan 2, sedangkan baris Lab B memuat 23.807895 pada baris 1 dan 3. Baris terakhir keluaran memastikan panjang hasil transform adalah 96, sama dengan panjang tabel. Bentuk inilah yang menjadi dasar penghitungan anomali pada bagian berikutnya.
Menandai anomali lokal dengan selisih#
Anomali lokal adalah bacaan yang menyimpang dari rata-rata ruangnya sendiri, dan selisih df["suhu_c"] - df["suhu_rata_ruang"] mengukurnya langsung. Nilai selisih besar menandai bacaan yang tidak wajar dibanding kondisi ruang tersebut, meskipun angkanya masih wajar dibanding seluruh bangunan. Tanda selisih juga informatif, yaitu positif berarti lebih panas dari rata-rata ruang dan negatif berarti lebih dingin.
import pandas as pd
df = pd.read_csv("sensor_log.csv", parse_dates=["waktu"])
df["suhu_rata_ruang"] = df.groupby("ruang")["suhu_c"].transform("mean")
df["selisih"] = (df["suhu_c"] - df["suhu_rata_ruang"]).round(2)
print(df.nlargest(4, "selisih")[["ruang", "suhu_c", "suhu_rata_ruang", "selisih"]]) ruang suhu_c suhu_rata_ruang selisih
45 Ruang Arsip 28.4 24.259091 4.14
68 Lab A 27.2 23.730556 3.47
61 Lab B 26.7 23.807895 2.89
44 Ruang Arsip 27.0 24.259091 2.74Empat selisih teratas seluruhnya bernilai positif dan berasal dari tiga ruang yang berbeda. Selisih terbesar adalah 4.14 derajat pada baris 45 milik Ruang Arsip dengan suhu 28.4 derajat, diikuti 3.47 derajat pada baris 68 milik Lab A. Tanpa transform, perbandingan semacam ini menuntut pencarian rata-rata per ruang secara manual untuk setiap baris, sehingga kolom rata-rata ruang mempersingkatnya menjadi satu operasi vektor.
Ambang setara antar grup dengan skor-z#
Selisih absolut kurang adil ketika sebaran antar grup berbeda, karena satu ruang yang tenang dapat menghasilkan selisih besar pada gangguan kecil. Skor-z per grup menyeragamkan selisih itu dengan simpangan baku ruangnya sendiri, yaitu selisih dibagi hasil transform("std"). Bacaan dengan skor-z di kisaran 2 sampai 3 lazim ditandai sebagai kandidat anomali tanpa klaim pasti, dan ambangnya konsisten antar ruang karena sudah dinormalisasi.
import pandas as pd
df = pd.read_csv("sensor_log.csv", parse_dates=["waktu"])
rata = df.groupby("ruang")["suhu_c"].transform("mean")
simpangan = df.groupby("ruang")["suhu_c"].transform("std")
df["z_suhu"] = ((df["suhu_c"] - rata) / simpangan).round(2)
print(df.nlargest(3, "z_suhu")[["ruang", "suhu_c", "z_suhu"]]) ruang suhu_c z_suhu
68 Lab A 27.2 2.76
45 Ruang Arsip 28.4 2.32
32 Lab A 26.2 1.96Baris 68 milik Lab A mencatat skor-z tertinggi 2.76, disusul baris 45 milik Ruang Arsip pada 2.32. Peringkatnya berubah dibanding selisih absolut karena setiap ruang dinormalisasi dengan sebarannya sendiri, sehingga bacaan 27.2 pada Lab A yang sebarannya sempit melampaui bacaan 28.4 pada Ruang Arsip. Dua nilai transform, yaitu mean dan std, sudah cukup untuk membangun deteksi anomali per grup ini.
Kesalahan umum#
Kesalahan paling umum adalah tertukar agg dan transform saat membentuk kolom baru, karena keduanya menerima nama fungsi yang mirip tetapi menghasilkan panjang yang berbeda. Hasil agg sepanjang tiga baris tidak dapat menempel ke 96 baris tabel, dan kegagalannya berbeda tergantung bentuk nilainya. Contoh berikut memperlihatkan dua kelakuan itu secara berurutan.
import pandas as pd
df = pd.read_csv("sensor_log.csv", parse_dates=["waktu"])
df["keliru"] = df.groupby("ruang")["suhu_c"].agg("mean")
print("NaN pada kolom keliru:", df["keliru"].isna().sum())
try:
df["keliru2"] = df.groupby("ruang")["suhu_c"].agg("mean").to_numpy()
except ValueError as e:
print("ValueError:", e)NaN pada kolom keliru: 96
ValueError: Length of values (3) does not match length of index (96)Penugasan versi Series tidak menimbulkan error, tetapi hasilnya menyesatkan karena pandas menyelaraskan nilai berdasarkan index. Series hasil agg berindex Lab A, Lab B, dan Ruang Arsip yang tidak ada padanannya pada index 0 sampai 95, sehingga seluruh 96 baris kolom keliru menjadi NaN. Versi array kehilangan index sehingga panjangnya dibandingkan mentah, dan pandas menolaknya dengan ValueError: Length of values (3) does not match length of index (96). Aturan praktisnya sederhana, yaitu memakai transform setiap kali hasilnya akan dijadikan kolom sepanjang data asal.
Ringkasan#
aggmenyusutkan tabel menjadi satu baris per grup, sedangkantransformmengembalikan hasil sepanjang baris asal.df.groupby("ruang")["suhu_c"].transform("mean")menempelkan rata-rata ruang ke setiap baris sehingga anomali lokal dihitung sebagai selisih dari rata-rata ruang.- Kombinasi
transform("mean")dantransform("std")membentuk skor-z per grup untuk ambang anomali yang setara antar ruang. aggyang dipakai pada tempat transform menghasilkan kolomNaNkarena penyelarasan index, atauValueErrorbila nilainya berupa array.
Referensi#
- pandas.core.groupby.DataFrameGroupBy.transform — pandas API reference — dokumentasi resmi transform beserta aturan bentuk keluarannya.
- Group by: split-apply-combine — pandas User Guide — bab resmi pengelompokan yang membedakan transform dan agregasi.