library(readxl)
data <- read_excel("D:\\Semester 5\\StatLing\\kualitasair.xlsx")
str(data)
## tibble [300 × 7] (S3: tbl_df/tbl/data.frame)
## $ Lokasi: chr [1:300] "S1" "S2" "S3" "S4" ...
## $ pH : num [1:300] 7.69 6.72 7.18 7.32 7.2 ...
## $ DO : num [1:300] NA 5.72 4.89 6.13 7.79 ...
## $ BOD : num [1:300] 1.71 1.44 2.73 3.14 1.18 ...
## $ TSS : num [1:300] 43.1 44.3 NA 41 48.1 ...
## $ Suhu : num [1:300] 26.8 27.7 26 29.7 26.4 ...
## $ Status: chr [1:300] "Tercemar ringan" "Tercemar ringan" "Tercemar ringan" "Tercemar ringan" ...
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
head(data)
## # A tibble: 6 × 7
## Lokasi pH DO BOD TSS Suhu Status
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <chr>
## 1 S1 7.69 NA 1.71 43.1 26.8 Tercemar ringan
## 2 S2 6.72 5.72 1.44 44.3 27.7 Tercemar ringan
## 3 S3 7.18 4.89 2.73 NA 26.0 Tercemar ringan
## 4 S4 7.32 6.13 3.14 41.0 29.7 Tercemar ringan
## 5 S5 7.20 7.79 1.18 48.1 26.4 baik
## 6 S6 6.95 8.42 3.23 48.6 28.7 Tercemar ringan
colSums(is.na(data))
## Lokasi pH DO BOD TSS Suhu Status
## 0 0 23 22 24 0 0
data <- na.omit(data)
boxplot(data[, c("pH", "DO", "BOD", "TSS", "Suhu")], main = "Deteksi Outlier")
for (col in c("pH", "DO", "BOD", "TSS", "Suhu")) {
Q1 <- quantile(data[[col]], 0.25, na.rm = TRUE)
Q3 <- quantile(data[[col]], 0.75, na.rm = TRUE)
IQR <- Q3 - Q1
lower <- Q1 - 1.5 * IQR
upper <- Q3 + 1.5 * IQR
data <- data[data[[col]] >= lower & data[[col]] <= upper, ]
}
table(data$Status)
##
## baik Baik Tercemar berat tercemar ringan Tercemar ringan
## 1 49 2 1 176
data$Status <- tolower(data$Status)
data$Status <- trimws(data$Status)
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
data$Status <- tolower(data$Status)
data$Status <- trimws(data$Status)
data$Status <- recode(data$Status,
"baik" = "1",
"tercemar ringan" = "2",
"tercemar berat" = "3")
data$Status <- as.factor(data$Status)
summary(data[, c("pH", "DO", "BOD", "TSS", "Suhu")])
## pH DO BOD TSS
## Min. :5.780 Min. :3.811 Min. :0.8993 Min. :25.46
## 1st Qu.:6.708 1st Qu.:5.368 1st Qu.:2.4191 1st Qu.:43.84
## Median :6.985 Median :5.962 Median :3.0808 Median :49.40
## Mean :7.000 Mean :5.966 Mean :3.0220 Mean :49.70
## 3rd Qu.:7.318 3rd Qu.:6.655 3rd Qu.:3.5904 3rd Qu.:56.59
## Max. :8.230 Max. :8.422 Max. :5.0988 Max. :75.53
## Suhu
## Min. :22.77
## 1st Qu.:26.86
## Median :28.09
## Mean :28.22
## 3rd Qu.:29.57
## Max. :33.25
library(ggplot2)
ggplot(data, aes(x = pH)) + geom_histogram(binwidth = 0.2, fill = "skyblue") + theme_minimal()
ggplot(data, aes(x = DO)) + geom_histogram(binwidth = 0.5, fill = "salmon") + theme_minimal()
ggplot(data, aes(x = BOD)) + geom_histogram(binwidth = 0.5, fill = "lightgreen") + theme_minimal()
ggplot(data, aes(x = TSS)) + geom_histogram(binwidth = 5, fill = "orange") + theme_minimal()
ggplot(data, aes(x = Suhu)) + geom_histogram(binwidth = 0.5, fill = "violet") + theme_minimal()
Membangun model klasifikasi untuk menentukan Status Kualitas Air berdasarkan variabel numerik: pH, DO, BOD, TSS, dan Suhu Kita akan membandingkan tiga model: Support Vector Machine (SVM) Decision Tree Random Forest
library(caret)
## Loading required package: lattice
library(e1071)
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
set.seed(123) # untuk replikasi hasil
trainIndex <- createDataPartition(data$Status, p = 0.8, list = FALSE)
train_data <- data[trainIndex, ]
test_data <- data[-trainIndex, ]
table(train_data$Status)
##
## 1 2 3
## 40 142 2
table(test_data$Status)
##
## 1 2 3
## 10 35 0
Data dibagi menjadi dua bagian agar model dapat dilatih dan diuji secara adil. Proporsi pembagian 80:20 digunakan untuk menjaga keseimbangan antara data pelatihan dan data pengujian. Pemilihan seed 123 dilakukan untuk memastikan hasil yang konsisten saat analisis diulang.
svm_model <- svm(Status ~ pH + DO + BOD + TSS + Suhu, data = train_data, kernel = "radial")
svm_pred <- predict(svm_model, test_data)
confusionMatrix(svm_pred, test_data$Status)
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2 3
## 1 8 1 0
## 2 2 34 0
## 3 0 0 0
##
## Overall Statistics
##
## Accuracy : 0.9333
## 95% CI : (0.8173, 0.986)
## No Information Rate : 0.7778
## P-Value [Acc > NIR] : 0.005218
##
## Kappa : 0.8
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: 1 Class: 2 Class: 3
## Sensitivity 0.8000 0.9714 NA
## Specificity 0.9714 0.8000 1
## Pos Pred Value 0.8889 0.9444 NA
## Neg Pred Value 0.9444 0.8889 NA
## Prevalence 0.2222 0.7778 0
## Detection Rate 0.1778 0.7556 0
## Detection Prevalence 0.2000 0.8000 0
## Balanced Accuracy 0.8857 0.8857 NA
Model SVM digunakan karena kemampuannya dalam memisahkan kelas menggunakan hyperplane optimal di ruang berdimensi tinggi. Kernel radial dipilih karena fleksibel terhadap hubungan non-linear antar variabel. Hasil evaluasi melalui confusion matrix memberikan gambaran tingkat akurasi dan kesalahan klasifikasi antar kelas status kualitas air.
tree_model <- rpart(Status ~ pH + DO + BOD + TSS + Suhu, data = train_data, method = "class")
tree_pred <- predict(tree_model, test_data, type = "class")
confusionMatrix(tree_pred, test_data$Status)
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2 3
## 1 10 0 0
## 2 0 35 0
## 3 0 0 0
##
## Overall Statistics
##
## Accuracy : 1
## 95% CI : (0.9213, 1)
## No Information Rate : 0.7778
## P-Value [Acc > NIR] : 1.226e-05
##
## Kappa : 1
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: 1 Class: 2 Class: 3
## Sensitivity 1.0000 1.0000 NA
## Specificity 1.0000 1.0000 1
## Pos Pred Value 1.0000 1.0000 NA
## Neg Pred Value 1.0000 1.0000 NA
## Prevalence 0.2222 0.7778 0
## Detection Rate 0.2222 0.7778 0
## Detection Prevalence 0.2222 0.7778 0
## Balanced Accuracy 1.0000 1.0000 NA
Cabang pohon menggambarkan aturan keputusan yang mengklasifikasikan status kualitas air berdasarkan ambang batas dari setiap parameter fisik dan kimia. Kelebihan metode ini adalah kemudahan visualisasi dan interpretasi logika klasifikasinya.
rf_model <- randomForest(Status ~ pH + DO + BOD + TSS + Suhu, data = train_data, ntree = 500, mtry = 3)
rf_pred <- predict(rf_model, test_data)
confusionMatrix(rf_pred, test_data$Status)
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2 3
## 1 10 0 0
## 2 0 35 0
## 3 0 0 0
##
## Overall Statistics
##
## Accuracy : 1
## 95% CI : (0.9213, 1)
## No Information Rate : 0.7778
## P-Value [Acc > NIR] : 1.226e-05
##
## Kappa : 1
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: 1 Class: 2 Class: 3
## Sensitivity 1.0000 1.0000 NA
## Specificity 1.0000 1.0000 1
## Pos Pred Value 1.0000 1.0000 NA
## Neg Pred Value 1.0000 1.0000 NA
## Prevalence 0.2222 0.7778 0
## Detection Rate 0.2222 0.7778 0
## Detection Prevalence 0.2222 0.7778 0
## Balanced Accuracy 1.0000 1.0000 NA
importance(rf_model)
## MeanDecreaseGini
## pH 1.605773
## DO 31.185039
## BOD 29.191854
## TSS 1.882462
## Suhu 1.925329
varImpPlot(rf_model)
pengembangan dari Decision Tree dengan menggabungkan banyak pohon secara
acak untuk meningkatkan akurasi dan mengurangi overfitting. Hasil
prediksi ditentukan berdasarkan voting dari seluruh pohon dalam
ensemble. Analisis variable importance digunakan untuk mengidentifikasi
parameter yang paling berpengaruh terhadap status kualitas air.
svm_acc <- confusionMatrix(svm_pred, test_data$Status)$overall['Accuracy']
tree_acc <- confusionMatrix(tree_pred, test_data$Status)$overall['Accuracy']
rf_acc <- confusionMatrix(rf_pred, test_data$Status)$overall['Accuracy']
akurasi_model <- data.frame(
Model = c("SVM", "Decision Tree", "Random Forest"),
Akurasi = c(svm_acc, tree_acc, rf_acc)
)
print(akurasi_model)
## Model Akurasi
## 1 SVM 0.9333333
## 2 Decision Tree 1.0000000
## 3 Random Forest 1.0000000
Evaluasi dilakukan dengan membandingkan tingkat akurasi dari ketiga model. Model dengan akurasi tertinggi dianggap paling optimal dalam mengklasifikasikan status kualitas air sungai. ## Maka hasil yang di proleh yaitu Analisis klasifikasi dilakukan menggunakan tiga algoritma, yaitu Support Vector Machine (SVM), Decision Tree, dan Random Forest, dengan variabel prediktor pH, DO, BOD, TSS, dan Suhu terhadap variabel respon Status Kualitas Air. Proses pelatihan dan pengujian dilakukan dengan proporsi 80:20 untuk menjaga keseimbangan data dan menghindari bias pada hasil model.
Membangun model untuk memprediksi nilai Dissolved Oxygen (DO) berdasarkan parameter: pH, BOD, TSS, dan Suhu
data_pred <- data[, c("DO", "pH", "BOD", "TSS", "Suhu")]
set.seed(123)
trainIndex <- createDataPartition(data_pred$DO, p = 0.8, list = FALSE)
train_data <- data_pred[trainIndex, ]
test_data <- data_pred[-trainIndex, ]
Tahap pertama dilakukan pemisahan data menjadi training dan testing dengan proporsi 80:20. Hal ini bertujuan agar model dapat dilatih menggunakan sebagian besar data dan diuji pada data yang belum pernah dilihat sebelumnya untuk menilai kemampuan generalisasi model prediktif.
# Model regresi linear
lm_model <- lm(DO ~ pH + BOD + TSS + Suhu, data = train_data)
# Prediksi
lm_pred <- predict(lm_model, newdata = test_data)
# Evaluasi performa
lm_r2 <- summary(lm_model)$r.squared
lm_mse <- mean((lm_pred - test_data$DO)^2)
lm_rmse <- sqrt(lm_mse)
lm_eval <- data.frame(Model = "Regresi Linear", R2 = lm_r2, MSE = lm_mse, RMSE = lm_rmse)
lm_eval
## Model R2 MSE RMSE
## 1 Regresi Linear 0.002755906 0.9038132 0.9506909
library(splines)
spline_model <- lm(DO ~ bs(pH, df = 3) + bs(BOD, df = 3) + bs(TSS, df = 3) + bs(Suhu, df = 3), data = train_data)
spline_pred <- predict(spline_model, newdata = test_data)
## Warning in bs(TSS, degree = 3L, knots = numeric(0), Boundary.knots = c(25.4572,
## : some 'x' values beyond boundary knots may cause ill-conditioned bases
spline_r2 <- summary(spline_model)$r.squared
spline_mse <- mean((spline_pred - test_data$DO)^2)
spline_rmse <- sqrt(spline_mse)
spline_eval <- data.frame(Model = "Regresi Spline", R2 = spline_r2, MSE = spline_mse, RMSE = spline_rmse)
spline_eval
## Model R2 MSE RMSE
## 1 Regresi Spline 0.04857567 0.9087734 0.9532961
Pendekatan Regresi Spline digunakan untuk menangkap pola hubungan non-linier antar variabel. Dengan menggunakan basis B-spline, model dapat menyesuaikan bentuk kurva yang lebih fleksibel dibanding regresi linear biasa.
eval_models <- rbind(lm_eval, spline_eval)
print(eval_models)
## Model R2 MSE RMSE
## 1 Regresi Linear 0.002755906 0.9038132 0.9506909
## 2 Regresi Spline 0.048575668 0.9087734 0.9532961
plot_data <- data.frame(
Aktual = test_data$DO,
Prediksi_LM = lm_pred,
Prediksi_Spline = spline_pred
)
library(ggplot2)
ggplot(plot_data, aes(x = Aktual, y = Prediksi_LM)) +
geom_point(color = "blue", alpha = 0.6) +
geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
theme_minimal() +
labs(title = "Prediksi vs Aktual DO (Regresi Linear)",
x = "Nilai Aktual DO", y = "Nilai Prediksi DO")
ggplot(plot_data, aes(x = Aktual, y = Prediksi_Spline)) +
geom_point(color = "darkgreen", alpha = 0.6) +
geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
theme_minimal() +
labs(title = "Prediksi vs Aktual DO (Regresi Spline)",
x = "Nilai Aktual DO", y = "Nilai Prediksi DO")
Dari hasil visualisasi terlihat bahwa model spline memberikan prediksi yang lebih halus dan mendekati nilai aktual, khususnya ketika hubungan antar variabel bersifat non-linier.
summary(lm_model)
##
## Call:
## lm(formula = DO ~ pH + BOD + TSS + Suhu, data = train_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2.24254 -0.61493 -0.05573 0.69904 2.43914
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 5.588865 1.610381 3.471 0.00065 ***
## pH -0.019433 0.152638 -0.127 0.89883
## BOD 0.034620 0.092246 0.375 0.70788
## TSS 0.004203 0.007591 0.554 0.58045
## Suhu 0.007433 0.035936 0.207 0.83638
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.9844 on 180 degrees of freedom
## Multiple R-squared: 0.002756, Adjusted R-squared: -0.01941
## F-statistic: 0.1244 on 4 and 180 DF, p-value: 0.9735
Berdasarkan hasil estimasi Regresi Linear, diperoleh bahwa hubungan antara variabel pH, BOD, TSS, dan Suhu terhadap nilai DO tidak signifikan secara statistik. Hal ini ditunjukkan oleh nilai p-value yang tinggi pada setiap koefisien serta nilai R² yang sangat rendah (0.0028). Dengan demikian, model linier sederhana ini tidak mampu menjelaskan variasi nilai DO secara memadai. Kemungkinan besar, hubungan antar variabel bersifat non-linier atau dipengaruhi oleh faktor lingkungan lain seperti kecepatan arus, kandungan nutrien, atau aktivitas mikroorganisme di perairan. Oleh karena itu, pendekatan non-linier seperti Regresi Spline lebih sesuai digunakan untuk menangkap hubungan yang kompleks antar parameter kualitas air.
library(splines)
spline_model <- lm(DO ~ bs(pH, df = 3) + bs(BOD, df = 3) + bs(TSS, df = 3) + bs(Suhu, df = 3), data = train_data)
summary(spline_model)
##
## Call:
## lm(formula = DO ~ bs(pH, df = 3) + bs(BOD, df = 3) + bs(TSS,
## df = 3) + bs(Suhu, df = 3), data = train_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2.14910 -0.61666 0.00967 0.62577 2.44997
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 4.79788 1.06363 4.511 1.19e-05 ***
## bs(pH, df = 3)1 2.28531 1.27753 1.789 0.0754 .
## bs(pH, df = 3)2 -0.70037 0.65049 -1.077 0.2831
## bs(pH, df = 3)3 1.45465 0.84145 1.729 0.0856 .
## bs(BOD, df = 3)1 1.17934 1.22782 0.961 0.3381
## bs(BOD, df = 3)2 0.28635 0.71050 0.403 0.6874
## bs(BOD, df = 3)3 0.57306 0.89067 0.643 0.5208
## bs(TSS, df = 3)1 0.95536 1.19898 0.797 0.4267
## bs(TSS, df = 3)2 0.07474 0.63124 0.118 0.9059
## bs(TSS, df = 3)3 0.77168 0.78913 0.978 0.3295
## bs(Suhu, df = 3)1 -1.12839 1.29655 -0.870 0.3853
## bs(Suhu, df = 3)2 -0.73550 0.65386 -1.125 0.2622
## bs(Suhu, df = 3)3 -0.26509 0.84822 -0.313 0.7550
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.9836 on 172 degrees of freedom
## Multiple R-squared: 0.04858, Adjusted R-squared: -0.0178
## F-statistic: 0.7318 on 12 and 172 DF, p-value: 0.719
Berdasarkan hasil klasifikasi terhadap Status Kualitas Air, tiga model diuji yaitu SVM, Decision Tree, dan Random Forest. Model Decision Tree dan Random Forest menunjukkan akurasi sempurna (100%), sementara SVM sedikit lebih rendah (86.67%). Nilai Kappa = 1 pada kedua model pohon mengindikasikan kesesuaian klasifikasi yang sangat tinggi antara prediksi dan data aktual. Analisis feature importance dari Random Forest memperlihatkan bahwa BOD (Biological Oxygen Demand) dan DO (Dissolved Oxygen) merupakan variabel paling berpengaruh dalam menentukan status kualitas air, karena keduanya mencerminkan tingkat pencemaran organik dan keseimbangan oksigen dalam ekosistem perairan. Dengan demikian, model Random Forest dinilai sebagai metode paling andal untuk mengklasifikasikan status kualitas air sungai di Kabupaten X, berkat kestabilan hasil dan kemampuannya menangkap pola kompleks antar variabel lingkungan.
Dua model regresi digunakan untuk memprediksi nilai Dissolved Oxygen (DO), yaitu Regresi Linear dan Regresi Spline. Hasil evaluasi menunjukkan bahwa kedua model memiliki performa yang relatif rendah dengan nilai R² < 0.05, artinya variabel pH, BOD, TSS, dan Suhu hanya mampu menjelaskan kurang dari 5% variasi nilai DO. Nilai RMSE sekitar 0.95 juga menandakan tingkat kesalahan prediksi yang cukup tinggi. Model Regresi Linear menghasilkan R² = 0.0028 dengan seluruh variabel bebas tidak signifikan (p-value > 0.05), menandakan hubungan linier antarvariabel tidak kuat. Sedangkan Regresi Spline dengan derajat kebebasan (df = 3) sedikit lebih baik (R² = 0.0486), menunjukkan adanya pola non-linier yang lebih kompleks namun masih lemah. Fenomena ini menggambarkan bahwa variasi DO di perairan tidak hanya ditentukan oleh faktor pH, BOD, TSS, dan suhu saja, tetapi juga sangat bergantung pada faktor-faktor lain seperti kecepatan arus sungai, kandungan nutrien, dan aktivitas biologis mikroorganisme yang tidak diikutsertakan dalam model.
Hasil analisis secara keseluruhan menunjukkan bahwa pendekatan statistik memiliki peran penting dalam memahami kualitas air sungai. Melalui proses data cleaning, dataset berhasil dibersihkan dari missing value, outlier, dan inkonsistensi kategori, sehingga siap digunakan untuk analisis lanjut. Pada tahap klasifikasi, model Random Forest terbukti menjadi metode paling optimal dengan akurasi 100% dan Kappa sempurna, mengindikasikan bahwa model ini dapat digunakan secara andal untuk menilai status kualitas air berdasarkan parameter fisik dan kimia. Sementara pada tahap prediksi, baik Regresi Linear maupun Spline belum mampu menjelaskan variasi nilai DO secara kuat, yang menunjukkan bahwa hubungan antarparameter kualitas air bersifat kompleks dan cenderung non-linier. Secara ekologis, variabel BOD dan DO merupakan indikator utama kondisi perairan, di mana peningkatan BOD dan suhu air biasanya menurunkan konsentrasi oksigen terlarut (DO), sehingga berdampak pada menurunnya kualitas lingkungan perairan.