# UNIVERSIDAD CENTRAL DEL ECUADOR
# Facultad de Ingeniería en Geología, Minas, Petroleos y Ambiental
# Ingeniería Ambiental
# Autor: Grupo 1
# Fecha: 07/02/2026
library(readr)
library(dplyr)
##
## Adjuntando el paquete: '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)
datos <- read.csv(
"china_water_pollution_data.csv",
header = TRUE,
sep = ",",
dec = ".",
fileEncoding = "UTF-8"
)
str(datos)
## 'data.frame': 3000 obs. of 25 variables:
## $ Province : chr "Zhejiang" "Sichuan" "Zhejiang" "Beijing" ...
## $ City : chr "Ningbo" "Mianyang" "Ningbo" "Beijing" ...
## $ Monitoring_Station : chr "Ningbo_Station_2" "Mianyang_Station_1" "Ningbo_Station_8" "Beijing_Station_10" ...
## $ Latitude : num 25.5 32.2 30 30 43.5 ...
## $ Longitude : num 123 113 125 118 122 ...
## $ Date : chr "2023-06-01" "2023-03-05" "2023-07-13" "2023-02-17" ...
## $ Water_Temperature_C : num 22.5 27.3 21 16.6 21.8 ...
## $ pH : num 6.93 6.89 6.02 7.31 7.77 7.16 7.2 7.38 7.77 7.17 ...
## $ Dissolved_Oxygen_mg_L : num 9.3 8.14 5.34 10.06 7.93 ...
## $ Conductivity_uS_cm : num 652 358 520 593 656 ...
## $ Turbidity_NTU : num 0.85 4.49 17.46 7.38 3.7 ...
## $ Nitrate_mg_L : num 2.14 2.06 2.11 1.9 1.8 2.69 1.99 2.7 1.82 2.11 ...
## $ Nitrite_mg_L : num 0.03 0.015 0.029 0.014 0.019 0.022 0.003 0.034 0.026 0.028 ...
## $ Ammonia_N_mg_L : num 0.38 0.38 0.3 0.2 0.22 0.44 0.29 0.32 0.71 0.53 ...
## $ Total_Phosphorus_mg_L : num 0.074 0.147 0.021 0.155 0.152 0.134 0.05 0.104 0.126 0.039 ...
## $ Total_Nitrogen_mg_L : num 2.71 3.15 3.39 2.91 3.45 2.87 3.05 2.75 3.68 3.52 ...
## $ COD_mg_L : num 15.4 16.8 17.3 17.9 20.1 ...
## $ BOD_mg_L : num 1.39 2.98 2.65 5.18 3.47 5.27 3.34 5.71 3.68 4.73 ...
## $ Heavy_Metals_Pb_ug_L : num 6.9 4.68 3.24 3.2 2.01 4.42 5.01 6.75 4.59 3.09 ...
## $ Heavy_Metals_Cd_ug_L : num 0.66 0.39 0.27 0.67 0.34 -0.03 0.6 0.5 0.21 0.35 ...
## $ Heavy_Metals_Hg_ug_L : num 0.02 0.1 0.11 0.11 0.14 0.12 0.09 0.08 0.16 0.04 ...
## $ Coliform_Count_CFU_100mL: int 87 116 110 99 82 91 92 91 95 108 ...
## $ Water_Quality_Index : num 36.6 66.2 98.7 71.3 16.1 ...
## $ Pollution_Level : chr "Very Poor" "Excellent" "Poor" "Poor" ...
## $ Remarks : chr "High pollution spike detected" "High pollution spike detected" "High pollution spike detected" "Monitoring recommended" ...
datos$COD_mg_L <- as.numeric(datos$COD_mg_L)
datos$BOD_mg_L <- as.numeric(datos$BOD_mg_L)
datos_depu <- datos %>%
select(BOD_mg_L, COD_mg_L) %>%
na.omit() %>%
filter(BOD_mg_L > 0, COD_mg_L > 0)
cat("Filtrado NA/positivos:", nrow(datos), "→", nrow(datos_depu), "filas\n")
## Filtrado NA/positivos: 3000 → 3000 filas
remove_iqr_outliers <- function(x) {
Q1 <- quantile(x, 0.25, na.rm = TRUE)
Q3 <- quantile(x, 0.75, na.rm = TRUE)
IQR <- Q3 - Q1
x >= Q1 - 1.5 * IQR & x <= Q3 + 1.5 * IQR
}
mask_bod <- remove_iqr_outliers(datos_depu$BOD_mg_L)
mask_cod <- remove_iqr_outliers(datos_depu$COD_mg_L)
datos_depu <- datos_depu[mask_bod & mask_cod, ]
cat("Filtrado IQR:", nrow(datos_depu), "filas limpias tras IQR\n")
## Filtrado IQR: 2966 filas limpias tras IQR
by_bin <- 0.591
datos_depu <- datos_depu %>%
mutate(BOD_BIN = cut(
BOD_mg_L,
breaks = seq(floor(min(BOD_mg_L)),
ceiling(max(BOD_mg_L)),
by = by_bin),
include.lowest = TRUE
))
# Medianas por bin
med_bin <- datos_depu %>%
group_by(BOD_BIN) %>%
summarise(
BOD_median = median(BOD_mg_L, na.rm = TRUE),
COD_median = median(COD_mg_L, na.rm = TRUE),
n_bin = n()
) %>%
drop_na()
med_bin
## # A tibble: 10 × 4
## BOD_BIN BOD_median COD_median n_bin
## <fct> <dbl> <dbl> <int>
## 1 [1,1.59] 1.46 18.7 16
## 2 (1.59,2.18] 2.00 18.9 70
## 3 (2.18,2.77] 2.54 19.8 246
## 4 (2.77,3.36] 3.13 20.0 447
## 5 (3.36,3.96] 3.69 20.4 621
## 6 (3.96,4.55] 4.22 20.0 662
## 7 (4.55,5.14] 4.82 19.8 495
## 8 (5.14,5.73] 5.34 19.5 276
## 9 (5.73,6.32] 5.94 20.3 107
## 10 (6.32,6.91] 6.42 21.3 26
# Mantener positivos
med_bin_pot <- med_bin %>%
filter(BOD_median > 0, COD_median > 0)
cat("Bins usados:", nrow(med_bin_pot), "\n")
## Bins usados: 10
by_bin <- 0.591
datos_depu <- datos_depu %>%
mutate(BOD_BIN = cut(
BOD_mg_L,
breaks = seq(floor(min(BOD_mg_L)),
ceiling(max(BOD_mg_L)),
by = by_bin),
include.lowest = TRUE
))
med_bin <- datos_depu %>%
group_by(BOD_BIN) %>%
summarise(
BOD_median = median(BOD_mg_L, na.rm = TRUE),
COD_median = median(COD_mg_L, na.rm = TRUE),
n_bin = n()
) %>%
drop_na()
med_bin_pot <- med_bin %>% filter(BOD_median > 0, COD_median > 0)
cat("Bins usados:", nrow(med_bin_pot), "\n")
## Bins usados: 10
med_bin_pot
## # A tibble: 10 × 4
## BOD_BIN BOD_median COD_median n_bin
## <fct> <dbl> <dbl> <int>
## 1 [1,1.59] 1.46 18.7 16
## 2 (1.59,2.18] 2.00 18.9 70
## 3 (2.18,2.77] 2.54 19.8 246
## 4 (2.77,3.36] 3.13 20.0 447
## 5 (3.36,3.96] 3.69 20.4 621
## 6 (3.96,4.55] 4.22 20.0 662
## 7 (4.55,5.14] 4.82 19.8 495
## 8 (5.14,5.73] 5.34 19.5 276
## 9 (5.73,6.32] 5.94 20.3 107
## 10 (6.32,6.91] 6.42 21.3 26
x <- med_bin_pot$BOD_median
y <- med_bin_pot$COD_median
# A partir del diagrama de dispersión se observa una
# relación no lineal entre la Demanda Bioquímica de Oxígeno (BOD)
# y la Demanda Química de Oxígeno (COD).
# La tendencia muestra un crecimiento suave de la COD conforme
# aumenta la BOD, lo que sugiere el uso de un modelo potencial
# del tipo y = a * x^b.
# Variables para la conjetura (medianas por bin)
x <- med_bin_pot$BOD_median
y <- med_bin_pot$COD_median
plot(
x, y,
pch = 19,
col = "steelblue",
xlab = "BOD (mediana por bin) [mg/L]",
ylab = "COD (mediana por bin) [mg/L]",
main = "Gráfica N°1: Relación entre la BOD y la COD",
font.main = 2
)
# Ajuste potencial en escala log-log:
# log(COD) = log(a) + b*log(BOD)
modelo_pot <- lm(log(COD_median) ~ log(BOD_median), data = med_bin_pot)
summary(modelo_pot)
##
## Call:
## lm(formula = log(COD_median) ~ log(BOD_median), data = med_bin_pot)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.043940 -0.013812 -0.003557 0.016481 0.036037
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.91019 0.02354 123.633 2.05e-14 ***
## log(BOD_median) 0.06160 0.01731 3.558 0.00742 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.02535 on 8 degrees of freedom
## Multiple R-squared: 0.6128, Adjusted R-squared: 0.5644
## F-statistic: 12.66 on 1 and 8 DF, p-value: 0.007418
# Parámetros del modelo potencial: COD = a * BOD^b
coef_pot <- coef(modelo_pot)
a_pot <- exp(coef_pot[1]) # a = e^(intercepto)
b_pot <- coef_pot[2] # b = pendiente
a_pot
## (Intercept)
## 18.36036
b_pot
## log(BOD_median)
## 0.06159927
# REPRESENTACIÓN DE LA ECUACIÓN POTENCIAL
plot(1, type = "n", axes = FALSE, xlab = "", ylab = "")
text(
x = 1, y = 1.1,
labels = "Ecuación Potencial",
col = "blue",
cex = 2,
font = 2
)
text(
x = 1, y = 1,
labels = "y = a · x^b",
col = "blue",
cex = 1.8,
font = 2
)
text(
x = 1, y = 0.85,
labels = paste0(
"y = ",
round(a_pot, 4),
" · x^",
round(b_pot, 5)
),
col = "blue",
cex = 1.8,
font = 2
)
# Curva ajustada
x_vals <- seq(min(med_bin_pot$BOD_median),
max(med_bin_pot$BOD_median),
length.out = 200)
y_pred <- a_pot * x_vals^b_pot
# Gráfica (puntos + curva)
plot(med_bin_pot$BOD_median, med_bin_pot$COD_median,
pch = 19, col = "steelblue",
xlab = "BOD (mediana por bin) [mg/L]",
ylab = "COD (mediana por bin) [mg/L]",
main = "Gráfica N°2: Modelo potencial ajustado entre BOD y COD\nChina, 2023",
font.main = 2)
lines(x_vals, y_pred, col = "darkgreen", lwd = 2)
legend("topleft",
legend = c("Medianas por bin",
paste0("COD = ", round(a_pot,4), " * BOD^", round(b_pot,4))),
col = c("steelblue", "darkgreen"),
pch = c(19, NA),
lwd = c(NA, 2),
bty = "n")
# Correlación de Pearson sobre variables transformadas (log-log)
r <- cor(
log(med_bin_pot$BOD_median),
log(med_bin_pot$COD_median),
method = "pearson"
)
r
## [1] 0.7828295
cat("Correlación de Pearson (%):", round(r * 100, 2), "%\n")
## Correlación de Pearson (%): 78.28 %
r2 <- summary(modelo_pot)$r.squared * 100
cat("R² potencial =", round(r2, 2), "%\n")
## R² potencial = 61.28 %
## 7. Restricciones del modelo
# Restricción 1: el modelo potencial requiere valores positivos (por el log)
# (en tu caso ya filtraste BOD_median > 0 y COD_median > 0, pero lo verificamos)
sum(med_bin_pot$BOD_median <= 0)
## [1] 0
sum(med_bin_pot$COD_median <= 0)
## [1] 0
# Restricción 2: el modelo es válido solo dentro del rango observado de BOD
rango_BOD <- range(med_bin_pot$BOD_median)
rango_COD <- range(med_bin_pot$COD_median)
cat("Rango válido de BOD (mg/L): [", round(rango_BOD[1], 2), ",", round(rango_BOD[2], 2), "]\n")
## Rango válido de BOD (mg/L): [ 1.46 , 6.42 ]
cat("Rango observado de COD (mg/L): [", round(rango_COD[1], 2), ",", round(rango_COD[2], 2), "]\n")
## Rango observado de COD (mg/L): [ 18.7 , 21.34 ]
## numeric(0)
# El modelo potencial COD = a * BOD^b solo está definido para valores positivos
# de la Demanda Bioquímica de Oxígeno (BOD > 0), debido a la transformación
# logarítmica utilizada en el ajuste del modelo.
# Asimismo, el modelo es válido únicamente dentro del rango observado de la BOD
# en los datos analizados y no debe extrapolarse fuera de dicho intervalo.
# Valor objetivo (puedes cambiarlo)
BOD_objetivo <- 4 # mg/L
# Estimación usando el modelo potencial: COD = a * BOD^b
COD_est <- a_pot * BOD_objetivo^b_pot
# Mostrar como en el ejemplo (texto centrado grande)
plot(1, type = "n", axes = FALSE, xlab = "", ylab = "")
text(
1, 1,
labels = paste0(
"¿Cuál es la concentración esperada de (COD)\n",
"cuando la (BOD) del agua es ", BOD_objetivo, " mg/L?\n\n",
"Resultado estimado: ", round(COD_est, 3), " mg/L"
),
cex = 1.3,
col = "blue",
font = 2
)
En el intervalo entre la Demanda Bioquímica de Oxígeno (BOD) y el carbono organico disuelto (COD) en los cuerpos de agua monitoreados existe una relación de tipo potencial, representada por y ̂=a⋅〖“BOD 〗^b, siendo la BOD la medida de la carga orgánica biodegradable del agua (mg/L) y y ̂la concentración estimada de COD en mg/L; dentro del intervalo seleccionado no existen restricciones, ya que el modelo potencial está definido para valores positivos de la variable independiente, y se espera que, cuando la BOD sea 4 mg/L, se obtenga una concentración de COD de aproximadamente 20 mg/L, de acuerdo con el modelo ajustado.