Apa yang Abbas Pelajari Bulan Ini

Konteks: Setelah bulan lalu Abbas sudah bisa menulis Python dasar, kini saatnya memahami cara membaca angka secara bermakna. Statistik deskriptif adalah bahasa pertama seorang analis data, cara meringkas ratusan atau ribuan baris data menjadi beberapa angka kunci yang langsung bisa dipahami siapa saja.

Di bulan ini, Abbas tidak hanya menghafalkan rumus. Abbas akan belajar kapan suatu ukuran lebih tepat dipakai dibanding yang lain, dan bagaimana data bisa "berbohong" jika salah dibaca.

Kompetensi yang Abbas Kuasai di Akhir Bulan

  • Menghitung dan menginterpretasikan mean, median, dan mode dari sebuah dataset nyata
  • Memilih ukuran pemusatan yang tepat tergantung karakteristik data (ada outlier atau tidak)
  • Menjelaskan sebaran data menggunakan range, varians, standar deviasi, dan IQR
  • Membaca dan membuat boxplot serta histogram untuk melihat distribusi data secara visual
  • Memahami konsep distribusi normal dan mengapa distribusi ini sering muncul di dunia nyata
  • Menghitung dan menginterpretasikan korelasi antara dua variabel secara benar
  • Menghindari jebakan umum: korelasi bukan kausalitas, mean yang menyesatkan karena outlier
  • Mengimplementasikan semua kalkulasi di atas menggunakan Python (NumPy dan statistik bawaan)
Refleksi Awal: Coba Abbas pikirkan: jika nilai ujian 30 siswa rata-ratanya 75, apakah artinya semua siswa pintar? Apa yang tidak terlihat dari satu angka rata-rata itu?

Minggu 1: Statistik Deskriptif: Mean, Median, Mode

Minggu pertama membangun fondasi paling dasar: ukuran pemusatan data. Abbas akan belajar menghitung, membedakan, dan memilih ukuran yang paling jujur untuk berbagai jenis data.

Hari 1
Apa Itu Statistik dan Mengapa Data Perlu Diringkas

Abbas mulai dengan pertanyaan mendasar: kenapa kita tidak cukup melihat semua data satu per satu?

  • Baca artikel pendek tentang perbedaan statistik deskriptif vs inferensial (cukup 15 menit)
  • Buat dataset manual: catat nilai ulangan 10 teman (boleh fiksi), simpan sebagai list Python
  • Eksplorasi fungsi bawaan Python: len(), sum(), min(), max() pada list tersebut
  • Tulis di notebook: apa pertanyaan yang bisa dijawab hanya dari 4 fungsi itu?
# Dataset nilai ulangan 10 siswa
nilai = [72, 85, 60, 91, 78, 85, 55, 88, 70, 85]

# Eksplorasi dasar
print("Jumlah siswa:", len(nilai))
print("Total nilai:", sum(nilai))
print("Nilai terendah:", min(nilai))
print("Nilai tertinggi:", max(nilai))
print("Rentang nilai:", max(nilai) - min(nilai))
Konsep: Statistik deskriptif bukan soal mencari kebenaran mutlak, tapi soal meringkas realita menjadi cerita yang bisa dipahami. Setiap angka ringkasan pasti kehilangan sebagian informasi, tugas Abbas adalah tahu informasi apa yang hilang.
Hari 2
Mean: Rata-rata dan Jebakan Outlier

Abbas menghitung mean secara manual dan dengan Python, lalu langsung melihat bagaimana satu angka ekstrem bisa merusak cerita.

  • Hitung mean dataset nilai dari Hari 1 secara manual (jumlah dibagi banyak data)
  • Verifikasi dengan Python menggunakan sum()/len() dan juga modul statistics
  • Tambahkan satu outlier ke dataset (misalnya nilai 20) dan hitung ulang mean, amati perubahan
  • Cari contoh nyata dari berita atau kehidupan sehari-hari di mana "rata-rata" bisa menyesatkan
import statistics

nilai = [72, 85, 60, 91, 78, 85, 55, 88, 70, 85]

# Hitung mean
mean_manual = sum(nilai) / len(nilai)
mean_lib = statistics.mean(nilai)

print(f"Mean manual: {mean_manual:.2f}")
print(f"Mean library: {mean_lib:.2f}")

# Tambah outlier
nilai_dengan_outlier = nilai + [20]
mean_baru = statistics.mean(nilai_dengan_outlier)
print(f"\nSetelah outlier ditambah:")
print(f"Mean baru: {mean_baru:.2f}")
print(f"Perubahan: {mean_baru - mean_manual:.2f} poin")
Tantangan: Abbas mencari data gaji pegawai di Indonesia (bisa dari BPS atau artikel online). Hitung mean gajinya. Apakah mean itu mewakili gaji "kebanyakan orang"? Tulis analisis singkat 3-5 kalimat.
Hari 3
Median: Ukuran yang Tahan Banting

Abbas belajar median sebagai alternatif yang lebih "jujur" untuk data yang memiliki outlier atau distribusi condong.

  • Hitung median secara manual: urutkan data, ambil nilai tengah (atau rata-rata dua nilai tengah jika genap)
  • Bandingkan median dataset asli vs dataset dengan outlier, perhatikan bedanya dengan mean
  • Gunakan statistics.median() untuk memverifikasi
  • Diskusikan di notebook: kapan Abbas lebih memilih median daripada mean?
import statistics

nilai = [72, 85, 60, 91, 78, 85, 55, 88, 70, 85]
nilai_dengan_outlier = nilai + [20]

# Perbandingan mean vs median
print("=== Dataset Asli ===")
print(f"Mean:   {statistics.mean(nilai):.2f}")
print(f"Median: {statistics.median(nilai):.2f}")

print("\n=== Setelah Outlier (nilai 20) ===")
print(f"Mean:   {statistics.mean(nilai_dengan_outlier):.2f}")
print(f"Median: {statistics.median(nilai_dengan_outlier):.2f}")

# Mean turun drastis, median hampir tidak berubah
print("\n=> Median lebih stabil terhadap outlier")
Tips: Saat Abbas membaca laporan yang menyebut "pendapatan rata-rata", selalu tanya: apakah itu mean atau median? Laporan resmi BPS untuk gaji biasanya menggunakan median justru karena lebih representatif.
Hari 4
Mode: Nilai yang Paling Sering Muncul

Abbas mempelajari mode, yang berguna khususnya untuk data kategorikal dan distribusi multimodal.

  • Hitung mode dari dataset nilai secara manual (hitung frekuensi masing-masing nilai)
  • Gunakan statistics.mode() dan statistics.multimode() untuk dataset yang memiliki beberapa mode
  • Buat contoh data kategorikal: warna baju favorit 20 orang, cari mode-nya
  • Pahami mengapa mode adalah satu-satunya ukuran pemusatan yang berlaku untuk data nominal
import statistics
from collections import Counter

nilai = [72, 85, 60, 91, 78, 85, 55, 88, 70, 85]

# Mode nilai ujian
print("Mode nilai:", statistics.mode(nilai))
print("Semua mode:", statistics.multimode(nilai))

# Frekuensi manual
frekuensi = Counter(nilai)
print("\nFrekuensi tiap nilai:")
for angka, jumlah in sorted(frekuensi.items()):
    print(f"  {angka}: {'*' * jumlah} ({jumlah}x)")

# Contoh data kategorikal
warna = ["biru", "merah", "biru", "hijau", "biru", "merah", "merah", "biru"]
print("\nWarna favorit mode:", statistics.mode(warna))
Refleksi: Jika Abbas diminta melaporkan "nilai paling umum" di kelas, mana yang lebih berguna: mean, median, atau mode? Apakah jawabannya selalu sama untuk semua konteks?
Hari 5
Latihan Gabungan: Mean, Median, Mode pada Data Nyata

Abbas mengintegrasikan ketiga ukuran pemusatan dalam satu analisis menggunakan dataset yang lebih besar dan nyata.

  • Unduh atau buat dataset sederhana: harga jajanan di kantin sekolah (minimal 20 item)
  • Hitung ketiga ukuran pemusatan dan bandingkan hasilnya
  • Buat fungsi Python ringkasan_pemusatan(data) yang mengembalikan dictionary berisi ketiganya
  • Tulis interpretasi: apa cerita yang bisa Abbas sampaikan dari ketiga angka itu?
import statistics

def ringkasan_pemusatan(data, label="Data"):
    """Mengembalikan ringkasan mean, median, mode."""
    hasil = {
        "mean": statistics.mean(data),
        "median": statistics.median(data),
        "mode": statistics.multimode(data),
        "n": len(data)
    }
    print(f"\n=== Ringkasan Pemusatan: {label} ===")
    print(f"Jumlah data  : {hasil['n']}")
    print(f"Mean         : {hasil['mean']:.2f}")
    print(f"Median       : {hasil['median']:.2f}")
    print(f"Mode         : {hasil['mode']}")
    return hasil

# Harga jajanan kantin (dalam ribuan rupiah)
harga_kantin = [3, 5, 5, 7, 3, 10, 5, 8, 3, 5,
                6, 4, 5, 3, 7, 5, 4, 6, 5, 25]

ringkasan_pemusatan(harga_kantin, "Harga Jajanan Kantin")
Tantangan Akhir Minggu: Dari dataset harga kantin, Abbas diminta menjelaskan kepada kepala sekolah: "Berapa harga khas jajanan di kantin kita?" Tulis penjelasan singkat yang menggunakan minimal dua dari tiga ukuran pemusatan, dan jelaskan mengapa Abbas memilih ukuran itu.

Minggu 2: Sebaran Data

Ukuran pemusatan saja tidak cukup. Dua dataset bisa memiliki mean yang sama tetapi distribusi yang sangat berbeda. Minggu ini Abbas belajar mengukur dan memahami sebaran data.

Hari 1
Range dan Mengapa Ukuran Sebaran Penting

Abbas memulai dengan ukuran sebaran paling sederhana: range, lalu langsung melihat keterbatasannya.

  • Buat dua dataset dengan mean yang sama tapi "terasa" berbeda saat dibaca satu per satu
  • Hitung range (max - min) untuk keduanya, amati apakah range sudah cukup membedakan
  • Diskusikan kasus nyata: dua kelas dengan nilai rata-rata sama, tapi satu lebih "merata" dari yang lain
  • Catat di notebook: apa informasi yang tidak bisa ditangkap oleh range saja?
import statistics

# Dua kelas dengan mean sama tapi sebaran berbeda
kelas_A = [70, 72, 74, 75, 76, 78, 78, 79, 80, 78]
kelas_B = [40, 50, 60, 70, 80, 90, 95, 98, 82, 75]

for nama, data in [("Kelas A", kelas_A), ("Kelas B", kelas_B)]:
    print(f"\n{nama}:")
    print(f"  Data  : {sorted(data)}")
    print(f"  Mean  : {statistics.mean(data):.2f}")
    print(f"  Range : {max(data) - min(data)}")

print("\n=> Mean sama, range berbeda. Tapi apakah range cukup?")
Konsep: Range hanya melihat ujung-ujung data. Dua dataset bisa memiliki range yang sama tetapi sebaran internal yang sangat berbeda. Itulah mengapa kita butuh ukuran yang melihat "seberapa jauh setiap titik dari pusat."
Hari 2
Varians dan Standar Deviasi

Abbas mempelajari dua ukuran sebaran paling penting dalam statistik: varians dan standar deviasi, lengkap dengan intuisi mengapa satuan standar deviasi lebih mudah dipahami.

  • Hitung varians secara manual: rata-rata kuadrat deviasi setiap titik dari mean
  • Hitung standar deviasi sebagai akar kuadrat varians
  • Bandingkan hasil manual dengan statistics.stdev() dan statistics.pstdev() (pahami bedanya: sampel vs populasi)
  • Interpretasikan: standar deviasi berarti "rata-rata seberapa jauh data dari mean"
import statistics
import math

data = [70, 72, 74, 75, 76, 78, 78, 79, 80, 78]
mean = statistics.mean(data)

# Varians manual (populasi)
deviasi_kuadrat = [(x - mean) ** 2 for x in data]
varians_manual = sum(deviasi_kuadrat) / len(data)
stdev_manual = math.sqrt(varians_manual)

print(f"Mean: {mean:.2f}")
print(f"\nDeviasi tiap titik: {[round(x - mean, 2) for x in data]}")
print(f"Deviasi kuadrat: {[round(d, 2) for d in deviasi_kuadrat]}")
print(f"\nVarians (populasi) manual : {varians_manual:.4f}")
print(f"Stdev  (populasi) manual  : {stdev_manual:.4f}")
print(f"\nVarians (sampel) library  : {statistics.variance(data):.4f}")
print(f"Stdev  (sampel) library   : {statistics.stdev(data):.4f}")
Tips: Gunakan pstdev() / pvariance() saat data adalah seluruh populasi (semua siswa di kelas itu). Gunakan stdev() / variance() saat data adalah sampel dari populasi yang lebih besar (30 siswa mewakili seluruh angkatan).
Hari 3
IQR, Kuartil, dan Deteksi Outlier

Abbas mempelajari interquartile range (IQR) sebagai ukuran sebaran yang tahan terhadap outlier, dan menggunakannya untuk mendeteksi nilai-nilai ekstrem secara sistematis.

  • Pahami konsep kuartil: Q1 (25%), Q2 (median/50%), Q3 (75%)
  • Hitung IQR = Q3 - Q1 untuk dataset nilai ujian
  • Terapkan aturan outlier: nilai di luar [Q1 - 1.5*IQR, Q3 + 1.5*IQR] dianggap outlier
  • Identifikasi outlier dari dataset harga kantin yang dibuat di Minggu 1
import statistics

def hitung_kuartil(data):
    data_sorted = sorted(data)
    n = len(data_sorted)
    q1 = statistics.median(data_sorted[:n//2])
    q2 = statistics.median(data_sorted)
    q3 = statistics.median(data_sorted[(n+1)//2:])
    return q1, q2, q3

harga = [3, 5, 5, 7, 3, 10, 5, 8, 3, 5,
         6, 4, 5, 3, 7, 5, 4, 6, 5, 25]

q1, q2, q3 = hitung_kuartil(harga)
iqr = q3 - q1

batas_bawah = q1 - 1.5 * iqr
batas_atas  = q3 + 1.5 * iqr

print(f"Q1: {q1}, Q2: {q2}, Q3: {q3}")
print(f"IQR: {iqr}")
print(f"Batas normal: [{batas_bawah:.2f}, {batas_atas:.2f}]")

outlier = [x for x in harga if x < batas_bawah or x > batas_atas]
print(f"\nOutlier terdeteksi: {outlier}")
Tantangan: Dari dataset harga kantin, hapus semua outlier yang terdeteksi, lalu hitung ulang mean dan standar deviasi. Seberapa besar perubahan yang terjadi? Tulis kesimpulan: apakah outlier itu sebuah kesalahan data atau memang data valid yang perlu dipertahankan?
Hari 4
Boxplot: Visualisasi Sebaran Data Secara Sekilas

Abbas mempelajari boxplot sebagai cara visual untuk melihat median, IQR, dan outlier dalam satu gambar sederhana.

  • Gambar boxplot secara manual di kertas: garis median, kotak IQR, whisker, dan titik outlier
  • Install dan gunakan matplotlib untuk membuat boxplot pertama Abbas
  • Buat perbandingan boxplot dua dataset: Kelas A vs Kelas B dari Hari 1
  • Latihan membaca boxplot orang lain: cari 1 boxplot dari artikel atau laporan, deskripsikan apa yang terlihat
import matplotlib.pyplot as plt

kelas_A = [70, 72, 74, 75, 76, 78, 78, 79, 80, 78]
kelas_B = [40, 50, 60, 70, 80, 90, 95, 98, 82, 75]

# Boxplot perbandingan
fig, ax = plt.subplots(figsize=(7, 5))
ax.boxplot(
    [kelas_A, kelas_B],
    labels=["Kelas A", "Kelas B"],
    patch_artist=True,
    boxprops=dict(facecolor="#e8f4f8")
)
ax.set_title("Perbandingan Sebaran Nilai Dua Kelas")
ax.set_ylabel("Nilai")
ax.grid(axis="y", alpha=0.4)
plt.tight_layout()
plt.savefig("boxplot_kelas.png", dpi=100)
plt.show()
print("Boxplot disimpan sebagai boxplot_kelas.png")
Konsep: Boxplot adalah ringkasan visual lima angka sekaligus: minimum, Q1, median, Q3, maksimum. Dalam satu pandang sekilas, Abbas bisa langsung melihat apakah data condong ke kiri, ke kanan, atau simetris, dan apakah ada outlier.
Hari 5
Latihan Gabungan: Laporan Sebaran Data Lengkap

Abbas membuat fungsi analisis sebaran lengkap yang mengintegrasikan semua ukuran yang dipelajari minggu ini.

  • Buat fungsi laporan_sebaran(data, label) yang mencetak semua ukuran sebaran dalam format yang rapi
  • Terapkan fungsi itu pada tiga dataset berbeda: nilai ujian, harga kantin, dan satu dataset pilihan Abbas
  • Buat satu boxplot untuk masing-masing dataset dan simpan sebagai file gambar
  • Tulis narasi singkat untuk tiap dataset: "Data ini tergolong... karena standar deviasinya..."
import statistics
import math

def laporan_sebaran(data, label="Dataset"):
    data_s = sorted(data)
    n = len(data_s)
    q1 = statistics.median(data_s[:n//2])
    q3 = statistics.median(data_s[(n+1)//2:])
    iqr = q3 - q1

    print(f"\n{'='*40}")
    print(f"Laporan Sebaran: {label}")
    print(f"{'='*40}")
    print(f"N              : {n}")
    print(f"Range          : {max(data) - min(data)}")
    print(f"Varians (samp) : {statistics.variance(data):.4f}")
    print(f"Stdev   (samp) : {statistics.stdev(data):.4f}")
    print(f"Q1             : {q1}")
    print(f"Median (Q2)    : {statistics.median(data)}")
    print(f"Q3             : {q3}")
    print(f"IQR            : {iqr}")

    batas_atas = q3 + 1.5 * iqr
    batas_bawah = q1 - 1.5 * iqr
    outlier = [x for x in data if x < batas_bawah or x > batas_atas]
    print(f"Outlier        : {outlier if outlier else 'Tidak ada'}")

nilai = [72, 85, 60, 91, 78, 85, 55, 88, 70, 85]
harga = [3, 5, 5, 7, 3, 10, 5, 8, 3, 5, 6, 4, 5, 3, 7, 5, 4, 6, 5, 25]

laporan_sebaran(nilai, "Nilai Ujian")
laporan_sebaran(harga, "Harga Jajanan Kantin")
Refleksi Akhir Minggu: Dari semua ukuran sebaran yang Abbas pelajari minggu ini (range, varians, stdev, IQR), mana yang menurut Abbas paling berguna? Apakah jawabannya tergantung konteks? Tulis pendapat Abbas dalam 5 kalimat.

Minggu 3: Distribusi dan Visualisasi Dasar

Minggu ini Abbas naik level: dari menghitung angka ke memahami bentuk data secara visual. Abbas akan belajar distribusi normal, histogram, dan bagaimana bentuk distribusi memengaruhi pilihan analisis.

Hari 1
Histogram: Melihat Bentuk Data

Abbas membuat histogram pertamanya dan belajar membaca bentuk distribusi dari gambar.

  • Pahami perbedaan histogram dengan bar chart: histogram untuk data kontinu, bar chart untuk kategoris
  • Buat histogram nilai ujian 30 siswa menggunakan matplotlib
  • Coba ubah jumlah bin (10, 5, 20) dan amati bagaimana bentuk grafik berubah
  • Identifikasi dari histogram: apakah distribusinya simetris, condong kiri, atau condong kanan?
import matplotlib.pyplot as plt
import random

# Buat dataset nilai 30 siswa
random.seed(42)
nilai_30 = [random.gauss(75, 10) for _ in range(30)]
nilai_30 = [max(0, min(100, round(n))) for n in nilai_30]

fig, axes = plt.subplots(1, 3, figsize=(14, 4))
for ax, bins, title in zip(axes, [5, 10, 20],
                            ["5 Bin", "10 Bin", "20 Bin"]):
    ax.hist(nilai_30, bins=bins, color="#4a90d9", edgecolor="white", alpha=0.85)
    ax.set_title(f"Histogram {title}")
    ax.set_xlabel("Nilai")
    ax.set_ylabel("Frekuensi")
    ax.grid(axis="y", alpha=0.3)

plt.tight_layout()
plt.savefig("histogram_bins.png", dpi=100)
plt.show()
print("Disimpan: histogram_bins.png")
Konsep: Jumlah bin yang terlalu sedikit menyembunyikan pola, terlalu banyak menciptakan noise. Aturan umum: akar kuadrat dari jumlah data adalah titik awal yang baik. Untuk 30 data, sekitar 5-6 bin sudah cukup informatif.
Hari 2
Distribusi Normal: Mengapa Bell Curve Ada di Mana-mana

Abbas mempelajari distribusi normal, memahami mengapa distribusi ini begitu sering muncul di alam dan data sosial, serta mengenal aturan 68-95-99.7.

  • Baca singkat tentang Central Limit Theorem: mengapa rata-rata banyak sampel cenderung berdistribusi normal
  • Gambar kurva distribusi normal menggunakan numpy dan matplotlib
  • Tandai zona: 1 stdev (68%), 2 stdev (95%), 3 stdev (99.7%) pada kurva
  • Cari 3 contoh nyata fenomena yang berdistribusi normal di kehidupan sehari-hari
import numpy as np
import matplotlib.pyplot as plt

# Parameter distribusi normal
mu, sigma = 75, 10
x = np.linspace(mu - 4*sigma, mu + 4*sigma, 300)
y = (1 / (sigma * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x - mu) / sigma)**2)

fig, ax = plt.subplots(figsize=(9, 5))
ax.plot(x, y, color="#2c6fad", linewidth=2)

# Zona 68%, 95%, 99.7%
for n_std, color, label in [(1, "#cce5ff", "68% (1 stdev)"),
                             (2, "#e8f4f8", "95% (2 stdev)"),
                             (3, "#f5fafe", "99.7% (3 stdev)")]:
    ax.fill_between(x, y,
                    where=(x >= mu - n_std*sigma) & (x <= mu + n_std*sigma),
                    color=color, label=label)

ax.axvline(mu, color="#555", linestyle="--", linewidth=1, label=f"Mean = {mu}")
ax.set_title("Distribusi Normal: Aturan 68-95-99.7")
ax.set_xlabel("Nilai")
ax.set_ylabel("Densitas")
ax.legend(fontsize=9)
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig("distribusi_normal.png", dpi=100)
plt.show()
Refleksi: Abbas mencoba menjawab: apakah nilai ujian seluruh siswa di Indonesia berdistribusi normal? Apa yang mungkin menyebabkan distribusinya tidak sempurna normal? Tulis 4 kalimat analisis Abbas.
Hari 3
Skewness: Distribusi yang Condong dan Artinya

Abbas mempelajari skewness (kemiringan distribusi) dan belajar membaca apa yang distribusi condong kiri atau kanan katakan tentang data.

  • Pahami arti positif skew (ekor di kanan) dan negatif skew (ekor di kiri)
  • Buat dua dataset yang condong berbeda arah menggunakan NumPy
  • Hitung skewness menggunakan rumus manual atau scipy.stats.skew()
  • Hubungkan skewness dengan posisi relatif mean dan median: mana yang lebih besar saat skew positif?
import numpy as np
import matplotlib.pyplot as plt

# Distribusi condong kanan (positif skew): banyak nilai kecil, sedikit sangat besar
data_skew_kanan = np.random.exponential(scale=5, size=300)

# Distribusi condong kiri (negatif skew): banyak nilai besar, sedikit sangat kecil
data_skew_kiri = 10 - np.random.exponential(scale=2, size=300)

fig, axes = plt.subplots(1, 2, figsize=(11, 4))

for ax, data, title, color in zip(
    axes,
    [data_skew_kanan, data_skew_kiri],
    ["Skew Positif (condong kanan)", "Skew Negatif (condong kiri)"],
    ["#f4a261", "#457b9d"]
):
    ax.hist(data, bins=30, color=color, edgecolor="white", alpha=0.85)
    ax.axvline(np.mean(data), color="red", linestyle="--", label=f"Mean={np.mean(data):.1f}")
    ax.axvline(np.median(data), color="green", linestyle="-.", label=f"Median={np.median(data):.1f}")
    ax.set_title(title)
    ax.legend(fontsize=8)
    ax.grid(axis="y", alpha=0.3)

plt.tight_layout()
plt.savefig("skewness_compare.png", dpi=100)
plt.show()
Tantangan: Abbas mengambil dataset harga rumah dari Kaggle (atau data fiksi harga 50 rumah). Buat histogramnya dan tentukan: apakah distribusinya positif skew, negatif skew, atau mendekati simetris? Jelaskan mengapa hasil itu masuk akal secara konteks.
Hari 4
Korelasi: Hubungan Antar Variabel

Abbas mempelajari korelasi Pearson sebagai cara mengukur kekuatan dan arah hubungan antara dua variabel numerik, sekaligus memahami jebakan terbesar: korelasi bukan berarti kausalitas.

  • Pahami skala korelasi: -1 (negatif sempurna), 0 (tidak ada hubungan), +1 (positif sempurna)
  • Buat scatter plot empat pasang variabel dengan tingkat korelasi berbeda
  • Hitung korelasi secara manual menggunakan rumus kovarians dibagi perkalian standar deviasi
  • Cari satu contoh "spurious correlation" yang lucu dari tylervigen.com atau sumber lain
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(7)
n = 50

# Empat skenario korelasi
jam_belajar = np.random.uniform(1, 10, n)
nilai_tinggi  = jam_belajar * 8 + np.random.normal(0, 5, n)   # r ~ +0.95
nilai_sedang  = jam_belajar * 4 + np.random.normal(0, 15, n)  # r ~ +0.5
nilai_tidak   = np.random.uniform(50, 100, n)                  # r ~ 0
nilai_negatif = -jam_belajar * 6 + 90 + np.random.normal(0, 8, n)  # r ~ -0.9

skenario = [
    (nilai_tinggi,  "Korelasi Kuat Positif"),
    (nilai_sedang,  "Korelasi Sedang Positif"),
    (nilai_tidak,   "Hampir Tidak Berkorelasi"),
    (nilai_negatif, "Korelasi Kuat Negatif"),
]

fig, axes = plt.subplots(2, 2, figsize=(10, 8))
for ax, (y, title) in zip(axes.flat, skenario):
    r = np.corrcoef(jam_belajar, y)[0, 1]
    ax.scatter(jam_belajar, y, alpha=0.6, color="#4a90d9", s=30)
    ax.set_title(f"{title}
r = {r:.2f}", fontsize=10)
    ax.set_xlabel("Jam Belajar")
    ax.set_ylabel("Nilai")
    ax.grid(alpha=0.3)

plt.tight_layout()
plt.savefig("korelasi_scatter.png", dpi=100)
plt.show()
Konsep: Korelasi hanya mengukur hubungan linear antara dua variabel. Dua variabel bisa sangat berkorelasi karena keduanya dipengaruhi variabel ketiga yang tidak terlihat, disebut confounding variable. Abbas tidak boleh pernah membuat klaim sebab-akibat hanya dari angka korelasi.
Hari 5
Ringkasan Visual: Dashboard Mini Distribusi Data

Abbas membuat satu output visual lengkap yang menggabungkan histogram, boxplot, dan scatter plot dalam satu figure, menjadi mini laporan analisis distribusi.

  • Pilih satu dataset (nilai 30 siswa atau harga kantin) sebagai bahan utama
  • Buat figure dengan 3 subplot: histogram, boxplot horizontal, dan scatter plot nilai vs nomor urut
  • Tambahkan anotasi teks langsung di grafik: nilai mean, median, dan stdev
  • Simpan hasil sebagai PNG resolusi tinggi, ini menjadi portofolio visual pertama Abbas
import numpy as np
import matplotlib.pyplot as plt
import statistics

random_seed = 42
np.random.seed(random_seed)
nilai = sorted([max(40, min(100, round(np.random.normal(75, 12))))
                for _ in range(30)])

mean_val   = statistics.mean(nilai)
median_val = statistics.median(nilai)
stdev_val  = statistics.stdev(nilai)

fig = plt.figure(figsize=(14, 5))
fig.suptitle("Dashboard Distribusi: Nilai Ujian 30 Siswa", fontsize=13, fontweight="bold")

# Subplot 1: Histogram
ax1 = fig.add_subplot(1, 3, 1)
ax1.hist(nilai, bins=8, color="#4a90d9", edgecolor="white", alpha=0.85)
ax1.axvline(mean_val, color="red", linestyle="--", linewidth=1.5, label=f"Mean={mean_val:.1f}")
ax1.axvline(median_val, color="green", linestyle="-.", linewidth=1.5, label=f"Median={median_val:.1f}")
ax1.set_title("Histogram")
ax1.legend(fontsize=8)
ax1.grid(axis="y", alpha=0.3)

# Subplot 2: Boxplot
ax2 = fig.add_subplot(1, 3, 2)
ax2.boxplot(nilai, vert=False, patch_artist=True,
            boxprops=dict(facecolor="#cce5ff"))
ax2.set_title("Boxplot")
ax2.set_xlabel("Nilai")
ax2.grid(axis="x", alpha=0.3)
ax2.annotate(f"Stdev = {stdev_val:.1f}", xy=(0.5, 0.1),
             xycoords="axes fraction", ha="center", fontsize=9)

# Subplot 3: Scatter
ax3 = fig.add_subplot(1, 3, 3)
ax3.scatter(range(1, 31), nilai, color="#e07b39", s=40, alpha=0.8)
ax3.axhline(mean_val, color="red", linestyle="--", linewidth=1, label=f"Mean")
ax3.set_title("Nilai per Urutan Rank")
ax3.set_xlabel("Rank Siswa")
ax3.set_ylabel("Nilai")
ax3.legend(fontsize=8)
ax3.grid(alpha=0.3)

plt.tight_layout()
plt.savefig("dashboard_distribusi.png", dpi=150)
plt.show()
print("Disimpan: dashboard_distribusi.png")
Tantangan Akhir Minggu: Abbas mengambil dataset lain (boleh download dari Kaggle atau BPS), membuat dashboard serupa, lalu menulis interpretasi singkat 5-7 kalimat yang menjelaskan distribusi data itu kepada seseorang yang tidak paham statistik. Simpan interpretasi di file teks terpisah bersama gambar.

Minggu 4: Python untuk Statistik

Minggu penutup bulan ini Abbas menyatukan semua yang dipelajari ke dalam kode Python yang rapi, reproducible, dan bisa digunakan kembali. Fokus: NumPy, alur analisis terstruktur, dan mini project akhir bulan.

Hari 1
NumPy untuk Statistik: Array dan Operasi Vektor

Abbas beralih dari modul statistics bawaan Python ke NumPy, yang jauh lebih cepat dan fleksibel untuk data berskala besar.

  • Pahami perbedaan NumPy array dengan Python list: broadcasting, operasi vektor, efisiensi memori
  • Konversi dataset nilai dan harga ke NumPy array, bandingkan kecepatan operasi sederhana
  • Gunakan np.mean(), np.median(), np.std(), np.percentile() untuk menggantikan kalkulasi manual
  • Buat array 2D (matriks) dari dataset dua variabel dan hitung statistik per baris dan per kolom
import numpy as np

# Konversi ke NumPy array
nilai = np.array([72, 85, 60, 91, 78, 85, 55, 88, 70, 85])

# Statistik dasar NumPy
print("=== NumPy Statistics ===")
print(f"Mean        : {np.mean(nilai):.2f}")
print(f"Median      : {np.median(nilai):.2f}")
print(f"Stdev (pop) : {np.std(nilai):.2f}")
print(f"Stdev (samp): {np.std(nilai, ddof=1):.2f}")
print(f"Varians     : {np.var(nilai, ddof=1):.2f}")
print(f"Min / Max   : {np.min(nilai)} / {np.max(nilai)}")
print(f"Q1  / Q3    : {np.percentile(nilai, 25)} / {np.percentile(nilai, 75)}")

# Operasi vektor: normalisasi nilai ke skala 0-1
nilai_norm = (nilai - np.min(nilai)) / (np.max(nilai) - np.min(nilai))
print(f"
Nilai ternormalisasi:")
print(np.round(nilai_norm, 3))
Tips: Parameter ddof=1 di np.std() berarti degrees of freedom = 1, yang menghasilkan standar deviasi sampel (sama dengan statistics.stdev()). Tanpa ddof, NumPy menghitung standar deviasi populasi secara default.
Hari 2
NumPy Lanjutan: Filtering, Masking, dan Aggregasi

Abbas mempelajari teknik filtering berbasis kondisi di NumPy, yang menjadi fondasi penting sebelum belajar Pandas di bulan mendatang.

  • Gunakan boolean masking untuk memfilter array: pilih semua nilai di atas mean
  • Hitung statistik untuk subset data yang difilter
  • Gunakan np.where() untuk membuat label berdasarkan kondisi (lulus/tidak lulus)
  • Latihan: dari 100 nilai random, hitung berapa persen yang masuk zona mean +/- 1 stdev
import numpy as np

np.random.seed(21)
nilai_100 = np.random.normal(loc=75, scale=10, size=100).clip(0, 100).round().astype(int)

mean_val  = np.mean(nilai_100)
stdev_val = np.std(nilai_100, ddof=1)

# Boolean masking
di_atas_mean = nilai_100[nilai_100 > mean_val]
di_bawah_mean = nilai_100[nilai_100 <= mean_val]

print(f"Mean: {mean_val:.2f}, Stdev: {stdev_val:.2f}")
print(f"Di atas mean: {len(di_atas_mean)} siswa ({len(di_atas_mean)}%)")
print(f"Di bawah mean: {len(di_bawah_mean)} siswa")

# Zona 1 stdev
dalam_1_stdev = nilai_100[
    (nilai_100 >= mean_val - stdev_val) &
    (nilai_100 <= mean_val + stdev_val)
]
print(f"
Dalam 1 stdev: {len(dalam_1_stdev)} siswa ({len(dalam_1_stdev):.0f}%)")

# Label lulus/tidak
label = np.where(nilai_100 >= 70, "Lulus", "Tidak Lulus")
lulus = np.sum(label == "Lulus")
print(f"
Lulus (>=70): {lulus} dari 100 siswa")
Konsep: Boolean masking adalah salah satu teknik paling sering dipakai saat membersihkan dan menganalisis data nyata. Saat Abbas nanti bekerja dengan Pandas, operasi ini akan menjadi df[df["kolom"] > nilai], mekanismenya sama persis dengan yang dipelajari hari ini.
Hari 3
Correlation Matrix dan Heatmap Sederhana

Abbas belajar menghitung matriks korelasi untuk banyak variabel sekaligus dan memvisualisasikannya sebagai heatmap, teknik yang sangat umum dipakai dalam eksplorasi data awal.

  • Buat dataset multi-variabel: 4 kolom (jam belajar, nilai, jam tidur, jarak ke sekolah) untuk 50 siswa
  • Hitung matriks korelasi menggunakan np.corrcoef()
  • Visualisasikan sebagai heatmap menggunakan matplotlib.imshow()
  • Interpretasikan: pasangan variabel mana yang paling berkorelasi? Apakah arahnya masuk akal?
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(5)
n = 50

jam_belajar = np.random.uniform(1, 8, n)
nilai       = jam_belajar * 7 + np.random.normal(0, 8, n)
jam_tidur   = np.random.uniform(5, 9, n)
jarak_km    = np.random.uniform(1, 20, n)

# Stack jadi matriks (4 x n), lalu transpose ke (n x 4)
data_matrix = np.column_stack([jam_belajar, nilai, jam_tidur, jarak_km])
labels = ["Jam Belajar", "Nilai", "Jam Tidur", "Jarak (km)"]

# Correlation matrix: np.corrcoef butuh (n_vars x n_obs)
corr_matrix = np.corrcoef(data_matrix.T)

fig, ax = plt.subplots(figsize=(6, 5))
im = ax.imshow(corr_matrix, cmap="coolwarm", vmin=-1, vmax=1)
plt.colorbar(im, ax=ax)
ax.set_xticks(range(4))
ax.set_yticks(range(4))
ax.set_xticklabels(labels, rotation=30, ha="right", fontsize=9)
ax.set_yticklabels(labels, fontsize=9)

for i in range(4):
    for j in range(4):
        ax.text(j, i, f"{corr_matrix[i, j]:.2f}",
                ha="center", va="center", fontsize=9,
                color="white" if abs(corr_matrix[i, j]) > 0.6 else "black")

ax.set_title("Correlation Matrix: Variabel Siswa")
plt.tight_layout()
plt.savefig("heatmap_korelasi.png", dpi=120)
plt.show()
Refleksi: Dari heatmap di atas, variabel mana yang berkorelasi paling kuat dengan nilai? Apakah ada korelasi yang mengejutkan atau tidak sesuai dugaan Abbas? Tulis interpretasi 4-5 kalimat.
Hari 4
Standardisasi dan Z-Score

Abbas mempelajari z-score sebagai cara mengubah data ke skala standar sehingga variabel dari satuan berbeda bisa dibandingkan secara adil.

  • Pahami formula z-score: (x - mean) / stdev, artinya "berapa stdev x dari mean"
  • Hitung z-score manual untuk dataset nilai, kemudian verifikasi dengan NumPy
  • Interpretasikan: z-score = 2 artinya nilai tersebut 2 standar deviasi di atas rata-rata
  • Gunakan z-score untuk membandingkan performa dua siswa di mata pelajaran yang berbeda skalanya
import numpy as np

nilai_mat   = np.array([85, 72, 90, 65, 78])
nilai_ing   = np.array([75, 80, 70, 90, 65])
nama_siswa  = ["Ari", "Budi", "Citra", "Dian", "Eka"]

def z_score(data):
    return (data - np.mean(data)) / np.std(data, ddof=1)

z_mat = z_score(nilai_mat)
z_ing = z_score(nilai_ing)

print(f"{'Nama':<8} {'Mat':>6} {'z-Mat':>7} {'Ing':>6} {'z-Ing':>7} {'Total z':>8}")
print("-" * 48)
for i, nama in enumerate(nama_siswa):
    total_z = z_mat[i] + z_ing[i]
    print(f"{nama:<8} {nilai_mat[i]:>6} {z_mat[i]:>+7.2f} "
          f"{nilai_ing[i]:>6} {z_ing[i]:>+7.2f} {total_z:>+8.2f}")

# Siapa yang performanya paling baik secara keseluruhan?
terbaik = nama_siswa[np.argmax(z_mat + z_ing)]
print(f"
Performa terbaik keseluruhan: {terbaik}")
Tantangan: Abbas membuat dataset 5 siswa dengan 3 mata pelajaran berbeda (skala dan rata-rata berbeda). Gunakan z-score untuk menentukan ranking siswa secara keseluruhan, lalu bandingkan hasilnya dengan ranking berdasarkan total nilai mentah. Apakah rankingnya berbeda? Mengapa?
Hari 5
Mini Project: Laporan Statistik Deskriptif Bulan 2

Abbas mengintegrasikan semua yang dipelajari bulan ini menjadi satu skrip analisis lengkap yang bisa langsung dijalankan ulang dengan dataset berbeda.

  • Pilih satu dataset nyata minimal 50 baris (nilai ujian sekolah, data cuaca, harga pasar, atau dari Kaggle)
  • Jalankan analisis lengkap: pemusatan, sebaran, distribusi, korelasi antar variabel
  • Buat figure empat panel: histogram, boxplot, scatter matrix, heatmap korelasi
  • Tulis ringkasan analisis minimal 200 kata dalam bentuk markdown di file laporan_bulan02.md
  • Simpan semua kode di analisis_bulan02.py dengan komentar yang jelas di setiap blok
import numpy as np
import matplotlib.pyplot as plt
import statistics

# ============================================================
# Mini Project Bulan 2: Analisis Deskriptif Dataset Nilai SMA
# Ganti dataset di bawah dengan data nyata pilihan Abbas
# ============================================================

np.random.seed(99)
# Dataset contoh: nilai 60 siswa, 3 mata pelajaran
mat = np.random.normal(72, 12, 60).clip(30, 100).round().astype(int)
ing = np.random.normal(68, 15, 60).clip(30, 100).round().astype(int)
bio = np.random.normal(75, 10, 60).clip(30, 100).round().astype(int)

variabel  = {"Matematika": mat, "Inggris": ing, "Biologi": bio}

# --- Ringkasan statistik ---
print("=" * 55)
print(f"{'Variabel':<14} {'Mean':>7} {'Median':>8} {'Stdev':>7} {'IQR':>7}")
print("-" * 55)
for nama, data in variabel.items():
    q1, q3 = np.percentile(data, [25, 75])
    print(f"{nama:<14} {np.mean(data):>7.2f} {np.median(data):>8.2f} "
          f"{np.std(data, ddof=1):>7.2f} {q3-q1:>7.2f}")

# --- Figure empat panel ---
fig, axes = plt.subplots(2, 2, figsize=(13, 9))
fig.suptitle("Laporan Statistik Deskriptif: Nilai 60 Siswa", fontsize=13, fontweight="bold")

# Panel 1: Histogram
ax = axes[0, 0]
for nama, data, color in zip(variabel.keys(), variabel.values(),
                              ["#4a90d9", "#e07b39", "#5cb85c"]):
    ax.hist(data, bins=12, alpha=0.55, label=nama, color=color, edgecolor="white")
ax.set_title("Distribusi Nilai per Mata Pelajaran")
ax.legend(fontsize=8)
ax.grid(axis="y", alpha=0.3)

# Panel 2: Boxplot
ax = axes[0, 1]
ax.boxplot(list(variabel.values()), labels=list(variabel.keys()),
           patch_artist=True,
           boxprops=dict(facecolor="#e8f4f8"))
ax.set_title("Boxplot Perbandingan")
ax.grid(axis="y", alpha=0.3)

# Panel 3: Scatter Mat vs Ing
ax = axes[1, 0]
ax.scatter(mat, ing, alpha=0.6, color="#4a90d9", s=35)
r = np.corrcoef(mat, ing)[0, 1]
ax.set_title(f"Scatter: Matematika vs Inggris (r={r:.2f})")
ax.set_xlabel("Matematika")
ax.set_ylabel("Inggris")
ax.grid(alpha=0.3)

# Panel 4: Heatmap korelasi
ax = axes[1, 1]
corr = np.corrcoef(np.column_stack([mat, ing, bio]).T)
nama_list = list(variabel.keys())
im = ax.imshow(corr, cmap="coolwarm", vmin=-1, vmax=1)
plt.colorbar(im, ax=ax)
ax.set_xticks(range(3))
ax.set_yticks(range(3))
ax.set_xticklabels(nama_list, rotation=20, ha="right", fontsize=8)
ax.set_yticklabels(nama_list, fontsize=8)
for i in range(3):
    for j in range(3):
        ax.text(j, i, f"{corr[i,j]:.2f}", ha="center", va="center", fontsize=9,
                color="white" if abs(corr[i, j]) > 0.6 else "black")
ax.set_title("Heatmap Korelasi")

plt.tight_layout()
plt.savefig("laporan_bulan02.png", dpi=150)
plt.show()
print("Selesai. Simpan juga laporan_bulan02.md dengan interpretasi teks.")
Refleksi Akhir Bulan: Abbas melihat kembali daftar kompetensi di bagian Intro. Beri tanda mana yang sudah dikuasai dengan baik, mana yang masih perlu latihan lebih. Tulis rencana satu paragraf: hal apa dari bulan ini yang akan Abbas perkuat sebelum masuk ke Bulan 3?

Buku Pendukung Bulan 02

Empat buku ini dipilih khusus untuk memperkuat pemahaman Abbas tentang statistik deskriptif, distribusi, dan cara membaca data secara kritis. Tidak harus dibaca tuntas, cukup bab yang relevan per minggu.

Statistik Konsep
Statistics (4th Edition)
David Freedman, Robert Pisani, Roger Purves
Buku ini menjelaskan statistik dari intuisi, bukan rumus. Bab tentang mean, median, standar deviasi, dan distribusi normal ditulis dengan analogi kehidupan nyata yang membuat konsep abstrak menjadi mudah dipahami. Sangat cocok untuk Abbas yang baru membangun fondasi berpikir statistis.
Berpikir Kritis
How to Lie with Statistics
Darrell Huff
Buku tipis (160 halaman) yang menunjukkan cara rata-rata, grafik, dan persentase bisa dipakai untuk menyesatkan. Abbas akan lebih waspada membaca laporan, berita, dan klaim berbasis data setelah membaca buku ini. Langsung relevan dengan topik Minggu 1 dan 2 bulan ini.
Python dan Data
Python Data Science Handbook
Jake VanderPlas
Bab 2 tentang NumPy adalah referensi terbaik untuk semua teknik array, masking, dan agregasi yang Abbas pelajari di Minggu 4. Tersedia gratis online di GitHub. Abbas bisa bookmark bagian NumPy sebagai referensi cepat saat butuh fungsi spesifik.
Visualisasi
Storytelling with Data
Cole Nussbaumer Knaflic
Buku ini mengajarkan Abbas cara membuat grafik yang benar-benar komunikatif, bukan sekadar benar secara teknis. Prinsip-prinsip visualisasi di buku ini akan membuat histogram dan scatter plot Abbas di Minggu 3 menjadi jauh lebih mudah dibaca oleh orang lain, bukan hanya oleh pembuatnya.

Tools yang Digunakan

Bulan ini Abbas memperkenalkan beberapa library Python yang akan menjadi alat kerja utama sepanjang sisa program. Pelajari instalasi dan cara impor masing-masing sejak awal.

Python statistics
Modul bawaan Python tanpa instalasi tambahan. Dipakai di Minggu 1 dan 2 untuk mean(), median(), stdev(), dan multimode(). Cocok untuk dataset kecil dan eksplorasi awal sebelum beralih ke NumPy.
NumPy
Library komputasi numerik yang menjadi fondasi hampir semua ekosistem data science Python. Abbas menggunakannya di Minggu 4 untuk operasi array, z-score, korelasi, dan generasi data acak. Instal dengan pip install numpy.
Matplotlib
Library visualisasi standar Python. Dipakai mulai Minggu 2 (boxplot) hingga Minggu 3 (histogram, scatter, heatmap) dan Minggu 4 (dashboard multi-panel). Instal dengan pip install matplotlib. Abbas akan terus menggunakannya hingga Bulan 6.
Jupyter Notebook
Lingkungan interaktif yang menggabungkan kode, output, grafik, dan catatan dalam satu dokumen. Direkomendasikan untuk semua latihan harian bulan ini agar Abbas bisa melihat hasil setiap baris kode secara langsung. Instal dengan pip install jupyter.
collections.Counter
Modul bawaan Python yang dipakai di Minggu 1 untuk menghitung frekuensi kemunculan nilai (mode manual). Juga berguna nanti saat Abbas bekerja dengan data kategorikal dan perlu menghitung distribusi nilai unik secara cepat.
Kaggle Datasets
Platform untuk mengunduh dataset nyata secara gratis. Abbas menggunakan Kaggle sebagai sumber data untuk latihan dan tantangan akhir minggu. Daftar akun gratis di kaggle.com, lalu gunakan kata kunci "student grades", "house prices", atau "sales data" untuk menemukan dataset yang relevan dengan topik bulan ini.

Tips Praktis dan Jebakan Pemula

Statistik deskriptif terlihat sederhana, tapi penuh jebakan yang bahkan analis berpengalaman kadang terjatuh di dalamnya. Bagian ini merangkum hal-hal yang sering salah dipahami pemula, lengkap dengan cara menghindarinya.

Tip 1: Selalu cek distribusi sebelum memilih ukuran pemusatan. Sebelum Abbas melaporkan mean atau median, buat histogram terlebih dahulu. Jika distribusi condong atau ada outlier yang jelas, median lebih jujur daripada mean. Aturan sederhana: jika mean dan median berbeda lebih dari 10% dari range data, laporkan keduanya sekaligus supaya pembaca bisa memilih mana yang lebih relevan.
Jebakan 1: Mean dari persentase tidak selalu benar. Jika Abbas ingin menghitung rata-rata pertumbuhan 30% dan 50%, jawabannya bukan 40%. Untuk angka persentase dan rasio, gunakan mean geometrik: scipy.stats.gmean([1.30, 1.50]) - 1. Mean aritmetik pada rasio pertumbuhan bisa melebih-lebihkan hasil secara sistematis dan menyesatkan kesimpulan akhir.
Tip 2: Gunakan ddof=1 untuk data sampel di NumPy. Saat Abbas menganalisis sebagian data (bukan seluruh populasi), selalu gunakan np.std(data, ddof=1) dan np.var(data, ddof=1). Tanpa ddof=1, NumPy meremehkan sebaran data karena membagi dengan N bukan N-1. Perbedaan ini penting terutama saat n kecil, misalnya kurang dari 30 data.
Jebakan 2: Nilai IQR dari NumPy vs perhitungan manual bisa berbeda. np.percentile(data, 75) - np.percentile(data, 25) kadang menghasilkan nilai berbeda dari perhitungan manual karena NumPy menggunakan interpolasi linear secara default. Jika Abbas butuh hasil yang konsisten dengan kalkulasi tangan, gunakan np.percentile(data, 75, method='midpoint') agar hasilnya sama dengan metode kuartil paling umum diajarkan di sekolah.
Tip 3: scipy.stats.describe() adalah pintasan yang sering dilupakan. Daripada menghitung mean, varians, min, max, dan skewness satu per satu, cukup panggil from scipy import stats; stats.describe(data). Fungsi ini mengembalikan semua ringkasan sekaligus dalam satu objek. Abbas bisa langsung akses hasilnya dengan desc.mean, desc.variance, desc.skewness, dan desc.kurtosis.
Tip 4: Jangan bandingkan standar deviasi dari dataset yang berbeda skala. Stdev nilai ujian (skala 0-100) dan stdev harga (dalam jutaan rupiah) tidak bisa langsung dibandingkan. Gunakan Coefficient of Variation (CV): cv = stdev / mean * 100. CV adalah standar deviasi dalam bentuk persentase dari mean, sehingga dua variabel dengan satuan berbeda bisa dibandingkan keragamannya secara adil dan setara.
Jebakan 3: Korelasi Pearson sensitif terhadap outlier. Satu titik outlier yang jauh bisa membuat korelasi Pearson terlihat sangat tinggi atau sangat rendah, padahal pola utama datanya tidak demikian. Jika Abbas mencurigai ada outlier, bandingkan dengan Spearman rank correlation: scipy.stats.spearmanr(x, y). Spearman lebih tahan terhadap outlier karena bekerja pada ranking, bukan nilai asli data.
Tip 5: Visualisasi selalu sebelum interpretasi numerik. Anscombe Quartet adalah contoh klasik: empat dataset dengan mean, varians, dan korelasi yang hampir identik, tapi bentuk scatter plot-nya sama sekali berbeda. Angka saja tidak cukup untuk mengenal data. Abbas harus selalu memplot data sebelum percaya pada ringkasan numeriknya, sekecil apapun datasetnya.
Jebakan 4: Histogram yang berbeda jumlah bin bisa memberikan kesan berbeda. Bin terlalu sedikit menyembunyikan pola multimodal; bin terlalu banyak menciptakan noise yang menyesatkan. Aturan Sturges (k = 1 + 3.322 * log10(n)) adalah titik awal yang baik secara matematis, tapi Abbas tetap harus mencoba beberapa pilihan bin dan memilih yang paling informatif untuk konteks datanya.

Latihan Tambahan: Tantangan Lebih Dalam

Latihan berikut dirancang untuk Abbas yang sudah menyelesaikan semua materi empat minggu dan ingin menguji pemahaman lebih jauh. Setiap latihan membutuhkan pemahaman lintas topik dan kemampuan memilih alat yang tepat untuk konteks yang diberikan.

Latihan A
Analisis Nilai Sekolah Lengkap dengan Scipy

Abbas membuat analisis statistik deskriptif komprehensif menggunakan scipy.stats.describe() dan menambahkan uji normalitas Shapiro-Wilk untuk memverifikasi apakah distribusi nilai mendekati normal secara statistik.

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

np.random.seed(77)

# Dataset nilai 40 siswa, 4 mata pelajaran
n = 40
nilai = {
    "Matematika" : np.random.normal(70, 12, n).clip(30, 100).round().astype(int),
    "Bahasa Indo": np.random.normal(78,  8, n).clip(40, 100).round().astype(int),
    "IPA"        : np.random.normal(65, 15, n).clip(30, 100).round().astype(int),
    "IPS"        : np.random.normal(75, 10, n).clip(40, 100).round().astype(int),
}

print(f"{'Mapel':<14} {'Mean':>6} {'Stdev':>7} {'Skew':>7} {'Kurt':>7} {'Normal?':>9}")
print("-" * 56)

for mapel, data in nilai.items():
    desc   = stats.describe(data)
    skew   = stats.skew(data)
    kurt   = stats.kurtosis(data)
    # Shapiro-Wilk: p > 0.05 berarti distribusi tidak berbeda signifikan dari normal
    _, p   = stats.shapiro(data)
    normal = "Ya" if p > 0.05 else "Tidak"
    print(f"{mapel:<14} {desc.mean:>6.1f} {np.sqrt(desc.variance):>7.2f} "
          f"{skew:>7.2f} {kurt:>7.2f} {normal:>7} (p={p:.3f})")

# Visualisasi distribusi tiap mapel dengan kurva normal teoritis
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
fig.suptitle("Distribusi Nilai per Mata Pelajaran", fontsize=12, fontweight="bold")

for ax, (mapel, data) in zip(axes.flat, nilai.items()):
    ax.hist(data, bins=10, color="#4a90d9", edgecolor="white", alpha=0.8, density=True)
    xmin, xmax = ax.get_xlim()
    x = np.linspace(xmin, xmax, 100)
    ax.plot(x, stats.norm.pdf(x, np.mean(data), np.std(data, ddof=1)),
            color="red", linewidth=2, linestyle="--", label="Normal teoritis")
    ax.axvline(np.mean(data), color="#e07b39", linewidth=1.5,
               label=f"Mean={np.mean(data):.1f}")
    ax.set_title(mapel)
    ax.legend(fontsize=7)
    ax.grid(axis="y", alpha=0.3)

plt.tight_layout()
plt.savefig("analisis_scipy_nilai.png", dpi=120)
plt.show()
print("Selesai. Catat nilai p Shapiro-Wilk dan interpretasinya di notebook.")
Tantangan: Abbas menjalankan kode di atas, lalu menjawab: mata pelajaran mana yang distribusi nilainya paling tidak normal berdasarkan p-value Shapiro-Wilk? Apa faktor nyata yang mungkin menyebabkan distribusi tersebut tidak simetris di dunia pendidikan? Tulis analisis 5 kalimat.
Refleksi: Shapiro-Wilk menguji apakah data "cukup normal" secara statistik. Untuk n kecil seperti 40 data, uji ini sering tidak cukup sensitif mendeteksi ketidaknormalan. Bagaimana Abbas memutuskan apakah distribusi "cukup normal" jika tidak bisa hanya mengandalkan p-value saja? Alat visual apa yang bisa membantu menjawab pertanyaan itu?
Latihan B
Deteksi Outlier Multi-Metode: IQR vs Z-Score vs Modified Z-Score

Abbas membandingkan tiga metode deteksi outlier pada dataset nilai yang sama dan menganalisis kapan masing-masing metode lebih tepat digunakan berdasarkan karakteristik data yang dihadapi.

import numpy as np
from scipy import stats

np.random.seed(13)
nilai_bersih = np.random.normal(75, 10, 45).clip(50, 100).round().astype(int)
outlier_vals = np.array([15, 20, 108, 112])   # nilai ekstrem sengaja dimasukkan
data = np.concatenate([nilai_bersih, outlier_vals])

print(f"Dataset: {len(data)} nilai, dengan {len(outlier_vals)} outlier buatan\n")

# Metode 1: IQR (threshold di luar 1.5 * IQR dari Q1 atau Q3)
q1, q3 = np.percentile(data, [25, 75])
iqr = q3 - q1
outlier_iqr = data[(data < q1 - 1.5 * iqr) | (data > q3 + 1.5 * iqr)]

# Metode 2: Z-Score standar (threshold |z| lebih dari 3)
z_scores = np.abs(stats.zscore(data))
outlier_z = data[z_scores > 3]

# Metode 3: Modified Z-Score berbasis MAD, lebih robust dari z-score biasa
median_val = np.median(data)
mad = np.median(np.abs(data - median_val))    # Median Absolute Deviation
mod_z = 0.6745 * (data - median_val) / (mad + 1e-9)
outlier_mod_z = data[np.abs(mod_z) > 3.5]

print("=== Hasil Deteksi Outlier ===")
print(f"Metode IQR          : {sorted(outlier_iqr)}")
print(f"Metode Z-Score      : {sorted(outlier_z)}")
print(f"Metode Modified Z   : {sorted(outlier_mod_z)}")
print(f"Outlier yang ditanam: {sorted(outlier_vals)}")

# Bandingkan statistik sebelum dan sesudah hapus outlier (metode IQR)
batas_bawah = q1 - 1.5 * iqr
batas_atas  = q3 + 1.5 * iqr
data_clean  = data[(data >= batas_bawah) & (data <= batas_atas)]

print(f"\n{'Metrik':<10} {'Semua Data':>12} {'Setelah IQR':>13}")
print("-" * 38)
for nama, fn in [("Mean", np.mean), ("Median", np.median),
                 ("Stdev", lambda x: np.std(x, ddof=1))]:
    print(f"{nama:<10} {fn(data):>12.2f} {fn(data_clean):>13.2f}")
Tantangan: Abbas mengubah outlier di kode menjadi nilai 65 dan 85 (yang konteksnya normal tapi posisinya agak di tepi distribusi). Jalankan ulang tiga metode. Apakah ketiganya masih mendeteksinya sebagai outlier? Apa artinya ini untuk keputusan "hapus atau pertahankan" dalam analisis data nilai sekolah yang nyata?
Refleksi: Modified Z-Score menggunakan MAD (Median Absolute Deviation) bukan standar deviasi biasa. Mengapa MAD lebih robust terhadap outlier dibanding standar deviasi? Jelaskan dengan kata-kata sendiri, tanpa menyebut rumus matematisnya secara detail.
Latihan C
Simulasi Central Limit Theorem dengan Dataset Nilai Bimodal

Abbas mensimulasikan Central Limit Theorem secara langsung: mengambil banyak sampel dari distribusi tidak normal (bimodal), menghitung mean tiap sampel, dan membuktikan bahwa distribusi mean-nya mendekati normal seiring bertambahnya ukuran sampel.

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

np.random.seed(42)

# Populasi bimodal: kelompok nilai rendah dan nilai tinggi dalam satu sekolah
populasi = np.concatenate([
    np.random.normal(45, 8, 300),   # kelompok siswa nilai rendah
    np.random.normal(88, 7, 300),   # kelompok siswa nilai tinggi
]).clip(0, 100).round().astype(int)

print(f"Populasi: n={len(populasi)}, mean={np.mean(populasi):.2f}, stdev={np.std(populasi):.2f}")

ukuran_sampel = [5, 15, 30]
n_simulasi    = 1000

fig, axes = plt.subplots(2, len(ukuran_sampel) + 1, figsize=(14, 8))
fig.suptitle("Simulasi Central Limit Theorem: Distribusi Nilai Bimodal",
             fontsize=11, fontweight="bold")

# Tampilkan distribusi populasi asli
axes[0, 0].hist(populasi, bins=30, color="#e07b39", edgecolor="white", alpha=0.85)
axes[0, 0].set_title("Distribusi Populasi\n(Bimodal, tidak normal)")
axes[0, 0].set_xlabel("Nilai")
axes[0, 0].grid(axis="y", alpha=0.3)
axes[1, 0].axis("off")
axes[1, 0].text(0.5, 0.5,
    f"Populasi\nn = {len(populasi)}\nmean = {np.mean(populasi):.1f}\nstdev = {np.std(populasi):.1f}",
    ha="center", va="center", fontsize=10,
    bbox=dict(boxstyle="round", facecolor="#f5f5f5"))

for col, n in enumerate(ukuran_sampel, start=1):
    sample_means = np.array([
        np.mean(np.random.choice(populasi, n, replace=False))
        for _ in range(n_simulasi)
    ])
    mu    = np.mean(sample_means)
    sigma = np.std(sample_means)

    ax = axes[0, col]
    ax.hist(sample_means, bins=30, color="#4a90d9", edgecolor="white", alpha=0.85, density=True)
    x = np.linspace(mu - 4*sigma, mu + 4*sigma, 200)
    ax.plot(x, stats.norm.pdf(x, mu, sigma), color="red", linewidth=2, label="Normal fit")
    ax.set_title(f"Mean Sampel (n={n})\n{n_simulasi} simulasi")
    ax.legend(fontsize=7)
    ax.grid(axis="y", alpha=0.3)

    ax2 = axes[1, col]
    ax2.axis("off")
    _, p_sw = stats.shapiro(sample_means[:50])
    ax2.text(0.5, 0.5,
        f"n per sampel = {n}\nmean means = {mu:.2f}\nstdev means = {sigma:.2f}\nShapiro p = {p_sw:.3f}",
        ha="center", va="center", fontsize=9,
        bbox=dict(boxstyle="round", facecolor="#e8f4f8"))

plt.tight_layout()
plt.savefig("clt_simulasi.png", dpi=120)
plt.show()
print("Simpan gambar dan tulis interpretasi: apakah CLT terbukti untuk data bimodal?")
Tantangan: Abbas memodifikasi kode untuk menggunakan populasi uniform (semua nilai 0-100 sama kemungkinannya, bukan bimodal). Apakah CLT tetap berlaku? Berapa ukuran sampel minimum agar distribusi mean terlihat mendekati normal? Dokumentasikan temuan dengan screenshot grafik dan tuliskan n minimum yang ditemukan Abbas.
Refleksi: CLT adalah alasan mengapa banyak uji statistik seperti t-test, ANOVA, dan regresi linear mengasumsikan normalitas pada mean sampel, bukan pada data mentah. Setelah melihat simulasi ini, apakah Abbas merasa lebih percaya diri menggunakan uji-uji tersebut meskipun data aslinya berbentuk bimodal? Tulis pendapat Abbas dalam 4 kalimat.
Latihan D
Perbandingan Distribusi Antar Kelas: Analisis Kompetitif

Abbas mensimulasikan skenario nyata: kepala sekolah ingin tahu kelas mana yang performanya paling merata dan paling tinggi. Abbas harus memberikan analisis lengkap dengan argumen yang didukung data statistik konkret, bukan hanya intuisi.

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

np.random.seed(55)

# Tiga kelas dengan karakteristik yang sengaja berbeda
kelas = {
    "X-A": np.random.normal(80,  6, 35).clip(55, 100).round().astype(int),   # Tinggi, merata
    "X-B": np.random.normal(72, 14, 35).clip(40, 100).round().astype(int),   # Sedang, beragam
    "X-C": np.concatenate([                                                    # Bimodal
        np.random.normal(60, 5, 20),
        np.random.normal(90, 5, 15)
    ]).clip(40, 100).round().astype(int),
}

# Ringkasan statistik komprehensif per kelas
print(f"{'Kelas':<8} {'Mean':>6} {'Median':>8} {'Stdev':>7} {'CV%':>6} {'IQR':>6} {'Skew':>7}")
print("-" * 50)
for nama, data in kelas.items():
    q1, q3 = np.percentile(data, [25, 75])
    cv = np.std(data, ddof=1) / np.mean(data) * 100
    print(f"{nama:<8} {np.mean(data):>6.1f} {np.median(data):>8.1f} "
          f"{np.std(data, ddof=1):>7.2f} {cv:>6.1f} {q3-q1:>6.1f} {stats.skew(data):>7.2f}")

# Visualisasi: histogram, boxplot, dan dot plot untuk tiap kelas
colors = {"X-A": "#4a90d9", "X-B": "#e07b39", "X-C": "#5cb85c"}
fig, axes = plt.subplots(3, 3, figsize=(13, 10))
fig.suptitle("Analisis Kompetitif: Perbandingan Tiga Kelas", fontsize=12, fontweight="bold")

for row, (nama, data) in enumerate(kelas.items()):
    q1, q3 = np.percentile(data, [25, 75])
    iqr = q3 - q1
    out_mask = (data < q1 - 1.5 * iqr) | (data > q3 + 1.5 * iqr)

    # Kolom 0: Histogram
    axes[row, 0].hist(data, bins=10, color=colors[nama], edgecolor="white", alpha=0.85)
    axes[row, 0].axvline(np.mean(data), color="red", linestyle="--", linewidth=1.5,
                          label=f"Mean={np.mean(data):.1f}")
    axes[row, 0].axvline(np.median(data), color="green", linestyle="-.", linewidth=1.5,
                          label=f"Median={np.median(data):.1f}")
    axes[row, 0].set_title(f"Histogram {nama}")
    axes[row, 0].legend(fontsize=7)
    axes[row, 0].grid(axis="y", alpha=0.3)

    # Kolom 1: Boxplot
    axes[row, 1].boxplot(data, patch_artist=True,
                          boxprops=dict(facecolor=colors[nama], alpha=0.5))
    axes[row, 1].set_title(f"Boxplot {nama}")
    axes[row, 1].set_ylabel("Nilai")
    axes[row, 1].grid(axis="y", alpha=0.3)

    # Kolom 2: Dot plot dengan highlight outlier
    jitter = np.random.uniform(-0.15, 0.15, len(data))
    axes[row, 2].scatter(jitter, data, alpha=0.6, color=colors[nama], s=25)
    if out_mask.any():
        axes[row, 2].scatter(jitter[out_mask], data[out_mask],
                              color="red", s=60, zorder=5, label="Outlier")
        axes[row, 2].legend(fontsize=7)
    axes[row, 2].axhline(np.mean(data), color="black", linestyle="--", linewidth=1)
    axes[row, 2].set_title(f"Dot Plot {nama}")
    axes[row, 2].set_ylabel("Nilai")
    axes[row, 2].set_xticks([])
    axes[row, 2].grid(axis="y", alpha=0.3)

plt.tight_layout()
plt.savefig("perbandingan_kelas.png", dpi=120)
plt.show()

# Rekomendasi otomatis berdasarkan CV sebagai ukuran kemerataan
cv_kelas      = {nama: np.std(d, ddof=1) / np.mean(d) * 100 for nama, d in kelas.items()}
paling_merata  = min(cv_kelas, key=cv_kelas.get)
paling_tinggi  = max(kelas, key=lambda k: np.mean(kelas[k]))
print(f"\nKelas paling merata (CV terkecil): {paling_merata} (CV={cv_kelas[paling_merata]:.1f}%)")
print(f"Kelas rata-rata tertinggi        : {paling_tinggi} (mean={np.mean(kelas[paling_tinggi]):.1f})")
Tantangan Akhir: Berdasarkan output analisis di atas, Abbas diminta menulis memo singkat kepada kepala sekolah (maksimal 150 kata) yang menjawab: "Kelas mana yang performanya paling baik, dan apa rekomendasimu untuk kelas yang paling bermasalah?" Gunakan minimal tiga angka statistik konkret sebagai bukti dalam memo tersebut, dan sebutkan sumber angkanya (mean, CV, IQR, atau lainnya).
Refleksi: Kelas X-C memiliki distribusi bimodal, artinya ada dua kelompok siswa yang sangat berbeda dalam satu kelas. Dari perspektif pendidikan, apa implikasi praktis dari pola distribusi seperti ini? Apakah nilai mean kelas X-C masih relevan sebagai ukuran performa keseluruhan kelas itu, atau ada angka lain yang lebih jujur?