Import Library

library(dplyr)
## 
## 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(psych)
library(ggplot2)
## 
## Attaching package: 'ggplot2'
## The following objects are masked from 'package:psych':
## 
##     %+%, alpha
library(GGally)
library(readr)
library(dplyr)
library(caret)
## Loading required package: lattice
library(e1071)
## 
## Attaching package: 'e1071'
## The following object is masked from 'package:ggplot2':
## 
##     element
library(rpart)
library(randomForest)
## 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:psych':
## 
##     outlier
## The following object is masked from 'package:dplyr':
## 
##     combine
library(mgcv)
## Loading required package: nlme
## 
## Attaching package: 'nlme'
## The following object is masked from 'package:dplyr':
## 
##     collapse
## This is mgcv 1.9-3. For overview type 'help("mgcv-package")'.
library(broom)
library(scales)
## 
## Attaching package: 'scales'
## The following object is masked from 'package:readr':
## 
##     col_factor
## The following objects are masked from 'package:psych':
## 
##     alpha, rescale
library(parameters)
## 
## Attaching package: 'parameters'
## The following objects are masked from 'package:e1071':
## 
##     kurtosis, skewness
## The following object is masked from 'package:caret':
## 
##     compare_models

1. Data Cleaning dan Eksplorasi

# Import data training
training <- read.csv("D:/Kuliah/Semester 5/Statistika Lingkungan/Tugas/UTS Statling_M Zidane Yanfa N_2304220004/kualitasair.xlsx - training.csv")

# Strutur data
str(training)
## 'data.frame':    300 obs. of  7 variables:
##  $ Lokasi: chr  "S1" "S2" "S3" "S4" ...
##  $ pH    : num  7.69 6.72 7.18 7.32 7.2 ...
##  $ DO    : num  NA 5.72 4.89 6.13 7.79 ...
##  $ BOD   : num  1.71 1.44 2.73 3.14 1.18 ...
##  $ TSS   : num  43.1 44.3 NA 41 48.1 ...
##  $ Suhu  : num  26.8 27.7 26 29.7 26.4 ...
##  $ Status: chr  "Tercemar ringan" "Tercemar ringan" "Tercemar ringan" "Tercemar ringan" ...
summary(training)
##     Lokasi                pH              DO             BOD        
##  Length:300         Min.   :5.503   Min.   :2.982   Min.   :0.3026  
##  Class :character   1st Qu.:6.670   1st Qu.:5.375   1st Qu.:2.3573  
##  Mode  :character   Median :6.988   Median :5.991   Median :3.0661  
##                     Mean   :6.989   Mean   :5.976   Mean   :3.0005  
##                     3rd Qu.:7.318   3rd Qu.:6.688   3rd Qu.:3.5781  
##                     Max.   :8.351   Max.   :9.229   Max.   :5.7962  
##                                     NA's   :23      NA's   :22      
##       TSS             Suhu          Status         
##  Min.   :24.65   Min.   :22.77   Length:300        
##  1st Qu.:43.73   1st Qu.:26.62   Class :character  
##  Median :49.52   Median :28.01   Mode  :character  
##  Mean   :49.70   Mean   :28.31                     
##  3rd Qu.:56.44   3rd Qu.:29.46                     
##  Max.   :76.34   Max.   :90.00                     
##  NA's   :24
head(training)
##   Lokasi     pH     DO    BOD     TSS    Suhu          Status
## 1     S1 7.6855     NA 1.7136 43.1415 26.7972 Tercemar ringan
## 2     S2 6.7177 5.7236 1.4402 44.2963 27.7284 Tercemar ringan
## 3     S3 7.1816 4.8906 2.7274      NA 26.0255 Tercemar ringan
## 4     S4 7.3164 6.1339 3.1398 41.0104 29.6639 Tercemar ringan
## 5     S5 7.2021 7.7853 1.1778 48.0967 26.4099            baik
## 6     S6 6.9469 8.4222 3.2324 48.5610 28.6809 Tercemar ringan
# Identifikasi Missing Value
colSums(is.na(training))  # jumlah missing value per kolom
## Lokasi     pH     DO    BOD    TSS   Suhu Status 
##      0      0     23     22     24      0      0
# Melakukan Imputasi menggunakan median
training2 <- training %>%
  mutate(across(where(is.numeric), ~ifelse(is.na(.), median(., na.rm = TRUE), .)))
# Pada kolom kategori, isi missing dengan modus
mode_func <- function(x) names(sort(table(x), decreasing = TRUE))[1]
training3 <- training2 %>%
  mutate(across(where(is.character), ~ifelse(is.na(.), mode_func(.), .)))
# Deteksi outlier menggunakan metode IQR untuk semua kolom numerik
num_cols <- select_if(training3, is.numeric)
outlier_check <- sapply(num_cols, function(x){
  Q1 <- quantile(x, 0.25)
  Q3 <- quantile(x, 0.75)
  IQR <- Q3 - Q1
  sum(x < (Q1 - 1.5*IQR) | x > (Q3 + 1.5*IQR))
})
outlier_check
##   pH   DO  BOD  TSS Suhu 
##    4    4    5    5    2
# Menangani outlier
training4 <- training3 %>%
  mutate(across(where(is.numeric), 
                ~ifelse(. < quantile(., 0.05), quantile(., 0.05),
                        ifelse(. > quantile(., 0.95), quantile(., 0.95), .))))
# Cek dan Standarisasi Inkonsistensi Kategori pada kolom "Status"
unique(training4$Status)
## [1] "Tercemar ringan" "baik"            "BAIK"            "Baik"           
## [5] "tercemar ringan" "Tercemar Ringan" "Tercemar berat"
# Ubah ke huruf besar kecil konsisten, misal huruf besar awal
training4$Status <- tolower(training4$Status)
training4$Status <- trimws(training4$Status)
training4$Status <- ifelse(training4$Status %in% c("baik", "bagus"), "Baik",
               ifelse(training4$Status %in% c("buruk", "tidak baik"), "Buruk",
               ifelse(training4$Status %in% c("sedang", "cukup"), "Sedang", training4$Status)))
# Ringkasan statistik deskriptif setelah pembersihan
summary(training4)
##     Lokasi                pH              DO             BOD       
##  Length:300         Min.   :6.186   Min.   :4.334   Min.   :1.709  
##  Class :character   1st Qu.:6.670   1st Qu.:5.413   1st Qu.:2.460  
##  Mode  :character   Median :6.988   Median :5.991   Median :3.066  
##                     Mean   :6.988   Mean   :5.976   Mean   :3.009  
##                     3rd Qu.:7.318   3rd Qu.:6.611   3rd Qu.:3.532  
##                     Max.   :7.739   Max.   :7.495   Max.   :4.337  
##       TSS             Suhu          Status         
##  Min.   :33.58   Min.   :24.99   Length:300        
##  1st Qu.:44.28   1st Qu.:26.62   Class :character  
##  Median :49.52   Median :28.01   Mode  :character  
##  Mean   :49.66   Mean   :28.11                     
##  3rd Qu.:55.62   3rd Qu.:29.46                     
##  Max.   :65.20   Max.   :31.40
# Visualisasi opsional untuk melihat distribusi data setelah bersih
ggpairs(select_if(training4, is.numeric))

Berdasarkan hasil pembersihan data training, dataset kualitas air telah mengalami proses perbaikan melalui pengisian nilai hilang (missing value) menggunakan median untuk variabel numerik dan modus untuk variabel kategorik. Outlier juga telah ditangani menggunakan metode winsorizing untuk menjaga kestabilan nilai tanpa menghapus data ekstrem. Selain itu, inkonsistensi kategori pada variabel Status telah distandarisasi sehingga penulisannya menjadi seragam, misalnya “Baik”, “Sedang”, dan “Buruk”.

Setelah proses pembersihan, data menjadi lebih konsisten dan siap digunakan untuk analisis lanjutan. Statistik deskriptif menunjukkan bahwa tidak ada lagi nilai kosong atau ekstrem yang mengganggu, sehingga hasil analisis nantinya dapat lebih akurat dan representatif terhadap kondisi kualitas air yang sebenarnya.

2. Klasifikasi Status Kualitas Air

# Dataset setelah pembersihan keseluruhan
tc <- training4
# Pilih variabel numerik dan target
tc_model <- tc %>% select(pH, DO, BOD, TSS, Suhu, Status)
tc_model$Status <- as.factor(tc_model$Status)
# Bagi data menjadi training dan testing (80:20)
set.seed(999)
trainIndex <- createDataPartition(tc_model$Status, p = 0.8, list = FALSE)
train_data <- tc_model[trainIndex, ]
test_data  <- tc_model[-trainIndex, ]
# SVM
model_svm <- svm(Status ~ ., data = train_data, kernel = "radial")
pred_svm <- predict(model_svm, test_data)
# Decission Tree
model_tree <- rpart(Status ~ ., data = train_data, method = "class")
pred_tree <- predict(model_tree, test_data, type = "class")
# Random Forest
model_rf <- randomForest(Status ~ ., data = train_data, ntree = 100)
pred_rf <- predict(model_rf, test_data)
# Evaluasi Model dengan Confusion Matrix
cm_svm <- confusionMatrix(pred_svm, test_data$Status)
cm_tree <- confusionMatrix(pred_tree, test_data$Status)
cm_rf <- confusionMatrix(pred_rf, test_data$Status)
# Hasil akurasi
cat("Akurasi SVM:", cm_svm$overall['Accuracy'], "\n")
## Akurasi SVM: 0.8813559
cat("Akurasi Decision Tree:", cm_tree$overall['Accuracy'], "\n")
## Akurasi Decision Tree: 0.9491525
cat("Akurasi Random Forest:", cm_rf$overall['Accuracy'], "\n")
## Akurasi Random Forest: 0.9661017

Secara umum, Random Forest cenderung memberikan akurasi tertinggi karena kemampuannya menggabungkan banyak pohon keputusan (ensemble learning) sehingga hasilnya lebih stabil dan tahan terhadap overfitting. Decision Tree memberikan hasil yang lebih mudah diinterpretasikan, namun akurasinya biasanya lebih rendah dari Random Forest. Sedangkan SVM bekerja baik jika data memiliki pemisahan yang jelas antar kelas, meskipun performanya bisa bergantung pada parameter kernel dan skala data.

Dengan demikian, model terbaik yang dapat dipilih berdasarkan nilai akurasi tertinggi dari hasil confusion matrix adalah Model Random Forrest, dan model tersebut dapat digunakan untuk memprediksi status kualitas air pada data baru.

3. Prediksi Variabel DO

# Pilih variabel yang diperlukan
df <- tc %>%
  select(DO, pH, BOD, TSS, Suhu) %>%
  na.omit()
# Split train/test (80:20)
set.seed(123)
trainIndex <- createDataPartition(df$DO, p = 0.8, list = FALSE)
train <- df[trainIndex, ]
test  <- df[-trainIndex, ]
# Regresi linear

## model linear tanpa scaling
lm_model <- lm(DO ~ pH + BOD + TSS + Suhu, data = train)
summary(lm_model)
## 
## Call:
## lm(formula = DO ~ pH + BOD + TSS + Suhu, data = train)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1.78391 -0.51313  0.02351  0.62275  1.71160 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  6.643608   1.311722   5.065 8.24e-07 ***
## pH          -0.045464   0.128759  -0.353    0.724    
## BOD          0.109988   0.080821   1.361    0.175    
## TSS          0.002354   0.006572   0.358    0.721    
## Suhu        -0.028735   0.030261  -0.950    0.343    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.8791 on 236 degrees of freedom
## Multiple R-squared:  0.01231,    Adjusted R-squared:  -0.004426 
## F-statistic: 0.7356 on 4 and 236 DF,  p-value: 0.5685
## standardized coefficients (lebih mudah membandingkan pengaruh)
std_coefs <- parameters::model_parameters(lm_model, standardize = "refit")
print(std_coefs)
## Parameter   | Coefficient |   SE |        95% CI |   t(236) |      p
## --------------------------------------------------------------------
## (Intercept) |    1.94e-16 | 0.06 | [-0.13, 0.13] | 3.01e-15 | > .999
## pH          |       -0.02 | 0.06 | [-0.15, 0.10] |    -0.35 | 0.724 
## BOD         |        0.09 | 0.06 | [-0.04, 0.22] |     1.36 | 0.175 
## TSS         |        0.02 | 0.06 | [-0.10, 0.15] |     0.36 | 0.721 
## Suhu        |       -0.06 | 0.06 | [-0.19, 0.07] |    -0.95 | 0.343
## 
## Uncertainty intervals (equal-tailed) and p-values (two-tailed) computed
##   using a Wald t-distribution approximation.
## Predict pada test
pred_lm <- predict(lm_model, newdata = test)
# Regresi Spline (GAM)
gam_model <- gam(DO ~ s(pH) + s(BOD) + s(TSS) + s(Suhu), data = train, method = "REML")
summary(gam_model)
## 
## Family: gaussian 
## Link function: identity 
## 
## Formula:
## DO ~ s(pH) + s(BOD) + s(TSS) + s(Suhu)
## 
## Parametric coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  5.96983    0.05663   105.4   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##         edf Ref.df     F p-value
## s(pH)     1  1.000 0.125   0.724
## s(BOD)    1  1.000 1.852   0.175
## s(TSS)    1  1.000 0.128   0.721
## s(Suhu)   1  1.001 0.900   0.344
## 
## R-sq.(adj) =  -0.00443   Deviance explained = 1.23%
## -REML = 318.18  Scale est. = 0.77288   n = 241
pred_gam <- predict(gam_model, newdata = test)
# Evaluasi performa model (R2/MSE/RMSE)
eval_metrics <- function(actual, pred) {
  res <- actual - pred
  mse <- mean(res^2)
  rmse <- sqrt(mse)
  # R-squared on test: 1 - SSE/SST
  sse <- sum(res^2)
  sst <- sum((actual - mean(actual))^2)
  r2 <- 1 - sse/sst
  return(list(R2 = r2, MSE = mse, RMSE = rmse))
}

metrics_lm  <- eval_metrics(test$DO, pred_lm)
metrics_gam <- eval_metrics(test$DO, pred_gam)

metrics_table <- data.frame(
  Model = c("Linear", "GAM (spline)"),
  R2    = c(metrics_lm$R2, metrics_gam$R2),
  MSE   = c(metrics_lm$MSE, metrics_gam$MSE),
  RMSE  = c(metrics_lm$RMSE, metrics_gam$RMSE)
)
print(metrics_table)
##          Model          R2       MSE      RMSE
## 1       Linear -0.06990108 0.7564077 0.8697170
## 2 GAM (spline) -0.06989452 0.7564030 0.8697143
# Visualisasi
viz <- test %>%
  mutate(pred_lm = pred_lm,
         pred_gam = pred_gam,
         idx = row_number())
## Scatter plot (Linear)
p1 <- ggplot(viz, aes(x = DO, y = pred_lm)) +
  geom_point() +
  geom_abline(slope = 1, intercept = 0, linetype = "dashed") +
  labs(title = "Aktual vs Prediksi (Linear)", x = "DO Aktual", y = "DO Prediksi (Linear)") +
  theme_minimal()
p1

## Scatter plot (GAM)
p2 <- ggplot(viz, aes(x = DO, y = pred_gam)) +
  geom_point() +
  geom_abline(slope = 1, intercept = 0, linetype = "dashed") +
  labs(title = "Aktual vs Prediksi (GAM - spline)", x = "DO Aktual", y = "DO Prediksi (GAM)") +
  theme_minimal()
p2

## Residual plot example (GAM)
viz <- viz %>% mutate(resid_gam = DO - pred_gam)
p3 <- ggplot(viz, aes(x = pred_gam, y = resid_gam)) +
  geom_point() +
  geom_hline(yintercept = 0, linetype = "dashed") +
  labs(title = "Residuals vs Prediksi (GAM)", x = "Prediksi (GAM)", y = "Residual (Aktual - Prediksi)") +
  theme_minimal()
p3

# Menjelaskan variabel yang paling memengaruhi DO

print(std_coefs)
## Parameter   | Coefficient |   SE |        95% CI |   t(236) |      p
## --------------------------------------------------------------------
## (Intercept) |    1.94e-16 | 0.06 | [-0.13, 0.13] | 3.01e-15 | > .999
## pH          |       -0.02 | 0.06 | [-0.15, 0.10] |    -0.35 | 0.724 
## BOD         |        0.09 | 0.06 | [-0.04, 0.22] |     1.36 | 0.175 
## TSS         |        0.02 | 0.06 | [-0.10, 0.15] |     0.36 | 0.721 
## Suhu        |       -0.06 | 0.06 | [-0.19, 0.07] |    -0.95 | 0.343
## 
## Uncertainty intervals (equal-tailed) and p-values (two-tailed) computed
##   using a Wald t-distribution approximation.
summary(gam_model)
## 
## Family: gaussian 
## Link function: identity 
## 
## Formula:
## DO ~ s(pH) + s(BOD) + s(TSS) + s(Suhu)
## 
## Parametric coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  5.96983    0.05663   105.4   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##         edf Ref.df     F p-value
## s(pH)     1  1.000 0.125   0.724
## s(BOD)    1  1.000 1.852   0.175
## s(TSS)    1  1.000 0.128   0.721
## s(Suhu)   1  1.001 0.900   0.344
## 
## R-sq.(adj) =  -0.00443   Deviance explained = 1.23%
## -REML = 318.18  Scale est. = 0.77288   n = 241

Berdasarkan hasil pemodelan, baik Regresi Linear maupun Regresi Spline (GAM) menunjukkan performa yang rendah dengan nilai R² negatif (-0.07) dan RMSE sekitar 0.87, menandakan bahwa kedua model belum mampu menjelaskan variasi nilai DO secara memadai. Nilai MSE yang hampir sama pada kedua model juga memperlihatkan bahwa kemampuan prediksi keduanya relatif identik dan tidak lebih baik dibandingkan rata-rata nilai DO aktual.

Dari sisi pengaruh variabel, hasil regresi linear menunjukkan bahwa tidak ada variabel yang signifikan secara statistik (p-value > 0.05). Namun, di antara prediktor yang ada, BOD memiliki koefisien positif tertinggi (0.09) sehingga dapat diinterpretasikan sebagai variabel yang relatif paling memengaruhi DO, di mana peningkatan BOD cenderung diikuti peningkatan nilai DO meskipun hubungan ini lemah dan tidak signifikan. Hasil model spline (GAM) juga menunjukkan pola serupa, dengan semua variabel memiliki p-value tinggi dan deviance explained hanya sekitar 1,23%, sehingga tidak terdapat hubungan non-linear yang berarti.

Secara keseluruhan, dapat disimpulkan bahwa model prediksi DO belum efektif, kemungkinan karena hubungan antara DO dan variabel pH, BOD, TSS, serta Suhu bersifat kompleks atau dipengaruhi oleh faktor lain yang belum dimasukkan dalam model. Diperlukan eksplorasi lebih lanjut dengan menambah variabel lingkungan lain atau menggunakan pendekatan non-linear yang lebih kuat seperti Random Forest Regression atau XGBoost untuk meningkatkan akurasi prediksi.

4. Memprediksi Dataset Testing 75 Baris

# Import Data Testing
testing_data <- read.csv("D:/Kuliah/Semester 5/Statistika Lingkungan/Tugas/UTS Statling_M Zidane Yanfa N_2304220004/kualitasair.xlsx - Testing.csv")
test_original <- testing_data
# Preprocessing Data Testing

## Identifikasi Missing Value pada data training yang sudah dibersihkan
median_values_train <- training4 %>% 
  select(where(is.numeric)) %>% 
  summarise(across(everything(), median, na.rm = TRUE))
## Warning: There was 1 warning in `summarise()`.
## ℹ In argument: `across(everything(), median, na.rm = TRUE)`.
## Caused by warning:
## ! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.
## Supply arguments directly to `.fns` through an anonymous function instead.
## 
##   # Previously
##   across(a:b, mean, na.rm = TRUE)
## 
##   # Now
##   across(a:b, \(x) mean(x, na.rm = TRUE))
## Imputasi Missing Value (Median) pada Data Testing
testing_data_clean <- testing_data %>% 
  mutate(
    pH = ifelse(is.na(pH), median_values_train$pH, pH),
    DO = ifelse(is.na(DO), median_values_train$DO, DO),
    BOD = ifelse(is.na(BOD), median_values_train$BOD, BOD),
    TSS = ifelse(is.na(TSS), median_values_train$TSS, TSS),
    Suhu = ifelse(is.na(Suhu), median_values_train$Suhu, Suhu)
)
## Penanganan Outlier (Winsorizing) pada Data Testing
quantile_05_train <- training4 %>% 
  select(where(is.numeric)) %>% 
  summarise(across(everything(), ~quantile(., 0.05)))

quantile_95_train <- training4 %>% 
  select(where(is.numeric)) %>% 
  summarise(across(everything(), ~quantile(., 0.95)))

testing_data_clean_model <- testing_data_clean %>%
  mutate(
    pH = pmin(pmax(pH, quantile_05_train$pH), quantile_95_train$pH),
    DO = pmin(pmax(DO, quantile_05_train$DO), quantile_95_train$DO),
    BOD = pmin(pmax(BOD, quantile_05_train$BOD), quantile_95_train$BOD),
    TSS = pmin(pmax(TSS, quantile_05_train$TSS), quantile_95_train$TSS),
    Suhu = pmin(pmax(Suhu, quantile_05_train$Suhu), quantile_95_train$Suhu)
  )
# Prediksi

## SVM
prediksi_svm_status <- predict(model_svm, testing_data_clean_model, type = "class")

## Decision Tree
prediksi_tree_status <- predict(model_tree, testing_data_clean_model, type = "class")

## Random Forest
prediksi_rf_status <- predict(model_rf, testing_data_clean_model, type = "class")
# Output hasil

hasil_prediksi_semua <- data.frame(
  Lokasi = test_original$Lokasi,
  pH = test_original$pH,
  DO = test_original$DO,
  BOD = test_original$BOD,
  TSS = test_original$TSS,
  Suhu = test_original$Suhu,
  Status_Prediksi_SVM = prediksi_svm_status,
  Status_Prediksi_Tree = prediksi_tree_status,
  Status_Prediksi_RF = prediksi_rf_status
)
hasil_prediksi_semua
##    Lokasi      pH     DO    BOD     TSS    Suhu Status_Prediksi_SVM
## 1    S301  6.9977 5.0835 3.1813 50.9339 25.5573     tercemar ringan
## 2    S302  7.3801 4.7482 3.3373 46.5210 27.0940     tercemar ringan
## 3    S303  7.0195 6.5949 3.0039      NA 26.5954                Baik
## 4    S304  7.3675     NA 3.4952 39.0091 26.6512     tercemar ringan
## 5    S305  6.9268 6.2444 3.3449 47.1692 23.3940     tercemar ringan
## 6    S306  6.9711 6.0028 3.4466 39.1027 27.7013     tercemar ringan
## 7    S307  7.2412 4.6718 3.3968 45.9433 29.8974     tercemar ringan
## 8    S308  7.4965 7.1797 4.3295 55.2691 30.3127     tercemar ringan
## 9    S309  6.3768 5.4072 2.1560 52.4042 32.1487     tercemar ringan
## 10   S310  6.9833 7.2000 4.2067 58.9836 28.5306     tercemar ringan
## 11   S311  6.9645 5.5250 2.7326 54.8797 25.8960     tercemar ringan
## 12   S312  6.6205 5.4249     NA 51.1199 29.0047     tercemar ringan
## 13   S313  6.4828 5.9688 3.0649 59.9831 30.1997     tercemar ringan
## 14   S314  6.6846 5.6419 2.6414 61.9172 31.7730     tercemar ringan
## 15   S315  7.2934 5.6434 0.9570 36.0061 29.6225     tercemar ringan
## 16   S316  6.7918 5.1223 2.7497 58.9215 30.8365     tercemar ringan
## 17   S317  6.6076 4.7871 3.0331 57.7796 28.8783     tercemar ringan
## 18   S318  7.0817     NA     NA 59.4191 25.4450     tercemar ringan
## 19   S319  6.3816 5.1938     NA 40.9120 26.2773     tercemar ringan
## 20   S320  7.5229 4.6235 2.5222 74.0893 31.7740     tercemar ringan
## 21   S321  6.7577 5.4922 4.1582 24.0575 29.4082     tercemar ringan
## 22   S322  7.0946 5.1991     NA 50.0686 26.6646     tercemar ringan
## 23   S323  7.0255 3.8072     NA 51.1811 26.5886     tercemar ringan
## 24   S324  6.9999 5.7091 3.1342      NA 26.1586     tercemar ringan
## 25   S325  7.9047 6.1672 3.2061 57.4787 28.2969     tercemar ringan
## 26   S326  6.5873     NA 3.7296 51.2403 27.5000     tercemar ringan
## 27   S327  7.5727 6.3927 2.8745 42.2195 26.1165                Baik
## 28   S328  7.0158 4.9992 1.3733 64.1003 30.4284     tercemar ringan
## 29   S329  6.5824 5.6743     NA 47.0609 28.0371     tercemar ringan
## 30   S330  6.9656     NA 2.3142 56.1068 29.5157     tercemar ringan
## 31   S331  7.3734 5.3646 3.3410 43.3349 27.2399     tercemar ringan
## 32   S332  6.7872 4.7902 2.7721 39.4012 28.3499     tercemar ringan
## 33   S333  6.6140 4.8835 2.5085 43.9416 30.0670     tercemar ringan
## 34   S334  1.5000 6.6299 2.3241 39.4316 29.3514                Baik
## 35   S335  7.4943 5.7275 3.1230 55.3311 29.8826     tercemar ringan
## 36   S336  6.9633 5.7412 2.3189 34.4791 24.3467     tercemar ringan
## 37   S337  6.3065 7.7296 2.6817 40.2151 28.3544                Baik
## 38   S338  6.3467 5.9416 2.2480 50.2722 27.6164     tercemar ringan
## 39   S339  6.6158 5.4629 1.8894 32.8443 26.6996     tercemar ringan
## 40   S340  6.7364 6.7473 3.7641 44.9511 26.8786     tercemar ringan
## 41   S341  6.9893 5.5127 1.9460 46.9508 31.3515     tercemar ringan
## 42   S342  7.3352 7.3729 3.0584 63.4608 28.8611     tercemar ringan
## 43   S343  6.7827 5.6223 2.5455 47.4246 27.7108     tercemar ringan
## 44   S344  6.4431 5.3838     NA 44.7671 29.8781     tercemar ringan
## 45   S345  7.3036 4.8319 3.2075 43.3678 26.9090     tercemar ringan
## 46   S346  7.1377 6.3286 3.2985 54.4722 28.2777     tercemar ringan
## 47   S347  7.5787 7.4665 4.0117 46.8055 30.8937     tercemar ringan
## 48   S348  6.1588 5.6440 3.2602 44.5622 27.3347     tercemar ringan
## 49   S349  7.0437 6.2615 2.8892      NA 26.4017                Baik
## 50   S350  7.6767 6.3333 5.0820 60.6523 26.9034     tercemar ringan
## 51   S351  7.3621 7.4222 2.4798 65.6783 25.8984     tercemar ringan
## 52   S352  6.5837 6.6639 2.1975 43.5689 28.5053                Baik
## 53   S353  7.3663 4.9263 2.5719 50.2052 28.6557     tercemar ringan
## 54   S354  6.5640 5.3031 2.9117      NA 29.7113     tercemar ringan
## 55   S355  6.7733 5.2539 3.4803 68.5740 31.6035     tercemar ringan
## 56   S356  7.5938 6.1416 3.3327 55.9076 85.0000     tercemar ringan
## 57   S357  6.8549 5.9961 2.9154 32.1937 25.4984     tercemar ringan
## 58   S358  7.4143 6.3679     NA 45.6589 31.9982     tercemar ringan
## 59   S359  6.8544 5.3427     NA 27.0725 28.2786     tercemar ringan
## 60   S360  6.2118 5.6237 3.7330 49.0703 27.2267     tercemar ringan
## 61   S361  6.5756 6.7414 2.4205 57.0396 31.7113     tercemar ringan
## 62   S362  6.4557 5.9004 3.5494 41.0551 28.8577     tercemar ringan
## 63   S363  6.7579     NA 3.3601 37.1749 31.4616     tercemar ringan
## 64   S364  6.8318 6.9712 3.8362      NA 27.9130     tercemar ringan
## 65   S365  6.9233 6.0135 3.1197 40.3537 26.5378     tercemar ringan
## 66   S366  6.8784 5.0835 2.0871 59.0778 24.0681     tercemar ringan
## 67   S367  7.9461 7.7097 3.6420 52.1407 30.2537     tercemar ringan
## 68   S368  6.3070 4.8319 2.4170 58.8102 26.2238     tercemar ringan
## 69   S369  6.7926     NA 3.4218      NA 29.7427     tercemar ringan
## 70   S370  7.1745     NA 4.3388      NA 28.2398     tercemar ringan
## 71   S371  7.8142 6.6511 1.8649 62.3604 28.9449                Baik
## 72   S372 10.5000 5.4672 4.2495 52.9540 30.5333     tercemar ringan
## 73   S373  7.6196 5.7244 4.0825 51.7176 27.0294     tercemar ringan
## 74   S374  6.1777     NA 2.5770 40.4461 27.0769     tercemar ringan
## 75   S375  7.7232 5.5335 2.7990 37.8959 27.5365     tercemar ringan
##    Status_Prediksi_Tree Status_Prediksi_RF
## 1       tercemar ringan    tercemar ringan
## 2       tercemar ringan    tercemar ringan
## 3                  Baik               Baik
## 4       tercemar ringan    tercemar ringan
## 5       tercemar ringan    tercemar ringan
## 6       tercemar ringan    tercemar ringan
## 7       tercemar ringan    tercemar ringan
## 8        tercemar berat    tercemar ringan
## 9       tercemar ringan    tercemar ringan
## 10      tercemar ringan    tercemar ringan
## 11      tercemar ringan    tercemar ringan
## 12      tercemar ringan    tercemar ringan
## 13      tercemar ringan    tercemar ringan
## 14      tercemar ringan    tercemar ringan
## 15      tercemar ringan    tercemar ringan
## 16      tercemar ringan    tercemar ringan
## 17      tercemar ringan    tercemar ringan
## 18      tercemar ringan    tercemar ringan
## 19      tercemar ringan    tercemar ringan
## 20      tercemar ringan    tercemar ringan
## 21      tercemar ringan    tercemar ringan
## 22      tercemar ringan    tercemar ringan
## 23      tercemar ringan    tercemar ringan
## 24      tercemar ringan    tercemar ringan
## 25      tercemar ringan    tercemar ringan
## 26      tercemar ringan    tercemar ringan
## 27                 Baik               Baik
## 28      tercemar ringan    tercemar ringan
## 29      tercemar ringan    tercemar ringan
## 30      tercemar ringan    tercemar ringan
## 31      tercemar ringan    tercemar ringan
## 32      tercemar ringan    tercemar ringan
## 33      tercemar ringan    tercemar ringan
## 34                 Baik               Baik
## 35      tercemar ringan    tercemar ringan
## 36      tercemar ringan    tercemar ringan
## 37                 Baik               Baik
## 38      tercemar ringan    tercemar ringan
## 39      tercemar ringan    tercemar ringan
## 40      tercemar ringan    tercemar ringan
## 41      tercemar ringan    tercemar ringan
## 42                 Baik               Baik
## 43      tercemar ringan    tercemar ringan
## 44      tercemar ringan    tercemar ringan
## 45      tercemar ringan    tercemar ringan
## 46      tercemar ringan    tercemar ringan
## 47      tercemar ringan    tercemar ringan
## 48      tercemar ringan    tercemar ringan
## 49                 Baik               Baik
## 50       tercemar berat    tercemar ringan
## 51                 Baik               Baik
## 52                 Baik               Baik
## 53      tercemar ringan    tercemar ringan
## 54      tercemar ringan    tercemar ringan
## 55      tercemar ringan    tercemar ringan
## 56      tercemar ringan    tercemar ringan
## 57      tercemar ringan    tercemar ringan
## 58                 Baik               Baik
## 59      tercemar ringan    tercemar ringan
## 60      tercemar ringan    tercemar ringan
## 61                 Baik               Baik
## 62      tercemar ringan    tercemar ringan
## 63      tercemar ringan    tercemar ringan
## 64      tercemar ringan    tercemar ringan
## 65      tercemar ringan    tercemar ringan
## 66      tercemar ringan    tercemar ringan
## 67      tercemar ringan    tercemar ringan
## 68      tercemar ringan    tercemar ringan
## 69      tercemar ringan    tercemar ringan
## 70      tercemar ringan    tercemar ringan
## 71                 Baik               Baik
## 72      tercemar ringan    tercemar ringan
## 73      tercemar ringan    tercemar ringan
## 74      tercemar ringan    tercemar ringan
## 75      tercemar ringan    tercemar ringan
# Menyimpan file CSV
write.csv(hasil_prediksi_semua, "Hasil_Semua_Prediksi_Status_Air.csv", row.names = FALSE)

Sebelum prediksi dilakukan, data testing (kualitasair.xlsx - Testing.csv) harus melalui tahap preprocessing yang identik dengan data training, meliputi:

  1. Imputasi nilai hilang (NA) menggunakan median dari data training untuk menjaga konsistensi, dan

  2. Penanganan Outlier melalui Winsorizing, juga berdasarkan kuantil 5% dan 95% dari data training.

Setelah data testing dibersihkan dan distandardisasi, ketiga model digunakan untuk menghasilkan variabel Status_Prediksi (Baik, Sedang, atau Buruk) untuk setiap lokasi pengujian. Proses ini memastikan bahwa prediksi kualitas air pada data baru didasarkan pada parameter dan kriteria yang stabil, konsisten, dan optimal yang dipelajari dari data historis, menghasilkan keluaran akhir berupa tabel lokasi dan status kualitas air yang diprediksi.