Agregasi merangkum banyak baris menjadi sedikit baris ringkasan, misalnya rata-rata nilai per kelas atau jumlah baris per kategori. Topik ini membahas groupBy untuk menetapkan kelompok, agg untuk menyatakan fungsi ringkasan beserta nama hasilnya, dan agregasi tanpa kelompok yang merangkum seluruh DataFrame sekaligus. Setelah membacanya, ringkasan bertingkat dapat dibangun dengan nama kolom yang rapi, dan alasan di balik pengaturan jumlah partisi hasil agregasi dapat dijelaskan.
Mekanisme groupBy: pemisahan lalu penggabungan#
Metode groupBy("kolom") menandai DataFrame untuk dipecah menjadi kelompok berdasarkan nilai kolomnya, tetapi belum menghitung apa pun. Sifat ini konsisten dengan evaluasi lazy Spark: pemanggilan groupBy mengembalikan objek GroupedData yang menunggu fungsi ringkasan. Baris-baris dengan nilai kunci yang sama tersebar di berbagai partisi, sehingga perhitungan ringkasannya menuntut pengumpulan ulang data lintas partisi, yaitu proses shuffle yang dibahas mendalam pada modul cara Spark berjalan.
Karena penggabungan tersebut, jumlah partisi hasil agregasi ditentukan oleh kunci konfigurasi spark.sql.shuffle.partitions yang defaultnya 200. Untuk data berukuran kecil, nilai 200 menghasilkan 200 task hampir kosong yang hanya menambah biaya penjadwalan, sehingga skrip contoh di jalur ini menurunkannya ke 2. Pada data berukuran besar, jumlah ini justru diperhitungkan bersama ukuran data, dan modul esensi penyetelan membahasnya lebih lanjut.
groupBy dan agg dengan nama hasil yang rapi#
Skrip berikut merangkum data nilai ujian per kelas. Pemanggilan pertama memakai bentuk singkat .count(), sedangkan pemanggilan kedua memakai .agg() dengan empat fungsi sekaligus, dan setiap hasilnya diberi nama melalui .alias().
from pyspark.sql import SparkSession
from pyspark.sql import functions as F
spark = (
SparkSession.builder
.master("local[2]")
.appName("ringkasan-agregasi")
.config("spark.sql.shuffle.partitions", "2")
.getOrCreate()
)
data = [
(1, "Ali", "10A", 85),
(2, "Budi", "10A", 90),
(3, "Citra", "10B", 78),
(4, "Dewi", "10B", 92),
(5, "Eko", "10A", 88),
(6, "Farah", "10B", 95),
]
df = spark.createDataFrame(data, "id long, nama string, kelas string, nilai long")
print("=== groupBy count ===")
df.groupBy("kelas").count().orderBy("kelas").show()
print("=== agg beberapa fungsi ===")
df.groupBy("kelas").agg(
F.count("*").alias("jumlah"),
F.avg("nilai").alias("rata_rata"),
F.min("nilai").alias("terendah"),
F.max("nilai").alias("tertinggi"),
).orderBy("kelas").show()
spark.stop()WARNING: Using incubator modules: jdk.incubator.vector
=== groupBy count ===
+-----+-----+
|kelas|count|
+-----+-----+
| 10A| 3|
| 10B| 3|
+-----+-----+
=== agg beberapa fungsi ===
+-----+------+-----------------+--------+---------+
|kelas|jumlah| rata_rata|terendah|tertinggi|
+-----+------+-----------------+--------+---------+
| 10A| 3|87.66666666666667| 85| 90|
| 10B| 3|88.33333333333333| 78| 95|
+-----+------+-----------------+--------+---------+Kolom hasil .count() otomatis bernama count, sedangkan pada .agg() setiap fungsi diberi nama melalui .alias() sehingga tabel ringkasannya langsung terbaca. Nilai rata_rata tercetak dengan banyak desimal karena F.avg mengembalikan bilangan pecahan penuh, dan pembulatan dilakukan pada lapisan penyajian, bukan pada agregasi. Kelompok 10A memuat nilai 85, 90, dan 88 yang rata-ratanya 87,67, sedangkan 10B memuat 78, 92, dan 95 yang rata-ratanya 88,33, dan kedua angka itu dapat dihitung ulang langsung dari data.
Fungsi ringkasan yang tersedia di modul pyspark.sql.functions jauh lebih banyak daripada empat fungsi di atas, misalnya F.sum, F.countDistinct, F.approx_count_distinct, dan F.collect_list. Prinsip penulisannya sama, yaitu setiap fungsi menerima ekspresi kolom dan hasilnya diberi nama eksplisit.
Agregasi tanpa kelompok#
agg juga dapat dipanggil langsung pada DataFrame tanpa groupBy, dan hasilnya satu baris ringkasan untuk seluruh data.
# tidak-dijalankan
df.agg(F.count("*").alias("jumlah_siswa"), F.avg("nilai").alias("rata_rata")).show()Bentuk tanpa kelompok ini berguna untuk garis dasar, misalnya jumlah baris dan rata-rata keseluruhan yang dibandingkan dengan rata-rata tiap kelompok. Karena hasilnya selalu tepat satu baris, keluarannya tidak bergantung urutan partisi dan tidak memerlukan orderBy.
Agregasi majemuk dan bertingkat#
Kunci groupBy dapat berupa beberapa kolom sekaligus, misalnya groupBy("kelas", "nama_jurusan"), dan hasilnya adalah satu baris untuk setiap kombinasi nilai. Bentuk agg juga menerima ekspresi yang lebih kompleks daripada fungsi sederhana, misalnya F.avg(F.col("nilai") * 2).alias("rata_dua_kali"), sehingga perhitungan turunan dapat digabung ke dalam agregasi tanpa kolom perantara.
Apabila ringkasannya perlu diringkas lagi, hasil groupBy pertama disimpan sebagai DataFrame baru, lalu diagregasi kedua kalinya. Pola ini lazim pada perhitungan berlapis, misalnya rata-rata per kelas lalu rata-rata antar kelas. Setiap tingkatan memicu shuffle-nya sendiri, sehingga jumlah tingkatan layak dipertimbangkan pada data besar.
Satu perbedaan semantik yang sering mengejutkan adalah F.count("*") dibandingkan F.count("kolom"). Bentuk pertama menghitung seluruh baris, sedangkan bentuk kedua menghitung baris yang kolomnya tidak null, sehingga keduanya memberi angka berbeda pada kolom yang memuat nilai kosong. Perbedaan serupa berlaku pada fungsi ringkasan lain yang mengabaikan null sesuai semantik SQL, seperti F.avg dan F.sum, sehingga jumlah baris yang dihitung fungsi tersebut patut diperiksa ketika kolomnya belum dibersihkan.
Kesalahan umum#
Kesalahan pertama adalah memanggil fungsi agregasi pada GroupedData tanpa .alias(), misalnya df.groupBy("kelas").agg(F.avg("nilai")). Keluarannya tetap benar, tetapi nama kolom hasilnya menjadi teks panjang yang rapuh ketika dirujuk pada transformasi berikutnya. Kebiasaan memberi nama eksplisit mencegah hal itu.
Kesalahan kedua adalah mencampur kolom biasa dan kolom agregat dalam satu agg tanpa groupBy, misalnya df.agg(F.avg("nilai"), "nama"). Kolom nama bukan hasil ringkasan, sehingga pembacaan gagal dengan error analisis. Kolom yang bukan agregat harus masuk dalam groupBy terlebih dahulu.
Kesalahan ketiga adalah mengharapkan urutan kelompok yang tetap tanpa orderBy. Urutan baris hasil agregasi mengikuti urutan keluaran partisi, dan urutan itu tidak dijamin sama antar eksekusi. Tambahkan orderBy pada kunci kelompok sebelum keluaran dicetak atau disimpan untuk keperluan yang sensitif terhadap urutan.
Ringkasan#
groupBymenandai pembentukan kelompok dan mengembalikan objekGroupedDatayang menunggu fungsi ringkasan, konsisten dengan sifat lazy Spark..agg()menerima beberapa fungsi sekaligus dan setiap hasilnya diberi nama melalui.alias(), sedangkan.count()adalah bentuk singkat yang menghasilkan kolom bernamacount.- Agregasi memicu shuffle, dan jumlah partisi hasilnya dikendalikan
spark.sql.shuffle.partitionsyang defaultnya 200 dan sering diturunkan untuk data kecil. aggtanpagroupBymerangkum seluruh DataFrame menjadi satu baris, dan kuncigroupByboleh berupa beberapa kolom untuk agregasi bertingkat.
Referensi#
- pyspark.sql.GroupedData — Apache Spark — Referensi objek GroupedData beserta agg, count, dan fungsi ringkasan lainnya.
- pyspark.sql.functions — Apache Spark — Daftar fungsi agregasi seperti avg, sum, dan approx_count_distinct.
- Spark SQL Tuning Guide — Apache Spark — Penjelasan partisi shuffle dan perilaku Adaptive Query Execution.