✓ Nilai yang hilang (missing values) ✓ Data yang tercatat ganda (duplicate records) ✓ Nilai ekstrim (outlier) pada variabel numerik
# Import library
library(readxl)
## Warning: package 'readxl' was built under R version 4.4.3
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.4.3
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(tidyr)
## Warning: package 'tidyr' was built under R version 4.4.2
library(ggplot2)
## Warning: package 'ggplot2' was built under R version 4.4.3
#Import data dari Excel
data <- read_excel("C:/Users/MyBook Hype/Downloads/heart.xlsx")
#1. mengeCek nilai yang hilang (missing values)
missing_summary <- colSums(is.na(data))
print("Jumlah Missing Values per Kolom")
## [1] "Jumlah Missing Values per Kolom"
print(missing_summary)
## age sex cp trestbps chol fbs restecg thalach
## 0 0 0 0 0 0 0 0
## exang oldpeak slope ca thal target
## 0 0 0 0 0 0
if (sum(missing_summary) == 0) {
print("✅ Tidak ada missing values.")
} else {
print("⚠️ Ada missing values!")
}
## [1] "✅ Tidak ada missing values."
# 2.mengeCek data yang tercatat ganda (duplicate records)
duplicate_rows <- data[duplicated(data), ]
num_duplicates <- nrow(duplicate_rows)
print("Pengecekan Data Duplikat")
## [1] "Pengecekan Data Duplikat"
cat("Jumlah baris duplikat:", num_duplicates, "\n")
## Jumlah baris duplikat: 1
if (num_duplicates == 0) {
print("✅ Tidak ada data yang duplikat.")
} else {
print("⚠️ Terdapat data duplikat. Berikut contoh baris duplikat:")
print(head(duplicate_rows))
}
## [1] "⚠️ Terdapat data duplikat. Berikut contoh baris duplikat:"
## # A tibble: 1 × 14
## age sex cp trestbps chol fbs restecg thalach exang oldpeak slope
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 38 1 2 138 175 0 1 173 0 0 2
## # ℹ 3 more variables: ca <dbl>, thal <dbl>, target <dbl>
# 3. mengeCek nilai ekstrim (outlier) pada variabel numerik
numeric_vars <- select(data, where(is.numeric))
# Menggunakan metode IQR (Interquartile Range)
outlier_check <- function(x) {
Q1 <- quantile(x, 0.25, na.rm = TRUE)
Q3 <- quantile(x, 0.75, na.rm = TRUE)
IQR <- Q3 - Q1
lower <- Q1 - 1.5 * IQR
upper <- Q3 + 1.5 * IQR
sum(x < lower | x > upper, na.rm = TRUE)
}
outliers_summary <- sapply(numeric_vars, outlier_check)
print("=== Jumlah Outlier per Variabel Numerik ===")
## [1] "=== Jumlah Outlier per Variabel Numerik ==="
print(outliers_summary)
## age sex cp trestbps chol fbs restecg thalach
## 0 0 0 9 5 45 0 1
## exang oldpeak slope ca thal target
## 0 5 0 25 2 0
interpretasi : Hasil pemeriksaan outlier pada data menunjukkan bahwa beberapa variabel numerik memiliki nilai-nilai yang menyimpang dari distribusi umum (outlier). Variabel fbs (fasting blood sugar) memiliki jumlah outlier tertinggi, yaitu sebanyak 45 nilai. Hal ini kemungkinan disebabkan karena variabel ini merupakan variabel biner (0 atau 1), tetapi terdapat nilai-nilai yang tidak sesuai atau distribusinya sangat tidak merata.
Selain itu, variabel ca (jumlah pembuluh darah utama yang terlihat melalui fluoroskopi) juga menunjukkan jumlah outlier yang cukup tinggi, yaitu sebanyak 25 nilai. Hal ini bisa jadi mengindikasikan adanya kesalahan input data atau distribusi nilai yang tidak seimbang.
Variabel lainnya yang menunjukkan adanya outlier adalah: - trestbps (tekanan darah saat istirahat) sebanyak 9 outlier, - chol (kolesterol) sebanyak 5 outlier, - oldpeak (depresi segmen ST) sebanyak 5 outlier, - thalach (detak jantung maksimum) sebanyak 1 outlier, dan - thal sebanyak 2 outlier.
Sementara itu, variabel seperti age, sex, cp, restecg, exang, slope, dan target tidak memiliki outlier sama sekali. Beberapa dari variabel ini, seperti sex, cp, dan exang, memang merupakan variabel kategorik yang biasanya tidak relevan untuk diuji outlier-nya secara statistik.
# Fungsi untuk menghapus outlier berdasarkan IQR
remove_outliers <- function(df) {
df_clean <- df
for (col in names(df)) {
if (is.numeric(df[[col]])) {
Q1 <- quantile(df[[col]], 0.25, na.rm = TRUE)
Q3 <- quantile(df[[col]], 0.75, na.rm = TRUE)
IQR <- Q3 - Q1
lower <- Q1 - 1.5 * IQR
upper <- Q3 + 1.5 * IQR
df_clean <- df_clean[df_clean[[col]] >= lower & df_clean[[col]] <= upper | is.na(df_clean[[col]]), ]
}
}
return(df_clean)
}
# Terapkan fungsi ke data numerik
data_no_outliers <- remove_outliers(data)
# Cek kembali jumlah outlier
numeric_vars_clean <- select(data_no_outliers, where(is.numeric))
outliers_summary_after <- sapply(numeric_vars_clean, outlier_check)
print("Jumlah outlier setelah dibersihkan:")
## [1] "Jumlah outlier setelah dibersihkan:"
print(outliers_summary_after)
## age sex cp trestbps chol fbs restecg thalach
## 0 0 0 0 1 0 0 1
## exang oldpeak slope ca thal target
## 0 0 0 0 0 0
interpretasi : Hampir semua variabel sudah tidak memiliki outlier lagi setelah dibersihkan. Namun, masih tersisa 1 outlier pada variabel: - chol (kolesterol) - thalach (detak jantung maksimum)
# Statistika Deskriptif untuk seluruh dataset (setelah dibersihkan)
desc_stats <- data_no_outliers %>%
summarise(across(where(is.numeric),
list(
mean = ~mean(. , na.rm = TRUE),
median = ~median(. , na.rm = TRUE),
sd = ~sd(. , na.rm = TRUE),
min = ~min(. , na.rm = TRUE),
max = ~max(. , na.rm = TRUE),
q1 = ~quantile(., 0.25, na.rm = TRUE),
q3 = ~quantile(., 0.75, na.rm = TRUE),
iqr = ~(quantile(., 0.75, na.rm = TRUE) - quantile(., 0.25, na.rm = TRUE))
),
.names = "{col}_{fn}"))
print("=== Statistika Deskriptif setelah Menghapus Outlier ===")
## [1] "=== Statistika Deskriptif setelah Menghapus Outlier ==="
print(desc_stats)
## # A tibble: 1 × 112
## age_mean age_median age_sd age_min age_max age_q1 age_q3 age_iqr sex_mean
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 53.3 54 9.23 29 76 45 60 15 0.675
## # ℹ 103 more variables: sex_median <dbl>, sex_sd <dbl>, sex_min <dbl>,
## # sex_max <dbl>, sex_q1 <dbl>, sex_q3 <dbl>, sex_iqr <dbl>, cp_mean <dbl>,
## # cp_median <dbl>, cp_sd <dbl>, cp_min <dbl>, cp_max <dbl>, cp_q1 <dbl>,
## # cp_q3 <dbl>, cp_iqr <dbl>, trestbps_mean <dbl>, trestbps_median <dbl>,
## # trestbps_sd <dbl>, trestbps_min <dbl>, trestbps_max <dbl>,
## # trestbps_q1 <dbl>, trestbps_q3 <dbl>, trestbps_iqr <dbl>, chol_mean <dbl>,
## # chol_median <dbl>, chol_sd <dbl>, chol_min <dbl>, chol_max <dbl>, …
# Statistika Deskriptif untuk masing-masing variabel numerik (setelah dibersihkan)
numeric_vars_clean <- select(data_no_outliers, where(is.numeric))
numeric_summary <- summary(numeric_vars_clean)
print("=== Statistik Deskriptif per Variabel ===")
## [1] "=== Statistik Deskriptif per Variabel ==="
print(numeric_summary)
## age sex cp trestbps
## Min. :29.00 Min. :0.0000 Min. :0.000 Min. : 94.0
## 1st Qu.:45.00 1st Qu.:0.0000 1st Qu.:0.000 1st Qu.:120.0
## Median :54.00 Median :1.0000 Median :1.000 Median :130.0
## Mean :53.33 Mean :0.6754 Mean :0.943 Mean :128.7
## 3rd Qu.:60.00 3rd Qu.:1.0000 3rd Qu.:2.000 3rd Qu.:140.0
## Max. :76.00 Max. :1.0000 Max. :3.000 Max. :170.0
## chol fbs restecg thalach exang
## Min. :131.0 Min. :0 Min. :0.0000 Min. : 88.0 Min. :0.0000
## 1st Qu.:209.8 1st Qu.:0 1st Qu.:0.0000 1st Qu.:137.5 1st Qu.:0.0000
## Median :239.0 Median :0 Median :1.0000 Median :155.0 Median :0.0000
## Mean :242.4 Mean :0 Mean :0.5482 Mean :151.1 Mean :0.3158
## 3rd Qu.:269.2 3rd Qu.:0 3rd Qu.:1.0000 3rd Qu.:168.2 3rd Qu.:1.0000
## Max. :360.0 Max. :0 Max. :2.0000 Max. :202.0 Max. :1.0000
## oldpeak slope ca thal
## Min. :0.0000 Min. :0.000 Min. :0.0000 Min. :1.000
## 1st Qu.:0.0000 1st Qu.:1.000 1st Qu.:0.0000 1st Qu.:2.000
## Median :0.6000 Median :1.500 Median :0.0000 Median :2.000
## Mean :0.9461 Mean :1.452 Mean :0.4781 Mean :2.316
## 3rd Qu.:1.6000 3rd Qu.:2.000 3rd Qu.:1.0000 3rd Qu.:3.000
## Max. :4.0000 Max. :2.000 Max. :2.0000 Max. :3.000
## target
## Min. :0.0000
## 1st Qu.:0.0000
## Median :1.0000
## Mean :0.5789
## 3rd Qu.:1.0000
## Max. :1.0000
interpretasi : 1. Umur (age) - Rata-rata: 53,33 tahun, rentang dari 29 hingga 76 tahun - Mayoritas peserta berusia 45–60 tahun (lihat kuartil 1 dan 3) 2. Jenis Kelamin (sex) - Nilai 0 = perempuan, 1 = laki-laki - Rata-rata: 0,6754 → sekitar 67,5% adalah laki-laki 3. Chest Pain Type (cp) - Skala 0–3 (semakin tinggi, semakin tidak khas nyeri dada angina) - Median = 1, rata-rata = 0,943 → kebanyakan mengalami tipe nyeri dada yang khas angina 4. Tekanan Darah Istirahat (trestbps) - Rata-rata: 128,7 mmHg, dengan median 130 mmHg - Dalam kisaran normal-tinggi 5. Kolesterol (chol) - Rata-rata: 242,4 mg/dL - Umumnya di atas batas normal (<200 mg/dL) → indikasi risiko penyakit jantung 6. Gula Darah (fbs) - Nilai 0 = normal, 1 = tinggi - Semua bernilai 0 → tidak ada yang mengalami gula darah puasa tinggi (>120 mg/dL) 7. EKG Saat Istirahat (restecg) - Nilai dominan = 0 atau 1, median = 1 - Umumnya tidak menunjukkan kelainan besar 8. Detak Jantung Maksimum (thalach) - Rata-rata: 151,1 bpm, median 155 bpm - Detak jantung relatif tinggi saat uji stres 9. Angina yang Diinduksi Latihan (exang) Rata-rata: 0,316 → mayoritas tidak mengalami angina saat latihan 10. oldpeak (depresi ST setelah latihan) - Rata-rata: 0,946 - Nilai rendah → sebagian besar pasien tidak mengalami perubahan signifikan pada EKG setelah latihan 11. Kemiringan Segmen ST (slope) - Median: 1,5, mayoritas pada kategori 1–2 - Slope naik/turun mencerminkan fungsi jantung saat stres 12. Jumlah Pembuluh Darah Tersumbat (ca) - Median: 0, rata-rata 0,478 - Sebagian besar tidak memiliki penyumbatan 13. thal (jenis kelainan aliran darah) - Skala 1–3, rata-rata 2,316 - Nilai dominan 2–3 → mayoritas menunjukkan kelainan perfusi 14. Target (Diagnosis Penyakit Jantung) - Nilai 0 = tidak ada penyakit, 1 = ada penyakit - Rata-rata 0,5789 → sekitar 58% pasien terdiagnosis memiliki penyakit jantung
# Tabulasi antara fbs dan target
tab <- data %>%
count(fbs, target)
print(tab)
## # A tibble: 4 × 3
## fbs target n
## <dbl> <dbl> <int>
## 1 0 0 116
## 2 0 1 142
## 3 1 0 22
## 4 1 1 23
# Hitung proporsi penderita penyakit jantung di masing-masing kategori fbs
proporsi <- data %>%
group_by(fbs) %>%
summarise(
total = n(),
penderita = sum(target == 1),
proporsi_penyakit_jantung = penderita / total
)
print(proporsi)
## # A tibble: 2 × 4
## fbs total penderita proporsi_penyakit_jantung
## <dbl> <int> <int> <dbl>
## 1 0 258 142 0.550
## 2 1 45 23 0.511
# 4. Visualisasi
proporsi %>%
mutate(fbs_label = ifelse(fbs == 1, "> 120 mg/dl", "≤ 120 mg/dl")) %>%
ggplot(aes(x = fbs_label, y = proporsi_penyakit_jantung, fill = fbs_label)) +
geom_col() +
labs(
title = "Proporsi Penyakit Jantung Berdasarkan Kadar Gula Darah",
x = "Kadar Gula Darah (fbs)",
y = "Proporsi Penyakit Jantung"
) +
theme_minimal()
interpretasi : Kadar gula darah tinggi (> 120 mg/dl) tidak serta
merta meningkatkan proporsi penyakit jantung pada data ini, walaupun
biasanya diharapkan sebaliknya. Ini bisa menunjukkan pengaruh faktor
lain yang lebih dominan.
# Tambahkan label jenis nyeri dada
cp_labels <- c(
"0" = "Typical Angina",
"1" = "Atypical Angina",
"2" = "Non-anginal Pain",
"3" = "Asymptomatic"
)
# Analisis dan visualisasi
data %>%
filter(target == 1) %>%
count(cp) %>%
mutate(cp_label = recode(as.character(cp), !!!cp_labels)) %>%
ggplot(aes(x = cp_label, y = n, fill = cp_label)) +
geom_col() +
labs(
title = "Jenis Nyeri Dada Paling Banyak pada Penderita Penyakit Jantung",
x = "Jenis Nyeri Dada",
y = "Jumlah Penderita"
) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
- Temuan: Jenis nyeri dada yang paling sering dialami oleh penderita
penyakit jantung adalah Non-anginal pain, diikuti oleh Atypical angina
dan Typical angina. Sementara itu, jenis nyeri dada Asymptomatic
merupakan yang paling jarang dijumpai. - Interpretasi: Hal ini
menunjukkan bahwa mayoritas penderita tidak mengalami nyeri dada khas
seperti pada angina tipikal. Justru mereka lebih banyak mengalami nyeri
dada yang tidak spesifik atau tidak berhubungan langsung dengan
aktivitas jantung (non-anginal). Kondisi ini bisa menyebabkan
keterlambatan dalam diagnosis penyakit jantung karena gejala tidak
terlihat mencolok atau sesuai dengan gejala klasik. Oleh karena itu,
penting untuk tidak mengabaikan jenis nyeri dada yang tidak khas ketika
menilai risiko penyakit jantung, terutama pada populasi yang memiliki
faktor risiko lainnya.
# Ganti nama kolom kalau beda
data_long <- data %>%
select(target, cp, ca, thal) %>%
pivot_longer(
cols = -target,
names_to = "variabel",
values_to = "kategori"
)
proporsi <- data_long %>%
group_by(variabel, kategori) %>%
summarise(
total = n(),
penderita = sum(target == 1),
proporsi_hd = penderita / total,
.groups = "drop"
) %>%
mutate(kategori = as.factor(kategori))
ggplot(proporsi, aes(x = kategori, y = proporsi_hd, fill = kategori)) +
geom_col(show.legend = FALSE) +
facet_wrap(~ variabel, scales = "free_x") +
labs(
title = "Proporsi Penyakit Jantung berdasarkan Variabel Kategorikal",
x = "Kategori",
y = "Proporsi Penyakit Jantung"
) +
theme_minimal()
interpretai: 1. Subplot ca (jumlah pembuluh darah yang terlihat
fluoroskopi): - Kategori 0 dan 4 menunjukkan proporsi tertinggi. -
Kategori 1–3 proporsinya jauh lebih rendah. Interpretasi: Pasien dengan
0 atau 4 pembuluh darah yang terdeteksi cenderung memiliki proporsi
penyakit jantung lebih tinggi. 2. Subplot cp (jenis nyeri dada): -
Kategori 1 (Atypical angina) dan 2 (Non-anginal pain) memiliki proporsi
tertinggi. Interpretasi: Jenis nyeri dada selain angina tipikal sering
terkait dengan penyakit jantung. 3. Subplot thal (hasil tes
thalassemia): - Kategori 2 menunjukkan proporsi tertinggi, diikuti oleh
0. Interpretasi: Hasil tes thal kategori 2 bisa menjadi indikator kuat
terhadap keberadaan penyakit jantung.
# Visualisasi hubungan age vs thalach
ggplot(data, aes(x = age, y = thalach, color = as.factor(target))) +
geom_point(alpha = 0.6) + # scatter point
geom_smooth(method = "lm", se = FALSE, linetype = "dashed") + # garis linear
geom_smooth(method = "loess", se = FALSE, linetype = "solid") + # garis non-linear
labs(
title = "Hubungan antara Usia (age) dan Denyut Jantung Maksimum (thalach)",
x = "Usia",
y = "Denyut Jantung Maksimum (thalach)",
color = "Penyakit Jantung"
) +
theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
interpretasi: Grafik ini menunjukkan hubungan antara usia dan denyut
jantung maksimum (thalach), dibedakan berdasarkan status penyakit
jantung. Terlihat bahwa secara umum, semakin bertambah usia, denyut
jantung maksimum cenderung menurun. Pola penurunan ini terlihat lebih
tajam pada kelompok penderita penyakit jantung dibandingkan yang tidak.
Artinya, penderita penyakit jantung cenderung memiliki denyut jantung
maksimum yang lebih rendah seiring bertambahnya usia.
library(reshape2) # untuk melt korelasi
## Warning: package 'reshape2' was built under R version 4.4.3
##
## Attaching package: 'reshape2'
## The following object is masked from 'package:tidyr':
##
## smiths
library(RColorBrewer)
# Pilih variabel numerik (interval/rasio)
data_numeric <- data %>%
select_if(is.numeric)
# Hitung matriks korelasi
cor_matrix <- cor(data_numeric, use = "complete.obs")
# Ubah ke format long (untuk ggplot)
cor_melt <- melt(cor_matrix)
# Plot heatmap
ggplot(cor_melt, aes(Var1, Var2, fill = value)) +
geom_tile(color = "white") +
scale_fill_gradient2(
low = "blue", high = "red", mid = "white",
midpoint = 0, limit = c(-1,1),
name = "Korelasi"
) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) +
labs(
title = "Heatmap Korelasi Antar Variabel Numerik",
x = "Variabel",
y = "Variabel"
)
interpretasi: Heatmap ini menunjukkan kekuatan dan arah hubungan antar
variabel numerik. Warna merah menunjukkan korelasi positif, sedangkan
ungu menunjukkan korelasi negatif. Korelasi tertinggi terhadap variabel
target (indikator penyakit jantung) terlihat pada variabel cp (chest
pain), thalach (denyut jantung maksimum), ca, dan thal. Sebaliknya,
oldpeak (depresi ST) dan exang (angina karena latihan) menunjukkan
korelasi negatif dengan target. Ini memberi gambaran awal
variabel-variabel yang mungkin penting dalam prediksi penyakit
jantung.
# Library yang dibutuhkan
library(ggplot2)
library(dplyr)
# Buat data sesuai tabel
data <- data.frame(
No = 1:24,
Konsumsi_GWh = c(10, 20, 30, 50, 70, 90, 40, 80, 120, 200, 300, 400, 15, 25, 35, 50, 70, 5, 10, 15, 20, 25, 30, 40),
Biaya_per_kWh = c(1500, 1450, 1400, 1350, 1300, 1250, 1300, 1250, 1200, 1150, 1100, 1050,
1600, 1550, 1500, 1450, 1400, 1700, 1600, 1550, 1500, 1450, 1400, 1350),
Konsumen = c(rep("Rumah Tangga", 6),
rep("Industri", 6),
rep("Kantor Pemerintah", 5),
rep("UMKM", 7))
)
# Plot
ggplot(data, aes(x = Konsumsi_GWh, y = Biaya_per_kWh, color = Konsumen)) +
geom_point(size = 3) +
geom_smooth(method = "lm", se = FALSE, linetype = "dashed") + # garis tren per konsumen
labs(
title = "Harga Listrik per kWh Menurut Skema Berdasarkan Konsumen",
x = "Total Konsumsi Listrik (GWh)",
y = "Biaya per kWh (Rp)"
) +
theme_minimal() +
theme(
plot.title = element_text(face = "bold", hjust = 0.5)
)
## `geom_smooth()` using formula = 'y ~ x'
penjelasan sintax :
memanggil library : “library(ggplot2)” dan “library(dplyr)” ggplot2: untuk membuat grafik visualisasi data. dplyr: untuk manipulasi data (walaupun dalam script ini belum digunakan secara aktif).
membuat data frame data <- data.frame( No = 1:24, Konsumsi_GWh = c(…), Biaya_per_kWh = c(…), Konsumen = c(…)
membuat plot menggunakan ggplot ggplot(data, aes(x = Konsumsi_GWh, y = Biaya_per_kWh, color = Konsumen)) +
menambahkan titik ke plot geom_point(size = 3) +
menambahkan garis trend geom_smooth(method = “lm”, se = FALSE, linetype = “dashed”) +
menambahkan judul dan label sumbu labs( title = “Harga Listrik per kWh Menurut Skema Berdasarkan Konsumen”, x = “Total Konsumsi Listrik (GWh)”, y = “Biaya per kWh (Rp)” ) +
tema tampilan minimalis theme_minimal() +
mengatur gaya judul theme( plot.title = element_text(face = “bold”, hjust = 0.5) )
interpretasi chart : Grafik ini memperlihatkan hubungan antara total konsumsi listrik (GWh) dan biaya per kWh untuk berbagai jenis konsumen. Terlihat bahwa semakin tinggi konsumsi listrik, biaya per kWh cenderung menurun untuk semua kategori konsumen. Konsumen industri memiliki konsumsi tertinggi dan tarif per kWh yang paling murah. Sebaliknya, UMKM dan rumah tangga memiliki tarif per kWh yang lebih mahal karena konsumsi mereka lebih rendah. Ini mencerminkan adanya skema tarif bertingkat yang memberi insentif bagi pengguna besar.