1. Pendahuluan

Dokumen ini merupakan panduan langkah demi langkah (tutorial) untuk melakukan analisis data pemantauan kualitas air tambak/kolam. Analisis ini mencakup:

  1. Persiapan Data (Data Cleaning & Preprocessing)
  2. Eksplorasi Data (Exploratory Data Analysis / EDA)
  3. Analisis Korelasi & Regresi
  4. Uji Hipotesis (T-Test & Wilcoxon Test)
  5. Analisis Komponen Utama (PCA)
  6. Pengelompokan Data (K-Means Clustering)

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)

2. Memuat dan Membersihkan Data

Langkah terpenting dalam analisis adalah memastikan data yang kita gunakan sudah bersih dan sesuai format.

2.1 Import Data

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_kolom

2.2 Penyesuaian Tipe & Format Data

Beberapa 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_kolam <- data_kolam |>
  mutate(
    status = as.factor(status),
    shift = as.factor(tolower(shift)), # Menyeragamkan menjadi huruf kecil (pagi, siang, malam)
    id = as.factor(id),
    tanggal = as.Date(tanggal)
  )

2.3 Menangani Nilai Kosong (Missing Values)

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)))
  )

2.4 Menangani Pencilan (Outliers)

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

3. Analisis Eksplorasi Data (EDA)

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)")


4. Analisis Korelasi

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.


5. Regresi Linear Berganda (Multiple Linear Regression)

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).


6. Pengetesan Hipotesis Dua Arah (T-Test & Wilcoxon Test)

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.


7. Analisis Komponen Utama (Principal Component Analysis - PCA)

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.

7.1 Regresi Komponen Utama (PCR) [Opsional]

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

8. Partisi Spasial (K-Means Clustering)

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)"
)

8.1 Keanggotaan dan Karakteristik Rata-Rata 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:"
print(keanggotaan_kolam)
## # 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:"
print(rata_rata_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>

Intisari Penutup

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.