Dokumen ini merupakan panduan langkah demi langkah (tutorial) untuk melakukan analisis data pemantauan kualitas air tambak/kolam. Analisis ini mencakup:
Pertama, mari kita siapkan library R yang dibutuhkan. Jika
Anda belum pernah meng-install paket-paket ini, hilangkan tanda pagar
(#) pada baris install.packages untuk
meng-installnya terlebih dahulu.
# install.packages("factoextra")
# install.packages("corrplot")
# install.packages("tidyverse")
library(factoextra) # Untuk visualisasi PCA & Clustering
library(corrplot) # Untuk visualisasi matriks korelasi
library(tidyverse) # Untuk manipulasi & visualisasi data (termasuk ggplot2, dplyr, tidyr)Langkah terpenting dalam analisis adalah memastikan data yang kita gunakan sudah bersih dan sesuai format.
Kita membaca data CSV yang berisi log pemantauan kolam. Tiga baris
pertama dilewati (skip = 3) karena seringkali berisi
rentetan judul atau header yang tidak beraturan dari sistem lain.
# Menggunakan relative path, asumsikan dataset berada di folder yang sama
path_data <- "Monitoring_Kolam_Dataset - Monitoring_Kolam_Dataset.csv"
data_kolam <- read.csv(path_data, header = FALSE, skip = 3)
# Menamai ulang kolom agar lebih mudah diproses dalam pemograman R
nama_kolom <- c(
"id", "tanggal", "shift", "suhu", "salinitas", "turbidity",
"pH_pagi", "pH_siang", "DO", "amonia", "nitrit", "alkalinitas",
"chlorella_sp", "chaetoceros", "total_bacteria", "vibrio_count",
"status"
)
colnames(data_kolam) <- nama_kolomBeberapa kolom seperti status dan id lebih
baik diubah menjadi tipe kelompok kategori (factor),
sedangkan tanggal diubah menjadi tipe Date.
Kita juga menstandarkan format penulisan pada kolom shift
menggunakan huruf kecil semua yang konsisten.
Data lapangan seringkali memiliki nilai yang belum diobservasi atau
hilang (NA). Mari kita periksa proporsi nilai kosong di
setiap parameter kolom.
# Melihat persentase data yang kosong tiap kolom
data.frame(Proporsi_Kosong = colMeans(is.na(data_kolam)))## Proporsi_Kosong
## id 0.00000000
## tanggal 0.00000000
## shift 0.00000000
## suhu 0.00000000
## salinitas 0.04998478
## turbidity 0.00000000
## pH_pagi 0.66666667
## pH_siang 0.66666667
## DO 0.09969559
## amonia 0.00000000
## nitrit 0.00000000
## alkalinitas 0.00000000
## chlorella_sp 0.00000000
## chaetoceros 0.00000000
## total_bacteria 0.08000000
## vibrio_count 0.00000000
## status 0.00000000
Kita akan mengatasi kekosongan kolom numerik yang terputus-putus
seperti (salinitas, DO,
total_bacteria) menggunakan metode Mean
Imputation. Selain itu, kita gabungkan kolom pH_pagi
dan pH_siang ke dalam ringkasan kondisi satu parameter:
pH seturut status parameter shift.
missing_column <- c("salinitas", "DO", "total_bacteria")
data_kolam <- data_kolam |>
mutate(
# Jika shift 'pagi', pilih kolom pH_pagi. Jika 'siang', pilih kolom pH_siang. Jika shift lainnya, cari rata-ratanya.
pH = case_when(
shift == "pagi" ~ pH_pagi,
shift == "siang" ~ pH_siang,
TRUE ~ (pH_pagi + pH_siang) / 2
),
# Mengisi kekosongan data/NA dipertahankan pada rata-rata kolomnya
across(all_of(missing_column), ~ replace_na(., mean(., na.rm = TRUE)))
)Terkadang alat instrumen ukur bermasalah dan mengeluarkan rentetan angka yang tidak wajar, seperti skala suhu tercatat pada batas 280 derajat, atau instrumen pH bermasalah hingga memuat angka lebih dari 75 (skala maksimal harusnya 14).
data_kolam <- data_kolam |>
mutate(
# Misal: Jika terindikasi 285 maka diperbaiki rasio komanya menjadi 28.5 (salah input operator)
suhu = if_else(suhu > 100, suhu / 10, suhu),
# Memperhalus skala rasio kelipatan 10 apabila operator salah menuliskan angka desimal
pH = if_else(pH > 14, pH / 10, pH)
) |>
# Menghapus kolom sisa riwayat yang sudah terwakili optimal pada variabel 'pH'
select(-pH_pagi, -pH_siang)
# Tinjauan Deskriptif Ringkas:
summary(data_kolam)## id tanggal shift suhu
## P01 : 1095 Min. :2024-01-01 malam:10950 Min. :21.52
## P02 : 1095 1st Qu.:2024-04-01 pagi :10950 1st Qu.:26.23
## P03 : 1095 Median :2024-07-01 siang:10950 Median :28.51
## P04 : 1095 Mean :2024-07-01 Mean :28.58
## P05 : 1095 3rd Qu.:2024-09-30 3rd Qu.:30.45
## P06 : 1095 Max. :2024-12-30 Max. :36.50
## (Other):26280
## salinitas turbidity DO amonia
## Min. : 0.00 Min. : 0.000 Min. :-5.500 Min. : 0.3761
## 1st Qu.:17.09 1st Qu.: 8.849 1st Qu.: 7.345 1st Qu.: 1.5373
## Median :20.50 Median : 12.680 Median : 7.571 Median : 1.9791
## Mean :20.50 Mean : 32.040 Mean : 7.566 Mean : 2.1674
## 3rd Qu.:23.99 3rd Qu.: 49.649 3rd Qu.: 7.931 3rd Qu.: 2.5839
## Max. :34.00 Max. :374.208 Max. : 9.512 Max. :20.0693
##
## nitrit alkalinitas chlorella_sp chaetoceros
## Min. : 0.2757 Min. : 55.24 Min. : 1766 Min. : 364.4
## 1st Qu.: 1.4101 1st Qu.:109.92 1st Qu.: 46258 1st Qu.: 26479.8
## Median : 1.9733 Median :120.14 Median : 103318 Median : 59230.5
## Mean : 2.1914 Mean :120.06 Mean : 510922 Mean : 121676.5
## 3rd Qu.: 2.8248 3rd Qu.:130.14 3rd Qu.: 861912 3rd Qu.: 132182.3
## Max. :11.0554 Max. :184.67 Max. :7295614 Max. :6286155.6
##
## total_bacteria vibrio_count status pH
## Min. : 3303 Min. : 44.95 Critical:21704 Min. : 6.912
## 1st Qu.: 16195 1st Qu.: 502.01 Healthy : 305 1st Qu.: 7.796
## Median : 23220 Median : 963.11 Warning :10841 Median : 8.074
## Mean : 24934 Mean : 2359.63 Mean : 8.248
## 3rd Qu.: 29851 3rd Qu.: 3382.94 3rd Qu.: 8.452
## Max. :215574 Max. :40030.53 Max. :10.440
## NA's :10950
Eksplorasi grafis sangat sentral untuk mendeskripsikan secara interaktif sebaran, rentang, dan profil pola mendasar di dalam data.
# 1. Histogram Pemeriksaan Distribusi pH
ggplot(data_kolam, aes(x = pH)) +
geom_histogram(fill = "steelblue", color = "black", bins = 30) +
theme_minimal() +
labs(title = "Distribusi Variasi Nilai pH Antar Kolam", x = "Level pH", y = "Frekuensi/Jumlah")# 2. Tren Pengerakan Salinitas antar Kolam dibedakan berdasarkan Status
ggplot(data_kolam, aes(x = tanggal, y = salinitas, color = status)) +
geom_smooth(method = "loess", se = FALSE) +
theme_minimal() +
labs(title = "Tren Pengerakan Salinitas Air Tambak", x = "Tanggal", y = "Salinitas / Kadar Garam")# 3. Keterkaitan Persebaran Suhu Melawan Populasi Bakteri
ggplot(data_kolam, aes(x = suhu, y = total_bacteria, color = status)) +
geom_point(alpha = 0.7) +
theme_minimal() +
labs(title = "Distribusi Persebaran Suhu Melawan Populasi Bakteri", x = "Suhu (°C)", y = "Jumlah Total Koloni Bakteri")# 4. Rasio Keadaan Ringkasan Kolam Keseluruhan (Bar Chart)
ggplot(data_kolam, aes(x = status, fill = status)) +
geom_bar() +
theme_minimal() +
labs(title = "Frekuensi Observasi Tiap Status Kualitas Tambak", x = "Indeks Kelayakan", y = "Hitungan Hari Observasi (Count)")Analisis korelasi meraba kuantitas relasi linear maupun kecenderungan (trend/Spearman) pada sekelompok pasangan metrik numerikal.
# Membuat cuplikan dataframe temporer numerik murni
data_numerik <- data_kolam |> select(where(is.numeric))
# Mengukur dengan pendekatan korelasi rank Spearman lengkap
cor_mat <- cor(data_numerik, method = "spearman", use = "complete.obs")
corrplot(
cor_mat,
method = "color",
type = "upper", # Menampilkan pojok segi-atas kanan agar minim redundansi
addCoef.col = "black", # Stempel angka pada petakan
number.cex = 0.55, # Mengecilkan skala ukuran font
tl.col = "black", # Menghitam-kan teks keterangan label
tl.cex = 0.8, # Ukuran keterangan label
diag = FALSE,
title = "Pemetaan Matriks Korelasi Kualitas Air Tambak\n",
mar = c(0, 0, 2, 0)
)Pedoman Cara Analisis Visual: - Korelasi Positif 1.0 (Biru menyala): Indikator naik bersamaan. - Korelasi Negatif -1.0 (Merah tajam): Sifat bertolak-belakang, mempresentasikan variabel yang mereduksi apabila variabel lain meningkat. - Korelasi Bebas 0.0 (Transparan): Tiada keterkaitan kuat antara parameter alam terkait.
Tujuan Fundamental: Membuat persamaan rasional matematis dengan niatan menebak variabel output/dependen (Target Y) atas komplotan variabel kontrol/independen.
# Membuat model regresi populasi bakteri ditunjang kualitas air biokimiawi
model <- lm(
total_bacteria ~ amonia + nitrit + vibrio_count + DO + salinitas + suhu,
data = data_kolam
)
# Bedah rinci signifikansi parameter independen
summary(model)##
## Call:
## lm(formula = total_bacteria ~ amonia + nitrit + vibrio_count +
## DO + salinitas + suhu, data = data_kolam)
##
## Residuals:
## Min 1Q Median 3Q Max
## -178575 -2343 -94 2423 36675
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -9.098e+02 4.828e+02 -1.884 0.0595 .
## amonia 1.132e+04 4.861e+01 232.774 < 2e-16 ***
## nitrit 1.269e+03 6.573e+01 19.314 < 2e-16 ***
## vibrio_count 1.079e-01 1.716e-02 6.288 3.25e-10 ***
## DO -6.561e-01 2.884e+01 -0.023 0.9818
## salinitas 1.133e+02 7.358e+00 15.396 < 2e-16 ***
## suhu -1.411e+02 1.205e+01 -11.709 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 5239 on 32843 degrees of freedom
## Multiple R-squared: 0.8303, Adjusted R-squared: 0.8302
## F-statistic: 2.678e+04 on 6 and 32843 DF, p-value: < 2.2e-16
# Cek distribusi residual lewat Normal QQ-Plot
plot(model, 2, main = "Kurva Plot Diagnosis Q-Q Residual Model")Catatan Analitik Output: Tengok kolom
Pr(>|t|)(p-value). Bila nilainya menyentuh batas di bawah 0.05 (biasanya diiringi tanda bintang*), dapat disimpulkan bahwa variabel penentu tersebut memiliki andil signifikansi yang kuat atas nilai regresi output sentral yang diukur (Target Y).
Kegunaan Analitik: Memastikan diferensiasi secara sahih komputasional atas kumpulan dua variabel observasi antara status kolam Healthy melawan Critical.
# Memisahkan kuantifikasi koloni bakteri
bakteri_sehat <- data_kolam |>
filter(status == "Healthy") |>
pull(total_bacteria)
bakteri_kritis <- data_kolam |>
filter(status == "Critical") |>
pull(total_bacteria)
# Menggunakan Uji Wilcoxon karena datanya rentan ada fluktuasi/outlier alami
wilcox.test(bakteri_sehat, bakteri_kritis)##
## Wilcoxon rank sum test with continuity correction
##
## data: bakteri_sehat and bakteri_kritis
## W = 267506, p-value < 2.2e-16
## alternative hypothesis: true location shift is not equal to 0
Langkah Kesimpulan: Bila p-value kurang dari
0.05, kita menegaskan bahwa ada diskrepansi rata-rata populasi bakteri yang signifikan secara statistik antara kolam yang sehat berhadapan terhadap kolam kritis.
Arah Riset PCA: Meringkas Dimensionality Reduction, alias mensintesa lusinan variabel berprofil saling tumpang-tindih, menjadi sedikit pilar komposit (Principal Components - PC).
# Cek dan hilangkan kolom yang tidak memiliki obyek varians
data_pca <- data_numerik |> select(where(~ var(., na.rm = TRUE) != 0))
# Ekstraksi khusus record data lengkap menghindari error asimetris baris
data_pca_komplit <- na.omit(data_pca)
hasil_pca <- prcomp(data_pca_komplit, center = TRUE, scale. = TRUE)
summary(hasil_pca)## Importance of components:
## PC1 PC2 PC3 PC4 PC5 PC6 PC7
## Standard deviation 1.8229 1.4800 1.3377 1.02547 1.00069 0.99820 0.92391
## Proportion of Variance 0.2769 0.1825 0.1491 0.08763 0.08345 0.08303 0.07113
## Cumulative Proportion 0.2769 0.4595 0.6086 0.69619 0.77964 0.86267 0.93381
## PC8 PC9 PC10 PC11 PC12
## Standard deviation 0.62074 0.45314 0.34427 0.28828 0.04508
## Proportion of Variance 0.03211 0.01711 0.00988 0.00693 0.00017
## Cumulative Proportion 0.96592 0.98303 0.99291 0.99983 1.00000
# Menautkan label status dari baris valid untuk memandu pewarnaan PC Plot secara konsisten
status_pca <- data_kolam[rownames(data_pca_komplit), ]$status
fviz_pca_biplot(hasil_pca,
geom.ind = "point",
pointsize = 1.3,
col.ind = status_pca,
palette = "jco",
addEllipses = TRUE,
title = "Bidang Ruang PCA: Pemilahan Status Kesehatan Air Tambak"
)Pengamatan Plot PCA: Irisan kluster warna Healthy & Critical yang memecah secara tersegregasi nyata menandai komposisi faktor hidrologi sebagai fondasi pembedanya. Panah beriringan searah merepresentasikan variabel linear identik.
Melanjutkan rancangan regresi berlipat namun terhindar dari ancaman multikolinearitas murni:
# Matriks basis analitika turunan murni PCA
pca_skor <- as.data.frame(hasil_pca$x)
# Mengawinkan nilai prediktor bakteri ke target dari sumber record komplit
pca_skor$total_bakteri <- data_kolam[rownames(data_pca_komplit), ]$total_bacteria
model_dari_pca <- lm(total_bakteri ~ PC1 + PC2 + PC3 + PC4, data = pca_skor)
summary(model_dari_pca)##
## Call:
## lm(formula = total_bakteri ~ PC1 + PC2 + PC3 + PC4, data = pca_skor)
##
## Residuals:
## Min 1Q Median 3Q Max
## -86484 -1592 63 1751 14978
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 24830.61 22.48 1104.52 <2e-16 ***
## PC1 4600.14 12.33 373.01 <2e-16 ***
## PC2 3082.07 15.19 202.90 <2e-16 ***
## PC3 -5450.18 16.81 -324.29 <2e-16 ***
## PC4 -1976.74 21.92 -90.17 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 3327 on 21895 degrees of freedom
## Multiple R-squared: 0.9306, Adjusted R-squared: 0.9306
## F-statistic: 7.34e+04 on 4 and 21895 DF, p-value: < 2.2e-16
Gagasan Riset: Menerapkan algoritma penjelajahan Unsupervised Machine Learning yang terkemuka bertujuan menguak pola klaster kedekatan profil numerik alamiah di antara data secara sendirinya — tanpa menyuntikkan arahan label status sama sekali.
# 1. Melakukan agregasi data (Menghitung rata-rata permanen tiap kolam)
data_agregasi_kolam <- data_kolam |>
group_by(id) |>
summarise(across(where(is.numeric), ~ mean(.x, na.rm = TRUE)), .groups = "drop")
# 2. Mempersiapkan struktur matriks khusus numerik utuh (ID kolam didorong menjadi baris label/rownames)
data_clustering_kolam <- data_agregasi_kolam |>
column_to_rownames("id") |>
select(where(~ var(., na.rm = TRUE) != 0)) |>
na.omit()
# 3. Standardisasi skalar mutlak (Z-Score) demi euklides yang seimbang
utk_clustering <- scale(data_clustering_kolam)
# 4. Mengeksekusi pusat pengikut sertaan K-Means kepada 3 kelompok sentral observasi permanen
set.seed(123)
cluster_kmeans <- kmeans(utk_clustering, centers = 3, nstart = 25)
# Visual mapping geometri Klaster
fviz_cluster(cluster_kmeans,
data = utk_clustering,
palette = "Set2",
geom = c("point", "text"), # Memunculkan teks ID di grafik persebaran
ellipse.type = "convex",
ggtheme = theme_minimal(),
main = "Distribusi Algoritma K-Means (Pemetaan Eksklusif 1 Kolam = 1 Kluster)"
)Setelah mendapatkan model klaster berbasis agregasi rata-rata kolam, kita dapat memastikan keberlakuan 1 kolam murni hanya bernaung di 1 kluster eksklusif. Di bawah ini kita memvalidasi nama-nama kolam anggotanya beserta rentang rata-rata karakteristik parameter airnya.
# Menggabungkan hasil cluster ke dalam data tabel agregasi
data_hasil_cluster <- data_agregasi_kolam |>
filter(id %in% rownames(data_clustering_kolam)) |>
mutate(Cluster = as.factor(cluster_kmeans$cluster))
# 1. Menampilkan Daftar Kolam Eksklusif pada Setiap Kluster
keanggotaan_kolam <- data_hasil_cluster |>
group_by(Cluster) |>
summarise(
Daftar_Kolam = paste(sort(id), collapse = ", "),
.groups = "drop"
)
print("Keanggotaan Eksklusif Kolam Berdasarkan Kluster:")## [1] "Keanggotaan Eksklusif Kolam Berdasarkan Kluster:"
## # A tibble: 3 × 2
## Cluster Daftar_Kolam
## <fct> <chr>
## 1 1 P21, P22, P23, P24, P25, P26, P27, P28, P29, P30
## 2 2 P01, P02, P03, P04, P05, P06, P07, P08, P09, P10
## 3 3 P11, P12, P13, P14, P15, P16, P17, P18, P19, P20
# 2. Menghitung Rata-Rata Karakteristik (Parameter Numerik) Setiap Kluster
rata_rata_kluster <- data_hasil_cluster |>
group_by(Cluster) |>
summarise(across(where(is.numeric), ~ mean(.x, na.rm = TRUE)))
print("Karakteristik Rata-Rata Parameter per Kluster:")## [1] "Karakteristik Rata-Rata Parameter per Kluster:"
## # A tibble: 3 × 13
## Cluster suhu salinitas turbidity DO amonia nitrit alkalinitas chlorella_sp
## <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 30.9 15.8 10.7 7.34 2.16 3.12 120. 83668.
## 2 2 26.4 25.3 10.6 7.77 2.18 1.73 120. 82636.
## 3 3 28.4 20.5 74.8 7.59 2.16 1.72 120. 1366460.
## # ℹ 4 more variables: chaetoceros <dbl>, total_bacteria <dbl>,
## # vibrio_count <dbl>, pH <dbl>
Melalui lembaran panduan ini, Anda baru saja membedah secara operasional kelengkapan siklus kerja (End-to-End Analytics Workflow) di dalam memproses laporan pengkajian kualitas hidrologi air tambak. Mulut operasional bermula dari sanitasi kebersihan (Data Hygiene), penggambaran visual eksploratif komprehensif, ditutup oleh diagnosis hipotesis kausal interaktif menggunakan model regresi regangan beserta pengelompokan dimensi tingkat lanjut via PCA dan terapan K-Means Clustering.