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(ggplot2)
library(tidyr)
training <- read_excel("kualitasair.xlsx", sheet = "Training")
head(training)
## # 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
datatesting <- read_excel("kualitasair.xlsx", sheet = "Testing")
head(datatesting)
## # 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
# struktur data
str(training)
## 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(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
# Identifikasi missing value
colSums(is.na(training))
## Lokasi pH DO BOD TSS Suhu Status
## 0 0 23 22 24 0 0
# menangani missing value
for (col in names(training)) {
if (is.numeric(training[[col]])) {
training[[col]][is.na(training[[col]])] <- median(training[[col]], na.rm = TRUE)
} else {
mode_val <- names(sort(table(training[[col]]), decreasing = TRUE))[1]
training[[col]][is.na(training[[col]])] <- mode_val
}
}
# Identifikasi missing value
colSums(is.na(training))
## Lokasi pH DO BOD TSS Suhu Status
## 0 0 0 0 0 0 0
numeric_cols <- sapply(training, is.numeric)
for (col in names(training)[numeric_cols]) {
Q1 <- quantile(training[[col]], 0.25, na.rm = TRUE)
Q3 <- quantile(training[[col]], 0.75, na.rm = TRUE)
IQR_val <- Q3 - Q1
batas_bawah <- Q1 - 1.5 * IQR_val
batas_atas <- Q3 + 1.5 * IQR_val
outlier_count <- sum(training[[col]] < batas_bawah | training[[col]] > batas_atas, na.rm = TRUE)
cat(col, ": jumlah outlier =", outlier_count, "\n")
}
## pH : jumlah outlier = 4
## DO : jumlah outlier = 4
## BOD : jumlah outlier = 5
## TSS : jumlah outlier = 5
## Suhu : jumlah outlier = 2
# Visualisasi outlier dengan box Plot
training %>%
pivot_longer(cols = names(training)[numeric_cols], names_to = "variabel", values_to = "nilai") %>%
ggplot(aes(x = variabel, y = nilai)) +
geom_boxplot(fill = "skyblue") +
theme_minimal() +
coord_flip()
# Menangani outlier
for (col in names(training)[numeric_cols]) {
Q1 <- quantile(training[[col]], 0.25, na.rm = TRUE)
Q3 <- quantile(training[[col]], 0.75, na.rm = TRUE)
IQR_val <- Q3 - Q1
batas_bawah <- Q1 - 1.5 * IQR_val
batas_atas <- Q3 + 1.5 * IQR_val
training[[col]] <- ifelse(training[[col]] < batas_bawah, batas_bawah,
ifelse(training[[col]] > batas_atas, batas_atas, training[[col]]))
}
# struktur data
str(datatesting)
## tibble [75 × 6] (S3: tbl_df/tbl/data.frame)
## $ Lokasi: chr [1:75] "S301" "S302" "S303" "S304" ...
## $ pH : num [1:75] 7 7.38 7.02 7.37 6.93 ...
## $ DO : num [1:75] 5.08 4.75 6.59 NA 6.24 ...
## $ BOD : num [1:75] 3.18 3.34 3 3.5 3.34 ...
## $ TSS : num [1:75] 50.9 46.5 NA 39 47.2 ...
## $ Suhu : num [1:75] 25.6 27.1 26.6 26.7 23.4 ...
summary(datatesting)
## Lokasi pH DO BOD
## Length:75 Min. : 1.500 Min. :3.807 Min. :0.957
## Class :character 1st Qu.: 6.615 1st Qu.:5.279 1st Qu.:2.528
## Mode :character Median : 6.965 Median :5.644 Median :3.062
## Mean : 6.946 Mean :5.820 Mean :3.031
## 3rd Qu.: 7.349 3rd Qu.:6.331 3rd Qu.:3.440
## Max. :10.500 Max. :7.730 Max. :5.082
## NA's :8 NA's :9
## TSS Suhu
## Min. :24.06 Min. :23.39
## 1st Qu.:41.93 1st Qu.:26.79
## Median :49.57 Median :28.28
## Mean :49.11 Mean :29.04
## 3rd Qu.:56.34 3rd Qu.:29.88
## Max. :74.09 Max. :85.00
## NA's :7
# Identifikasi missing value
colSums(is.na(datatesting))
## Lokasi pH DO BOD TSS Suhu
## 0 0 8 9 7 0
# menangani missing value
for (col in names(datatesting)) {
if (is.numeric(datatesting[[col]])) {
datatesting[[col]][is.na(datatesting[[col]])] <- median(datatesting[[col]], na.rm = TRUE)
} else {
mode_val <- names(sort(table(datatesting[[col]]), decreasing = TRUE))[1]
datatesting[[col]][is.na(datatesting[[col]])] <- mode_val
}
}
# Identifikasi missing value
colSums(is.na(datatesting))
## Lokasi pH DO BOD TSS Suhu
## 0 0 0 0 0 0
numeric_cols <- sapply(datatesting, is.numeric)
for (col in names(datatesting)[numeric_cols]) {
Q1 <- quantile(datatesting[[col]], 0.25, na.rm = TRUE)
Q3 <- quantile(datatesting[[col]], 0.75, na.rm = TRUE)
IQR_val <- Q3 - Q1
batas_bawah <- Q1 - 1.5 * IQR_val
batas_atas <- Q3 + 1.5 * IQR_val
outlier_count <- sum(datatesting[[col]] < batas_bawah | datatesting[[col]] > batas_atas, na.rm = TRUE)
cat(col, ": jumlah outlier =", outlier_count, "\n")
}
## pH : jumlah outlier = 2
## DO : jumlah outlier = 3
## BOD : jumlah outlier = 2
## TSS : jumlah outlier = 2
## Suhu : jumlah outlier = 1
# Visualisasi outlier dengan box Plot
datatesting %>%
pivot_longer(cols = names(training)[numeric_cols], names_to = "variabel", values_to = "nilai") %>%
ggplot(aes(x = variabel, y = nilai)) +
geom_boxplot(fill = "skyblue") +
theme_minimal() +
coord_flip()
# Menangani outlier
for (col in names(datatesting)[numeric_cols]) {
Q1 <- quantile(datatesting[[col]], 0.25, na.rm = TRUE)
Q3 <- quantile(datatesting[[col]], 0.75, na.rm = TRUE)
IQR_val <- Q3 - Q1
batas_bawah <- Q1 - 1.5 * IQR_val
batas_atas <- Q3 + 1.5 * IQR_val
datatesting[[col]] <- ifelse(datatesting[[col]] < batas_bawah, batas_bawah,
ifelse(datatesting[[col]] > batas_atas, batas_atas, datatesting[[col]]))
}
for (col in "Status") {
cat("\nKolom:", col, "\n")
print(table(training[[col]], useNA = "ifany"))
}
##
## Kolom: Status
##
## baik Baik BAIK Tercemar berat tercemar ringan
## 1 70 1 7 1
## Tercemar ringan Tercemar Ringan
## 219 1
# normalisasi huruf & trimming spasi
training <- training %>%
mutate(across(all_of(col), ~trimws(tolower(.))))
# Ringkasan umum semua kolom
summary(training)
## 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 Length:300
## 1st Qu.:44.28 1st Qu.:26.62 Class :character
## Median :49.52 Median :28.01 Mode :character
## Mean :49.68 Mean :28.12
## 3rd Qu.:55.62 3rd Qu.:29.46
## Max. :72.62 Max. :33.73
# Ringkasan numerik saja
training %>%
select(where(is.numeric)) %>%
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
# Ringkasan kategorik
training %>%
select(where(is.character)) %>%
summarise(across(everything(), ~paste(unique(.), collapse = ", ")))
## # A tibble: 1 × 2
## Lokasi Status
## <chr> <chr>
## 1 S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14, S15, S16,… terce…
# Distribusi frekuensi Status
cat("\nDistribusi Kategori Status:\n")
##
## Distribusi Kategori Status:
print(table(training$Status, useNA = "ifany"))
##
## baik tercemar berat tercemar ringan
## 72 7 221
training %>%
summarise(across(where(is.numeric),
list(mean = ~mean(., na.rm = TRUE),
sd = ~sd(., na.rm = TRUE),
min = ~min(., na.rm = TRUE),
max = ~max(., na.rm = TRUE))))
## # A tibble: 1 × 20
## pH_mean pH_sd pH_min pH_max DO_mean DO_sd DO_min DO_max BOD_mean BOD_sd
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 6.99 0.491 5.70 8.29 5.98 0.946 3.61 8.41 3.00 0.796
## # ℹ 10 more variables: BOD_min <dbl>, BOD_max <dbl>, TSS_mean <dbl>,
## # TSS_sd <dbl>, TSS_min <dbl>, TSS_max <dbl>, Suhu_mean <dbl>, Suhu_sd <dbl>,
## # Suhu_min <dbl>, Suhu_max <dbl>
library(ggplot2)
ggplot(training, aes(x = Status)) +
geom_bar(fill = "skyblue") +
theme_minimal() +
labs(title = "Distribusi Kategori Status", x = "Status", y = "Jumlah")
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:dplyr':
##
## combine
library(dplyr)
library(rpart.plot)
# Ambil variabel numerik dan target
data_model <- training %>%
select(pH, DO, BOD, TSS, Suhu, Status)
# Pastikan Status bertipe faktor
data_model$Status <- as.factor(data_model$Status)
set.seed(123)
split <- createDataPartition(data_model$Status, p = 0.75, list = FALSE)
training <- data_model[split, ]
testing <- data_model[-split, ]
preproc <- preProcess(training[, 1:5], method = c("center", "scale"))
training_scaled <- training
training_scaled[, 1:5] <- predict(preproc, training[, 1:5])
testing_scaled <- testing
testing_scaled[, 1:5] <- predict(preproc, testing[, 1:5])
## SVM
set.seed(123)
model_svm <- train(
Status ~ pH + DO + BOD + TSS + Suhu,
data = training_scaled,
method = "svmLinear",
trControl = trainControl(method = "cv", number = 5)
)
## Prediksi di data testing
pred_svm <- predict(model_svm, newdata = testing_scaled)
## Evaluasi model
cat("\n=== Evaluasi SVM ===\n")
##
## === Evaluasi SVM ===
conf_svm <- confusionMatrix(pred_svm, testing_scaled$Status)
print(conf_svm)
## Confusion Matrix and Statistics
##
## Reference
## Prediction baik tercemar berat tercemar ringan
## baik 6 0 6
## tercemar berat 0 0 0
## tercemar ringan 12 1 49
##
## Overall Statistics
##
## Accuracy : 0.7432
## 95% CI : (0.6284, 0.8378)
## No Information Rate : 0.7432
## P-Value [Acc > NIR] : 0.5613
##
## Kappa : 0.24
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: baik Class: tercemar berat Class: tercemar ringan
## Sensitivity 0.33333 0.00000 0.8909
## Specificity 0.89286 1.00000 0.3158
## Pos Pred Value 0.50000 NaN 0.7903
## Neg Pred Value 0.80645 0.98649 0.5000
## Prevalence 0.24324 0.01351 0.7432
## Detection Rate 0.08108 0.00000 0.6622
## Detection Prevalence 0.16216 0.00000 0.8378
## Balanced Accuracy 0.61310 0.50000 0.6033
Hasil evaluasi model SVM menunjukkan bahwa model memiliki tingkat akurasi sebesar 74,32%, yang berarti sekitar tiga perempat dari seluruh data uji berhasil diklasifikasikan dengan benar. Namun, nilai Kappa sebesar 0,24 menandakan bahwa kesesuaian antara hasil prediksi dan kondisi sebenarnya masih tergolong rendah, atau hanya sedikit lebih baik dibandingkan tebakan acak. Selain itu, nilai p-value sebesar 0,5613 menunjukkan bahwa akurasi model tidak jauh berbeda dari akurasi yang bisa dicapai hanya dengan menebak kelas dominan, sehingga secara statistik model belum signifikan lebih baik.
Jika dilihat berdasarkan masing-masing kelas, model cukup baik dalam mengenali kategori “tercemar ringan” dengan sensitivitas 0,89 dan precision 0,79, meskipun nilai specificity-nya hanya 0,32, yang menandakan adanya kecenderungan model untuk mengklasifikasikan banyak data ke dalam kelas ini. Hal ini sejalan dengan distribusi data, di mana kelas “tercemar ringan” merupakan kelas yang paling dominan. Untuk kelas “baik”, model hanya mampu mendeteksi sekitar 33% dari data yang seharusnya termasuk ke dalam kategori tersebut, meskipun cukup baik dalam menghindari kesalahan klasifikasi ke kelas lain (specificity 0,89). Sementara itu, pada kelas “tercemar berat”, model tidak mampu mengenali satupun data dengan benar (sensitivitas 0,00), menandakan bahwa SVM gagal mempelajari pola dari kelas ini kemungkinan besar karena jumlah datanya yang sangat sedikit.
# Bangun model Decision Tree
set.seed(123)
model_tree <- rpart(
Status ~ pH + DO + BOD + TSS + Suhu,
data = training,
method = "class",
control = rpart.control(cp = 0.05) # cp = complexity parameter
)
# Visualisasi pohon keputusan
rpart.plot(model_tree, type = 3, extra = 101, fallen.leaves = TRUE, main = "Decision Tree Kualitas Air")
# Prediksi di data testing
pred_tree <- predict(model_tree, newdata = testing, type = "class")
# Evaluasi performa model
conf_tree <- confusionMatrix(pred_tree, testing$Status)
print(conf_tree)
## Confusion Matrix and Statistics
##
## Reference
## Prediction baik tercemar berat tercemar ringan
## baik 15 0 2
## tercemar berat 0 0 0
## tercemar ringan 3 1 53
##
## Overall Statistics
##
## Accuracy : 0.9189
## 95% CI : (0.8318, 0.9697)
## No Information Rate : 0.7432
## P-Value [Acc > NIR] : 0.0001203
##
## Kappa : 0.7818
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: baik Class: tercemar berat Class: tercemar ringan
## Sensitivity 0.8333 0.00000 0.9636
## Specificity 0.9643 1.00000 0.7895
## Pos Pred Value 0.8824 NaN 0.9298
## Neg Pred Value 0.9474 0.98649 0.8824
## Prevalence 0.2432 0.01351 0.7432
## Detection Rate 0.2027 0.00000 0.7162
## Detection Prevalence 0.2297 0.00000 0.7703
## Balanced Accuracy 0.8988 0.50000 0.8766
# Lihat akurasi
cat("\nAkurasi Decision Tree:", round(conf_tree$overall["Accuracy"], 3), "\n")
##
## Akurasi Decision Tree: 0.919
Hasil evaluasi model Decision Tree menunjukkan bahwa model memiliki akurasi tinggi sebesar 91,89%, dengan interval kepercayaan 95% antara 83,18% hingga 96,97%, dan p-value 0,00012 menunjukkan bahwa akurasi model secara signifikan lebih baik daripada tebakan berdasarkan kelas dominan (No Information Rate 74,32%). Nilai Kappa 0,7818 mengindikasikan kesesuaian prediksi model dengan kondisi sebenarnya tergolong kuat, sehingga model dapat dikatakan memiliki performa yang andal secara keseluruhan.
Dilihat dari analisis per kelas, model mampu mengenali kelas “baik” dengan baik (sensitivitas 0,83, precision 0,88) dan juga cukup baik dalam menghindari kesalahan klasifikasi ke kelas lain (specificity 0,96). Kelas “tercemar ringan” bahkan lebih berhasil dikenali (sensitivitas 0,96, precision 0,93), meskipun specificity-nya lebih rendah (0,79), yang menunjukkan beberapa data dari kelas lain masih terprediksi sebagai “tercemar ringan”. Namun, kelas “tercemar berat” tetap menjadi tantangan, karena model tidak berhasil mendeteksi satupun data dari kelas ini (sensitivitas 0,00), meskipun specificity-nya sempurna (1,00), menandakan tidak ada kesalahan klasifikasi data lain ke kelas ini.
Secara keseluruhan, Decision Tree menunjukkan performa yang sangat baik secara umum, terutama pada kelas “baik” dan “tercemar ringan”, namun masih perlu perbaikan untuk mendeteksi kelas minoritas “tercemar berat”, misalnya melalui penyeimbangan data, penyesuaian parameter tree, atau teknik boosting agar semua kelas dapat dikenali secara lebih merata.
# Bangun model Random Forest
set.seed(123)
model_rf <- randomForest(
Status ~ pH + DO + BOD + TSS + Suhu,
data = training,
ntree = 500, # jumlah pohon
mtry = 3, # jumlah variabel acak per pohon
importance = TRUE # biar bisa lihat variabel paling berpengaruh
)
# menampilkan ringkasan model
print(model_rf)
##
## Call:
## randomForest(formula = Status ~ pH + DO + BOD + TSS + Suhu, data = training, ntree = 500, mtry = 3, importance = TRUE)
## Type of random forest: classification
## Number of trees: 500
## No. of variables tried at each split: 3
##
## OOB estimate of error rate: 4.42%
## Confusion matrix:
## baik tercemar berat tercemar ringan class.error
## baik 50 0 4 0.074074074
## tercemar berat 0 1 5 0.833333333
## tercemar ringan 0 1 165 0.006024096
# Evaluasi performa pada data testing
pred_rf <- predict(model_rf, newdata = testing)
conf_rf <- confusionMatrix(pred_rf, testing$Status)
print(conf_rf)
## Confusion Matrix and Statistics
##
## Reference
## Prediction baik tercemar berat tercemar ringan
## baik 15 0 2
## tercemar berat 0 1 0
## tercemar ringan 3 0 53
##
## Overall Statistics
##
## Accuracy : 0.9324
## 95% CI : (0.8493, 0.9777)
## No Information Rate : 0.7432
## P-Value [Acc > NIR] : 2.87e-05
##
## Kappa : 0.8229
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: baik Class: tercemar berat Class: tercemar ringan
## Sensitivity 0.8333 1.00000 0.9636
## Specificity 0.9643 1.00000 0.8421
## Pos Pred Value 0.8824 1.00000 0.9464
## Neg Pred Value 0.9474 1.00000 0.8889
## Prevalence 0.2432 0.01351 0.7432
## Detection Rate 0.2027 0.01351 0.7162
## Detection Prevalence 0.2297 0.01351 0.7568
## Balanced Accuracy 0.8988 1.00000 0.9029
# Akurasi
cat("\nAkurasi Random Forest:", round(conf_rf$overall["Accuracy"], 3), "\n")
##
## Akurasi Random Forest: 0.932
importance(model_rf)
## baik tercemar berat tercemar ringan MeanDecreaseAccuracy
## pH -1.741323 0.3514679 -1.1585749 -1.730003
## DO 83.255958 13.2402293 74.7870724 98.122494
## BOD 69.565794 15.5795929 74.4966015 87.405875
## TSS 3.009945 -1.6746405 0.3214843 1.520602
## Suhu -3.242164 1.3019204 -1.1474031 -2.259675
## MeanDecreaseGini
## pH 2.527638
## DO 40.762923
## BOD 39.904209
## TSS 3.831115
## Suhu 3.397850
varImpPlot(model_rf, main = "Pentingnya Variabel dalam Model Random Forest")
Hasil evaluasi model Random Forest menunjukkan performa yang sangat baik
dengan akurasi keseluruhan sebesar 93,24% dan interval kepercayaan 95%
antara 84,93% hingga 97,77%, sementara p-value 2,87e-05 menunjukkan
bahwa akurasi ini secara signifikan lebih baik dibandingkan tebakan
berdasarkan kelas dominan (No Information Rate 74,32%). Nilai Kappa
0,8229 mengindikasikan bahwa prediksi model sangat konsisten dengan
kondisi sebenarnya, sehingga Random Forest dapat dikatakan sangat andal
untuk klasifikasi ini.
Jika dilihat berdasarkan kelas, model mampu mendeteksi kelas “baik” dengan baik (sensitivitas 0,83, precision 0,88) dan mampu membedakan dengan kelas lain (specificity 0,96). Untuk kelas “tercemar berat”, model berhasil sempurna dalam mendeteksi seluruh data yang termasuk kelas ini (sensitivitas 1,00, precision 1,00, specificity 1,00), meskipun jumlah datanya sedikit, menunjukkan bahwa Random Forest sangat efektif menangani kelas minoritas. Kelas “tercemar ringan” juga dikenali dengan baik (sensitivitas 0,96, precision 0,95, specificity 0,84), meskipun beberapa data dari kelas lain masih terprediksi sebagai kelas ini.
Analisis pentingnya variabel menunjukkan bahwa DO (dissolved oxygen) dan BOD memiliki kontribusi terbesar dalam klasifikasi, baik dilihat dari Mean Decrease Accuracy maupun Mean Decrease Gini, menandakan bahwa kedua parameter ini paling menentukan kualitas air dalam model. Variabel TSS dan Suhu memiliki pengaruh yang lebih kecil, sedangkan pH tampak kurang berpengaruh atau bahkan memberikan kontribusi negatif pada beberapa kelas.
library(writexl)
prediksi_baru <- predict(model_svm, newdata = datatesting)
print(prediksi_baru)
## [1] tercemar ringan tercemar ringan tercemar ringan tercemar ringan
## [5] tercemar ringan tercemar ringan tercemar ringan tercemar ringan
## [9] tercemar ringan tercemar ringan tercemar ringan tercemar ringan
## [13] tercemar ringan tercemar ringan baik tercemar ringan
## [17] tercemar ringan tercemar ringan tercemar ringan tercemar ringan
## [21] tercemar ringan tercemar ringan tercemar ringan tercemar ringan
## [25] tercemar ringan tercemar ringan tercemar ringan tercemar ringan
## [29] tercemar ringan tercemar ringan tercemar ringan tercemar ringan
## [33] tercemar ringan baik tercemar ringan tercemar ringan
## [37] baik tercemar ringan tercemar ringan tercemar ringan
## [41] tercemar ringan tercemar ringan tercemar ringan tercemar ringan
## [45] tercemar ringan tercemar ringan tercemar ringan tercemar ringan
## [49] tercemar ringan tercemar ringan tercemar ringan baik
## [53] tercemar ringan tercemar ringan tercemar ringan tercemar ringan
## [57] tercemar ringan tercemar ringan tercemar ringan tercemar ringan
## [61] tercemar ringan tercemar ringan tercemar ringan tercemar ringan
## [65] tercemar ringan tercemar ringan tercemar ringan tercemar ringan
## [69] tercemar ringan tercemar ringan tercemar ringan tercemar ringan
## [73] tercemar ringan tercemar ringan tercemar ringan
## Levels: baik tercemar berat tercemar ringan
# Gabungkan hasil dengan data aslinya
hasil_prediksi <- cbind(datatesting, Prediksi = prediksi_baru)
# Simpan ke Excel
write_xlsx(hasil_prediksi, "hasil_prediksi_SVM.xlsx")
# Prediksi pakai model Decision Tree
prediksi_tree <- predict(model_tree, newdata = datatesting, type = "class")
# Gabungkan hasil dengan data asli
hasil_prediksi_tree <- cbind(datatesting, Prediksi_Status = prediksi_tree)
# Simpan ke Excel
write_xlsx(hasil_prediksi_tree, "hasil_prediksi_decision_tree.xlsx")
# Prediksi Status menggunakan model Random Forest
prediksi_rf <- predict(model_rf, newdata = datatesting)
# Gabungkan hasil dengan data asli
hasil_prediksi_rf <- cbind(datatesting, Prediksi_Status = prediksi_rf)
# Simpan hasil ke file Excel
write_xlsx(hasil_prediksi_rf, "hasil_prediksi_random_forest.xlsx")
# Pastikan library aktif
library(caret)
library(splines)
library(ggplot2)
# Gunakan data training yang sudah ada
set.seed(666)
# Model Regresi Linear
model_lm <- lm(DO ~ pH + BOD + TSS + Suhu, data = training)
summary(model_lm)
##
## Call:
## lm(formula = DO ~ pH + BOD + TSS + Suhu, data = training)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2.39717 -0.50827 -0.00661 0.67022 2.40421
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 5.819259 1.382750 4.208 3.74e-05 ***
## pH 0.017234 0.136645 0.126 0.900
## BOD 0.102284 0.079421 1.288 0.199
## TSS 0.001482 0.007114 0.208 0.835
## Suhu -0.011727 0.030713 -0.382 0.703
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.9704 on 221 degrees of freedom
## Multiple R-squared: 0.00797, Adjusted R-squared: -0.009985
## F-statistic: 0.4439 on 4 and 221 DF, p-value: 0.7768
Hasil analisis regresi linier untuk memodelkan DO (dissolved oxygen) berdasarkan variabel pH, BOD, TSS, dan Suhu menunjukkan bahwa model tidak mampu menjelaskan variasi DO secara signifikan. Hal ini tercermin dari nilai R-squared sebesar 0,00797 dan adjusted R-squared negatif (-0,009985), yang menunjukkan bahwa kurang dari 1% variasi DO dapat dijelaskan oleh keempat prediktor tersebut. Nilai F-statistic 0,4439 dengan p-value 0,7768 juga menegaskan bahwa secara keseluruhan model tidak signifikan secara statistik.
Secara individual, koefisien variabel pH (0,0172), BOD (0,1023), TSS (0,0015), dan Suhu (-0,0117) semuanya memiliki p-value lebih besar dari 0,05, sehingga tidak ada satupun variabel yang memiliki pengaruh signifikan terhadap DO dalam dataset ini. Residual model menunjukkan distribusi yang relatif simetris dengan median mendekati nol (-0,0066) dan residual standard error 0,9704, namun karena variabel prediktor tidak signifikan, model ini tidak dapat digunakan untuk prediksi atau interpretasi hubungan yang bermakna antara DO dan faktor-faktor lingkungan yang diteliti.
Secara keseluruhan, model regresi linier sederhana ini tidak cocok untuk memprediksi DO, dan diperlukan pendekatan lain, seperti model non-linear, machine learning, atau penambahan variabel prediktor lain yang lebih relevan dengan kualitas air.
# Model Regresi Spline
model_spline <- lm(
DO ~ bs(pH, df = 3) + bs(BOD, df = 3) + bs(TSS, df = 3) + bs(Suhu, df = 3),
data = training
)
summary(model_spline)
##
## Call:
## lm(formula = DO ~ bs(pH, df = 3) + bs(BOD, df = 3) + bs(TSS,
## df = 3) + bs(Suhu, df = 3), data = training)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2.46230 -0.55490 0.06861 0.61789 2.58778
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 5.7776 0.9548 6.051 6.4e-09 ***
## bs(pH, df = 3)1 0.5110 1.2455 0.410 0.68202
## bs(pH, df = 3)2 -0.2893 0.5997 -0.482 0.62997
## bs(pH, df = 3)3 0.5855 0.8380 0.699 0.48553
## bs(BOD, df = 3)1 2.6487 1.0375 2.553 0.01138 *
## bs(BOD, df = 3)2 -0.4990 0.5720 -0.872 0.38399
## bs(BOD, df = 3)3 2.0147 0.7033 2.865 0.00459 **
## bs(TSS, df = 3)1 -0.8436 0.9704 -0.869 0.38562
## bs(TSS, df = 3)2 -0.2036 0.5675 -0.359 0.72007
## bs(TSS, df = 3)3 -0.2172 0.6376 -0.341 0.73368
## bs(Suhu, df = 3)1 -1.1733 1.0834 -1.083 0.28006
## bs(Suhu, df = 3)2 -0.4977 0.5573 -0.893 0.37287
## bs(Suhu, df = 3)3 -0.6150 0.7124 -0.863 0.38893
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.9636 on 213 degrees of freedom
## Multiple R-squared: 0.05717, Adjusted R-squared: 0.004048
## F-statistic: 1.076 on 12 and 213 DF, p-value: 0.3816
Hasil analisis regresi spline (menggunakan basis splines dengan df = 3 untuk masing-masing variabel pH, BOD, TSS, dan Suhu) menunjukkan bahwa model hanya sedikit mampu menjelaskan variasi DO. Multiple R-squared sebesar 0,05717 dan adjusted R-squared 0,004048 mengindikasikan bahwa hanya sekitar 5,7% variasi DO yang dijelaskan oleh model, dan setelah disesuaikan untuk jumlah prediktor, kontribusi efektifnya hampir nihil. F-statistic 1,076 dengan p-value 0,3816 menunjukkan bahwa secara keseluruhan, model tidak signifikan secara statistik, sehingga prediktor spline secara kolektif tidak memberikan pengaruh yang jelas terhadap DO.
Secara individu, hanya beberapa koefisien spline dari BOD yang signifikan (bs(BOD, df=3)1 dengan p=0,01138 dan bs(BOD, df=3)3 dengan p=0,00459), sedangkan semua komponen spline dari pH, TSS, dan Suhu tidak signifikan (p-value > 0,05). Ini menunjukkan bahwa BOD mungkin memiliki hubungan non-linear tertentu dengan DO, tetapi variabel lainnya tidak berkontribusi signifikan dalam model ini. Residual model menunjukkan residual standard error 0,9636 dan distribusi residual yang masih relatif simetris, tetapi karena sebagian besar prediktor tidak signifikan, model spline ini tidak cukup baik untuk prediksi DO.
Kesimpulannya, pendekatan regresi spline menunjukkan sedikit peningkatan fleksibilitas dibanding regresi linier sederhana, tetapi tetap kurang efektif, sehingga diperlukan data tambahan, variabel lain, atau metode non-linear/machine learning yang lebih kompleks untuk memodelkan DO dengan lebih akurat.
# Prediksi di data testing
pred_lm <- predict(model_lm, newdata = testing)
pred_spline <- predict(model_spline, newdata = testing)
# Fungsi untuk menghitung metrik
mse <- function(actual, pred) mean((actual - pred)^2)
rmse <- function(actual, pred) sqrt(mse(actual, pred))
r2 <- function(actual, pred) cor(actual, pred)^2
# Hitung performa
perf <- data.frame(
Model = c("Regresi Linear", "Regresi Spline"),
R2 = c(r2(testing$DO, pred_lm), r2(testing$DO, pred_spline)),
MSE = c(mse(testing$DO, pred_lm), mse(testing$DO, pred_spline)),
RMSE = c(rmse(testing$DO, pred_lm), rmse(testing$DO, pred_spline))
)
print(perf)
## Model R2 MSE RMSE
## 1 Regresi Linear 2.963834e-05 0.7839207 0.8853929
## 2 Regresi Spline 2.751446e-05 0.8179485 0.9044051
Hasil perbandingan kinerja kedua model menunjukkan bahwa regresi linier dan regresi spline memiliki performa yang sangat rendah dalam memprediksi DO. Nilai R² untuk regresi linier (2,96×10⁻⁵) dan regresi spline (2,75×10⁻⁵) hampir nol, menunjukkan bahwa hampir tidak ada variasi DO yang dijelaskan oleh variabel prediktor pada kedua model.
Untuk metrik kesalahan, regresi linier memiliki MSE 0,784 dan RMSE 0,885, sedangkan regresi spline sedikit lebih tinggi dengan MSE 0,818 dan RMSE 0,904, menandakan bahwa prediksi spline justru sedikit lebih meleset dibanding regresi linier. Secara keseluruhan, kedua model ini tidak cocok untuk prediksi DO, dan perbedaan kinerjanya sangat kecil, sehingga perlu pendekatan lain, seperti model non-linear lebih kompleks atau metode machine learning, untuk meningkatkan akurasi prediksi.
# Gabungkan hasil prediksi
df_plot <- data.frame(
Actual = testing$DO,
Linear = pred_lm,
Spline = pred_spline
)
# Visualisasi
ggplot(df_plot, aes(x = Actual)) +
geom_point(aes(y = Linear, color = "Linear"), alpha = 0.6) +
geom_point(aes(y = Spline, color = "Spline"), alpha = 0.6) +
geom_abline(slope = 1, intercept = 0, linetype = "dashed") +
labs(title = "Prediksi vs Aktual DO",
x = "Nilai DO Aktual",
y = "Nilai DO Prediksi",
color = "Model") +
theme_minimal()