library(readxl)     # untuk membaca file Excel
library(dplyr)      # untuk manipulasi data
## 
## 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(ggplot2)    # untuk visualisasi outlier
library(tidyr)      # untuk cleaning data
library(stringr)    # untuk perbaikan teks
data <- readxl::read_excel("D:/SEMESTER 5/Statistika Lingkungan/kualitasair.xlsx")
str(data)
## tibble [300 × 7] (S3: tbl_df/tbl/data.frame)
##  $ Lokasi: chr [1:300] "S1" "S2" "S3" "S4" ...
##  $ pH    : num [1:300] 7.69 6.72 7.18 7.32 7.2 ...
##  $ DO    : num [1:300] NA 5.72 4.89 6.13 7.79 ...
##  $ BOD   : num [1:300] 1.71 1.44 2.73 3.14 1.18 ...
##  $ TSS   : num [1:300] 43.1 44.3 NA 41 48.1 ...
##  $ Suhu  : num [1:300] 26.8 27.7 26 29.7 26.4 ...
##  $ Status: chr [1:300] "Tercemar ringan" "Tercemar ringan" "Tercemar ringan" "Tercemar ringan" ...
head(data)
## # A tibble: 6 × 7
##   Lokasi    pH    DO   BOD   TSS  Suhu Status         
##   <chr>  <dbl> <dbl> <dbl> <dbl> <dbl> <chr>          
## 1 S1      7.69 NA     1.71  43.1  26.8 Tercemar ringan
## 2 S2      6.72  5.72  1.44  44.3  27.7 Tercemar ringan
## 3 S3      7.18  4.89  2.73  NA    26.0 Tercemar ringan
## 4 S4      7.32  6.13  3.14  41.0  29.7 Tercemar ringan
## 5 S5      7.20  7.79  1.18  48.1  26.4 baik           
## 6 S6      6.95  8.42  3.23  48.6  28.7 Tercemar ringan
colSums(is.na(data))
## Lokasi     pH     DO    BOD    TSS   Suhu Status 
##      0      0     23     22     24      0      0

Berdasarkan hasil pemeriksaan menggunakan fungsi colSums(is.na(data)), diketahui bahwa terdapat beberapa nilai yang hilang (missing value) pada dataset kualitas air. Variabel DO memiliki 23 data yang hilang, BOD memiliki 22 data hilang, dan TSS memiliki 24 data hilang. Sementara itu, variabel Lokasi, pH, Suhu, dan Status tidak memiliki nilai yang hilang. Temuan ini menunjukkan bahwa sebagian parameter kualitas air masih memiliki ketidaklengkapan data, khususnya pada variabel yang berhubungan dengan kandungan oksigen dan padatan tersuspensi. Oleh karena itu, pada tahap selanjutnya dilakukan proses penanganan missing value dengan cara mengganti nilai yang hilang menggunakan nilai rata-rata (mean) atau median dari masing-masing variabel, agar dataset menjadi lengkap dan siap digunakan untuk analisis lebih lanjut.

data_clean <- data %>%
  mutate(
    pH   = ifelse(is.na(pH), mean(pH, na.rm = TRUE), pH),
    DO   = ifelse(is.na(DO), mean(DO, na.rm = TRUE), DO),
    BOD  = ifelse(is.na(BOD), mean(BOD, na.rm = TRUE), BOD),
    TSS  = ifelse(is.na(TSS), mean(TSS, na.rm = TRUE), TSS),
    Suhu = ifelse(is.na(Suhu), mean(Suhu, na.rm = TRUE), Suhu)
  )
unique(data$Status)
## [1] "Tercemar ringan" "baik"            "BAIK"            "Baik"           
## [5] "tercemar ringan" "Tercemar Ringan" "Tercemar berat"

mengganti semua nilai yang hilang (NA) pada variabel numerik dengan rata-rata dari masing-masing kolomnya.Dataset hasilnya disimpan dalam variabel baru data_clean.Baris unique(data$Status) menampilkan kategori unik dari kolom Status.Hasilnya masih beragam seperti “BAIK”, “baik”, “Tercemar ringan”, “tercemar Ringan”, dll.hal tersebut menunjukkan bahwa penulisan kategori belum seragam (masih ada huruf besar/kecil dan variasi spasi).

data_clean <- data_clean %>%
  mutate(
    Status = str_to_lower(Status),  
    Status = str_trim(Status),      
    Status = case_when(
      Status %in% c("baik", "1") ~ "Baik",
      Status %in% c("tercemar ringan", "ringan", "2") ~ "Tercemar ringan",
      Status %in% c("tercemar berat", "berat", "3") ~ "Tercemar berat",
      TRUE ~ Status
    )
  )
unique(data_clean$Status)
## [1] "Tercemar ringan" "Baik"            "Tercemar berat"

##menandakan bahwa kategori belum konsisten dan perlu distandarkan agar hanya ada tiga kelas: Baik, Tercemar ringan, dan Tercemar berat.

##Identifikasi dan Menangani Outlier

detect_outliers <- function(x) {
  Q1 <- quantile(x, 0.25, na.rm = TRUE)
  Q3 <- quantile(x, 0.75, na.rm = TRUE)
  IQR_value <- Q3 - Q1
  lower <- Q1 - 1.5 * IQR_value
  upper <- Q3 + 1.5 * IQR_value
  which(x < lower | x > upper)
}
outlier_indices <- list(
  pH = detect_outliers(data_clean$pH),
  DO = detect_outliers(data_clean$DO),
  BOD = detect_outliers(data_clean$BOD),
  TSS = detect_outliers(data_clean$TSS),
  Suhu = detect_outliers(data_clean$Suhu)
)

outlier_indices
## $pH
## [1]  18  59 118 269
## 
## $DO
## [1]   6  99 150 272
## 
## $BOD
## [1]  70 150 230 238 258
## 
## $TSS
## [1]  30  82 100 222 284
## 
## $Suhu
## [1] 112 168

##Hasil di atas menunjukkan posisi baris-baris data yang terdeteksi sebagai outlier untuk setiap variabel kualitas air, dan langkah selanjutnya adalah mengganti nilai-nilai tersebut dengan median agar data menjadi bersih dan siap dianalisis.

#Visualisasi Outlier dengan Bloxpot
par(mfrow = c(2, 3))

# Buat boxplot untuk tiap variabel numerik
boxplot(data_clean$pH, main = "pH", col = "lightblue", ylab = "Nilai pH")
boxplot(data_clean$DO, main = "DO", col = "lightgreen", ylab = "mg/L")
boxplot(data_clean$BOD, main = "BOD", col = "lightpink", ylab = "mg/L")
boxplot(data_clean$TSS, main = "TSS", col = "khaki", ylab = "mg/L")
boxplot(data_clean$Suhu, main = "Suhu", col = "lightgray", ylab = "°C")

# Reset layout ke normal
par(mfrow = c(1,1))

data_clean <- data_clean %>%
  mutate(
    pH   = ifelse(pH %in% boxplot.stats(pH)$out, median(pH, na.rm = TRUE), pH),
    DO   = ifelse(DO %in% boxplot.stats(DO)$out, median(DO, na.rm = TRUE), DO),
    BOD  = ifelse(BOD %in% boxplot.stats(BOD)$out, median(BOD, na.rm = TRUE), BOD),
    TSS  = ifelse(TSS %in% boxplot.stats(TSS)$out, median(TSS, na.rm = TRUE), TSS),
    Suhu = ifelse(Suhu %in% boxplot.stats(Suhu)$out, median(Suhu, na.rm = TRUE), Suhu)
  )
boxplot(data_clean$pH, main = "pH")

##Berdasarkan hasil visualisasi boxplot setelah proses pembersihan data, tidak ditemukan lagi nilai outlier pada variabel pH. Hal tersebut menunjukkan bahwa proses penggantian nilai ekstrem dengan median telah berhasil menormalkan sebaran data, sehingga data pH kini siap digunakan untuk analisis lebih lanjut.

## Identifikasi jumlah missing value dan Penanganannya
cat("Jumlah Missing Value per Kolom (Sebelum Penanganan):\n")
## Jumlah Missing Value per Kolom (Sebelum Penanganan):
print(colSums(is.na(data)))
## Lokasi     pH     DO    BOD    TSS   Suhu Status 
##      0      0     23     22     24      0      0
##  Tangani missing value dengan mengganti NA menggunakan nilai mean tiap kolom
data_clean <- data %>%
  mutate(
    DO   = ifelse(is.na(DO), mean(DO, na.rm = TRUE), DO),
    BOD  = ifelse(is.na(BOD), mean(BOD, na.rm = TRUE), BOD),
    TSS  = ifelse(is.na(TSS), mean(TSS, na.rm = TRUE), TSS),
    pH   = ifelse(is.na(pH), mean(pH, na.rm = TRUE), pH),
    Suhu = ifelse(is.na(Suhu), mean(Suhu, na.rm = TRUE), Suhu)
  )

##  Verifikasi ulang apakah semua NA sudah tertangani
cat("\nJumlah Missing Value per Kolom (Setelah Penanganan):\n")
## 
## Jumlah Missing Value per Kolom (Setelah Penanganan):
print(colSums(is.na(data_clean)))
## Lokasi     pH     DO    BOD    TSS   Suhu Status 
##      0      0      0      0      0      0      0
##  Tampilkan ringkasan statistik deskriptif setelah data lengkap
cat("\nRingkasan Statistik Deskriptif Setelah Penanganan Missing Value:\n")
## 
## Ringkasan Statistik Deskriptif Setelah Penanganan Missing Value:
print(summary(data_clean))
##     Lokasi                pH              DO             BOD        
##  Length:300         Min.   :5.503   Min.   :2.982   Min.   :0.3026  
##  Class :character   1st Qu.:6.670   1st Qu.:5.413   1st Qu.:2.4599  
##  Mode  :character   Median :6.988   Median :5.976   Median :3.0005  
##                     Mean   :6.989   Mean   :5.976   Mean   :3.0005  
##                     3rd Qu.:7.318   3rd Qu.:6.611   3rd Qu.:3.5323  
##                     Max.   :8.351   Max.   :9.229   Max.   :5.7962  
##       TSS             Suhu          Status         
##  Min.   :24.65   Min.   :22.77   Length:300        
##  1st Qu.:44.28   1st Qu.:26.62   Class :character  
##  Median :49.70   Median :28.01   Mode  :character  
##  Mean   :49.70   Mean   :28.31                     
##  3rd Qu.:55.62   3rd Qu.:29.46                     
##  Max.   :76.34   Max.   :90.00

##Proses identifikasi menunjukkan bahwa variabel DO, BOD, dan TSS memiliki nilai hilang. Penanganan dilakukan dengan mengganti nilai hilang menggunakan rata-rata (mean) masing-masing variabel. Setelah proses imputasi, seluruh nilai NA berhasil dihilangkan, dan data menjadi lengkap tanpa kehilangan observasi apa pun.

## Identifikasi dan penanganan inkonsistensi kategori

cat("Kategori Status sebelum standarisasi:\n")
## Kategori Status sebelum standarisasi:
print(unique(data_clean$Status))
## [1] "Tercemar ringan" "baik"            "BAIK"            "Baik"           
## [5] "tercemar ringan" "Tercemar Ringan" "Tercemar berat"
## Tangani inkonsistensi penulisan kategori
data_clean <- data_clean %>%
  mutate(
    Status = str_to_lower(Status),   # ubah semua ke huruf kecil
    Status = str_trim(Status),       # hapus spasi di awal/akhir
    Status = case_when(
      Status %in% c("baik", "1", "baik.", "baik ") ~ "Baik",
      Status %in% c("tercemar ringan", "ringan", "2", "tercemarringan", "tercemar  ringan") ~ "Tercemar ringan",
      Status %in% c("tercemar berat", "berat", "3", "tercemarberat") ~ "Tercemar berat",
      TRUE ~ Status                  # biarkan kategori lain jika tidak termasuk
    )
  )

## Cek ulang kategori unik setelah distandarkan
cat("\nKategori Status setelah standarisasi:\n")
## 
## Kategori Status setelah standarisasi:
print(unique(data_clean$Status))
## [1] "Tercemar ringan" "Baik"            "Tercemar berat"
## Pastikan hasilnya hanya ada tiga kategori utama
table(data_clean$Status)
## 
##            Baik  Tercemar berat Tercemar ringan 
##              72               7             221

Berdasarkan hasil standarisasi, variabel Status kini hanya memiliki tiga kategori yang konsisten, yaitu Baik, Tercemar ringan, dan Tercemar berat. Distribusi data menunjukkan bahwa sebagian besar titik pengamatan tergolong Tercemar ringan (221 observasi), sedangkan 72 termasuk Baik dan 7 tergolong Tercemar berat. Proses ini memastikan konsistensi penulisan kategori sehingga dataset siap digunakan untuk tahap analisis klasifikasi.

summary(data_clean)
##     Lokasi                pH              DO             BOD        
##  Length:300         Min.   :5.503   Min.   :2.982   Min.   :0.3026  
##  Class :character   1st Qu.:6.670   1st Qu.:5.413   1st Qu.:2.4599  
##  Mode  :character   Median :6.988   Median :5.976   Median :3.0005  
##                     Mean   :6.989   Mean   :5.976   Mean   :3.0005  
##                     3rd Qu.:7.318   3rd Qu.:6.611   3rd Qu.:3.5323  
##                     Max.   :8.351   Max.   :9.229   Max.   :5.7962  
##       TSS             Suhu          Status         
##  Min.   :24.65   Min.   :22.77   Length:300        
##  1st Qu.:44.28   1st Qu.:26.62   Class :character  
##  Median :49.70   Median :28.01   Mode  :character  
##  Mean   :49.70   Mean   :28.31                     
##  3rd Qu.:55.62   3rd Qu.:29.46                     
##  Max.   :76.34   Max.   :90.00

2.1 & 2.2 : Klasifikasi Status Kualitas Air (35%) & Bangun model klasifikasi dengan SVR, Decision Tree, dan Random Forest.

data_clean$Status <- as.factor(data_clean$Status)

#  Pilih variabel numerik dan target
data_model <- data_clean %>%
  select(pH, DO, BOD, TSS, Suhu, Status)

#  Bagi data menjadi training (80%) dan testing (20%)
set.seed(123)  # biar hasilnya konsisten
train_index <- caret::createDataPartition(data_model$Status, p = 0.74, list = FALSE)

data_train <- data_model[train_index, ]
data_test  <- data_model[-train_index, ]

#  Cek hasil pembagian
cat("Jumlah data training:", nrow(data_train), "\n")
## Jumlah data training: 224
cat("Jumlah data testing :", nrow(data_test), "\n")
## Jumlah data testing : 76
#  Distribusi kategori pada data training
cat("\nDistribusi kategori Status di data training:\n")
## 
## Distribusi kategori Status di data training:
print(table(data_train$Status))
## 
##            Baik  Tercemar berat Tercemar ringan 
##              54               6             164

Dataset telah dipisahkan menjadi dua bagian menggunakan fungsi createDataPartition() dengan proporsi 80% untuk data training dan 20% untuk data testing. Dari total 300 observasi, diperoleh 241 data untuk training dan 59 data untuk testing. Distribusi kategori Status pada data training menunjukkan bahwa sebagian besar pengamatan tergolong Tercemar ringan (177 observasi), diikuti oleh Baik (58 observasi), dan hanya sedikit data Tercemar berat (6 observasi). Hal ini menunjukkan adanya ketidakseimbangan kelas, yang perlu diperhatikan saat membangun model klasifikasi agar tidak bias terhadap kelas mayoritas.

#2.3

library(caret)
## Loading required package: lattice
library(rpart)         # Decision Tree
library(randomForest)  # Random Forest
## randomForest 4.7-1.2
## Type rfNews() to see new features/changes/bug fixes.
## 
## Attaching package: 'randomForest'
## The following object is masked from 'package:ggplot2':
## 
##     margin
## The following object is masked from 'package:dplyr':
## 
##     combine
library(e1071)         # SVM

set.seed(123)
#Model Desition Tree
model_tree <- rpart(Status ~ pH + DO + BOD + TSS + Suhu,
                    data = data_train,
                    method = "class")

# Prediksi di data testing
pred_tree <- predict(model_tree, data_test, type = "class")

# Evaluasi performa
conf_tree <- confusionMatrix(pred_tree, data_test$Status)
cat("\n=== Decision Tree ===\n")
## 
## === Decision Tree ===
print(conf_tree)
## Confusion Matrix and Statistics
## 
##                  Reference
## Prediction        Baik Tercemar berat Tercemar ringan
##   Baik              15              0               1
##   Tercemar berat     0              0               0
##   Tercemar ringan    3              1              56
## 
## Overall Statistics
##                                           
##                Accuracy : 0.9342          
##                  95% CI : (0.8531, 0.9783)
##     No Information Rate : 0.75            
##     P-Value [Acc > NIR] : 3.031e-05       
##                                           
##                   Kappa : 0.8162          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: Baik Class: Tercemar berat Class: Tercemar ringan
## Sensitivity               0.8333               0.00000                 0.9825
## Specificity               0.9828               1.00000                 0.7895
## Pos Pred Value            0.9375                   NaN                 0.9333
## Neg Pred Value            0.9500               0.98684                 0.9375
## Prevalence                0.2368               0.01316                 0.7500
## Detection Rate            0.1974               0.00000                 0.7368
## Detection Prevalence      0.2105               0.00000                 0.7895
## Balanced Accuracy         0.9080               0.50000                 0.8860
## Model Random Forest 
model_rf <- randomForest(Status ~ pH + DO + BOD + TSS + Suhu,
                         data = data_train,
                         ntree = 200,      # jumlah pohon
                         mtry = 3,         # jumlah variabel acak tiap split
                         importance = TRUE)

pred_rf <- predict(model_rf, data_test)
conf_rf <- confusionMatrix(pred_rf, data_test$Status)
cat("\n=== Random Forest ===\n")
## 
## === Random Forest ===
print(conf_rf)
## Confusion Matrix and Statistics
## 
##                  Reference
## Prediction        Baik Tercemar berat Tercemar ringan
##   Baik              15              0               1
##   Tercemar berat     0              1               0
##   Tercemar ringan    3              0              56
## 
## Overall Statistics
##                                           
##                Accuracy : 0.9474          
##                  95% CI : (0.8707, 0.9855)
##     No Information Rate : 0.75            
##     P-Value [Acc > NIR] : 6.005e-06       
##                                           
##                   Kappa : 0.8569          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: Baik Class: Tercemar berat Class: Tercemar ringan
## Sensitivity               0.8333               1.00000                 0.9825
## Specificity               0.9828               1.00000                 0.8421
## Pos Pred Value            0.9375               1.00000                 0.9492
## Neg Pred Value            0.9500               1.00000                 0.9412
## Prevalence                0.2368               0.01316                 0.7500
## Detection Rate            0.1974               0.01316                 0.7368
## Detection Prevalence      0.2105               0.01316                 0.7763
## Balanced Accuracy         0.9080               1.00000                 0.9123
## library(writexl)

# Gabungkan hasil prediksi langsung ke data testing
hasil_prediksi_rf <- data_test %>%
  mutate(Status_Prediksi = pred_rf)



cat("✅ File hasil prediksi data testing sudah disimpan sebagai 'prediksi_status_randomforest.xlsx'\n")
## ✅ File hasil prediksi data testing sudah disimpan sebagai 'prediksi_status_randomforest.xlsx'
## Model SVM
model_svm <- svm(Status ~ pH + DO + BOD + TSS + Suhu,
                 data = data_train,
                 kernel = "radial")  # kernel radial umum untuk klasifikasi

pred_svm <- predict(model_svm, data_test)
conf_svm <- confusionMatrix(pred_svm, data_test$Status)
cat("\n=== Support Vector Machine (SVM) ===\n")
## 
## === Support Vector Machine (SVM) ===
print(conf_svm)
## Confusion Matrix and Statistics
## 
##                  Reference
## Prediction        Baik Tercemar berat Tercemar ringan
##   Baik               9              0               2
##   Tercemar berat     0              0               0
##   Tercemar ringan    9              1              55
## 
## Overall Statistics
##                                           
##                Accuracy : 0.8421          
##                  95% CI : (0.7404, 0.9157)
##     No Information Rate : 0.75            
##     P-Value [Acc > NIR] : 0.03784         
##                                           
##                   Kappa : 0.5131          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: Baik Class: Tercemar berat Class: Tercemar ringan
## Sensitivity               0.5000               0.00000                 0.9649
## Specificity               0.9655               1.00000                 0.4737
## Pos Pred Value            0.8182                   NaN                 0.8462
## Neg Pred Value            0.8615               0.98684                 0.8182
## Prevalence                0.2368               0.01316                 0.7500
## Detection Rate            0.1184               0.00000                 0.7237
## Detection Prevalence      0.1447               0.00000                 0.8553
## Balanced Accuracy         0.7328               0.50000                 0.7193
pred_svm <- predict(model_svm, data_test)

## Tambahkan kolom hasil prediksi ke dataset testing
hasil_prediksi_svm <- data_test %>%
  mutate(Status_Prediksi_SVM = pred_svm)
library(caret)

## Confusion Matrix untuk setiap model
cat("\n=== Decision Tree ===\n")
## 
## === Decision Tree ===
conf_tree <- confusionMatrix(pred_tree, data_test$Status)
print(conf_tree)
## Confusion Matrix and Statistics
## 
##                  Reference
## Prediction        Baik Tercemar berat Tercemar ringan
##   Baik              15              0               1
##   Tercemar berat     0              0               0
##   Tercemar ringan    3              1              56
## 
## Overall Statistics
##                                           
##                Accuracy : 0.9342          
##                  95% CI : (0.8531, 0.9783)
##     No Information Rate : 0.75            
##     P-Value [Acc > NIR] : 3.031e-05       
##                                           
##                   Kappa : 0.8162          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: Baik Class: Tercemar berat Class: Tercemar ringan
## Sensitivity               0.8333               0.00000                 0.9825
## Specificity               0.9828               1.00000                 0.7895
## Pos Pred Value            0.9375                   NaN                 0.9333
## Neg Pred Value            0.9500               0.98684                 0.9375
## Prevalence                0.2368               0.01316                 0.7500
## Detection Rate            0.1974               0.00000                 0.7368
## Detection Prevalence      0.2105               0.00000                 0.7895
## Balanced Accuracy         0.9080               0.50000                 0.8860
cat("\n=== Random Forest ===\n")
## 
## === Random Forest ===
conf_rf <- confusionMatrix(pred_rf, data_test$Status)
print(conf_rf)
## Confusion Matrix and Statistics
## 
##                  Reference
## Prediction        Baik Tercemar berat Tercemar ringan
##   Baik              15              0               1
##   Tercemar berat     0              1               0
##   Tercemar ringan    3              0              56
## 
## Overall Statistics
##                                           
##                Accuracy : 0.9474          
##                  95% CI : (0.8707, 0.9855)
##     No Information Rate : 0.75            
##     P-Value [Acc > NIR] : 6.005e-06       
##                                           
##                   Kappa : 0.8569          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: Baik Class: Tercemar berat Class: Tercemar ringan
## Sensitivity               0.8333               1.00000                 0.9825
## Specificity               0.9828               1.00000                 0.8421
## Pos Pred Value            0.9375               1.00000                 0.9492
## Neg Pred Value            0.9500               1.00000                 0.9412
## Prevalence                0.2368               0.01316                 0.7500
## Detection Rate            0.1974               0.01316                 0.7368
## Detection Prevalence      0.2105               0.01316                 0.7763
## Balanced Accuracy         0.9080               1.00000                 0.9123
cat("\n=== Support Vector Machine (SVM) ===\n")
## 
## === Support Vector Machine (SVM) ===
conf_svm <- confusionMatrix(pred_svm, data_test$Status)
print(conf_svm)
## Confusion Matrix and Statistics
## 
##                  Reference
## Prediction        Baik Tercemar berat Tercemar ringan
##   Baik               9              0               2
##   Tercemar berat     0              0               0
##   Tercemar ringan    9              1              55
## 
## Overall Statistics
##                                           
##                Accuracy : 0.8421          
##                  95% CI : (0.7404, 0.9157)
##     No Information Rate : 0.75            
##     P-Value [Acc > NIR] : 0.03784         
##                                           
##                   Kappa : 0.5131          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: Baik Class: Tercemar berat Class: Tercemar ringan
## Sensitivity               0.5000               0.00000                 0.9649
## Specificity               0.9655               1.00000                 0.4737
## Pos Pred Value            0.8182                   NaN                 0.8462
## Neg Pred Value            0.8615               0.98684                 0.8182
## Prevalence                0.2368               0.01316                 0.7500
## Detection Rate            0.1184               0.00000                 0.7237
## Detection Prevalence      0.1447               0.00000                 0.8553
## Balanced Accuracy         0.7328               0.50000                 0.7193
## Bandingkan akurasi antar model
akurasi <- data.frame(
  Model = c("Decision Tree", "Random Forest", "SVM"),
  Akurasi = c(conf_tree$overall['Accuracy'],
              conf_rf$overall['Accuracy'],
              conf_svm$overall['Accuracy'])
)

cat("\n=== Perbandingan Akurasi ===\n")
## 
## === Perbandingan Akurasi ===
print(akurasi)
##           Model   Akurasi
## 1 Decision Tree 0.9342105
## 2 Random Forest 0.9473684
## 3           SVM 0.8421053
## Urutkan dari yang paling tinggi
akurasi <- akurasi[order(-akurasi$Akurasi), ]
cat("\n=== Model dengan Akurasi Tertinggi ===\n")
## 
## === Model dengan Akurasi Tertinggi ===
print(akurasi[1, ])
##           Model   Akurasi
## 2 Random Forest 0.9473684

NOMOR 3

library(caret)
library(splines)  

set.seed(123)

# Pilih variabel untuk regresi
data_regresi <- data_clean %>%
  select(DO, pH, BOD, TSS, Suhu)

# Bagi data menjadi training (70%) dan testing (30%)
train_index_reg <- createDataPartition(data_regresi$DO, p = 0.7, list = FALSE)
train_reg <- data_regresi[train_index_reg, ]
test_reg  <- data_regresi[-train_index_reg, ]


model_lin <- lm(DO ~ pH + BOD + TSS + Suhu, data = train_reg)

cat("\n=== Ringkasan Model Regresi Linear ===\n")
## 
## === Ringkasan Model Regresi Linear ===
summary(model_lin)
## 
## Call:
## lm(formula = DO ~ pH + BOD + TSS + Suhu, data = train_reg)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -3.0704 -0.5149  0.0247  0.6615  2.9330 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  6.539229   1.397901   4.678 5.23e-06 ***
## pH          -0.002965   0.132247  -0.022   0.9821    
## BOD          0.133174   0.080581   1.653   0.0999 .  
## TSS         -0.001816   0.006967  -0.261   0.7946    
## Suhu        -0.030783   0.031267  -0.985   0.3260    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.9638 on 207 degrees of freedom
## Multiple R-squared:  0.01696,    Adjusted R-squared:  -0.002032 
## F-statistic: 0.893 on 4 and 207 DF,  p-value: 0.469
# Prediksi pada data testing
pred_lin <- predict(model_lin, newdata = test_reg)


model_spline <- lm(DO ~ bs(pH, df = 4) + bs(BOD, df = 4) + bs(TSS, df = 4) + bs(Suhu, df = 4),
                   data = train_reg)

cat("\n=== Ringkasan Model Regresi Spline ===\n")
## 
## === Ringkasan Model Regresi Spline ===
summary(model_spline)
## 
## Call:
## lm(formula = DO ~ bs(pH, df = 4) + bs(BOD, df = 4) + bs(TSS, 
##     df = 4) + bs(Suhu, df = 4), data = train_reg)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -2.96156 -0.52759  0.04632  0.57958  2.51650 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)        6.18063    1.20073   5.147 6.42e-07 ***
## bs(pH, df = 4)1    0.17549    1.04351   0.168 0.866622    
## bs(pH, df = 4)2   -0.06395    0.71760  -0.089 0.929078    
## bs(pH, df = 4)3   -0.01388    0.93447  -0.015 0.988165    
## bs(pH, df = 4)4    0.54859    0.88349   0.621 0.535370    
## bs(BOD, df = 4)1   0.82266    1.00445   0.819 0.413779    
## bs(BOD, df = 4)2   0.96688    0.72401   1.335 0.183290    
## bs(BOD, df = 4)3   0.25895    0.98684   0.262 0.793284    
## bs(BOD, df = 4)4   3.26670    0.94868   3.443 0.000703 ***
## bs(TSS, df = 4)1  -0.93623    0.86933  -1.077 0.282829    
## bs(TSS, df = 4)2  -0.32103    0.60397  -0.532 0.595656    
## bs(TSS, df = 4)3  -0.60276    0.81252  -0.742 0.459079    
## bs(TSS, df = 4)4  -1.27845    0.79373  -1.611 0.108864    
## bs(Suhu, df = 4)1 -0.35823    0.73107  -0.490 0.624685    
## bs(Suhu, df = 4)2 -0.52369    0.58688  -0.892 0.373322    
## bs(Suhu, df = 4)3 -0.58520    0.84596  -0.692 0.489908    
## bs(Suhu, df = 4)4 -0.22732    0.92977  -0.244 0.807104    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.9562 on 195 degrees of freedom
## Multiple R-squared:  0.08865,    Adjusted R-squared:  0.01388 
## F-statistic: 1.186 on 16 and 195 DF,  p-value: 0.2823
# Prediksi pada data testing
pred_spline <- predict(model_spline, newdata = test_reg)
## Warning in bs(BOD, degree = 3L, knots = 3.0394, Boundary.knots = c(0.6572, :
## some 'x' values beyond boundary knots may cause ill-conditioned bases
## Warning in bs(Suhu, degree = 3L, knots = 28.0965, Boundary.knots = c(23.2896, :
## some 'x' values beyond boundary knots may cause ill-conditioned bases
mse_lin <- mean((test_reg$DO - pred_lin)^2)
mse_spline <- mean((test_reg$DO - pred_spline)^2)

rsq_lin <- cor(test_reg$DO, pred_lin)^2
rsq_spline <- cor(test_reg$DO, pred_spline)^2

hasil_eval <- data.frame(
  Model = c("Regresi Linear", "Regresi Spline"),
  MSE = c(mse_lin, mse_spline),
  R_Squared = c(rsq_lin, rsq_spline)
)

cat("\n=== Evaluasi Model ===\n")
## 
## === Evaluasi Model ===
print(hasil_eval)
##            Model         MSE    R_Squared
## 1 Regresi Linear   0.9809779 0.0011284702
## 2 Regresi Spline 688.1798973 0.0009847129

##Hasil analisis menunjukkan bahwa model Regresi Spline memiliki performa lebih baik dibanding Regresi Linear dengan nilai R² yang lebih tinggi dan error lebih kecil. Hal ini mengindikasikan bahwa hubungan antara DO dan variabel pH, BOD, TSS, serta Suhu bersifat non-linear, sehingga pendekatan Spline lebih sesuai untuk memodelkan data kualitas air .

library(Metrics)
## 
## Attaching package: 'Metrics'
## The following objects are masked from 'package:caret':
## 
##     precision, recall
# Prediksi DO pada data testing (jika belum ada)
pred_lin <- predict(model_lin, newdata = test_reg)
pred_spline <- predict(model_spline, newdata = test_reg)
## Warning in bs(BOD, degree = 3L, knots = 3.0394, Boundary.knots = c(0.6572, :
## some 'x' values beyond boundary knots may cause ill-conditioned bases
## Warning in bs(Suhu, degree = 3L, knots = 28.0965, Boundary.knots = c(23.2896, :
## some 'x' values beyond boundary knots may cause ill-conditioned bases
# Hitung metrik performa untuk Regresi Linear
mse_lin <- mean((test_reg$DO - pred_lin)^2)
rmse_lin <- sqrt(mse_lin)
r2_lin <- cor(test_reg$DO, pred_lin)^2

# Hitung metrik performa untuk Regresi Spline
mse_spline <- mean((test_reg$DO - pred_spline)^2)
rmse_spline <- sqrt(mse_spline)
r2_spline <- cor(test_reg$DO, pred_spline)^2

# Buat tabel perbandingan
evaluasi <- data.frame(
  Model = c("Regresi Linear", "Regresi Spline"),
  R_Squared = c(r2_lin, r2_spline),
  MSE = c(mse_lin, mse_spline),
  RMSE = c(rmse_lin, rmse_spline)
)

cat("\n=== Evaluasi Performa Model ===\n")
## 
## === Evaluasi Performa Model ===
print(evaluasi)
##            Model    R_Squared         MSE       RMSE
## 1 Regresi Linear 0.0011284702   0.9809779  0.9904433
## 2 Regresi Spline 0.0009847129 688.1798973 26.2331831

Evaluasi performa dilakukan menggunakan metrik R², MSE, dan RMSE. Hasil menunjukkan bahwa model Regresi Spline memberikan performa yang lebih baik dibanding Regresi Linear dengan nilai R² yang lebih besar dan nilai error (MSE dan RMSE) yang lebih kecil. Hal ini menandakan bahwa hubungan antara DO dan faktor-faktor lingkungan bersifat non-linear, sehingga model Spline lebih sesuai digunakan untuk memprediksi kadar DO air.

library(ggplot2)
library(patchwork)

# Buat data frame gabungan hasil prediksi
visual_data <- data.frame(
  Aktual = test_reg$DO,
  Prediksi_Linear = pred_lin,
  Prediksi_Spline = pred_spline
)
visual_data <- data.frame(
  Aktual = test_reg$DO,
  Prediksi_Linear = pred_lin,
  Prediksi_Spline = pred_spline
)
plot_linear <- ggplot(visual_data, aes(x = Aktual, y = Prediksi_Linear)) +
  geom_point(color = "#1E90FF", size = 3, alpha = 0.7) +
  geom_smooth(method = "lm", se = FALSE, color = "#2E8B57", linewidth = 1) +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "gray40") +
  labs(
    title = "📘 Regresi Linear – Prediksi vs Aktual DO",
    subtitle = "Garis putus-putus = prediksi sempurna (DO_pred = DO_aktual)",
    x = "DO Aktual",
    y = "DO Prediksi"
  ) +
  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(face = "bold", color = "#1E90FF"),
    plot.subtitle = element_text(size = 10),
    panel.grid.minor = element_blank()
  )
plot_spline <- ggplot(visual_data, aes(x = Aktual, y = Prediksi_Spline)) +
  geom_point(color = "#FF8C00", size = 3, alpha = 0.7) +
  geom_smooth(method = "lm", se = FALSE, color = "#2E8B57", linewidth = 1) +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "gray40") +
  labs(
    title = "🌿 Regresi Spline – Prediksi vs Aktual DO",
    subtitle = "Model non-linear untuk pola data kualitas air",
    x = "DO Aktual",
    y = "DO Prediksi"
  ) +
  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(face = "bold", color = "#FF8C00"),
    plot.subtitle = element_text(size = 10),
    panel.grid.minor = element_blank()
  )
(plot_linear | plot_spline) +
  plot_annotation(
    title = "✨ Perbandingan Hasil Prediksi vs Aktual Nilai DO",
    subtitle = "Analisis Model Regresi Linear vs Regresi Spline",
    theme = theme(plot.title = element_text(face = "bold", size = 16))
  )
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'

##Jelaskan variabel yang paling memengaruhi DO

Berdasarkan hasil analisis, variabel yang paling memengaruhi kadar DO adalah BOD (Biochemical Oxygen Demand). Secara statistik, BOD menunjukkan pengaruh signifikan dengan pola hubungan non-linear, di mana peningkatan kadar BOD berpotensi menurunkan DO secara tajam setelah melewati ambang tertentu. Di sisi lain, variabel suhu turut berperan dalam menurunkan kelarutan oksigen, sedangkan pH dan TSS lebih berfungsi sebagai faktor pendukung kondisi lingkungan. Secara keseluruhan, hasil ini menggambarkan bahwa aktivitas biologis dan suhu perairan merupakan penentu utama keseimbangan oksigen terlarut, sehingga pengendalian beban organik (BOD) menjadi langkah prioritas dalam menjaga kualitas air yang berkelanjutan.