Tabel jawaban pada Bagian 8 sudah lengkap, tetapi menyampaikannya kepada pembaca laporan lewat tabel angka membutuhkan usaha — grafik menerjemahkannya dalam sekali pandang. Pada bagian ini kita membuat tiga grafik untuk laporan akhir: scatter plot jam belajar versus nilai yang juga memeriksa klaim linearitas dari Bagian 7, grafik batang rata-rata kedua kelas, dan scatter plot bertambah garis regresi. Ketiganya mengikuti kaidah yang pernah kita pelajari pada chapter Scatter Plot . Sesuai kontrak proyek, blok plot cukup dijalankan sampai tuntas tanpa keluaran teks, sehingga bentuk grafiknya kita deskripsikan dengan prosa.
Scatter Plot: Jam Belajar versus Nilai per Kelas #
Grafik pertama memvisualkan jawaban P1 sekaligus P3: sumbu mendatar memuat jam belajar, sumbu tegak memuat nilai, dan warna titik membedakan kelas:
import matplotlib.pyplot as plt
import pandas as pd
def muat_bersih(path):
"""Muat data mentah dan terapkan seluruh langkah pembersihan."""
df = pd.read_csv(path).drop_duplicates().reset_index(drop=True)
df["kelas"] = df["kelas"].str.upper()
df["kehadiran_persen"] = pd.to_numeric(df["kehadiran_persen"].replace("-", pd.NA))
df = df.dropna(subset=["kehadiran_persen"]).reset_index(drop=True)
df["kehadiran_persen"] = df["kehadiran_persen"].astype(int)
return df
bersih = muat_bersih("../data/nilai_siswa.csv")
fig, ax = plt.subplots(figsize=(7, 4.5))
for kelas, warna in (("A", "#2a6f4e"), ("B", "#c07a3a")):
kelompok = bersih[bersih["kelas"] == kelas]
ax.scatter(kelompok["jam_belajar_mingguan"], kelompok["nilai_ujian"],
color=warna, s=70, alpha=0.8, label=f"kelas {kelas}")
ax.set_title("Jam Belajar Mingguan vs Nilai Ujian\n30 siswa kelas A dan B")
ax.set_xlabel("Jam belajar per minggu")
ax.set_ylabel("Nilai ujian")
ax.grid(alpha=0.3)
ax.legend()
fig.tight_layout()
plt.show()Bentuk yang muncul adalah jawaban visual P1: tiga puluh titik membentuk pita yang menanjak dari kiri bawah ke kanan atas — siswa yang belajar sedikit menghuni ujung bawah (Zaki di pojok 2 jam bernilai 48, Elsa 3 jam bernilai 44), dan perpindahan ke kanan hampir selalu diikuti kenaikan ke atas hingga Nia di pojok 16 jam bernilai 91. Pandangan yang sama sekaligus menjawab P3: titik hijau (kelas A) dan jingga (kelas B) bercampur sepanjang pita tanpa garis batas — tidak ada wilayah yang dikuasai satu kelas, versi gambar dari p = 0,44 pada Bagian 8. Beberapa hal dari kodenya:
- Perulangan atas pasangan
("A", hijau)dan("B", jingga)menggambar dua seri pada satu sumbu;labelpada tiap pemanggilanscattermengisi legend secara otomatis, sehingga keterangan kelas tidak perlu ditulis manual. s=70memperbesar ukuran penanda danalpha=0.8sedikit membuatnya tembus pandang — dengan hanya 30 titik, ukuran penala standar terlihat kecil pada kanvas selebar ini.- Titik-titik yang berjalan searah pita juga membenarkan klaim linearitas saat memilih korelasi Pearson pada Bagian 7: tidak tampak pola melengkung yang berarti, jadi garis lurus adalah bentuk yang wajar.
Grafik Batang: Rata-rata Nilai per Kelas #
Jawaban P3 berupa satu p-value; perbandingannya paling jujur ditampilkan lewat dua batang dengan sumbu penuh. Grafik kecil ini menjadi penutup bagian temuan karena ringkas dan lugas:
import matplotlib.pyplot as plt
import pandas as pd
def muat_bersih(path):
"""Muat data mentah dan terapkan seluruh langkah pembersihan."""
df = pd.read_csv(path).drop_duplicates().reset_index(drop=True)
df["kelas"] = df["kelas"].str.upper()
df["kehadiran_persen"] = pd.to_numeric(df["kehadiran_persen"].replace("-", pd.NA))
df = df.dropna(subset=["kehadiran_persen"]).reset_index(drop=True)
df["kehadiran_persen"] = df["kehadiran_persen"].astype(int)
return df
bersih = muat_bersih("../data/nilai_siswa.csv")
rata_kelas = bersih.groupby("kelas")["nilai_ujian"].mean()
rata_total = bersih["nilai_ujian"].mean()
fig, ax = plt.subplots(figsize=(5.5, 4))
batang = ax.bar(rata_kelas.index, rata_kelas.values, color=["#2a6f4e", "#c07a3a"])
ax.bar_label(batang, fmt="%.1f", padding=3)
ax.axhline(rata_total, color="#888888", linestyle="--", linewidth=1,
label=f"rata-rata seluruh siswa {rata_total:.1f}")
ax.set_title("Rata-rata Nilai Ujian per Kelas")
ax.set_xlabel("Kelas")
ax.set_ylabel("Rata-rata nilai ujian")
ax.set_ylim(0, 100)
ax.grid(axis="y", alpha=0.3)
ax.legend()
fig.tight_layout()
plt.show()Gambar yang terbentuk menampilkan dua batang yang nyaris setinggi: kelas A di 70,6 dan kelas B di 66,9 pada skala penuh 0 sampai 100, dengan garis putus-putus rata-rata keseluruhan 68,7 berdiri persis di antara keduanya. Jarak puncak kedua batang tampak kecil dibanding tinggi batangnya — dan itulah pesan jujurnya: selisih 3,7 poin pada sebaran setiap kelas yang mencapai belasan poin memang bukan hal yang mencolok, sejalan dengan kesimpulan uji-t yang tidak menemukan perbedaan bermakna. Dua keputusan pada kodenya menopang kejujuran itu:
ax.set_ylim(0, 100)mengunci sumbu tegak pada skala nilai ujian yang sesungguhnya, bukan rentang sempit di sekitar kedua rata-rata.bar_labelmenempelkan angka tepat di ujung batang sehingga pembaca yang ingin tahu selisihnya tidak perlu mengira-gira dari garis.
Scatter Plot dengan Garis Regresi #
Grafik ketiga menggabungkan jawaban P1 dan P4 dalam satu gambar: sebaran titik seperti grafik pertama, kini dengan garis hasil regresi Bagian 8 menindihnya:
import matplotlib.pyplot as plt
import pandas as pd
from scipy import stats
def muat_bersih(path):
"""Muat data mentah dan terapkan seluruh langkah pembersihan."""
df = pd.read_csv(path).drop_duplicates().reset_index(drop=True)
df["kelas"] = df["kelas"].str.upper()
df["kehadiran_persen"] = pd.to_numeric(df["kehadiran_persen"].replace("-", pd.NA))
df = df.dropna(subset=["kehadiran_persen"]).reset_index(drop=True)
df["kehadiran_persen"] = df["kehadiran_persen"].astype(int)
return df
bersih = muat_bersih("../data/nilai_siswa.csv")
hasil = stats.linregress(bersih["jam_belajar_mingguan"], bersih["nilai_ujian"])
rentang = range(int(bersih["jam_belajar_mingguan"].min()),
int(bersih["jam_belajar_mingguan"].max()) + 1)
garis = [hasil.intercept + hasil.slope * jam for jam in rentang]
fig, ax = plt.subplots(figsize=(7, 4.5))
ax.scatter(bersih["jam_belajar_mingguan"], bersih["nilai_ujian"],
color="#4a5d7a", s=70, alpha=0.8, label="siswa")
ax.plot(rentang, garis, color="#b03a2e", linewidth=2,
label=f"garis regresi: {hasil.slope:.2f} x jam + {hasil.intercept:.1f}")
ax.set_title("Garis Regresi Nilai Ujian atas Jam Belajar\n(R-kuadrat = "
f"{hasil.rvalue ** 2:.2f})")
ax.set_xlabel("Jam belajar per minggu")
ax.set_ylabel("Nilai ujian")
ax.grid(alpha=0.3)
ax.legend()
fig.tight_layout()
plt.show()Garis merah miring membelah awan titik dari kiri bawah ke kanan atas dengan kemiringan yang persis dibaca legend: nilai = 2,88 × jam + 42,1. Titik-titik biru kebanyakan berhimpit di sekitar garis — hanya satu-dua siswa menjauh, misalnya Maya yang hadir sangat rajin tetapi bernilai 72 di bawah garis — itulah wujud dari R-kuadrat 0,88 pada judul grafik. Perhatikan dua keputusan pentingnya:
- Garis hanya digambar pada rentang 2 sampai 16 jam —
rangedari jam minimum sampai maksimum data. Garis tidak diperpanjang ke nol atau ke 25 jam, karena di luar rentang itu kita tidak punya satu titik data pun untuk menopangnya; memanjangkannya sama dengan menampilkan ekstrapolasi seolah-olah fakta. - Persamaan dan R-kuadrat ditulis pada judul serta legend, bukan disimpan di teks terpisah — pembaca grafik selalu bertanya “seberapa pas garisnya”, dan judul menjawabnya tanpa berpindah halaman.
Tiga grafik ini melengkapi tabel Bagian 8: satu menampilkan sebaran beserta warna kelas, satu membandingkan kelompok dengan jujur, satu merangkai model perkiraan. Angka dan gambar kini bercerita tentang hal yang sama; tinggal menentukan kalimat apa yang boleh diucapkan dari cerita itu — pekerjaan Bagian 10: Menginterpretasikan Hasil .