transform: Nilai per Grup

Mengembalikan hasil agregasi level grup ke bentuk baris dengan transform.

Daftar isi

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.

python transform mengembalikan satu nilai per baris
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")))
text Keluaran
                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: 96

Kolom 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.

python Selisih suhu terhadap rata-rata ruang
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"]])
text Keluaran
          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.74

Empat 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.

python Skor-z per ruang dengan transform
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"]])
text Keluaran
          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.96

Baris 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.

python Menggunakan agg pada tempat transform
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)
text Keluaran
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#

  • agg menyusutkan tabel menjadi satu baris per grup, sedangkan transform mengembalikan 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") dan transform("std") membentuk skor-z per grup untuk ambang anomali yang setara antar ruang.
  • agg yang dipakai pada tempat transform menghasilkan kolom NaN karena penyelarasan index, atau ValueError bila nilainya berupa array.

Referensi#