Rata-rata adalah jawaban refleks untuk hampir semua pertanyaan tentang angka: berapa rata-rata transaksi, berapa rata-rata kunjungan, berapa rata-rata rating. Padahal satu ukuran ringkasan yang salah pilih bisa membuat keputusan meleset — terutama ketika data memuat nilai ekstrem. Pada chapter ini kita melengkapi kotak alat numerik secara utuh: tendensi sentral, penyebaran dengan simpangan baku dan varians, kuantil untuk meletakkan nilai pada peta sebaran, serta janji dari chapter Analisis Distribusi yang belum kita tunaikan — dampak satu nilai ekstrem terhadap mean dan median.

Tiga Pertanyaan Dasar untuk Kolom Numerik #

Setiap analisis kolom numerik bermuara pada tiga pertanyaan. Pertama, di mana nilai-nilai berkumpul — pertanyaan tendensi sentral. Kedua, seberapa lebar sebarannya — pertanyaan penyebaran. Ketiga, di posisi mana sebuah nilai berada dibanding nilai lainnya — pertanyaan letak. Ketiganya dijawab oleh kelompok fungsi yang berbeda:

PertanyaanUkuranFungsi pandas
Di mana nilai berkumpul?mean, median.mean(), .median()
Seberapa lebar sebarannya?simpangan baku, varians.std(), .var()
Di posisi mana sebuah nilai?kuartil dan kuantil.quantile()

Ketiga kelompok tersebut saling melengkapi, bukan saling menggantikan. Mean tanpa ukuran penyebaran menyembunyikan perbedaan antara cabang yang stabil dan cabang yang naik-turun; penyebaran tanpa letak tidak bisa menjawab apakah 45 ribu termasuk transaksi besar atau biasa saja. Karena itu kita hitung ketiganya sekaligus pada data yang sama.

Tendensi Sentral dan Penyebaran dalam Satu Kode #

Kita pakai kembali dunia Toko Kopi Kita: 24 transaksi dari dua cabang selama sepekan, termasuk satu pesanan katering kantor seharga 150 ribu rupiah di cabang Bandung. Simpangan baku mengukur rata-rata jarak setiap nilai dari mean — rumusnya adalah akar dari rata-rata kuadrat jarak, dan pandas memakai versi sampel yang membagi n-1 karena data kita adalah sampel, bukan seluruh populasi:

python
import pandas as pd

transaksi = pd.DataFrame({
    "cabang": ["Bandung"] * 12 + ["Surabaya"] * 12,
    "nilai_ribu": [22, 28, 24, 45, 21, 27, 24, 150, 23, 26, 24, 30,
                   19, 26, 22, 24, 20, 26, 21, 28, 23, 25, 24, 18],
    "gelas": [1, 1, 1, 2, 1, 1, 1, 6, 1, 1, 1, 1,
              1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1],
})

nilai = transaksi["nilai_ribu"]
print("Jumlah data      :", nilai.count())
print("Rata-rata        :", nilai.mean())
print("Median           :", nilai.median())
print("Simpangan baku   :", round(nilai.std(), 2))
print("Varians          :", round(nilai.var(), 2))

Output yang Dihasilkan:

text
Jumlah data      : 24
Rata-rata        : 30.0
Median           : 24.0
Simpangan baku   : 26.08
Varians          : 680.35

Mari kita telaah hasilnya:

  1. Rata-rata 30,0 vs median 24,0: dua ukuran tendensi sentral yang berjauhan adalah sinyal kemiringan kanan yang sudah kita kenal pada chapter Analisis Distribusi — separuh transaksi bernilai 24 ribu ke bawah, tetapi mean terseret naik oleh ekor kanan.
  2. Simpangan baku 26,08: mayoritas transaksi hanya bergerak di kisaran 18-30 ribu, sehingga jarak rata-rata “seharusnya” beberapa ribu saja. Angka 26 ribu yang besar ini adalah petunjuk ada nilai yang menjauh sangat kencang dari kerumunan.
  3. Varians 680,35: tepat kuadrat dari simpangan baku (26,0835² = 680,35). Varians dipakai dalam perhitungan statistika lanjutan, tetapi untuk dibaca manusia std lebih ramah karena satuannya kembali ke satuan data (ribu rupiah), bukan kuadratnya.

Dampak Satu Pesanan Katering terhadap Mean dan Median #

Sekarang kita uji janji chapter Analisis Distribusi secara eksplisit: seberapa besar sesunguhnya satu nilai ekstrem menggeser kedua ukuran? Kita hitung mean dan median dua kali — dengan seluruh 24 transaksi, lalu tanpa satu baris katering 150 ribu:

python
import pandas as pd

# Tabel yang sama dengan blok sebelumnya, dibangun lagi agar blok mandiri.
transaksi = pd.DataFrame({
    "cabang": ["Bandung"] * 12 + ["Surabaya"] * 12,
    "nilai_ribu": [22, 28, 24, 45, 21, 27, 24, 150, 23, 26, 24, 30,
                   19, 26, 22, 24, 20, 26, 21, 28, 23, 25, 24, 18],
    "gelas": [1, 1, 1, 2, 1, 1, 1, 6, 1, 1, 1, 1,
              1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1],
})

semua = transaksi["nilai_ribu"]
umum = transaksi.loc[transaksi["nilai_ribu"] < 150, "nilai_ribu"]

print(f"Semua transaksi : mean {semua.mean():.2f} | median {semua.median():.2f}")
print(f"Tanpa katering  : mean {umum.mean():.2f} | median {umum.median():.2f}")
geser = semua.mean() - umum.mean()
print(f"Mean bergeser   : {geser:.2f} ribu ({geser / semua.mean() * 100:.1f}% dari mean semula)")

Output yang Dihasilkan:

text
Semua transaksi : mean 30.00 | median 24.00
Tanpa katering  : mean 24.78 | median 24.00
Mean bergeser   : 5.22 ribu (17.4% dari mean semula)

Bedah kode dan hasilnya:

  1. semua memuat 24 nilai, umum menyaring 23 nilai di bawah 150 — hanya satu baris yang dikeluarkan, yaitu pesanan katering kantor.
  2. Mean turun dari 30,00 ke 24,78 — satu baris dari 24 (4% data) menggeser mean sebesar 17,4%. Angka “rata-rata transaksi 30 ribu” ternyata tidak menggambarkan transaksi yang biasa terjadi.
  3. Median tidak bergeser (tetap 24,00): median hanya peduli urutan, dan mengeluarkan nilai terbesar tidak mengubah posisi tengah. Inilah alasan median disebut tahan (robust) terhadap nilai ekstrem.
UkuranSemua 24 transaksiTanpa 1 kateringPerubahan
Mean30,0024,78turun 17,4%
Median24,0024,00tidak berubah

Meringkas Angka per Kelompok dengan groupby #

Satu angka untuk seluruh tabel menyembunyikan perbedaan antar kelompok. Kita telah bertemu groupby() pada chapter Ringkasan Dataset; kali ini kita tuntaskan dengan menghitung lima ukuran sekaligus untuk kedua kolom numerik — jumlah pesanan dan gelas dari kolom yang berbeda-beda:

python
import pandas as pd

# Tabel yang sama dengan blok sebelumnya, dibangun lagi agar blok mandiri.
transaksi = pd.DataFrame({
    "cabang": ["Bandung"] * 12 + ["Surabaya"] * 12,
    "nilai_ribu": [22, 28, 24, 45, 21, 27, 24, 150, 23, 26, 24, 30,
                   19, 26, 22, 24, 20, 26, 21, 28, 23, 25, 24, 18],
    "gelas": [1, 1, 1, 2, 1, 1, 1, 6, 1, 1, 1, 1,
              1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1],
})

per_cabang = transaksi.groupby("cabang").agg(
    pesanan=("nilai_ribu", "count"),
    gelas=("gelas", "sum"),
    rata_nilai=("nilai_ribu", "mean"),
    median_nilai=("nilai_ribu", "median"),
    sebaran=("nilai_ribu", "std"),
)
print(per_cabang.round(2))

Output yang Dihasilkan:

text
          pesanan  gelas  rata_nilai  median_nilai  sebaran
cabang                                                     
Bandung        12     18        37.0          25.0    36.14
Surabaya       12     13        23.0          23.5     3.07

Cara membaca tabelnya:

  1. pesanan=("nilai_ribu", "count") dan gelas=("gelas", "sum"): agregasi kini menyentuh dua kolom berbeda sekaligus — hitungan transaksi dari nilai_ribu, total gelas dari gelas — masing-masing dengan nama kolom hasil sendiri.
  2. rata_nilai Bandung 37,00 vs Surabaya 23,00: kalau kita berhenti di sini, kesimpulannya “Bandung jauh lebih premium daripada Surabaya”.
  3. median_nilai 25,00 vs 23,50 dan sebaran 36,14 vs 3,07: dua kolom berikutnya membantah kesan awal itu. Median kedua cabang nyaris sama — transaksi yang biasa terjadi serupa; tetapi sebaran Bandung hampir dua belas kali lipat Surabaya karena satu katering. Perbedaan mean yang mencolok berasal dari satu baris, bukan dari perbedaan pelanggan secara umum.

Pola mean >> median pada satu kelompok adalah detektor nilai ekstrem yang paling murah: sebelum memakai alat deteksi formal, cukup bandingkan dua ukuran ini per kelompok.

Meletakkan Nilai pada Peta Sebaran dengan Kuantil #

Pertanyaan terakhir — di posisi mana sebuah nilai berada — dijawab kuantil. Kita hitung tiga kuartil tiap cabang, lalu ukur posisi si katering dengan cara paling sederhana: hitung persentase data yang berada di bawahnya:

python
import pandas as pd

# Tabel yang sama dengan blok sebelumnya, dibangun lagi agar blok mandiri.
transaksi = pd.DataFrame({
    "cabang": ["Bandung"] * 12 + ["Surabaya"] * 12,
    "nilai_ribu": [22, 28, 24, 45, 21, 27, 24, 150, 23, 26, 24, 30,
                   19, 26, 22, 24, 20, 26, 21, 28, 23, 25, 24, 18],
    "gelas": [1, 1, 1, 2, 1, 1, 1, 6, 1, 1, 1, 1,
              1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1],
})

kuartil = transaksi.groupby("cabang")["nilai_ribu"].quantile([0.25, 0.5, 0.75]).unstack()
print(kuartil)
print()
persen_bawah = (transaksi["nilai_ribu"] < 150).mean() * 100
print(f"Persentase transaksi di bawah 150 ribu: {persen_bawah:.1f}%")

Output yang Dihasilkan:

text
           0.25  0.50   0.75
cabang                      
Bandung   23.75  25.0  28.50
Surabaya  20.75  23.5  25.25

Persentase transaksi di bawah 150 ribu: 95.8%

Bedah kode di atas:

  1. .quantile([0.25, 0.5, 0.75]) menghitung tiga kuartil sekaligus untuk tiap kelompok cabang; .unstack() menyusun hasil bertingkat itu menjadi tabel lebar — kuartil menjadi kolom, cabang menjadi baris.
  2. Tiga kuartil kedua cabang berdekatan (selisih terbesar cuma 3,25): 50% transaksi tengah kedua cabang praktis hidup di rentang yang sama. Bandingkan dengan mean yang berbeda 14 ribu — kuartil tidak ikut tertarik oleh katering karena hanya bergantung pada urutan.
  3. 95,8% transaksi bernilai di bawah 150 ribu: cara baca posisi yang paling jujur. Pesanan katering berdiri di atas 95 dari 100 transaksi — nomor satu paling ekstrem, tetapi bukan mustahil; segmen katering memang langka.

Rangkuman #

  • Tiga pertanyaan kolom numerik — di mana berkumpul, seberapa lebar sebar, di posisi mana — dijawab berturut-turut oleh mean/median, std/varians, dan kuantil.
  • Satu nilai ekstrem menggeser mean jauh (17,4% pada contoh kita) tetapi nyaris tidak menyentuh median dan kuartil; karena itu laporkan keduanya berdampingan.
  • var() adalah kuadrat std(); untuk dibaca manusia, pakai std karena satuannya kembali ke satuan data.
  • groupby().agg() dengan pasangan (kolom, statistik) merangkum beberapa kolom sekaligus per kelompok; pola mean >> median pada satu kelompok adalah penanda nilai ekstrem paling murah.
  • (data < x).mean() mengukur posisi sebuah nilai secara sederhana: persentase data yang berada di bawahnya.

Satu kolom numerik kini kita baca dari segala arah. Tetapi kolom jarang berdiri sendiri: nilai transaksi berjalan beriringan dengan menu, cabang, suhu, dan hari. Seberapa erat dua kolom bergerak bersama — dan kapan kita boleh menyebut yang satu menjelaskan yang lain — adalah materi chapter berikutnya, Hubungan antar Variabel .

Referensi #

  1. pandas — User Guide
  2. pandas — Getting Started: Intro Tutorials
  3. pandas — API Reference: DataFrame.groupby