# ===== UBAH BAGIAN INI =====
file_data <- "shared_Dataset_Simulasi_Data_Cleaning_1200x15_-_Copy.xlsx" # data MENTAH (kotor)
file_bersih <- "Dataset_bersih_mahassiwa.xlsx" # data BERSIH hasil Excel (untuk pembanding)
# Letakkan kedua file sefolder dengan file .Rmd
# ===========================
# Bila nama file tidak ditemukan, cari otomatis berdasarkan kata kunci pada nama file
if (!file.exists(file_data)) {
kandidat <- list.files(pattern = "Simulasi_Data_Cleaning.*\\.(xlsx|csv)$", ignore.case = TRUE)
if (length(kandidat)) file_data <- kandidat[1]
}
if (!file.exists(file_bersih)) {
kandidat <- list.files(pattern = "bersih.*\\.xlsx$", ignore.case = TRUE)
if (length(kandidat)) file_bersih <- kandidat[1]
}
baca_file <- function(f) {
if (!file.exists(f)) return(NULL)
if (grepl("\\.csv$", f, ignore.case = TRUE)) {
if (grepl(";", readLines(f, n = 1, warn = FALSE))) read_csv2(f, show_col_types = FALSE) else read_csv(f, show_col_types = FALSE)
} else read_excel(f)
}
# Samakan nama kolom dengan penulisan baku dan pastikan tipe datanya benar
kanon <- c("ID_Mahasiswa", "Nama", "Gender", "Umur", "Semester", "Program_Studi", "Kota_Asal",
"Jam_Belajar_Per_Minggu", "Kehadiran_Persen", "Nilai_Tugas", "Nilai_Ujian", "Nilai_Akhir",
"Jam_Internet_Per_Hari", "Jam_Tidur_Per_Hari", "Kepuasan_Kuliah")
kolom_num <- setdiff(kanon, c("ID_Mahasiswa", "Nama", "Gender", "Program_Studi", "Kota_Asal"))
rapikan <- function(d) {
if (is.null(d)) return(NULL)
nama_rapi <- gsub("\\s+", "_", trimws(names(d)))
cocok <- match(tolower(nama_rapi), tolower(kanon))
names(d) <- ifelse(is.na(cocok), nama_rapi, kanon[cocok])
d %>% mutate(across(any_of(kolom_num), ~ suppressWarnings(as.numeric(.x))),
ID_Mahasiswa = as.character(ID_Mahasiswa))
}
df_mentah <- rapikan(baca_file(file_data))
df_excel <- rapikan(baca_file(file_bersih)) # boleh NULL; hanya dipakai untuk perbandingan
pakai_simulasi <- is.null(df_mentah)
if (pakai_simulasi) { df_mentah <- buat_data_simulasi(); df_excel <- NULL }file_data
pada chunk muat-data.
Sebelum dianalisis, data harus dipercaya dulu. Laporan ini memeriksa dataset mahasiswa yang penuh masalah, membersihkannya langkah demi langkah, lalu memakainya untuk menjawab satu pertanyaan: apakah jam belajar dan jam internet berkaitan dengan lama tidur mahasiswa?
Data cleaningData wranglingIntegrasi dan encodingRegresi linear bergandaUji asumsi klasik
Data mahasiswa menyimpan banyak cerita: siapa mereka, bagaimana kondisi akademiknya, seberapa lama mereka belajar dan berselancar di internet, berapa jam mereka tidur, sampai seberapa puas mereka dengan perkuliahan. Cerita itu hanya berguna kalau datanya bersih.
Karena itu, sebelum analisis lanjutan dijalankan, kualitas data perlu diperiksa lebih dulu. Ada empat hal yang dicari: nilai yang kosong, penulisan yang tidak seragam, baris yang tercatat dua kali, dan angka yang mustahil terjadi. Laporan ini merekam seluruh proses itu pada sebuah dataset mahasiswa.
kamus <- tribble(
~Variabel, ~Jenis, ~Arti,
"ID_Mahasiswa", "Teks", "Kode unik tiap mahasiswa",
"Nama", "Teks", "Nama mahasiswa",
"Gender", "Kategori", "Jenis kelamin",
"Umur", "Numerik", "Usia dalam tahun",
"Semester", "Numerik", "Semester yang sedang ditempuh",
"Program_Studi", "Kategori", "Program studi",
"Kota_Asal", "Kategori", "Kota asal mahasiswa",
"Jam_Belajar_Per_Minggu", "Numerik", "Total jam belajar dalam seminggu",
"Kehadiran_Persen", "Numerik", "Persentase kehadiran kuliah (0 sampai 100)",
"Nilai_Tugas", "Numerik", "Nilai tugas (skala 0 sampai 100)",
"Nilai_Ujian", "Numerik", "Nilai ujian (skala 0 sampai 100)",
"Nilai_Akhir", "Numerik", "Nilai akhir (skala 0 sampai 100)",
"Jam_Internet_Per_Hari", "Numerik", "Lama memakai internet dalam sehari",
"Jam_Tidur_Per_Hari", "Numerik", "Lama tidur dalam sehari",
"Kepuasan_Kuliah", "Ordinal", "Tingkat kepuasan kuliah (skala 1 sampai 5)"
)
kable(kamus)| Variabel | Jenis | Arti |
|---|---|---|
| ID_Mahasiswa | Teks | Kode unik tiap mahasiswa |
| Nama | Teks | Nama mahasiswa |
| Gender | Kategori | Jenis kelamin |
| Umur | Numerik | Usia dalam tahun |
| Semester | Numerik | Semester yang sedang ditempuh |
| Program_Studi | Kategori | Program studi |
| Kota_Asal | Kategori | Kota asal mahasiswa |
| Jam_Belajar_Per_Minggu | Numerik | Total jam belajar dalam seminggu |
| Kehadiran_Persen | Numerik | Persentase kehadiran kuliah (0 sampai 100) |
| Nilai_Tugas | Numerik | Nilai tugas (skala 0 sampai 100) |
| Nilai_Ujian | Numerik | Nilai ujian (skala 0 sampai 100) |
| Nilai_Akhir | Numerik | Nilai akhir (skala 0 sampai 100) |
| Jam_Internet_Per_Hari | Numerik | Lama memakai internet dalam sehari |
| Jam_Tidur_Per_Hari | Numerik | Lama tidur dalam sehari |
| Kepuasan_Kuliah | Ordinal | Tingkat kepuasan kuliah (skala 1 sampai 5) |
datatable(df_mentah, rownames = FALSE, class = "compact stripe",
options = list(pageLength = 6, scrollX = TRUE, dom = "tip"),
caption = "Data mentah sebelum dibersihkan. Gulir ke kanan untuk melihat semua kolom.")Data eksternal dan sekunder. Asal-usul pembuat dataset tidak
diketahui, sehingga sumbernya belum terverifikasi. Data ini memang
disiapkan sebagai bahan latihan pembersihan (dirty data) dan tidak
diambil langsung dari para mahasiswanya, jadi statusnya data
sekunder.
Data terstruktur. Datanya rapi dalam baris dan
kolom, punya skema variabel yang jelas, dan mudah dibuka di Excel maupun
R.
Ada empat kelompok masalah. Kita telusuri satu per satu.
na_tbl <- df_mentah %>%
summarise(across(everything(), ~ sum(is.na(.x)))) %>%
pivot_longer(everything(), names_to = "Variabel", values_to = "Kosong") %>%
filter(Kosong > 0) %>%
arrange(desc(Kosong))
ggplot(na_tbl, aes(reorder(Variabel, Kosong), Kosong)) +
geom_col(fill = pal$steel, width = .7) +
geom_text(aes(label = Kosong), hjust = -.3, color = pal$navy, fontface = "bold") +
coord_flip() + scale_y_continuous(expand = expansion(mult = c(0, .15))) +
labs(title = "Sel kosong per variabel",
subtitle = paste("Total", n_kosong, "sel kosong pada data mentah"),
x = NULL, y = "Jumlah data kosong")Sel kosong tersebar di beberapa variabel, terutama jam belajar dan kehadiran. Jika dibiarkan, perhitungan rata-rata dan model statistik bisa terganggu atau bahkan gagal jalan.
Satu kategori bisa ditulis dengan banyak cara. Bagi manusia artinya sama, tetapi R membacanya sebagai kategori yang berbeda.
kelompok_gender <- function(x) case_when(
str_to_lower(str_trim(x)) %in% c("l", "laki-laki", "male", "pria") ~ "Laki-laki",
str_to_lower(str_trim(x)) %in% c("p", "perempuan", "female", "wanita") ~ "Perempuan",
TRUE ~ "Lainnya")
g1 <- df_mentah %>% count(Gender) %>% filter(!is.na(Gender)) %>%
mutate(Kelompok = kelompok_gender(Gender)) %>%
ggplot(aes(reorder(Gender, n), n, fill = Kelompok)) +
geom_col(width = .7) + coord_flip() +
scale_fill_manual(values = c("Laki-laki" = pal$steel, "Perempuan" = pal$orange, "Lainnya" = "grey60")) +
labs(title = "Gender: satu makna, banyak tulisan", x = NULL, y = "Jumlah baris", fill = NULL)
k1 <- df_mentah %>% count(Kota_Asal) %>% filter(!is.na(Kota_Asal)) %>%
mutate(Kota_Baku = str_to_title(str_trim(Kota_Asal))) %>%
ggplot(aes(reorder(Kota_Asal, n), n, fill = Kota_Baku)) +
geom_col(width = .7) + coord_flip() +
scale_fill_viridis_d(option = "mako", end = .85) +
labs(title = "Kota_Asal: beda huruf besar dan kecil", x = NULL, y = "Jumlah baris", fill = NULL) +
theme(legend.position = "none")
g1 + k1tibble(Tahap = factor(c("Data mentah", "Setelah duplikat dibuang"),
levels = c("Data mentah", "Setelah duplikat dibuang")),
Baris = c(n_awal, n_setelah_dup)) %>%
ggplot(aes(Tahap, Baris, fill = Tahap)) +
geom_col(width = .55) +
geom_text(aes(label = fmt_n(Baris)), vjust = -.5, fontface = "bold", color = pal$navy, size = 5) +
scale_fill_manual(values = c(pal$red, pal$green), guide = "none") +
scale_y_continuous(expand = expansion(mult = c(0, .12))) +
labs(title = "Jumlah baris sebelum dan sesudah menghapus duplikat",
subtitle = paste0(n_id_kembar, " ID muncul lebih dari sekali; ", n_baris_sama, " baris identik persis"),
x = NULL, y = NULL)Mahasiswa yang tercatat dua kali akan terhitung dua kali dan bisa menggeser hasil analisis. Berikut ID yang bermasalah:
Setiap variabel punya batas kewajaran: umur tidak mungkin negatif, kehadiran tidak boleh lebih dari 100 persen, satu hari hanya 24 jam, dan skala kepuasan hanya 1 sampai 5.
rules %>%
mutate(`Nilai tidak valid` = n_noisy_var,
`Rentang wajar` = paste(min, "sampai", max)) %>%
select(Variabel = var, `Rentang wajar`, `Nilai tidak valid`) %>%
filter(`Nilai tidak valid` > 0) %>%
arrange(desc(`Nilai tidak valid`)) %>%
kable()| Variabel | Rentang wajar | Nilai tidak valid |
|---|---|---|
| Jam_Internet_Per_Hari | 0 sampai 24 | 3 |
| Umur | 15 sampai 60 | 2 |
| Kehadiran_Persen | 0 sampai 100 | 2 |
| Nilai_Tugas | 0 sampai 100 | 2 |
| Nilai_Ujian | 0 sampai 100 | 2 |
| Kepuasan_Kuliah | 1 sampai 5 | 2 |
| Nilai_Akhir | 0 sampai 100 | 1 |
Pembersihan dilakukan dalam lima langkah berurutan. Urutannya penting: nilai mustahil diubah menjadi kosong terlebih dahulu, baru semua sel kosong diisi bersama-sama.
# Langkah 1. Buang baris kembar (identik, lalu ID kembar: ambil kemunculan pertama)
df_1 <- df_mentah %>% distinct() %>% distinct(ID_Mahasiswa, .keep_all = TRUE)
# Langkah 2 dan 3. Seragamkan huruf kota dan tulisan gender
df_2 <- df_1 %>%
mutate(
Kota_Asal = str_to_title(str_trim(Kota_Asal)),
Gender = case_when(
str_to_lower(str_trim(Gender)) %in% c("l", "laki-laki", "male", "pria") ~ "Laki-laki",
str_to_lower(str_trim(Gender)) %in% c("p", "perempuan", "female", "wanita") ~ "Perempuan",
TRUE ~ NA_character_))
# Langkah 4. Ubah nilai di luar batas wajar menjadi kosong (NA)
di_luar <- function(x, lo, hi) ifelse(x < lo | x > hi, NA_real_, x)
df_3 <- df_2
for (i in seq_len(nrow(rules))) {
v <- rules$var[i]
df_3[[v]] <- di_luar(df_3[[v]], rules$min[i], rules$max[i])
}
# Langkah 5. Isi semua sel kosong pada kolom numerik dengan median kolomnya
df_bersih <- df_3
kolom_isi <- df_bersih %>% select(where(is.numeric)) %>% names() %>% setdiff("ID_Mahasiswa")
for (k in kolom_isi) {
df_bersih[[k]][is.na(df_bersih[[k]])] <- median(df_bersih[[k]], na.rm = TRUE)
}Kenapa median, bukan rata-rata? Median tidak ikut tertarik oleh nilai ekstrem, jadi hasil pengisiannya lebih stabil pada data yang sempat berantakan.
Hati-hati saat merapikan teks. Di Excel, fitur Replace All tanpa opsi “Match entire cell contents” ikut mengganti potongan kata: mengganti “Male” menjadi “Laki-laki” akan mengubah “Female” menjadi “FeLaki-laki”. Kode R di atas mencocokkan seluruh isi sel, sehingga jebakan ini tidak terjadi.
tabel_hasil <- tibble(
Pemeriksaan = c("Baris data", "Sel kosong", "Varian Gender tidak baku", "Kota_Asal tidak baku"),
Sebelum = c(n_awal, n_kosong, n_gender_var, n_kota_var),
Sesudah = c(nrow(df_bersih), sum(is.na(df_bersih[kolom_num])),
sum(!df_bersih$Gender %in% c("Laki-laki", "Perempuan"), na.rm = TRUE),
sum(df_bersih$Kota_Asal != str_to_title(str_trim(df_bersih$Kota_Asal)), na.rm = TRUE))
)
kable(tabel_hasil, format.args = list(big.mark = "."))| Pemeriksaan | Sebelum | Sesudah |
|---|---|---|
| Baris data | 1.215 | 1.200 |
| Sel kosong | 46 | 0 |
| Varian Gender tidak baku | 544 | 0 |
| Kota_Asal tidak baku | 373 | 0 |
g2 <- df_bersih %>% count(Gender) %>%
ggplot(aes(Gender, n, fill = Gender)) + geom_col(width = .6) +
geom_text(aes(label = n), vjust = -.4, fontface = "bold", color = pal$navy) +
scale_fill_manual(values = c("Laki-laki" = pal$steel, "Perempuan" = pal$orange), na.value = "grey60", guide = "none") +
scale_y_continuous(expand = expansion(mult = c(0, .15))) +
labs(title = "Gender setelah dirapikan", x = NULL, y = "Jumlah mahasiswa")
k2 <- df_bersih %>% count(Kota_Asal) %>%
ggplot(aes(reorder(Kota_Asal, n), n, fill = Kota_Asal)) + geom_col(width = .6) +
geom_text(aes(label = n), hjust = -.3, fontface = "bold", color = pal$navy) +
coord_flip() + scale_fill_viridis_d(option = "mako", end = .85, guide = "none") +
scale_y_continuous(expand = expansion(mult = c(0, .15))) +
labs(title = "Kota asal setelah dirapikan", x = NULL, y = "Jumlah mahasiswa")
g2 + k2Setelah semua langkah, dataset berisi 1.200 baris, tanpa sel kosong, tanpa ID kembar, dengan penulisan kategori yang seragam dan semua angka berada dalam batas wajar.
Pertanyaan analisis: apakah durasi memakai internet per hari dan waktu belajar per minggu berkaitan dengan lama tidur mahasiswa per hari?
Sebelum modelnya dibuat, data dipastikan siap lewat lima pemeriksaan:
variabel_model <- c("Jam_Tidur_Per_Hari", "Jam_Belajar_Per_Minggu", "Jam_Internet_Per_Hari")
tibble(Variabel = variabel_model,
Numerik = map_lgl(variabel_model, ~ is.numeric(df_bersih[[.x]])),
`Sel kosong` = map_int(variabel_model, ~ sum(is.na(df_bersih[[.x]]))),
Minimum = map_dbl(variabel_model, ~ min(df_bersih[[.x]])),
Maksimum = map_dbl(variabel_model, ~ max(df_bersih[[.x]]))) %>%
kable()| Variabel | Numerik | Sel kosong | Minimum | Maksimum |
|---|---|---|---|---|
| Jam_Tidur_Per_Hari | TRUE | 0 | 3.5 | 9.1 |
| Jam_Belajar_Per_Minggu | TRUE | 0 | 1.0 | 30.1 |
| Jam_Internet_Per_Hari | TRUE | 0 | 0.5 | 9.2 |
Untuk melatih integrasi, informasi dipisah menjadi dua tabel lalu
digabung kembali. Kuncinya adalah ID_Mahasiswa, supaya
setiap kategori belajar menempel pada mahasiswa yang benar.
Jam belajar dikelompokkan menjadi tiga tingkat, lalu tiap tingkat diberi kode angka (encoding) agar bisa dipakai dalam hitungan statistik:
| Jam belajar per minggu | Kategori | Kode |
|---|---|---|
| kurang dari 10 jam | Rendah | 1 |
| 10 sampai 20 jam | Sedang | 2 |
| lebih dari 20 jam | Tinggi | 3 |
data1 <- df_bersih %>%
select(ID_Mahasiswa, Jam_Tidur_Per_Hari, Jam_Belajar_Per_Minggu, Jam_Internet_Per_Hari)
data2 <- df_bersih %>%
transmute(
ID_Mahasiswa,
Jam_Belajar_Factor = factor(
ifelse(Jam_Belajar_Per_Minggu < 10, "Rendah",
ifelse(Jam_Belajar_Per_Minggu <= 20, "Sedang", "Tinggi")),
levels = c("Rendah", "Sedang", "Tinggi"), ordered = TRUE),
Jam_Belajar_Code = as.numeric(Jam_Belajar_Factor))
df_final <- data1 %>% left_join(data2, by = "ID_Mahasiswa")
# Pemeriksaan integritas sebelum dan sesudah penggabungan
tibble(
Pemeriksaan = c("ID pada Data 1 unik", "ID pada Data 2 unik",
"ID Data 1 tanpa pasangan di Data 2", "Jumlah baris data gabungan"),
Hasil = c(ifelse(anyDuplicated(data1$ID_Mahasiswa) == 0, "Lolos", "Gagal"),
ifelse(anyDuplicated(data2$ID_Mahasiswa) == 0, "Lolos", "Gagal"),
as.character(nrow(anti_join(data1, data2, by = "ID_Mahasiswa"))),
fmt_n(nrow(df_final)))
) %>% kable()| Pemeriksaan | Hasil |
|---|---|
| ID pada Data 1 unik | Lolos |
| ID pada Data 2 unik | Lolos |
| ID Data 1 tanpa pasangan di Data 2 | 0 |
| Jumlah baris data gabungan | 1.200 |
warna_kat <- c("Rendah" = pal$orange, "Sedang" = pal$steel, "Tinggi" = pal$navy)
p_kat <- df_final %>% count(Jam_Belajar_Factor) %>%
mutate(persen = n / sum(n)) %>%
ggplot(aes(Jam_Belajar_Factor, n, fill = Jam_Belajar_Factor)) +
geom_col(width = .6) +
geom_text(aes(label = paste0(n, " (", percent(persen, accuracy = .1), ")")), vjust = -.4, color = pal$navy, fontface = "bold") +
scale_fill_manual(values = warna_kat, guide = "none") +
scale_y_continuous(expand = expansion(mult = c(0, .15))) +
labs(title = "Sebaran tingkat jam belajar", x = NULL, y = "Jumlah mahasiswa")
p_vio <- ggplot(df_final, aes(Jam_Belajar_Factor, Jam_Tidur_Per_Hari, fill = Jam_Belajar_Factor)) +
geom_violin(alpha = .75, color = NA) +
geom_boxplot(width = .13, fill = "white", outlier.alpha = .3) +
scale_fill_manual(values = warna_kat, guide = "none") +
labs(title = "Jam tidur menurut tingkat belajar", x = NULL, y = "Jam tidur per hari")
p_kat + p_viodf_final %>%
select(`Jam tidur per hari` = Jam_Tidur_Per_Hari,
`Jam belajar per minggu` = Jam_Belajar_Per_Minggu,
`Jam internet per hari` = Jam_Internet_Per_Hari) %>%
pivot_longer(everything()) %>%
ggplot(aes(value)) +
geom_histogram(aes(y = after_stat(density)), bins = 30, fill = pal$mist, color = "white") +
geom_density(color = pal$navy, linewidth = 1) +
facet_wrap(~ name, scales = "free") +
labs(title = "Sebaran tiga variabel utama", x = NULL, y = "Kepadatan")Model yang dibangun menempatkan jam tidur sebagai variabel yang dijelaskan, dan jam belajar serta jam internet sebagai penjelasnya.
model <- lm(Jam_Tidur_Per_Hari ~ Jam_Belajar_Per_Minggu + Jam_Internet_Per_Hari, data = df_final)
hasil <- tidy(model, conf.int = TRUE) %>%
mutate(Variabel = recode(term, `(Intercept)` = "Konstanta",
Jam_Belajar_Per_Minggu = "Jam belajar per minggu",
Jam_Internet_Per_Hari = "Jam internet per hari"),
Signifikan = ifelse(p.value < .05, "Ya", "Tidak")) %>%
transmute(Variabel, Koefisien = estimate, `Galat baku` = std.error, `Nilai t` = statistic,
`Nilai p` = p.value, `Batas bawah` = conf.low, `Batas atas` = conf.high, Signifikan)
kable(hasil, digits = 4)| Variabel | Koefisien | Galat baku | Nilai t | Nilai p | Batas bawah | Batas atas | Signifikan |
|---|---|---|---|---|---|---|---|
| Konstanta | 8.1731 | 0.0894 | 91.3801 | 0 | 7.9977 | 8.3486 | Ya |
| Jam belajar per minggu | -0.0507 | 0.0042 | -12.1153 | 0 | -0.0589 | -0.0425 | Ya |
| Jam internet per hari | -0.2501 | 0.0133 | -18.7777 | 0 | -0.2762 | -0.2239 | Ya |
ringkas <- glance(model)
b_bel <- coef(model)["Jam_Belajar_Per_Minggu"]; p_bel <- summary(model)$coefficients["Jam_Belajar_Per_Minggu", 4]
b_int <- coef(model)["Jam_Internet_Per_Hari"]; p_int <- summary(model)$coefficients["Jam_Internet_Per_Hari", 4]
arah <- function(b) ifelse(b < 0, "menurunkan", "menaikkan")
sig <- function(p) ifelse(p < .05, "signifikan", "tidak signifikan")hasil %>% filter(Variabel != "Konstanta") %>%
ggplot(aes(Koefisien, Variabel)) +
geom_vline(xintercept = 0, linetype = "dashed", color = "grey50") +
geom_errorbarh(aes(xmin = `Batas bawah`, xmax = `Batas atas`), height = .15, color = pal$steel, linewidth = 1) +
geom_point(size = 4.5, color = pal$orange) +
labs(title = "Koefisien regresi dan selang kepercayaan 95%",
subtitle = "Selang yang tidak menyentuh garis nol berarti pengaruhnya signifikan",
x = "Perubahan jam tidur per satu satuan prediktor", y = NULL)Cara membaca hasil. Setiap tambahan satu jam internet per hari menurunkan jam tidur sekitar 0.25 jam (signifikan, nilai p = 0,0000), dengan jam belajar dianggap tetap. Setiap tambahan satu jam belajar per minggu menurunkan jam tidur sekitar 0.051 jam (signifikan, nilai p = 0,0000). Model menjelaskan sekitar 28.9% keragaman jam tidur (R2 terkoreksi = 0.288).
Model regresi hanya layak dipercaya bila asumsinya terpenuhi. Dua asumsi diuji di sini: residual berdistribusi normal, dan ragam residual sama besar (homogen).
##
## Asymptotic one-sample Kolmogorov-Smirnov test
##
## data: residu
## D = 0.016722, p-value = 0.8905
## alternative hypothesis: two-sided
p_h <- ggplot(tibble(residu), aes(residu)) +
geom_histogram(aes(y = after_stat(density)), bins = 30, fill = pal$mist, color = "white") +
stat_function(fun = dnorm, args = list(mean = mean(residu), sd = sd(residu)), color = pal$red, linewidth = 1.1) +
labs(title = "Histogram residual", subtitle = "Garis merah: kurva normal pembanding", x = "Residual", y = "Kepadatan")
p_q <- ggplot(tibble(residu), aes(sample = residu)) +
stat_qq(color = pal$steel, alpha = .55) +
stat_qq_line(color = pal$red, linewidth = 1) +
labs(title = "Q-Q plot residual", subtitle = "Titik yang menempel pada garis menandakan normal",
x = "Kuantil teoretis", y = "Kuantil residual")
p_h + p_q##
## studentized Breusch-Pagan test
##
## data: model
## BP = 0.082133, df = 2, p-value = 0.9598
tibble(fitted = fitted(model), residu = residu) %>%
ggplot(aes(fitted, residu)) +
geom_hline(yintercept = 0, linetype = "dashed", color = "grey50") +
geom_point(color = pal$steel, alpha = .45) +
geom_smooth(method = "loess", se = FALSE, color = pal$orange, linewidth = 1.2) +
labs(title = "Residual terhadap nilai prediksi",
subtitle = "Sebaran yang merata tanpa pola corong menandakan ragam homogen",
x = "Nilai prediksi jam tidur", y = "Residual")Kualitas data. Dataset mentah berisi 1.215 baris dan mengandung keempat jenis masalah: 46 sel kosong, penulisan gender dan kota yang tidak seragam, 15 ID mahasiswa yang kembar, dan 14 nilai yang berada di luar batas wajar. Setelah lima langkah pembersihan, tersisa 1.200 baris yang lengkap, seragam, dan masuk akal.
Integrasi dan encoding. Dua tabel berhasil digabung
lewat ID_Mahasiswa tanpa kehilangan baris, dan jam belajar
berhasil diubah menjadi kategori Rendah, Sedang, dan Tinggi berkode 1,
2, dan 3.
Hasil analisis. Pada model regresi, jam internet per hari signifikan dalam memengaruhi jam tidur, sedangkan jam belajar per minggu signifikan. Asumsi normalitas residual terpenuhi (nilai p = 0,8905) dan asumsi homogenitas ragam terpenuhi (nilai p = 0,9598).
Pesan utamanya: data yang bersih membuat kesimpulan statistik bisa dipercaya. Tanpa pembersihan, ID kembar dan angka mustahil seperti umur 150 tahun atau internet 30 jam sehari akan diam-diam merusak hasil.
Disusun oleh Riswan Nurintan bersama Kelompok 1, Universitas Riau. Dosen pengampu: Anisa Nurizki, M.Si dan Rezi Wahyuni, M.Si. Dibuat dengan R Markdown.
## R version 4.6.1 (2026-06-24 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26200)
##
## Matrix products: default
## LAPACK version 3.12.1
##
## locale:
## [1] LC_COLLATE=English_United States.utf8
## [2] LC_CTYPE=English_United States.utf8
## [3] LC_MONETARY=English_United States.utf8
## [4] LC_NUMERIC=C
## [5] LC_TIME=English_United States.utf8
##
## time zone: Asia/Bangkok
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] knitr_1.52 broom_1.0.13 patchwork_1.3.2 DT_0.34.0
## [5] scales_1.4.0 lmtest_0.9-40 zoo_1.9-1 readxl_1.5.0.1
## [9] lubridate_1.9.5 forcats_1.0.1 stringr_1.6.0 dplyr_1.2.1
## [13] purrr_1.2.2 readr_2.2.0 tidyr_1.3.2 tibble_3.3.1
## [17] ggplot2_4.0.3 tidyverse_2.0.0
##
## loaded via a namespace (and not attached):
## [1] sass_0.4.10 generics_0.1.4 stringi_1.8.9 lattice_0.22-9
## [5] hms_1.1.4 digest_0.6.39 magrittr_2.0.5 evaluate_1.0.5
## [9] grid_4.6.1 timechange_0.4.0 RColorBrewer_1.1-3 fastmap_1.2.0
## [13] Matrix_1.7-5 cellranger_1.1.0 jsonlite_2.0.0 backports_1.5.1
## [17] mgcv_1.9-4 viridisLite_0.4.3 crosstalk_1.2.2 jquerylib_0.1.4
## [21] cli_3.6.6 rlang_1.3.0 splines_4.6.1 withr_3.0.3
## [25] cachem_1.1.0 yaml_2.3.12 otel_0.2.0 tools_4.6.1
## [29] tzdb_0.5.0 vctrs_0.7.3 R6_2.6.1 stats4_4.6.1
## [33] lifecycle_1.0.5 htmlwidgets_1.6.4 pkgconfig_2.0.3 pillar_1.11.1
## [37] bslib_0.12.0 gtable_0.3.6 glue_1.8.1 xfun_0.61
## [41] tidyselect_1.2.1 rstudioapi_0.19.0 farver_2.1.2 nlme_3.1-169
## [45] htmltools_0.5.9 labeling_0.4.3 rmarkdown_2.32 compiler_4.6.1
## [49] S7_0.2.2