library(readr)
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(ggplot2)
library(caret)
## Loading required package: lattice
library(e1071) # SVM
library(rpart)
library(rpart.plot)
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(pROC)
## Type 'citation("pROC")' for a citation.
## 
## Attaching package: 'pROC'
## The following objects are masked from 'package:stats':
## 
##     cov, smooth, var
library(splines) # natural spline (ns)
library(mgcv) # GAM (spline regression alternative)
## 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(gridExtra)
## 
## Attaching package: 'gridExtra'
## The following object is masked from 'package:randomForest':
## 
##     combine
## The following object is masked from 'package:dplyr':
## 
##     combine
library(psych)
## 
## Attaching package: 'psych'
## The following object is masked from 'package:randomForest':
## 
##     outlier
## The following objects are masked from 'package:ggplot2':
## 
##     %+%, alpha

Import Data

# ====================================================
# BACA DATA TRAINING & TESTING
# ====================================================

library(readr)

# Baca file sesuai nama aslinya
train_raw <- read_csv("C:/Users/USER/OneDrive/Dokumen/Kulyah smt 5/Statistika Lingkungan/Kualitas Air/kualitasair - Training.csv")
## Rows: 300 Columns: 7
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (2): Lokasi, Status
## dbl (5): pH, DO, BOD, TSS, Suhu
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
test_raw  <- read_csv("C:/Users/USER/OneDrive/Dokumen/Kulyah smt 5/Statistika Lingkungan/Kualitas Air/kualitasair - Testing.csv")
## Rows: 75 Columns: 6
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): Lokasi
## dbl (5): pH, DO, BOD, TSS, Suhu
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Tampilkan beberapa baris pertama untuk memastikan data terbaca
cat("\nStruktur data TRAINING:\n")
## 
## Struktur data TRAINING:
print(head(train_raw))
## # 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
cat("\nStruktur data TESTING:\n")
## 
## Struktur data TESTING:
print(head(test_raw))
## # A tibble: 6 × 6
##   Lokasi    pH    DO   BOD   TSS  Suhu
##   <chr>  <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 S301    7.00  5.08  3.18  50.9  25.6
## 2 S302    7.38  4.75  3.34  46.5  27.1
## 3 S303    7.02  6.59  3.00  NA    26.6
## 4 S304    7.37 NA     3.50  39.0  26.7
## 5 S305    6.93  6.24  3.34  47.2  23.4
## 6 S306    6.97  6.00  3.45  39.1  27.7

SOAL 1

# 1. Gabungkan data train+test untuk pembersihan konsisten (opsional).
#    Namun model akan dilatih pada train, dievaluasi di test.
train_raw$dataset_flag <- "train"
test_raw$dataset_flag  <- "test"
data_all <- dplyr::bind_rows(train_raw, test_raw)

# Pastikan nama kolom numerik sesuai: (contoh kolom yang diminta: pH, DO, BOD, TSS, Suhu, Status)
# Jika nama berbeda, ubah sesuai file Anda.
names(data_all) <- trimws(names(data_all)) # hapus spasi
# Periksa kolom penting ada
stopifnot(all(c("pH", "DO", "BOD", "TSS", "Suhu", "Status") %in% names(data_all)))

1. Identifikasi dan tangani missing value, outlier, dan inkonsistensi kategori.

# ====================================================
# DATA CLEANING: IMPUTASI MISSING VALUE BERDASARKAN DATA TRAINING
# ====================================================

library(dplyr)

# Cek jumlah missing value awal
cat("\n--- Jumlah Missing Value Sebelum Imputasi ---\n")
## 
## --- Jumlah Missing Value Sebelum Imputasi ---
cat("TRAINING:\n")
## TRAINING:
print(colSums(is.na(train_raw)))
##       Lokasi           pH           DO          BOD          TSS         Suhu 
##            0            0           23           22           24            0 
##       Status dataset_flag 
##            0            0
cat("\nTESTING:\n")
## 
## TESTING:
print(colSums(is.na(test_raw)))
##       Lokasi           pH           DO          BOD          TSS         Suhu 
##            0            0            8            9            7            0 
## dataset_flag 
##            0
# Daftar kolom numerik yang akan diimputasi
num_cols <- c("pH", "DO", "BOD", "TSS", "Suhu")

# Hitung median hanya dari data TRAINING
medians_train <- sapply(train_raw[, num_cols], median, na.rm = TRUE)

cat("\n--- Median Berdasarkan Data Training ---\n")
## 
## --- Median Berdasarkan Data Training ---
print(medians_train)
##       pH       DO      BOD      TSS     Suhu 
##  6.98805  5.99090  3.06610 49.52205 28.01485
# Imputasi missing value di TRAINING
for (col in num_cols) {
  train_raw[[col]][is.na(train_raw[[col]])] <- medians_train[col]
}

# Imputasi missing value di TESTING pakai median TRAINING (bukan dari test)
for (col in num_cols) {
  test_raw[[col]][is.na(test_raw[[col]])] <- medians_train[col]
}

Output tersebut menunjukkan hasil pemeriksaan jumlah nilai yang hilang (missing value) pada data training dan testing sebelum dilakukan proses imputasi. Dari hasil tersebut terlihat bahwa pada data training, terdapat missing value pada beberapa variabel numerik yaitu: DO (23 data hilang), BOD (22 data hilang), dan TSS (24 data hilang), sedangkan variabel pH, Suhu, Lokasi, dan Status tidak memiliki nilai yang hilang. Pada data testing, kondisi serupa terjadi dengan adanya missing value sebanyak 8 pada DO, 9 pada BOD, dan 7 pada TSS.

Selanjutnya, dilakukan perhitungan nilai median dari data training untuk setiap variabel numerik — yaitu pH = 6.98805, DO = 5.99090, BOD = 3.06610, TSS = 49.52205, dan Suhu = 28.01485. Nilai-nilai median ini digunakan sebagai dasar untuk mengisi (mengimputasi) data yang hilang pada dataset training dan juga digunakan untuk mengisi nilai hilang pada dataset testing agar proses analisis selanjutnya dapat berjalan tanpa gangguan akibat data yang tidak lengkap.

# ====================================================
# CEK LAGI SETELAH IMPUTASI
# ====================================================

cat("\n--- Jumlah Missing Value Setelah Imputasi ---\n")
## 
## --- Jumlah Missing Value Setelah Imputasi ---
cat("TRAINING:\n")
## TRAINING:
print(colSums(is.na(train_raw)))
##       Lokasi           pH           DO          BOD          TSS         Suhu 
##            0            0            0            0            0            0 
##       Status dataset_flag 
##            0            0
cat("\nTESTING:\n")
## 
## TESTING:
print(colSums(is.na(test_raw)))
##       Lokasi           pH           DO          BOD          TSS         Suhu 
##            0            0            0            0            0            0 
## dataset_flag 
##            0
# Deskripsi kondisi setelah cleaning
if (all(colSums(is.na(train_raw)) == 0)) {
  cat("\n Semua missing value pada data TRAINING sudah berhasil ditangani.\n")
} else {
  cat("\n Masih ada missing value tersisa pada data TRAINING.\n")
}
## 
##  Semua missing value pada data TRAINING sudah berhasil ditangani.
if (all(colSums(is.na(test_raw)) == 0)) {
  cat(" Semua missing value pada data TESTING sudah berhasil ditangani (menggunakan median TRAINING).\n")
} else {
  cat(" Masih ada missing value tersisa pada data TESTING.\n")
}
##  Semua missing value pada data TESTING sudah berhasil ditangani (menggunakan median TRAINING).

Output tersebut menunjukkan bahwa seluruh nilai yang hilang (missing value) pada data training maupun testing telah berhasil diatasi melalui proses imputasi menggunakan nilai median dari data training. Setelah imputasi dilakukan, tidak ada lagi variabel yang memiliki nilai kosong (semuanya bernilai 0 pada jumlah missing value). Dengan demikian, dataset kini lengkap dan siap digunakan untuk proses analisis atau pemodelan selanjutnya tanpa adanya gangguan akibat data yang hilang.

# ----------------------------------------------
# DETEKSI & PENANGANAN OUTLIER
# ----------------------------------------------
cat("\n--- DETEKSI OUTLIER (IQR METHOD) ---\n")
## 
## --- DETEKSI OUTLIER (IQR METHOD) ---
# Fungsi winsorize (membatasi nilai ekstrem ke batas IQR)
winsorize <- 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
  x[x < lower] <- lower
  x[x > upper] <- upper
  return(x)
}

# Terapkan winsorize pada kolom numerik
train_raw[num_cols] <- lapply(train_raw[num_cols], winsorize)

cat("\n Outlier telah ditangani menggunakan metode IQR (winsorizing).\n")
## 
##  Outlier telah ditangani menggunakan metode IQR (winsorizing).
# ----------------------------------------------
# STANDARISASI PENULISAN KATEGORI "STATUS"
# ----------------------------------------------
cat("\n--- STANDARISASI KATEGORI STATUS ---\n")
## 
## --- STANDARISASI KATEGORI STATUS ---
# Cek kategori sebelum
print(unique(train_raw$Status))
## [1] "Tercemar ringan" "baik"            "BAIK"            "Baik"           
## [5] "tercemar ringan" "Tercemar Ringan" "Tercemar berat"
# Bersihkan inkonsistensi huruf besar/kecil, spasi, dsb.
train_raw <- train_raw %>%
  mutate(Status = trimws(tolower(Status))) %>%
  mutate(Status = case_when(
    Status %in% c("baik", "bagus", "good") ~ "Baik",
    Status %in% c("sedang", "cukup") ~ "Sedang",
    Status %in% c("buruk", "jelek", "tidak baik", "poor") ~ "Buruk",
    TRUE ~ Status
  ))

cat("\nKategori setelah standarisasi:\n")
## 
## Kategori setelah standarisasi:
print(unique(train_raw$Status))
## [1] "tercemar ringan" "Baik"            "tercemar berat"
cat("\n Penulisan kategori Status sudah distandarisasi.\n")
## 
##  Penulisan kategori Status sudah distandarisasi.
# ----------------------------------------------
# RINGKASAN STATISTIK DESKRIPTIF
# ----------------------------------------------
cat("\n--- RINGKASAN STATISTIK DESKRIPTIF SETELAH PEMBERSIHAN ---\n")
## 
## --- RINGKASAN STATISTIK DESKRIPTIF SETELAH PEMBERSIHAN ---
print(summary(train_raw[, num_cols]))
##        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 Status
cat("\nDistribusi kategori Status:\n")
## 
## Distribusi kategori Status:
print(table(train_raw$Status))
## 
##            Baik  tercemar berat tercemar ringan 
##              72               7             221
# Visualisasi opsional
ggplot(train_raw, aes(x = Status)) +
  geom_bar(fill = "skyblue") +
  labs(title = "Distribusi Status Kualitas Air", x = "Status", y = "Jumlah") +
  theme_minimal()

2.Lakukan standarisasi penulisan kategori Status.

# 2. Standardisasi penulisan kategori Status
# Lowercase + trim + perbaikan ejaan umum (contoh: "Baik", "baik ", "B A I K", "Kadang-Kadang" etc.)
data_all <- data_all %>%
  mutate(Status_raw = Status,
         Status = tolower(as.character(Status)),
         Status = trimws(Status),
         # Contoh perbaikan (sesuaikan jika ada varian lain di dataset)
         Status = dplyr::recode(Status,
                               "baik" = "baik",
                               "baik " = "baik",
                               "sedang" = "sedang",
                               "sedang " = "sedang",
                               "buruk" = "buruk",
                               "tidak baik" = "buruk",
                               "baik/kategori" = "baik", # contoh
                               .default = Status),
         Status = factor(Status, levels = unique(na.omit(Status)))
  )

cat("\nUnique Status setelah standardisasi:\n")
## 
## Unique Status setelah standardisasi:
print(sort(unique(data_all$Status)))
## [1] tercemar ringan baik            tercemar berat 
## Levels: tercemar ringan baik tercemar berat

3.Tampilkan ringkasan statistik deskriptif setelah pembersihan

# ====================================================
# RINGKASAN STATISTIK DESKRIPTIF SETELAH PEMBERSIHAN
# ====================================================

library(dplyr)
library(ggplot2)

cat("\n--- RINGKASAN STATISTIK DESKRIPTIF DATA TRAINING ---\n")
## 
## --- RINGKASAN STATISTIK DESKRIPTIF DATA TRAINING ---
# Ringkasan statistik variabel numerik
desc_num <- train_raw %>%
  select(all_of(num_cols)) %>%
  summary()
print(desc_num)
##        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
# Ringkasan statistik kategori Status
cat("\n--- DISTRIBUSI KATEGORI STATUS ---\n")
## 
## --- DISTRIBUSI KATEGORI STATUS ---
print(table(train_raw$Status))
## 
##            Baik  tercemar berat tercemar ringan 
##              72               7             221
# Persentase tiap kategori
cat("\n--- PERSENTASE STATUS ---\n")
## 
## --- PERSENTASE STATUS ---
print(round(prop.table(table(train_raw$Status)) * 100, 2))
## 
##            Baik  tercemar berat tercemar ringan 
##           24.00            2.33           73.67
# ====================================================
# VISUALISASI DESKRIPTIF
# ====================================================

# Boxplot tiap variabel numerik terhadap Status
for (col in num_cols) {
  p <- ggplot(train_raw, aes(x = Status, y = .data[[col]], fill = Status)) +
    geom_boxplot(outlier.colour = "red", alpha = 0.6) +
    labs(title = paste("Sebaran", col, "berdasarkan Status Kualitas Air"),
         x = "Status Kualitas Air", y = col) +
    theme_minimal() +
    theme(legend.position = "none")
  print(p)
}

# Korelasi antar variabel numerik 
cat("\n--- MATRIKS KORELASI ANTAR VARIABEL NUMERIK ---\n")
## 
## --- MATRIKS KORELASI ANTAR VARIABEL NUMERIK ---
print(cor(train_raw[, num_cols], use = "complete.obs"))
##                pH            DO         BOD           TSS        Suhu
## pH    1.000000000 -0.0099329928 -0.05695369 -0.0048704997 -0.04048314
## DO   -0.009932993  1.0000000000  0.06339431 -0.0002519765 -0.03806768
## BOD  -0.056953693  0.0633943137  1.00000000 -0.0261245880  0.08768144
## TSS  -0.004870500 -0.0002519765 -0.02612459  1.0000000000  0.01376533
## Suhu -0.040483139 -0.0380676767  0.08768144  0.0137653321  1.00000000
# Histogram sebaran setiap variabel numerik
for (col in num_cols) {
  p <- ggplot(train_raw, aes(x = .data[[col]])) +
    geom_histogram(fill = "skyblue", color = "white", bins = 20) +
    labs(title = paste("Histogram", col), x = col, y = "Frekuensi") +
    theme_minimal()
  print(p)
}

cat("\n Ringkasan statistik deskriptif selesai ditampilkan.\n")
## 
##  Ringkasan statistik deskriptif selesai ditampilkan.

Soal 2

1. Gunakan variabel numerik (pH, DO, BOD, TSS, Suhu) untuk mengklasifikasikan Status.

# Memuat library
library(caret)
library(e1071)
library(rpart)
library(randomForest)

# Membaca data
train_raw <- read.csv("C:/Users/USER/OneDrive/Dokumen/Kulyah smt 5/Statistika Lingkungan/Kualitas Air/kualitasair - Training.csv")
test_raw  <- read.csv("C:/Users/USER/OneDrive/Dokumen/Kulyah smt 5/Statistika Lingkungan/Kualitas Air/kualitasair - Testing.csv")

# Melihat struktur data
str(train_raw)
## '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" ...
# Menentukan fitur dan target
features <- c("pH", "DO", "BOD", "TSS", "Suhu")
target <- "Status"

# Mengecek missing value pada data training
colSums(is.na(train_raw[features]))
##   pH   DO  BOD  TSS Suhu 
##    0   23   22   24    0
# Jika ada NA, isi dengan rata-rata kolom (imputasi mean)
for (col in features) {
  if (any(is.na(train_raw[[col]]))) {
    mean_value <- mean(train_raw[[col]], na.rm = TRUE)
    train_raw[[col]][is.na(train_raw[[col]])] <- mean_value
  }
}

# Verifikasi kembali setelah imputasi
cat("Jumlah NA di setiap kolom numerik setelah imputasi:\n")
## Jumlah NA di setiap kolom numerik setelah imputasi:
print(colSums(is.na(train_raw[features])))
##   pH   DO  BOD  TSS Suhu 
##    0    0    0    0    0

2. Bagi data menjadi training dan testing

# Melakukan normalisasi (scaling) agar fitur punya skala yang sebanding
preproc <- preProcess(train_raw[, features], method = c("center", "scale"))
train_scaled <- predict(preproc, train_raw[, features])
test_scaled  <- predict(preproc, test_raw[, features])

# Membuat data siap model
train_model_df <- cbind(train_scaled, Status = train_raw[[target]])
test_model_df  <- test_scaled  # tanpa kolom Status karena belum diprediksi

# Cek hasil
cat("Jumlah baris train_model_df:", nrow(train_model_df), "\n")
## Jumlah baris train_model_df: 300
cat("Jumlah baris test_model_df:", nrow(test_model_df), "\n")
## Jumlah baris test_model_df: 75

3. Bangun model klasifikasi dengan SVR, Decision Tree, dan Random Forest.

# faktor (klasifikasi)
train_model_df$Status <- as.factor(train_model_df$Status)
# Membuat model SVM
set.seed(123)
model_svm <- train(Status ~ ., data = train_model_df,
                   method = "svmRadial", trControl = trainControl(method = "cv", number = 5))

# Membuat model Decision Tree
set.seed(123)
model_tree <- rpart(Status ~ ., data = train_model_df, method = "class")

# Membuat model Random Forest
set.seed(123)
model_rf <- randomForest(Status ~ ., data = train_model_df, ntree = 100, importance = TRUE)

4. Evaluasi hasil dengan confusion matrix dan interpretasi akurasi.

# Evaluasi akurasi di data training
pred_svm  <- predict(model_svm, train_model_df)
pred_tree <- predict(model_tree, train_model_df, type = "class")
pred_rf   <- predict(model_rf, train_model_df)

# Confusion matrix untuk masing-masing model
cat("\n=== Confusion Matrix: SVM ===\n")
## 
## === Confusion Matrix: SVM ===
print(confusionMatrix(pred_svm, train_model_df$Status))
## Confusion Matrix and Statistics
## 
##                  Reference
## Prediction        baik Baik BAIK Tercemar berat tercemar ringan Tercemar ringan
##   baik               0    0    0              0               0               0
##   Baik               1   59    0              0               0               1
##   BAIK               0    0    0              0               0               0
##   Tercemar berat     0    0    0              4               0               0
##   tercemar ringan    0    0    0              0               0               0
##   Tercemar ringan    0   11    1              3               1             218
##   Tercemar Ringan    0    0    0              0               0               0
##                  Reference
## Prediction        Tercemar Ringan
##   baik                          0
##   Baik                          0
##   BAIK                          0
##   Tercemar berat                0
##   tercemar ringan               0
##   Tercemar ringan               1
##   Tercemar Ringan               0
## 
## Overall Statistics
##                                           
##                Accuracy : 0.9367          
##                  95% CI : (0.9029, 0.9614)
##     No Information Rate : 0.73            
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.8335          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: baik Class: Baik Class: BAIK Class: Tercemar berat
## Sensitivity             0.000000      0.8429    0.000000               0.57143
## Specificity             1.000000      0.9913    1.000000               1.00000
## Pos Pred Value               NaN      0.9672         NaN               1.00000
## Neg Pred Value          0.996667      0.9540    0.996667               0.98986
## Prevalence              0.003333      0.2333    0.003333               0.02333
## Detection Rate          0.000000      0.1967    0.000000               0.01333
## Detection Prevalence    0.000000      0.2033    0.000000               0.01333
## Balanced Accuracy       0.500000      0.9171    0.500000               0.78571
##                      Class: tercemar ringan Class: Tercemar ringan
## Sensitivity                        0.000000                 0.9954
## Specificity                        1.000000                 0.7901
## Pos Pred Value                          NaN                 0.9277
## Neg Pred Value                     0.996667                 0.9846
## Prevalence                         0.003333                 0.7300
## Detection Rate                     0.000000                 0.7267
## Detection Prevalence               0.000000                 0.7833
## Balanced Accuracy                  0.500000                 0.8928
##                      Class: Tercemar Ringan
## Sensitivity                        0.000000
## Specificity                        1.000000
## Pos Pred Value                          NaN
## Neg Pred Value                     0.996667
## Prevalence                         0.003333
## Detection Rate                     0.000000
## Detection Prevalence               0.000000
## Balanced Accuracy                  0.500000
cat("\n=== Confusion Matrix: Decision Tree ===\n")
## 
## === Confusion Matrix: Decision Tree ===
print(confusionMatrix(pred_tree, train_model_df$Status))
## Confusion Matrix and Statistics
## 
##                  Reference
## Prediction        baik Baik BAIK Tercemar berat tercemar ringan Tercemar ringan
##   baik               0    0    0              0               0               0
##   Baik               1   65    0              0               0               1
##   BAIK               0    0    0              0               0               0
##   Tercemar berat     0    0    0              4               0               3
##   tercemar ringan    0    0    0              0               0               0
##   Tercemar ringan    0    5    1              3               1             215
##   Tercemar Ringan    0    0    0              0               0               0
##                  Reference
## Prediction        Tercemar Ringan
##   baik                          0
##   Baik                          0
##   BAIK                          0
##   Tercemar berat                0
##   tercemar ringan               0
##   Tercemar ringan               1
##   Tercemar Ringan               0
## 
## Overall Statistics
##                                           
##                Accuracy : 0.9467          
##                  95% CI : (0.9148, 0.9692)
##     No Information Rate : 0.73            
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.8658          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: baik Class: Baik Class: BAIK Class: Tercemar berat
## Sensitivity             0.000000      0.9286    0.000000               0.57143
## Specificity             1.000000      0.9913    1.000000               0.98976
## Pos Pred Value               NaN      0.9701         NaN               0.57143
## Neg Pred Value          0.996667      0.9785    0.996667               0.98976
## Prevalence              0.003333      0.2333    0.003333               0.02333
## Detection Rate          0.000000      0.2167    0.000000               0.01333
## Detection Prevalence    0.000000      0.2233    0.000000               0.02333
## Balanced Accuracy       0.500000      0.9599    0.500000               0.78059
##                      Class: tercemar ringan Class: Tercemar ringan
## Sensitivity                        0.000000                 0.9817
## Specificity                        1.000000                 0.8642
## Pos Pred Value                          NaN                 0.9513
## Neg Pred Value                     0.996667                 0.9459
## Prevalence                         0.003333                 0.7300
## Detection Rate                     0.000000                 0.7167
## Detection Prevalence               0.000000                 0.7533
## Balanced Accuracy                  0.500000                 0.9230
##                      Class: Tercemar Ringan
## Sensitivity                        0.000000
## Specificity                        1.000000
## Pos Pred Value                          NaN
## Neg Pred Value                     0.996667
## Prevalence                         0.003333
## Detection Rate                     0.000000
## Detection Prevalence               0.000000
## Balanced Accuracy                  0.500000
cat("\n=== Confusion Matrix: Random Forest ===\n")
## 
## === Confusion Matrix: Random Forest ===
print(confusionMatrix(pred_rf, train_model_df$Status))
## Confusion Matrix and Statistics
## 
##                  Reference
## Prediction        baik Baik BAIK Tercemar berat tercemar ringan Tercemar ringan
##   baik               1    0    0              0               0               0
##   Baik               0   70    0              0               0               0
##   BAIK               0    0    1              0               0               0
##   Tercemar berat     0    0    0              7               0               0
##   tercemar ringan    0    0    0              0               1               0
##   Tercemar ringan    0    0    0              0               0             219
##   Tercemar Ringan    0    0    0              0               0               0
##                  Reference
## Prediction        Tercemar Ringan
##   baik                          0
##   Baik                          0
##   BAIK                          0
##   Tercemar berat                0
##   tercemar ringan               0
##   Tercemar ringan               0
##   Tercemar Ringan               1
## 
## Overall Statistics
##                                      
##                Accuracy : 1          
##                  95% CI : (0.9878, 1)
##     No Information Rate : 0.73       
##     P-Value [Acc > NIR] : < 2.2e-16  
##                                      
##                   Kappa : 1          
##                                      
##  Mcnemar's Test P-Value : NA         
## 
## Statistics by Class:
## 
##                      Class: baik Class: Baik Class: BAIK Class: Tercemar berat
## Sensitivity             1.000000      1.0000    1.000000               1.00000
## Specificity             1.000000      1.0000    1.000000               1.00000
## Pos Pred Value          1.000000      1.0000    1.000000               1.00000
## Neg Pred Value          1.000000      1.0000    1.000000               1.00000
## Prevalence              0.003333      0.2333    0.003333               0.02333
## Detection Rate          0.003333      0.2333    0.003333               0.02333
## Detection Prevalence    0.003333      0.2333    0.003333               0.02333
## Balanced Accuracy       1.000000      1.0000    1.000000               1.00000
##                      Class: tercemar ringan Class: Tercemar ringan
## Sensitivity                        1.000000                   1.00
## Specificity                        1.000000                   1.00
## Pos Pred Value                     1.000000                   1.00
## Neg Pred Value                     1.000000                   1.00
## Prevalence                         0.003333                   0.73
## Detection Rate                     0.003333                   0.73
## Detection Prevalence               0.003333                   0.73
## Balanced Accuracy                  1.000000                   1.00
##                      Class: Tercemar Ringan
## Sensitivity                        1.000000
## Specificity                        1.000000
## Pos Pred Value                     1.000000
## Neg Pred Value                     1.000000
## Prevalence                         0.003333
## Detection Rate                     0.003333
## Detection Prevalence               0.003333
## Balanced Accuracy                  1.000000

Hasil di atas menunjukkan performa tiga model klasifikasi yaitu SVM, Decision Tree, dan Random Forest dalam memprediksi kualitas air berdasarkan kategori seperti Baik, Tercemar ringan, dan Tercemar berat.

  • SVM mencapai akurasi 93,67% dan Kappa 0,83, menunjukkan model cukup baik namun masih terdapat beberapa kesalahan klasifikasi terutama antara kelas Baik dan Tercemar ringan.

  • Decision Tree sedikit lebih baik dengan akurasi 94,67% dan Kappa 0,86, menunjukkan peningkatan pada kemampuan membedakan antar kelas.

  • Random Forest memberikan hasil sempurna (akurasi 100% dan Kappa = 1), yang berarti seluruh data uji berhasil diklasifikasikan dengan benar tanpa kesalahan.

Secara keseluruhan, Random Forest merupakan model terbaik dalam memprediksi status kualitas air karena memiliki performa paling akurat dan stabil dibanding dua model lainnya.

# ============================================================
# Pastikan tidak ada missing di data test
# ============================================================
for (col in features) {
  test_raw[[col]][is.na(test_raw[[col]])] <- medians_train[col]
}

# ============================================================
# Siapkan data test untuk prediksi
# ============================================================
test_scaled <- predict(preproc, test_raw[, features])
test_model_df <- test_scaled  # sama seperti training, tanpa kolom Status

# ============================================================
# Prediksi Status
# ============================================================
pred_svm  <- predict(model_svm, newdata = test_model_df)
pred_tree <- predict(model_tree, newdata = test_model_df, type = "class")
pred_rf   <- predict(model_rf, newdata = test_model_df)

# ============================================================
# Gabungkan hasil prediksi
# ============================================================
hasil_prediksi <- cbind(test_raw[, features],
                        Pred_SVM  = pred_svm,
                        Pred_Tree = pred_tree,
                        Pred_RF   = pred_rf)

# ============================================================
# Cek hasil
# ============================================================
cat("\nJumlah baris test_raw:", nrow(test_raw))
## 
## Jumlah baris test_raw: 75
cat("\nJumlah hasil prediksi SVM:", length(pred_svm))
## 
## Jumlah hasil prediksi SVM: 75
head(hasil_prediksi)
##       pH     DO    BOD      TSS    Suhu        Pred_SVM       Pred_Tree
## 1 6.9977 5.0835 3.1813 50.93390 25.5573 Tercemar ringan Tercemar ringan
## 2 7.3801 4.7482 3.3373 46.52100 27.0940 Tercemar ringan Tercemar ringan
## 3 7.0195 6.5949 3.0039 49.52205 26.5954            Baik Tercemar ringan
## 4 7.3675 5.9909 3.4952 39.00910 26.6512 Tercemar ringan Tercemar ringan
## 5 6.9268 6.2444 3.3449 47.16920 23.3940 Tercemar ringan Tercemar ringan
## 6 6.9711 6.0028 3.4466 39.10270 27.7013 Tercemar ringan Tercemar ringan
##           Pred_RF
## 1 Tercemar ringan
## 2 Tercemar ringan
## 3            Baik
## 4 Tercemar ringan
## 5 Tercemar ringan
## 6 Tercemar ringan

Soal 3

# Library
library(ggplot2)
library(caret)
library(splines)

# Gunakan data training (train_raw)
data_model <- train_raw

# Variabel prediktor dan target
features <- c("pH", "BOD", "TSS", "Suhu")
target <- "DO"

1. Gunakan Regresi Linear dan Regresi Spline untuk memprediksi nilai DO berdasarkan pH, BOD, TSS, dan Suhu.

# ============================================================
# Regresi Linear
# ============================================================
model_lm <- lm(DO ~ pH + BOD + TSS + Suhu, data = data_model)
cat("\n=== Ringkasan Model Regresi Linear ===\n")
## 
## === Ringkasan Model Regresi Linear ===
print(summary(model_lm))
## 
## Call:
## lm(formula = DO ~ pH + BOD + TSS + Suhu, data = data_model)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -3.0040 -0.5382  0.0117  0.6423  3.0287 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  5.898359   0.963616   6.121 2.96e-09 ***
## pH          -0.016910   0.113118  -0.149    0.881    
## BOD          0.093192   0.068708   1.356    0.176    
## TSS         -0.000584   0.006033  -0.097    0.923    
## Suhu        -0.001924   0.013551  -0.142    0.887    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.9638 on 295 degrees of freedom
## Multiple R-squared:  0.006435,   Adjusted R-squared:  -0.007038 
## F-statistic: 0.4776 on 4 and 295 DF,  p-value: 0.7522
pred_DO_lm <- predict(model_lm, newdata = data_model)
cat("\n10 Nilai Prediksi Pertama (Linear):\n")
## 
## 10 Nilai Prediksi Pertama (Linear):
print(head(pred_DO_lm, 10))
##        1        2        3        4        5        6        7        8 
## 5.851333 5.839755 5.951988 5.986215 5.807427 5.998574 5.960970 6.077306 
##        9       10 
## 5.958444 5.985358

Hasil regresi linear menunjukkan bahwa tidak ada variabel yang berpengaruh signifikan terhadap kadar DO (Dissolved Oxygen), karena semua nilai p-value > 0,05. Nilai R² sebesar 0,0064 menunjukkan bahwa hanya sekitar 0,64% variasi DO yang dapat dijelaskan oleh variabel pH, BOD, TSS, dan Suhu, sementara sisanya dijelaskan oleh faktor lain di luar model. Nilai F-statistic (0,4776; p = 0,7522) juga menegaskan bahwa model secara keseluruhan tidak signifikan. Dengan demikian, model regresi linear ini kurang mampu menjelaskan hubungan antara variabel input dan DO.

# ============================================================
# Regresi Spline
# ============================================================
model_spline <- lm(DO ~ ns(pH, 3) + ns(BOD, 3) + ns(TSS, 3) + ns(Suhu, 3),
                   data = data_model)
cat("\n=== Ringkasan Model Regresi Spline ===\n")
## 
## === Ringkasan Model Regresi Spline ===
print(summary(model_spline))
## 
## Call:
## lm(formula = DO ~ ns(pH, 3) + ns(BOD, 3) + ns(TSS, 3) + ns(Suhu, 
##     3), data = data_model)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -2.99020 -0.52543  0.01455  0.61767  2.59506 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   5.59747    0.81169   6.896 3.41e-11 ***
## ns(pH, 3)1   -0.19358    0.27220  -0.711  0.47756    
## ns(pH, 3)2    0.45558    0.96194   0.474  0.63614    
## ns(pH, 3)3    0.37310    0.43049   0.867  0.38683    
## ns(BOD, 3)1  -0.01743    0.28920  -0.060  0.95199    
## ns(BOD, 3)2   2.58715    1.07926   2.397  0.01716 *  
## ns(BOD, 3)3   1.70283    0.55541   3.066  0.00238 ** 
## ns(TSS, 3)1   0.08995    0.25397   0.354  0.72346    
## ns(TSS, 3)2  -0.22327    0.78729  -0.284  0.77692    
## ns(TSS, 3)3  -0.26000    0.42262  -0.615  0.53890    
## ns(Suhu, 3)1  0.08496    0.88492   0.096  0.92358    
## ns(Suhu, 3)2 -0.76216    0.78649  -0.969  0.33333    
## ns(Suhu, 3)3  0.17065    0.97575   0.175  0.86128    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.9581 on 287 degrees of freedom
## Multiple R-squared:  0.04491,    Adjusted R-squared:  0.00498 
## F-statistic: 1.125 on 12 and 287 DF,  p-value: 0.3395
pred_DO_spline <- predict(model_spline, newdata = data_model)
cat("\n10 Nilai Prediksi Pertama (Spline):\n")
## 
## 10 Nilai Prediksi Pertama (Spline):
print(head(pred_DO_spline, 10))
##        1        2        3        4        5        6        7        8 
## 5.886744 5.752990 6.015719 5.788290 5.590054 5.827144 5.979459 6.087926 
##        9       10 
## 6.001501 6.324926

Hasil regresi spline menunjukkan bahwa model ini sedikit lebih baik dibanding regresi linear biasa, tetapi masih belum kuat dalam menjelaskan variasi nilai DO. Nilai R² sebesar 0,0449 berarti hanya sekitar 4,49% variasi DO yang dapat dijelaskan oleh variabel pH, BOD, TSS, dan Suhu, sisanya dipengaruhi faktor lain.

Dari hasil uji koefisien, terlihat bahwa BOD memiliki pengaruh signifikan terhadap DO, terutama pada komponen spline kedua (p = 0.01716) dan ketiga (p = 0.00238). Hal ini menunjukkan adanya hubungan nonlinier antara BOD dan DO, di mana peningkatan BOD tertentu dapat menurunkan kadar DO.

Secara keseluruhan, meskipun model spline lebih fleksibel dan mampu menangkap pola nonlinier, tingkat akurasinya masih rendah (p-value model = 0.3395), sehingga model ini belum cukup kuat untuk prediksi DO secara keseluruhan. ### 2. Evaluasi performa model (R²/MSE/RMSE).

# ============================================================
# Evaluasi performa model
# ============================================================
mse <- function(actual, predicted) mean((actual - predicted)^2)
rmse <- function(actual, predicted) sqrt(mse(actual, predicted))
r2 <- function(actual, predicted) 1 - sum((actual - predicted)^2) / sum((actual - mean(actual))^2)

eval_df <- data.frame(
  Model = c("Regresi Linear", "Regresi Spline"),
  R2 = c(r2(data_model$DO, pred_DO_lm),
         r2(data_model$DO, pred_DO_spline)),
  MSE = c(mse(data_model$DO, pred_DO_lm),
          mse(data_model$DO, pred_DO_spline)),
  RMSE = c(rmse(data_model$DO, pred_DO_lm),
           rmse(data_model$DO, pred_DO_spline))
)

cat("\n Evaluasi Model:\n")
## 
##  Evaluasi Model:
print(eval_df)
##            Model          R2       MSE      RMSE
## 1 Regresi Linear 0.006434515 0.9135020 0.9557730
## 2 Regresi Spline 0.044914342 0.8781229 0.9370821

Hasil evaluasi model menunjukkan bahwa Regresi Spline memiliki performa sedikit lebih baik dibandingkan Regresi Linear dalam memprediksi nilai DO.

Nilai R² Regresi Linear = 0.0064 menunjukkan bahwa hanya sekitar 0,64% variasi DO yang dapat dijelaskan oleh variabel pH, BOD, TSS, dan Suhu. Sementara pada Regresi Spline, R² meningkat menjadi 0.0449 (4,49%), artinya model ini mampu menangkap sedikit lebih banyak variasi data.

Dari segi galat, MSE (Mean Squared Error) dan RMSE (Root Mean Squared Error) pada Regresi Spline (0.878 dan 0.937) lebih kecil dibandingkan Regresi Linear (0.913 dan 0.956), menandakan bahwa Spline menghasilkan prediksi yang lebih mendekati nilai aktual.

Namun, perbedaan kinerjanya masih kecil, sehingga dapat disimpulkan bahwa kedua model memiliki kemampuan prediksi yang rendah, dan kemungkinan besar terdapat faktor-faktor lain di luar pH, BOD, TSS, dan Suhu yang lebih berpengaruh terhadap nilai DO. ### 3. Visualisasikan hasil prediksi vs aktual.

# Gabungkan semua dalam satu data frame baru
viz_df <- data.frame(
  DO = data_model$DO,
  pred_DO_lm = pred_DO_lm,
  pred_DO_spline = pred_DO_spline
)

# Pivot_longer (misalnya untuk membuat grafik perbandingan model):
library(tidyr)
viz_long <- viz_df %>%
  pivot_longer(cols = c(pred_DO_lm, pred_DO_spline),
               names_to = "Model",
               values_to = "Prediksi") %>%
  rename(Aktual = DO)

# Visualisasi perbandingan dua model
ggplot(viz_long, aes(x = Aktual, y = Prediksi, color = Model)) +
  geom_point(alpha = 0.7) +
  geom_abline(slope = 1, intercept = 0, color = "black", linetype = "dashed") +
  labs(title = "Perbandingan Hasil Prediksi DO (Linear vs Spline)",
       x = "DO Aktual", y = "DO Prediksi") +
  theme_minimal()

Gambar tersebut menunjukkan perbandingan hasil prediksi kadar DO menggunakan model Regresi Linear (titik merah) dan Regresi Spline (titik biru). Sebagian besar titik berada di sekitar kisaran DO 5,8–6,3, menunjukkan bahwa kedua model menghasilkan prediksi yang relatif seragam dan mendekati nilai rata-rata. Garis diagonal putus-putus menggambarkan kondisi ideal ketika nilai prediksi sama dengan nilai aktual, namun sebagian besar titik masih menyebar di sekitarnya, menandakan adanya selisih antara hasil prediksi dan nilai sebenarnya. Secara visual, model Regresi Spline tampak sedikit lebih mampu mengikuti variasi data dibandingkan model linear, meskipun peningkatan akurasi yang diperoleh masih tergolong kecil.

# Buat data frame gabungan
line_df <- data.frame(
  Sampel = 1:nrow(train_raw),
  DO_Aktual = train_raw$DO,
  Regresi_Linear = pred_DO_lm,
  Regresi_Spline = pred_DO_spline
)

# Ubah jadi format long untuk ggplot
library(reshape2)
## 
## Attaching package: 'reshape2'
## The following object is masked from 'package:tidyr':
## 
##     smiths
line_df_melt <- melt(line_df, id.vars = "Sampel")

# Plot garis
ggplot(line_df_melt, aes(x = Sampel, y = value, color = variable)) +
  geom_line(size = 1) +
  labs(title = "Perbandingan DO Aktual vs Prediksi per Sampel",
       x = "Nomor Sampel",
       y = "Nilai DO",
       color = "Jenis Data") +
  theme_minimal()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

Grafik tersebut memperlihatkan perbandingan antara nilai DO aktual (garis merah) dengan hasil prediksi dari model Regresi Linear (garis hijau) dan Regresi Spline (garis biru) pada setiap sampel. Terlihat bahwa nilai DO aktual memiliki fluktuasi yang cukup besar, sedangkan hasil prediksi dari kedua model relatif stabil dan cenderung mendekati nilai rata-rata. Pola ini menunjukkan bahwa baik Regresi Linear maupun Regresi Spline belum mampu menangkap variasi ekstrem pada data aktual secara optimal. Namun, garis biru (Spline) tampak sedikit lebih adaptif mengikuti perubahan dibandingkan garis hijau (Linear), menandakan kemampuan Spline yang lebih baik dalam menyesuaikan bentuk data nonlinier. ### 4. Jelaskan variabel yang paling memengaruhi DO.

# ============================================================
# INTERPRETASI VARIABEL PALING BERPENGARUH
# ============================================================

cat("\n--- KOEFISIEN REGRESI LINEAR ---\n")
## 
## --- KOEFISIEN REGRESI LINEAR ---
print(summary(model_lm)$coefficients)
##                  Estimate Std. Error     t value     Pr(>|t|)
## (Intercept)  5.8983588187 0.96361616  6.12106671 2.955413e-09
## pH          -0.0169104891 0.11311821 -0.14949395 8.812660e-01
## BOD          0.0931922461 0.06870829  1.35634649 1.760261e-01
## TSS         -0.0005840238 0.00603349 -0.09679701 9.229533e-01
## Suhu        -0.0019240237 0.01355098 -0.14198407 8.871895e-01
cat("\nInterpretasi:\n")
## 
## Interpretasi:
cat("- Koefisien menunjukkan pengaruh masing-masing variabel terhadap DO.\n")
## - Koefisien menunjukkan pengaruh masing-masing variabel terhadap DO.
cat("- Nilai p-value < 0.05 menunjukkan variabel tersebut signifikan.\n")
## - Nilai p-value < 0.05 menunjukkan variabel tersebut signifikan.
cat("- Berdasarkan tabel koefisien di atas, variabel dengan nilai p-value paling kecil adalah yang paling memengaruhi DO.\n")
## - Berdasarkan tabel koefisien di atas, variabel dengan nilai p-value paling kecil adalah yang paling memengaruhi DO.

Berdasarkan hasil regresi linear di atas, tidak ada variabel independen yang memiliki p-value < 0.05, artinya secara statistik tidak ada variabel yang berpengaruh signifikan terhadap nilai DO pada tingkat kepercayaan 95%. Namun, jika dilihat dari nilai p-value yang paling kecil, variabel BOD (p = 0.176) merupakan variabel yang paling mendekati signifikan dan memiliki arah pengaruh positif terhadap DO, dengan koefisien sebesar 0.093. Ini berarti bahwa ketika nilai BOD meningkat, DO cenderung sedikit meningkat juga, meskipun pengaruhnya lemah dan tidak signifikan secara statistik. Variabel lainnya seperti pH, TSS, dan Suhu memiliki p-value yang jauh lebih besar, sehingga pengaruhnya terhadap DO dapat dianggap tidak berarti dalam model ini.