library(readxl)
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(readxl)
library(dplyr)
library(caret)        
## Loading required package: ggplot2
## 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:dplyr':
## 
##     combine
library(splines)     
library(ggplot2)
# Baca dataset
data <- read_excel("C:/Users/Ahnaf Zelfa/Documents/aini/SEMESTER 5/kualitasair.xlsx")

# Lihat struktur awal
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" ...
summary(data)
##     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

Soal 1: Data Cleaning dan Eksplorasi (30%)

  1. Identifikasi dan tangani missing value, outlier, dan inkonsistensi kategori.
  2. Lakukan standarisasi penulisan kategori Status.
  3. Tampilkan ringkasan statistik deskriptif setelah pembersihan.
# Identifikasi Missing Value
colSums(is.na(data))
## Lokasi     pH     DO    BOD    TSS   Suhu Status 
##      0      0     23     22     24      0      0
# Imputasi Missing Value
# Median untuk numerik, modus untuk kategori
for (col in c("pH", "DO", "BOD", "TSS", "Suhu")) {
  data[[col]][is.na(data[[col]])] <- median(data[[col]], na.rm = TRUE)
}

# Fungsi modus
modus <- function(x) {
  uniqx <- na.omit(unique(x))
  uniqx[which.max(tabulate(match(x, uniqx)))]
}
data$Status[is.na(data$Status)] <- modus(data$Status)

# Deteksi dan Tangani Outlier (Metode IQR)
cap_outlier <- 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
  x[x < lower] <- lower
  x[x > upper] <- upper
  return(x)
}
for (col in c("pH", "DO", "BOD", "TSS", "Suhu")) {
  data[[col]] <- cap_outlier(data[[col]])
}

data$Status <- tolower(trimws(data$Status))

data$Status <- ifelse(grepl("baik", data$Status) | data$Status == "1", 1,
               ifelse(grepl("ringan", data$Status) | data$Status == "2", 2,
               ifelse(grepl("berat", data$Status) | data$Status == "3", 3, NA)))

data$Status <- factor(data$Status,
                      levels = c(1,2,3),
                      labels = c("Baik", "Tercemar Ringan", "Tercemar Berat"))
summary(data)
##     Lokasi                pH              DO             BOD        
##  Length:300         Min.   :5.697   Min.   :3.615   Min.   :0.8513  
##  Class :character   1st Qu.:6.670   1st Qu.:5.413   1st Qu.:2.4599  
##  Mode  :character   Median :6.988   Median :5.991   Median :3.0661  
##                     Mean   :6.990   Mean   :5.977   Mean   :3.0041  
##                     3rd Qu.:7.318   3rd Qu.:6.611   3rd Qu.:3.5323  
##                     Max.   :8.290   Max.   :8.409   Max.   :5.1409  
##       TSS             Suhu                   Status   
##  Min.   :27.28   Min.   :22.77   Baik           : 72  
##  1st Qu.:44.28   1st Qu.:26.62   Tercemar Ringan:221  
##  Median :49.52   Median :28.01   Tercemar Berat :  7  
##  Mean   :49.68   Mean   :28.12                        
##  3rd Qu.:55.62   3rd Qu.:29.46                        
##  Max.   :72.62   Max.   :33.73
# Alternatif ringkasan statistik numerik saja
data %>%
  select(pH, DO, BOD, TSS, Suhu) %>%
  summary()
##        pH              DO             BOD              TSS       
##  Min.   :5.697   Min.   :3.615   Min.   :0.8513   Min.   :27.28  
##  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.990   Mean   :5.977   Mean   :3.0041   Mean   :49.68  
##  3rd Qu.:7.318   3rd Qu.:6.611   3rd Qu.:3.5323   3rd Qu.:55.62  
##  Max.   :8.290   Max.   :8.409   Max.   :5.1409   Max.   :72.62  
##       Suhu      
##  Min.   :22.77  
##  1st Qu.:26.62  
##  Median :28.01  
##  Mean   :28.12  
##  3rd Qu.:29.46  
##  Max.   :33.73
# Distribusi kategori Status
table(data$Status)
## 
##            Baik Tercemar Ringan  Tercemar Berat 
##              72             221               7
# Simpan data hasil cleaning
write.csv(data, "data_kualitasair_clean.csv", row.names = FALSE)

Proses data cleaning dan eksplorasi dilakukan untuk memastikan kualitas data sebelum analisis. Dataset dibaca dan diperiksa strukturnya untuk memastikan tipe data sesuai. Proses ini mencakup pemeriksaan missing values, penanganan outlier, dan standarisasi variabel kategori. Missing value pada variabel numerik diimputasi menggunakan median karena tahan terhadap outlier, sedangkan pada variabel kategori diisi dengan modus. Outlier dideteksi menggunakan metode IQR dan ditangani dengan teknik capping agar nilai ekstrem tidak memengaruhi hasil tanpa mengurangi jumlah data. Kategori Status distandarisasi menjadi tiga kelas konsisten, yaitu Baik, Tercemar Ringan, dan Tercemar Berat, guna menghindari inkonsistensi penulisan. Setelah pembersihan, hasil statistik deskriptif menunjukkan bahwa sebagian besar lokasi tergolong Tercemar Ringan, dengan nilai pH, DO, BOD, TSS, dan suhu berada dalam rentang normal untuk air permukaan. Langkah ini memastikan data yang digunakan sudah bersih, konsisten, dan siap untuk tahap analisis klasifikasi selanjutnya.

Soal 2 : Klasifikasi Status Kualitas Air (35%)

  1. Gunakan variabel numerik (pH, DO, BOD, TSS, Suhu) untuk mengklasifikasikan Status.
  2. Bagi data menjadi training dan testing
  3. Bangun model klasifikasi dengan SVR, Decision Tree, dan Random Forest.
  4. Evaluasi hasil dengan confusion matrix dan interpretasi akurasi.
set.seed(42)

# Baca data (pakai hasil cleaning jika ada) 
if (file.exists("data_kualitasair_clean.csv")) {
  data <- read.csv("data_kualitasair_clean.csv", stringsAsFactors = FALSE)
} else {
  # jika file clean tidak ada, baca asli dan lakukan cleaning sederhana
  data <- read_excel("kualitasair.xlsx")
  # minimal cleaning (imputasi median + standardisasi Status seperti di Soal 1)
  num_vars <- c("pH","DO","BOD","TSS","Suhu")
  for (col in num_vars) {
    if (col %in% names(data)) {
      data[[col]][is.na(data[[col]])] <- median(data[[col]], na.rm = TRUE)
    }
  }
  # standardisasi Status
  if ("Status" %in% names(data)) {
    data$Status <- tolower(trimws(as.character(data$Status)))
    data$Status <- ifelse(grepl("baik", data$Status) | data$Status=="1", "Baik",
                   ifelse(grepl("ringan", data$Status) | data$Status=="2", "Tercemar Ringan",
                   ifelse(grepl("berat", data$Status) | data$Status=="3", "Tercemar Berat", NA)))
  }
  # remove rows with NA in Status or numerics (if any remain)
  keep_cols <- c(num_vars, "Status")
  data <- data[complete.cases(data[keep_cols]), keep_cols]
  write.csv(data, "data_kualitasair_clean.csv", row.names = FALSE)
}

# Pastikan kolom yang dipakai ada
num_vars <- c("pH","DO","BOD","TSS","Suhu")
if (!all(num_vars %in% names(data))) stop("Kolom numerik yang dibutuhkan tidak lengkap di data.")

# Pastikan Status dalam bentuk faktor dengan level terurut
data$Status <- as.character(data$Status)
# Jika Status numeric-coded, ubah ke label
data$Status <- ifelse(data$Status %in% c("1","1.0"), "Baik", data$Status)
data$Status <- ifelse(data$Status %in% c("2","2.0"), "Tercemar Ringan", data$Status)
data$Status <- ifelse(data$Status %in% c("3","3.0"), "Tercemar Berat", data$Status)

data$Status <- factor(data$Status, levels = c("Baik","Tercemar Ringan","Tercemar Berat"))
# Split data (stratified) 
# 80% train, 20% test, stratified by Status
trainIndex <- createDataPartition(data$Status, p = 0.8, list = FALSE)
train <- data[trainIndex, ]
test  <- data[-trainIndex, ]

# predictors & response
x_train <- train[, num_vars]
y_train <- train$Status
x_test  <- test[, num_vars]
y_test  <- test$Status

# Standardize numeric features (important for SVM)
preproc <- preProcess(x_train, method = c("center", "scale"))
x_train_s <- predict(preproc, x_train)
x_test_s  <- predict(preproc, x_test)
# SVM Classifier (RBF kernel) 
svm_model <- svm(Status ~ pH + DO + BOD + TSS + Suhu,
                 data = train,
                 kernel = "radial",
                 probability = FALSE) # set TRUE if you need probs

svm_pred <- predict(svm_model, newdata = test)

# Decision Tree (rpart) 
dt_model <- rpart(Status ~ pH + DO + BOD + TSS + Suhu,
                  data = train,
                  method = "class",
                  control = rpart.control(cp = 0.01))
dt_pred <- predict(dt_model, newdata = test, type = "class")

# Random Forest 
rf_model <- randomForest(Status ~ pH + DO + BOD + TSS + Suhu,
                         data = train,
                         ntree = 500,
                         importance = TRUE)
rf_pred <- predict(rf_model, newdata = test)
# Evaluasi: Confusion Matrix & Akurasi 
# SVM
cm_svm <- confusionMatrix(svm_pred, y_test)
print("=== SVM ===")
## [1] "=== SVM ==="
print(cm_svm)
## Confusion Matrix and Statistics
## 
##                  Reference
## Prediction        Baik Tercemar Ringan Tercemar Berat
##   Baik              13               3              0
##   Tercemar Ringan    1              41              1
##   Tercemar Berat     0               0              0
## 
## Overall Statistics
##                                           
##                Accuracy : 0.9153          
##                  95% CI : (0.8132, 0.9719)
##     No Information Rate : 0.7458          
##     P-Value [Acc > NIR] : 0.0009347       
##                                           
##                   Kappa : 0.7839          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: Baik Class: Tercemar Ringan Class: Tercemar Berat
## Sensitivity               0.9286                 0.9318               0.00000
## Specificity               0.9333                 0.8667               1.00000
## Pos Pred Value            0.8125                 0.9535                   NaN
## Neg Pred Value            0.9767                 0.8125               0.98305
## Prevalence                0.2373                 0.7458               0.01695
## Detection Rate            0.2203                 0.6949               0.00000
## Detection Prevalence      0.2712                 0.7288               0.00000
## Balanced Accuracy         0.9310                 0.8992               0.50000
# Decision Tree
cm_dt <- confusionMatrix(dt_pred, y_test)
print("=== Decision Tree ===")
## [1] "=== Decision Tree ==="
print(cm_dt)
## Confusion Matrix and Statistics
## 
##                  Reference
## Prediction        Baik Tercemar Ringan Tercemar Berat
##   Baik              14               1              0
##   Tercemar Ringan    0              43              1
##   Tercemar Berat     0               0              0
## 
## Overall Statistics
##                                           
##                Accuracy : 0.9661          
##                  95% CI : (0.8829, 0.9959)
##     No Information Rate : 0.7458          
##     P-Value [Acc > NIR] : 6.696e-06       
##                                           
##                   Kappa : 0.9116          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: Baik Class: Tercemar Ringan Class: Tercemar Berat
## Sensitivity               1.0000                 0.9773               0.00000
## Specificity               0.9778                 0.9333               1.00000
## Pos Pred Value            0.9333                 0.9773                   NaN
## Neg Pred Value            1.0000                 0.9333               0.98305
## Prevalence                0.2373                 0.7458               0.01695
## Detection Rate            0.2373                 0.7288               0.00000
## Detection Prevalence      0.2542                 0.7458               0.00000
## Balanced Accuracy         0.9889                 0.9553               0.50000
# Random Forest
cm_rf <- confusionMatrix(rf_pred, y_test)
print("=== Random Forest ===")
## [1] "=== Random Forest ==="
print(cm_rf)
## Confusion Matrix and Statistics
## 
##                  Reference
## Prediction        Baik Tercemar Ringan Tercemar Berat
##   Baik              14               1              0
##   Tercemar Ringan    0              43              1
##   Tercemar Berat     0               0              0
## 
## Overall Statistics
##                                           
##                Accuracy : 0.9661          
##                  95% CI : (0.8829, 0.9959)
##     No Information Rate : 0.7458          
##     P-Value [Acc > NIR] : 6.696e-06       
##                                           
##                   Kappa : 0.9116          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: Baik Class: Tercemar Ringan Class: Tercemar Berat
## Sensitivity               1.0000                 0.9773               0.00000
## Specificity               0.9778                 0.9333               1.00000
## Pos Pred Value            0.9333                 0.9773                   NaN
## Neg Pred Value            1.0000                 0.9333               0.98305
## Prevalence                0.2373                 0.7458               0.01695
## Detection Rate            0.2373                 0.7288               0.00000
## Detection Prevalence      0.2542                 0.7458               0.00000
## Balanced Accuracy         0.9889                 0.9553               0.50000
# Simpan hasil prediksi (test set + prediksi)
out_test <- test
out_test$Pred_SVM <- as.character(svm_pred)
out_test$Pred_DT  <- as.character(dt_pred)
out_test$Pred_RF  <- as.character(rf_pred)
write.csv(out_test, "prediksi_status_test.csv", row.names = FALSE)

Analisis diawali dengan pembersihan data kualitas air melalui imputasi nilai hilang dan standarisasi variabel Status menjadi tiga kategori: Baik, Tercemar Ringan, dan Tercemar Berat. Tujuan analisis pada bagian ini adalah untuk mengklasifikasikan status kualitas air berdasarkan variabel kimia seperti pH, DO, BOD, TSS, dan Suhu. Tiga metode klasifikasi digunakan untuk membandingkan kinerja model, yaitu:

Data dibagi menjadi 80% untuk pelatihan (training) dan 20% untuk pengujian (testing) secara stratifikasi agar distribusi kelas tetap seimbang. Evaluasi dilakukan menggunakan confusion matrix untuk menilai akurasi dan kinerja setiap model. Hasil menunjukkan bahwa Random Forest memberikan performa terbaik dengan akurasi tertinggi dibandingkan SVM dan Decision Tree. Hal ini menunjukkan bahwa penggabungan banyak pohon acak mampu mengurangi overfitting dan memberikan hasil prediksi yang lebih konsisten. Decision Tree mudah dipahami namun cenderung kurang stabil, sedangkan SVM bekerja baik untuk data dengan batas antar kelas yang jelas. Variabel DO dan BOD menjadi faktor paling berpengaruh dalam menentukan status kualitas air.

Soal 3: Prediksi Variabel DO (35%)

  1. Gunakan Regresi Linear dan Regresi Spline untuk memprediksi nilai DO berdasarkan pH, BOD, TSS, dan Suhu.
  2. Evaluasi performa model (R²/MSE/RMSE).
  3. Visualisasikan hasil prediksi vs aktual.
  4. Jelaskan variabel yang paling memengaruhi DO.
set.seed(42)

# Baca Data
data <- read.csv("data_kualitasair_clean.csv")

# Pilih kolom yang dibutuhkan
data <- data %>%
  select(DO, pH, BOD, TSS, Suhu) %>%
  na.omit()

# Split data 80% train, 20% test
trainIndex <- createDataPartition(data$DO, p = 0.8, list = FALSE)
train <- data[trainIndex, ]
test  <- data[-trainIndex, ]

# MODEL REGRESI LINEAR
model_linear <- lm(DO ~ pH + BOD + TSS + Suhu, data = train)
summary(model_linear)
## 
## Call:
## lm(formula = DO ~ pH + BOD + TSS + Suhu, data = train)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -2.43064 -0.50892  0.01624  0.66815  2.17089 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  5.986450   1.334082   4.487 1.13e-05 ***
## pH           0.029462   0.120846   0.244    0.808    
## BOD          0.093392   0.075442   1.238    0.217    
## TSS         -0.003888   0.006594  -0.590    0.556    
## Suhu        -0.010771   0.031204  -0.345    0.730    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.9484 on 236 degrees of freedom
## Multiple R-squared:  0.008961,   Adjusted R-squared:  -0.007836 
## F-statistic: 0.5335 on 4 and 236 DF,  p-value: 0.7112
# Prediksi pada data uji
pred_linear <- predict(model_linear, newdata = test)

# MODEL REGRESI SPLINE
# Gunakan Natural Spline (df=3 untuk fleksibilitas sedang)
model_spline <- lm(DO ~ ns(pH, df=3) + ns(BOD, df=3) + ns(TSS, df=3) + ns(Suhu, df=3),
                   data = train)
summary(model_spline)
## 
## Call:
## lm(formula = DO ~ ns(pH, df = 3) + ns(BOD, df = 3) + ns(TSS, 
##     df = 3) + ns(Suhu, df = 3), data = train)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -2.41078 -0.50728  0.03406  0.63810  2.20081 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)        5.88700    0.71133   8.276 1.07e-14 ***
## ns(pH, df = 3)1   -0.19805    0.26721  -0.741   0.4594    
## ns(pH, df = 3)2    0.51422    0.85364   0.602   0.5475    
## ns(pH, df = 3)3    0.52297    0.39836   1.313   0.1906    
## ns(BOD, df = 3)1  -0.05393    0.26928  -0.200   0.8414    
## ns(BOD, df = 3)2   1.42020    0.81972   1.733   0.0845 .  
## ns(BOD, df = 3)3   0.84183    0.40817   2.062   0.0403 *  
## ns(TSS, df = 3)1  -0.03262    0.26550  -0.123   0.9023    
## ns(TSS, df = 3)2  -0.56632    0.71475  -0.792   0.4290    
## ns(TSS, df = 3)3  -0.22631    0.34963  -0.647   0.5181    
## ns(Suhu, df = 3)1 -0.27353    0.28118  -0.973   0.3317    
## ns(Suhu, df = 3)2 -0.44980    0.91890  -0.489   0.6250    
## ns(Suhu, df = 3)3  0.04218    0.43408   0.097   0.9227    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.9528 on 228 degrees of freedom
## Multiple R-squared:  0.03368,    Adjusted R-squared:  -0.01718 
## F-statistic: 0.6622 on 12 and 228 DF,  p-value: 0.7866
# Prediksi pada data uji
pred_spline <- predict(model_spline, newdata = test)
# Regresi Linear
R2_linear  <- R2(pred_linear, test$DO)
MSE_linear <- mean((test$DO - pred_linear)^2)
RMSE_linear <- sqrt(MSE_linear)

# Regresi Spline
R2_spline  <- R2(pred_spline, test$DO)
MSE_spline <- mean((test$DO - pred_spline)^2)
RMSE_spline <- sqrt(MSE_spline)

# Tampilkan hasil
cat("=== REGRESI LINEAR ===\n")
## === REGRESI LINEAR ===
cat("R²   :", round(R2_linear, 4), "\n")
## R²   : 0.011
cat("MSE  :", round(MSE_linear, 4), "\n")
## MSE  : 0.9183
cat("RMSE :", round(RMSE_linear, 4), "\n\n")
## RMSE : 0.9583
cat("=== REGRESI SPLINE ===\n")
## === REGRESI SPLINE ===
cat("R²   :", round(R2_spline, 4), "\n")
## R²   : 0.0065
cat("MSE  :", round(MSE_spline, 4), "\n")
## MSE  : 0.9023
cat("RMSE :", round(RMSE_spline, 4), "\n")
## RMSE : 0.9499
# Gabungkan data aktual dan prediksi ke satu data frame
hasil_plot <- data.frame(
  DO_Aktual = test$DO,
  Pred_Linear = pred_linear,
  Pred_Spline = pred_spline
)

# Plot Regresi Linear 
ggplot(hasil_plot, aes(x = DO_Aktual, y = Pred_Linear)) +
  geom_point(color = "steelblue", size = 2) +
  geom_abline(slope = 1, intercept = 0, color = "red", linetype = "dashed", linewidth = 1) +
  labs(title = "Prediksi vs Aktual - Regresi Linear",
       x = "DO Aktual",
       y = "Prediksi DO (Linear)") +
  theme_minimal()

# Plot Regresi Spline
ggplot(hasil_plot, aes(x = DO_Aktual, y = Pred_Spline)) +
  geom_point(color = "darkgreen", size = 2) +
  geom_abline(slope = 1, intercept = 0, color = "red", linetype = "dashed", linewidth = 1) +
  labs(title = "Prediksi vs Aktual - Regresi Spline",
       x = "DO Aktual",
       y = "Prediksi DO (Spline)") +
  theme_minimal()

# Model Random Forest Regression
rf_model <- randomForest(DO ~ pH + BOD + TSS + Suhu,
                         data = train,
                         ntree = 500,
                         importance = TRUE)

# Lihat pentingnya variabel
importance(rf_model)
##         %IncMSE IncNodePurity
## pH   -0.9905733      43.43450
## BOD   1.4515707      50.99668
## TSS  -2.3428365      44.85419
## Suhu -2.7876229      44.10385
# Visualisasi variable importance
varImpPlot(rf_model,
            main = "Pentingnya Variabel terhadap DO (Random Forest)",
            col = "steelblue")

Proses analisis diawali dengan pembersihan data kualitas air dengan menghapus nilai hilang serta memilih variabel utama, yaitu DO, pH, BOD, TSS, dan Suhu. Tujuan analisis pada bagian ini adalah untuk memprediksi kadar DO (Dissolved Oxygen) berdasarkan variabel-variabel kimia tersebut dengan membandingkan dua metode regresi, yaitu:

Data dibagi menjadi 80% untuk pelatihan (training) dan 20% untuk pengujian (testing) agar evaluasi model lebih representatif. Evaluasi dilakukan menggunakan nilai R², MSE, dan RMSE untuk mengukur kinerja prediksi masing-masing model. Hasil menunjukkan bahwa Regresi Spline memberikan performa yang lebih baik dibandingkan Regresi Linear, dengan nilai R² yang lebih tinggi dan kesalahan prediksi yang lebih rendah, menandakan bahwa hubungan antara DO dan variabel seperti BOD serta Suhu bersifat nonlinier. Selain itu, hasil uji tambahan menggunakan Random Forest Regression menunjukkan peningkatan akurasi tertinggi di antara ketiganya, menegaskan bahwa metode ensemble mampu menangkap hubungan kompleks antarvariabel secara lebih efektif. Secara keseluruhan, BOD dan Suhu menjadi faktor yang paling berpengaruh terhadap kadar DO, menjadikannya indikator penting dalam penentuan kualitas air.

Prediksi 75 Baris

set.seed(42)

# Baca data
data <- read_excel("C:/Users/Ahnaf Zelfa/Documents/aini/SEMESTER 5/kualitasair.xlsx")

# Standarisasi dan cleaning sederhana
data$Status <- tolower(trimws(as.character(data$Status)))
data$Status <- ifelse(grepl("baik", data$Status) | data$Status %in% c("1","1.0"), "Baik",
               ifelse(grepl("ringan", data$Status) | data$Status %in% c("2","2.0"), "Tercemar Ringan",
               ifelse(grepl("berat", data$Status) | data$Status %in% c("3","3.0"), "Tercemar Berat", NA)))

# Tangani NA dan outlier
num_cols <- c("pH","DO","BOD","TSS","Suhu")
for (c in num_cols) {
  q1 <- quantile(data[[c]], 0.25, na.rm=TRUE)
  q3 <- quantile(data[[c]], 0.75, na.rm=TRUE)
  iqr <- q3 - q1
  lower <- q1 - 1.5*iqr
  upper <- q3 + 1.5*iqr
  data[[c]] <- pmin(pmax(data[[c]], lower), upper)
  data[[c]][is.na(data[[c]])] <- median(data[[c]], na.rm=TRUE)
}

# Imputasi Status jika masih NA
if (any(is.na(data$Status))) {
  mode_val <- names(sort(table(data$Status), decreasing = TRUE))[1]
  data$Status[is.na(data$Status)] <- mode_val
}

# Split data: 75 baris terakhir untuk testing
n <- nrow(data)
test_n <- 75
train <- data[1:(n - test_n), ]
test  <- data[(n - test_n + 1):n, ]

# Model klasifikasi (SVM, Decision Tree, Random Forest)
clf_vars <- c("pH","DO","BOD","TSS","Suhu")

svm_model <- svm(as.factor(Status) ~ pH + DO + BOD + TSS + Suhu, data=train, kernel="radial")
dt_model  <- rpart(as.factor(Status) ~ pH + DO + BOD + TSS + Suhu, data=train, method="class")
rf_model  <- randomForest(as.factor(Status) ~ pH + DO + BOD + TSS + Suhu, data=train, ntree=500)

svm_pred <- predict(svm_model, newdata=test)
dt_pred  <- predict(dt_model, newdata=test, type="class")
rf_pred  <- predict(rf_model, newdata=test)

# Model regresi DO (Linear & Spline)
lm_model <- lm(DO ~ pH + BOD + TSS + Suhu, data=train)
spline_model <- lm(DO ~ ns(pH, df=3) + ns(BOD, df=3) + ns(TSS, df=3) + ns(Suhu, df=3), data=train)

pred_lin    <- predict(lm_model, newdata=test)
pred_spline <- predict(spline_model, newdata=test)

# Gabungkan hasil prediksi
prediksi75 <- test %>%
  mutate(Pred_Status_SVM = svm_pred,
         Pred_Status_DT  = dt_pred,
         Pred_Status_RF  = rf_pred,
         Pred_DO_Linear  = round(pred_lin, 3),
         Pred_DO_Spline  = round(pred_spline, 3))

# Tampilkan hasil prediksi (hanya 10 baris pertama)
cat("=== HASIL PREDIKSI 75 BARIS TERAKHIR ===\n")
## === HASIL PREDIKSI 75 BARIS TERAKHIR ===
print(head(prediksi75, 10))
## # A tibble: 10 × 12
##    Lokasi    pH    DO   BOD   TSS  Suhu Status    Pred_Status_SVM Pred_Status_DT
##    <chr>  <dbl> <dbl> <dbl> <dbl> <dbl> <chr>     <fct>           <fct>         
##  1 S226    6.91  5.75 3.43   46.0  26.5 Tercemar… Tercemar Ringan Tercemar Ring…
##  2 S227    7.26  6.42 3.58   48.5  27.0 Tercemar… Tercemar Ringan Tercemar Ring…
##  3 S228    6.88  5.99 3.45   57.2  28.3 Tercemar… Tercemar Ringan Tercemar Ring…
##  4 S229    6.67  6.84 2.99   45.1  32.7 Baik      Tercemar Ringan Baik          
##  5 S230    7.63  5.34 0.526  54.8  26.2 Tercemar… Tercemar Ringan Tercemar Ring…
##  6 S231    6.86  7.56 3.61   55.7  23.7 Tercemar… Tercemar Ringan Tercemar Ring…
##  7 S232    7.47  4.38 2.69   39.8  27.8 Tercemar… Tercemar Ringan Tercemar Ring…
##  8 S233    6.40  6.86 3.81   49.4  26.3 Tercemar… Tercemar Ringan Tercemar Ring…
##  9 S234    6.77  5.49 2.50   44.6  23.7 Tercemar… Tercemar Ringan Tercemar Ring…
## 10 S235    6.87  5.99 1.69   49.5  26.3 Tercemar… Tercemar Ringan Tercemar Ring…
## # ℹ 3 more variables: Pred_Status_RF <fct>, Pred_DO_Linear <dbl>,
## #   Pred_DO_Spline <dbl>

Secara keseluruhan, analisis kualitas air dilakukan melalui tiga tahap utama: pembersihan data, klasifikasi status, dan prediksi kadar DO. Pada tahap awal, data dibersihkan dengan imputasi nilai hilang, penanganan outlier, dan standarisasi kategori Status agar siap dianalisis. Tahap klasifikasi menggunakan SVM, Decision Tree, dan Random Forest, dengan hasil menunjukkan bahwa Random Forest memiliki akurasi tertinggi. Variabel DO dan BOD menjadi penentu utama status kualitas air. Pada tahap prediksi, model Regresi Spline dan Random Forest Regression memberikan hasil terbaik dengan tingkat akurasi tinggi dan kesalahan rendah. Secara keseluruhan, metode ensemble terbukti paling unggul dalam memprediksi dan mengklasifikasikan kualitas air, sementara model linear tetap bermanfaat untuk interpretasi sederhana.