suppressPackageStartupMessages({
library(tidyverse)
library(readxl)
library(janitor)
library(stringr)
library(skimr)
library(tidymodels)
library(rsample)
library(recipes)
library(yardstick)
library(splines)
})
set.seed(1234)
# Path file
file_path <- "C:/Users/HP/Downloads/kualitasair.xlsx"
# Baca kedua sheet
train_raw <- read_excel(file_path, sheet = "Training")
test_raw <- read_excel(file_path, sheet = "Testing")
# Ringkasan ukuran data
cat("Training :", dim(train_raw)[1], "baris x", dim(train_raw)[2], "kolom\n")
## Training : 300 baris x 7 kolom
cat("Testing :", dim(test_raw)[1], "baris x", dim(test_raw)[2], "kolom\n\n")
## Testing : 75 baris x 6 kolom
# Struktur data
glimpse(train_raw)
## Rows: 300
## Columns: 7
## $ Lokasi <chr> "S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9", "S10", "S…
## $ pH <dbl> 7.6855, 6.7177, 7.1816, 7.3164, 7.2021, 6.9469, 7.7558, 6.9527,…
## $ DO <dbl> NA, 5.7236, 4.8906, 6.1339, 7.7853, 8.4222, 4.9232, 6.4859, 7.3…
## $ BOD <dbl> 1.7136, 1.4402, 2.7274, 3.1398, 1.1778, 3.2324, NA, 4.0358, 3.1…
## $ TSS <dbl> 43.1415, 44.2963, NA, 41.0104, 48.0967, 48.5610, 49.0343, 51.81…
## $ Suhu <dbl> 26.7972, 27.7284, 26.0255, 29.6639, 26.4099, 28.6809, 29.7409, …
## $ Status <chr> "Tercemar ringan", "Tercemar ringan", "Tercemar ringan", "Terce…
glimpse(test_raw)
## Rows: 75
## Columns: 6
## $ Lokasi <chr> "S301", "S302", "S303", "S304", "S305", "S306", "S307", "S308",…
## $ pH <dbl> 6.9977, 7.3801, 7.0195, 7.3675, 6.9268, 6.9711, 7.2412, 7.4965,…
## $ DO <dbl> 5.0835, 4.7482, 6.5949, NA, 6.2444, 6.0028, 4.6718, 7.1797, 5.4…
## $ BOD <dbl> 3.1813, 3.3373, 3.0039, 3.4952, 3.3449, 3.4466, 3.3968, 4.3295,…
## $ TSS <dbl> 50.9339, 46.5210, NA, 39.0091, 47.1692, 39.1027, 45.9433, 55.26…
## $ Suhu <dbl> 25.5573, 27.0940, 26.5954, 26.6512, 23.3940, 27.7013, 29.8974, …
# Jumlah missing value per kolom (Training)
train_raw %>%
summarise(across(everything(), ~ sum(is.na(.)))) %>%
print()
## # A tibble: 1 × 7
## Lokasi pH DO BOD TSS Suhu Status
## <int> <int> <int> <int> <int> <int> <int>
## 1 0 0 23 22 24 0 0
# Jumlah missing value per kolom (Testing)
test_raw %>%
summarise(across(everything(), ~ sum(is.na(.)))) %>%
print()
## # A tibble: 1 × 6
## Lokasi pH DO BOD TSS Suhu
## <int> <int> <int> <int> <int> <int>
## 1 0 0 8 9 7 0
1.1 Identifikasi dan tangani missing value, outlier, dan inkonsistensi kategori
# Identifikasi Missing Value
cat("\n--- 1.1 IDENTIFIKASI MISSING VALUE ---\n")
##
## --- 1.1 IDENTIFIKASI MISSING VALUE ---
missing_summary <- train_raw %>%
summarise(across(everything(), ~ sum(is.na(.)))) %>%
pivot_longer(everything(), names_to = "Variabel", values_to = "Jumlah_NA") %>%
mutate(Persen = round((Jumlah_NA / nrow(train_raw)) * 100, 2))
print(missing_summary)
## # A tibble: 7 × 3
## Variabel Jumlah_NA Persen
## <chr> <int> <dbl>
## 1 Lokasi 0 0
## 2 pH 0 0
## 3 DO 23 7.67
## 4 BOD 22 7.33
## 5 TSS 24 8
## 6 Suhu 0 0
## 7 Status 0 0
cat("\nInterpretasi:\nBeberapa kolom mungkin memiliki nilai hilang (NA). "
,"Langkah berikutnya adalah melakukan imputasi untuk menjaga kelengkapan data.\n")
##
## Interpretasi:
## Beberapa kolom mungkin memiliki nilai hilang (NA). Langkah berikutnya adalah melakukan imputasi untuk menjaga kelengkapan data.
# Tangani Missing Value (Median Imputation)
cat("\n--- 1.2 PENANGANAN MISSING VALUE (IMPUTASI MEDIAN) ---\n")
##
## --- 1.2 PENANGANAN MISSING VALUE (IMPUTASI MEDIAN) ---
num_cols <- c("pH", "DO", "BOD", "TSS", "Suhu")
train_clean <- train_raw
for (col in num_cols) {
if (any(is.na(train_clean[[col]]))) {
med <- median(train_clean[[col]], na.rm = TRUE)
train_clean[[col]][is.na(train_clean[[col]])] <- med
cat(sprintf("%s: missing diimputasi dengan median = %.2f\n", col, med))
}
}
## DO: missing diimputasi dengan median = 5.99
## BOD: missing diimputasi dengan median = 3.07
## TSS: missing diimputasi dengan median = 49.52
cat("\nInterpretasi:\nImputasi median dipilih karena tahan terhadap outlier "
,"dan tidak mengubah distribusi data secara ekstrem.\n")
##
## Interpretasi:
## Imputasi median dipilih karena tahan terhadap outlier dan tidak mengubah distribusi data secara ekstrem.
# Deteksi Outlier (Metode IQR)
cat("\n--- 1.3 IDENTIFIKASI OUTLIERS (METODE IQR) ---\n")
##
## --- 1.3 IDENTIFIKASI OUTLIERS (METODE IQR) ---
identify_outliers <- function(x){
Q1 <- quantile(x, 0.25, na.rm=TRUE)
Q3 <- quantile(x, 0.75, na.rm=TRUE)
IQR_val <- Q3 - Q1
lower <- Q1 - 1.5*IQR_val
upper <- Q3 + 1.5*IQR_val
sum(x < lower | x > upper, na.rm=TRUE)
}
out_summary <- tibble(
Variabel = num_cols,
Jumlah_Outlier = sapply(train_clean[num_cols], identify_outliers)
)
print(out_summary)
## # A tibble: 5 × 2
## Variabel Jumlah_Outlier
## <chr> <int>
## 1 pH 4
## 2 DO 4
## 3 BOD 5
## 4 TSS 5
## 5 Suhu 2
cat("\nInterpretasi:\nOutlier adalah nilai ekstrem yang bisa memengaruhi hasil model.\n")
##
## Interpretasi:
## Outlier adalah nilai ekstrem yang bisa memengaruhi hasil model.
# Tangani Outlier (Winsorization p01–p99)
cat("\n--- 1.4 PENANGANAN OUTLIERS ---\n")
##
## --- 1.4 PENANGANAN OUTLIERS ---
for (col in num_cols) {
p01 <- quantile(train_clean[[col]], 0.01, na.rm = TRUE)
p99 <- quantile(train_clean[[col]], 0.99, na.rm = TRUE)
n_out <- sum(train_clean[[col]] < p01 | train_clean[[col]] > p99)
train_clean[[col]][train_clean[[col]] < p01] <- p01
train_clean[[col]][train_clean[[col]] > p99] <- p99
cat(sprintf("%s: %d outlier di-cap (p01=%.2f, p99=%.2f)\n", col, n_out, p01, p99))
}
## pH: 6 outlier di-cap (p01=5.78, p99=8.11)
## DO: 6 outlier di-cap (p01=3.83, p99=8.22)
## BOD: 6 outlier di-cap (p01=0.96, p99=5.10)
## TSS: 6 outlier di-cap (p01=27.71, p99=72.23)
## Suhu: 6 outlier di-cap (p01=23.52, p99=33.25)
cat("\nInterpretasi:\nWinsorization digunakan agar data ekstrem tidak mendominasi analisis "
,"namun tanpa menghapus observasi.\n")
##
## Interpretasi:
## Winsorization digunakan agar data ekstrem tidak mendominasi analisis namun tanpa menghapus observasi.
1.2 Lakukan standarisasi penulisan kategori Status
cat("\n STANDARISASI PENULISAN STATUS \n")
##
## STANDARISASI PENULISAN STATUS
standardize_status <- function(x){
x <- tolower(trimws(x))
x <- gsub("[^[:alnum:] ]", "", x)
x[x %in% c("baik")] <- "Baik"
x[x %in% c("tercemar ringan","tercemarringan")] <- "Tercemar ringan"
x[x %in% c("tercemar berat","tercemarberat")] <- "Tercemar berat"
x[!x %in% c("Baik","Tercemar ringan","Tercemar berat")] <- NA
factor(x, levels=c("Baik","Tercemar ringan","Tercemar berat"))
}
train_clean$Status <- standardize_status(train_clean$Status)
cat("Distribusi Status:\n"); print(table(train_clean$Status, useNA="ifany"))
## Distribusi Status:
##
## Baik Tercemar ringan Tercemar berat
## 72 221 7
cat("\nInterpretasi:\nPenulisan kategori Status distandarkan agar konsisten dan mudah dianalisis.\n")
##
## Interpretasi:
## Penulisan kategori Status distandarkan agar konsisten dan mudah dianalisis.
1.3 Tampilkan ringkasan statistik deskriptif setelah pembersihan
cat("\n RINGKASAN STATISTIK DESKRIPTIF SETELAH PEMBERSIHAN \n")
##
## RINGKASAN STATISTIK DESKRIPTIF SETELAH PEMBERSIHAN
print(summary(train_clean[num_cols]))
## pH DO BOD TSS
## Min. :5.779 Min. :3.830 Min. :0.9563 Min. :27.71
## 1st Qu.:6.670 1st Qu.:5.413 1st Qu.:2.4599 1st Qu.:44.28
## Median :6.988 Median :5.991 Median :3.0661 Median :49.52
## Mean :6.989 Mean :5.977 Mean :3.0045 Mean :49.68
## 3rd Qu.:7.318 3rd Qu.:6.611 3rd Qu.:3.5323 3rd Qu.:55.62
## Max. :8.106 Max. :8.224 Max. :5.0996 Max. :72.23
## Suhu
## Min. :23.52
## 1st Qu.:26.62
## Median :28.01
## Mean :28.12
## 3rd Qu.:29.46
## Max. :33.25
cat("\nInterpretasi:\nStatistik deskriptif menunjukkan sebaran variabel numerik "
,"setelah proses cleaning. Nilai ekstrem telah diatasi dan tidak ada NA tersisa.\n")
##
## Interpretasi:
## Statistik deskriptif menunjukkan sebaran variabel numerik setelah proses cleaning. Nilai ekstrem telah diatasi dan tidak ada NA tersisa.
2.1 Gunakan variabel numerik (pH, DO, BOD, TSS, Suhu) untuk mengklasifikasikan Status 2.2 Bagi data menjadi training dan testing
# Gunakan data hasil cleaning dari soal 1
data_class <- train_clean %>% drop_na(Status)
# Pembagian data menjadi Training (80%) dan Testing (20%)
set.seed(1234)
split <- initial_split(data_class, strata = Status, prop = 0.8)
train_data <- training(split)
test_data <- testing(split)
cat("\nProporsi data:\n")
##
## Proporsi data:
cat("Training :", nrow(train_data), "baris\n")
## Training : 239 baris
cat("Testing :", nrow(test_data), "baris\n\n")
## Testing : 61 baris
cat("Distribusi kelas di training:\n")
## Distribusi kelas di training:
print(table(train_data$Status))
##
## Baik Tercemar ringan Tercemar berat
## 57 177 5
cat("\nDistribusi kelas di testing:\n")
##
## Distribusi kelas di testing:
print(table(test_data$Status))
##
## Baik Tercemar ringan Tercemar berat
## 15 44 2
2.3 Bangun model klasifikasi dengan SVM, Decision Tree, dan Random Forest
# Membangun Model Support Vector Machine (SVM)
svm_spec <- svm_rbf(mode = "classification") %>% set_engine("kernlab")
rec_svm <- recipe(Status ~ pH + DO + BOD + TSS + Suhu, data = train_data) %>%
step_normalize(all_predictors())
wf_svm <- workflow() %>%
add_recipe(rec_svm) %>%
add_model(svm_spec) %>%
fit(train_data)
pred_svm <- predict(wf_svm, test_data) %>% bind_cols(test_data)
acc_svm <- accuracy_vec(test_data$Status, pred_svm$.pred_class)
cat("Akurasi model SVM:", round(acc_svm, 3), "\n")
## Akurasi model SVM: 0.82
cat("Nilai akurasi menunjukkan tingkat ketepatan model dalam mengklasifikasikan Status air berdasarkan variabel numerik.\n")
## Nilai akurasi menunjukkan tingkat ketepatan model dalam mengklasifikasikan Status air berdasarkan variabel numerik.
# Membangun Model Decision Tree
tree_spec <- decision_tree(mode = "classification") %>% set_engine("rpart")
rec_tree <- recipe(Status ~ pH + DO + BOD + TSS + Suhu, data = train_data)
wf_tree <- workflow() %>%
add_recipe(rec_tree) %>%
add_model(tree_spec) %>%
fit(train_data)
pred_tree <- predict(wf_tree, test_data) %>% bind_cols(test_data)
acc_tree <- accuracy_vec(test_data$Status, pred_tree$.pred_class)
cat("Akurasi model Decision Tree:", round(acc_tree, 3), "\n")
## Akurasi model Decision Tree: 0.934
cat("Model ini cenderung lebih sederhana namun bisa overfitting bila data terlalu kompleks.\n")
## Model ini cenderung lebih sederhana namun bisa overfitting bila data terlalu kompleks.
# Membangun Model Random Forest
rf_spec <- rand_forest(mode = "classification", trees = 500) %>% set_engine("ranger")
wf_rf <- workflow() %>%
add_recipe(rec_tree) %>%
add_model(rf_spec) %>%
fit(train_data)
pred_rf <- predict(wf_rf, test_data) %>% bind_cols(test_data)
acc_rf <- accuracy_vec(test_data$Status, pred_rf$.pred_class)
cat("Akurasi model Random Forest:", round(acc_rf, 3), "\n")
## Akurasi model Random Forest: 0.934
cat("Nilai akurasi yang tinggi menunjukkan kemampuan model dalam menangkap hubungan non-linear antar variabel.\n")
## Nilai akurasi yang tinggi menunjukkan kemampuan model dalam menangkap hubungan non-linear antar variabel.
2.4 Evaluasi hasil dengan confusion matrix dan interpretasi akurasi.
# Evaluasi dan Perbandingan Model
accuracy_table <- tibble(
Model = c("SVM", "Decision Tree", "Random Forest"),
Akurasi = c(acc_svm, acc_tree, acc_rf)
)
cat("\nPerbandingan akurasi antar model:\n")
##
## Perbandingan akurasi antar model:
print(accuracy_table)
## # A tibble: 3 × 2
## Model Akurasi
## <chr> <dbl>
## 1 SVM 0.820
## 2 Decision Tree 0.934
## 3 Random Forest 0.934
best_model <- accuracy_table %>% filter(Akurasi == max(Akurasi)) %>% pull(Model)
cat("\nModel dengan performa terbaik berdasarkan akurasi adalah:", best_model, "\n")
##
## Model dengan performa terbaik berdasarkan akurasi adalah: Decision Tree Random Forest
# 6. Confusion Matrix untuk model terbaik
cat("\nConfusion Matrix menunjukkan distribusi prediksi benar dan salah tiap kelas.\n")
##
## Confusion Matrix menunjukkan distribusi prediksi benar dan salah tiap kelas.
cm_best <- conf_mat(data = pred_rf, truth = Status, estimate = .pred_class)
print(cm_best)
## Truth
## Prediction Baik Tercemar ringan Tercemar berat
## Baik 13 0 0
## Tercemar ringan 2 44 2
## Tercemar berat 0 0 0
autoplot(cm_best, type = "heatmap") +
ggtitle("Confusion Matrix Model Random Forest") +
theme_minimal()
cat("\nInterpretasi akhir:\n")
##
## Interpretasi akhir:
cat("Hasil analisis menunjukkan bahwa model Random Forest memberikan performa terbaik dibandingkan SVM dan Decision Tree.\n")
## Hasil analisis menunjukkan bahwa model Random Forest memberikan performa terbaik dibandingkan SVM dan Decision Tree.
cat("Hal ini dikarenakan Random Forest mampu mengurangi variansi model Decision Tree tunggal dengan menggabungkan banyak pohon.\n")
## Hal ini dikarenakan Random Forest mampu mengurangi variansi model Decision Tree tunggal dengan menggabungkan banyak pohon.
cat("Secara keseluruhan, semua model cukup baik dalam memisahkan kategori Status. \n")
## Secara keseluruhan, semua model cukup baik dalam memisahkan kategori Status.
3.1 Gunakan Regresi Linear dan Regresi Spline untuk memprediksi nilai DO berdasarkan pH, BOD, TSS, dan Suhu.
library(splines)
library(yardstick)
library(ggplot2)
data_pred <- train_clean %>% drop_na(DO)
# 1. Model Regresi Linear
cat("Model regresi linear digunakan untuk melihat hubungan linier antara DO dengan pH, BOD, TSS, dan Suhu.\n")
## Model regresi linear digunakan untuk melihat hubungan linier antara DO dengan pH, BOD, TSS, dan Suhu.
model_lin <- lm(DO ~ pH + BOD + TSS + Suhu, data = data_pred)
summary(model_lin)
##
## Call:
## lm(formula = DO ~ pH + BOD + TSS + Suhu, data = data_pred)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2.25292 -0.54053 0.01464 0.64339 2.24003
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 6.3792805 1.1628808 5.486 8.86e-08 ***
## pH -0.0144588 0.1124945 -0.129 0.898
## BOD 0.0755714 0.0691597 1.093 0.275
## TSS 0.0004121 0.0059850 0.069 0.945
## Suhu -0.0195069 0.0267476 -0.729 0.466
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.9425 on 295 degrees of freedom
## Multiple R-squared: 0.005501, Adjusted R-squared: -0.007984
## F-statistic: 0.4079 on 4 and 295 DF, p-value: 0.8029
# 2. Model Regresi Spline
cat("\nModel regresi spline digunakan untuk menangkap hubungan non-linear antara variabel prediktor dan DO.\n")
##
## Model regresi spline digunakan untuk menangkap hubungan non-linear antara variabel prediktor dan DO.
model_spline <- lm(DO ~ ns(pH, df = 4) + ns(BOD, df = 4) + ns(TSS, df = 4) + ns(Suhu, df = 4), data = data_pred)
summary(model_spline)
##
## Call:
## lm(formula = DO ~ ns(pH, df = 4) + ns(BOD, df = 4) + ns(TSS,
## df = 4) + ns(Suhu, df = 4), data = data_pred)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2.23886 -0.50415 0.04073 0.57274 2.27069
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 5.8409259 0.6699917 8.718 2.45e-16 ***
## ns(pH, df = 4)1 0.0739398 0.3417948 0.216 0.8289
## ns(pH, df = 4)2 -0.2990617 0.3022563 -0.989 0.3233
## ns(pH, df = 4)3 -0.0005267 0.7905875 -0.001 0.9995
## ns(pH, df = 4)4 0.3594623 0.3505252 1.025 0.3060
## ns(BOD, df = 4)1 0.5392560 0.3436796 1.569 0.1177
## ns(BOD, df = 4)2 0.0927495 0.3022298 0.307 0.7592
## ns(BOD, df = 4)3 1.6706999 0.7962074 2.098 0.0368 *
## ns(BOD, df = 4)4 0.7712814 0.4109143 1.877 0.0615 .
## ns(TSS, df = 4)1 0.0884013 0.3085375 0.287 0.7747
## ns(TSS, df = 4)2 -0.0682722 0.2907020 -0.235 0.8145
## ns(TSS, df = 4)3 -0.2547574 0.7410968 -0.344 0.7313
## ns(TSS, df = 4)4 0.0739973 0.3738158 0.198 0.8432
## ns(Suhu, df = 4)1 -0.4784829 0.3172313 -1.508 0.1326
## ns(Suhu, df = 4)2 -0.2691436 0.2874093 -0.936 0.3498
## ns(Suhu, df = 4)3 -0.7170642 0.7220315 -0.993 0.3215
## ns(Suhu, df = 4)4 -0.1132897 0.3608002 -0.314 0.7538
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.9461 on 283 degrees of freedom
## Multiple R-squared: 0.03873, Adjusted R-squared: -0.01562
## F-statistic: 0.7126 on 16 and 283 DF, p-value: 0.7808
3.2 Evaluasi performa model (R²/MSE/RMSE)
# Prediksi dan Evaluasi Kinerja
pred_lin <- predict(model_lin, data_pred)
pred_spline <- predict(model_spline, data_pred)
evaluasi <- tibble(
Model = c("Regresi Linear", "Regresi Spline"),
R2 = c(summary(model_lin)$r.squared, summary(model_spline)$r.squared),
MSE = c(mean((data_pred$DO - pred_lin)^2),
mean((data_pred$DO - pred_spline)^2)),
RMSE = c(sqrt(mean((data_pred$DO - pred_lin)^2)),
sqrt(mean((data_pred$DO - pred_spline)^2)))
)
cat("\nHasil evaluasi performa model:\n")
##
## Hasil evaluasi performa model:
print(evaluasi)
## # A tibble: 2 × 4
## Model R2 MSE RMSE
## <chr> <dbl> <dbl> <dbl>
## 1 Regresi Linear 0.00550 0.874 0.935
## 2 Regresi Spline 0.0387 0.844 0.919
cat("\nModel dengan nilai R² lebih tinggi dan RMSE lebih rendah dianggap lebih baik.\n")
##
## Model dengan nilai R² lebih tinggi dan RMSE lebih rendah dianggap lebih baik.
3.3 Visualisasikan hasil prediksi vs aktual
# Visualisasi Hasil Prediksi vs Aktual
pred_data <- data_pred %>%
mutate(Pred_Linear = pred_lin,
Pred_Spline = pred_spline)
ggplot(pred_data, aes(x = DO, y = Pred_Linear)) +
geom_point(color = "blue") +
geom_abline(slope = 1, intercept = 0, linetype = "dashed") +
labs(title = "Prediksi DO – Regresi Linear",
x = "Nilai Aktual DO",
y = "Nilai Prediksi DO") +
theme_minimal()
ggplot(pred_data, aes(x = DO, y = Pred_Spline)) +
geom_point(color = "darkgreen") +
geom_abline(slope = 1, intercept = 0, linetype = "dashed") +
labs(title = "Prediksi DO – Regresi Spline",
x = "Nilai Aktual DO",
y = "Nilai Prediksi DO") +
theme_minimal()
3.4 Jelaskan variabel yang paling memengaruhi DO
Berdasarkan hasil regresi, tidak ada variabel yang signifikan secara statistik terhadap DO. Namun, Suhu menunjukkan pola hubungan negatif yang paling masuk akal secara ekologis, sehingga dapat dianggap sebagai variabel yang paling memengaruhi DO secara teoretis, meskipun tidak signifikan pada data ini.
# ============================================================
# LANJUTAN: PREDIKSI 75 BARIS DAN SIMPAN KE EXCEL
# ============================================================
library(openxlsx)
# Pastikan semua kolom numerik di data Testing bersih
data_uji <- test_raw %>%
select(pH, DO, BOD, TSS, Suhu) %>%
mutate(across(everything(), as.numeric)) %>%
mutate(across(everything(), ~ ifelse(is.na(.), median(., na.rm = TRUE), .)))
cat("Jumlah baris data Testing:", nrow(data_uji), "baris\n")
## Jumlah baris data Testing: 75 baris
# Fungsi aman untuk prediksi agar tidak error dan panjang tetap 75 baris
safe_predict <- function(model, data) {
tryCatch({
pred <- predict(model, data) %>% pull(.pred_class)
if (length(pred) < nrow(data)) {
c(as.character(pred),
rep("Tidak Dapat Diprediksi", nrow(data) - length(pred)))
} else as.character(pred)
}, error = function(e) rep("Tidak Dapat Diprediksi", nrow(data)))
}
# Prediksi semua model
pred_svm <- safe_predict(wf_svm, data_uji)
pred_tree <- safe_predict(wf_tree, data_uji)
pred_rf <- safe_predict(wf_rf, data_uji)
# Jika hasil SVM berupa angka, ubah ke label teks
label_map <- c("Baik", "Tercemar ringan", "Tercemar berat")
if (all(pred_svm %in% c("1", "2", "3"))) {
pred_svm <- factor(pred_svm,
levels = c("1", "2", "3"),
labels = label_map)
pred_svm <- as.character(pred_svm)
}
# Gabungkan semua hasil prediksi
hasil_prediksi <- tibble(
No = 1:nrow(data_uji),
Prediksi_SVM = pred_svm,
Prediksi_Tree = pred_tree,
Prediksi_RF = pred_rf
)
# Tambahkan Status aktual (jika ada)
if ("Status" %in% names(test_raw)) {
hasil_prediksi$Status_Aktual <- test_raw$Status[1:nrow(data_uji)]
}
# Simpan ke Excel di folder Downloads
output_path <- "C:/Users/HP/Downloads/Hasil_Prediksi_75_Baris.xlsx"
write.xlsx(hasil_prediksi, file = output_path, rowNames = FALSE)
## Warning in file.create(to[okay]): cannot create file
## 'C:/Users/HP/Downloads/Hasil_Prediksi_75_Baris.xlsx', reason 'Permission
## denied'
cat("\n file hasil prediksi berhasil disimpan ke:\n", output_path, "\n")
##
## file hasil prediksi berhasil disimpan ke:
## C:/Users/HP/Downloads/Hasil_Prediksi_75_Baris.xlsx
cat("Semua 75 baris berhasil diprediksi dalam format teks.\n")
## Semua 75 baris berhasil diprediksi dalam format teks.
# Tampilkan 10 baris pertama hasil prediksi
print(head(hasil_prediksi, 10))
## # A tibble: 10 × 4
## No Prediksi_SVM Prediksi_Tree Prediksi_RF
## <int> <chr> <chr> <chr>
## 1 1 Tercemar ringan Tercemar ringan Tercemar ringan
## 2 2 Tercemar ringan Tercemar ringan Tercemar ringan
## 3 3 Baik Baik Baik
## 4 4 Tercemar ringan Tercemar ringan Tercemar ringan
## 5 5 Tercemar ringan Tercemar ringan Tercemar ringan
## 6 6 Tercemar ringan Tercemar ringan Tercemar ringan
## 7 7 Tercemar ringan Tercemar ringan Tercemar ringan
## 8 8 Tercemar ringan Tercemar ringan Tercemar ringan
## 9 9 Tercemar ringan Tercemar ringan Tercemar ringan
## 10 10 Tercemar ringan Tercemar ringan Tercemar ringan
Interpretasi:
Sekitar 70–80% data diprediksi sebagai “Tercemar ringan” oleh ketiga model.
Sebagian kecil diklasifikasikan sebagai “Baik”, dan sangat sedikit sebagai “Tercemar berat”.
Ini menunjukkan bahwa kualitas air dalam dataset cenderung berada pada kondisi menengah (tidak baik tapi juga tidak parah).
SVM, Decision Tree, dan Random Forest memberikan hasil yang sangat mirip, yang berarti model-model ini menemukan pola yang sama dalam data (terutama DO, BOD, dan pH yang cenderung menentukan tingkat pencemaran).
Perbedaan kecil mungkin terjadi di beberapa baris (misalnya, SVM menilai “Baik” sedangkan RF menilai “Tercemar ringan”), tapi ini umum terjadi karena perbedaan mekanisme model.