0. librerías

library(readr)
library(dplyr)
library(knitr)
library(kableExtra)
library(ggplot2)

1. Leer datos

Cargamos la base de datos de calidad del aire y clima para obtener las mediciones registradas de NO2 y SO2 requeridas en el análisis.

variables <- read.csv("C:/Users/dellh/Downloads/GlobalWeatherRepository.csv")

2. Selección (causa y efecto)

Elegimos analizar el Dióxido de Nitrógeno (NO2) y el Dióxido de Azufre (SO2) porque las altas emisiones de gases de combustión aumentan la densidad (concentración) de contaminantes en el aire, haciendo que el incremento de NO2 acelere la presencia del gas irritante SO2 en la atmósfera.

# X (Causa) = Dióxido de Nitrógeno (Nitrogen_dioxide)
# Y (Efecto) = Dióxido de Azufre (Sulphur_dioxide)

y <- as.numeric(variables$air_quality_Sulphur_dioxide)
x <- as.numeric(variables$air_quality_Nitrogen_dioxide)
TPP <- data.frame(y, x)

3. Tabla de pares de valores

Verificamos que ambas variables tengan el mismo tamaño muestral limpios para evitar errores para proceder a realizar la tabla y los gráficos.

3.1. Tamaño muestra

# Limpieza de datos y conversión a formato numérico sin perder filas
limpiar_columna_exacta <- function(df, cols) {
  for (col in cols) {
    # Convertimos a carácter primero
    temp_col <- as.character(df[[col]])
    # Si hay celdas vacías o espacios, los convertimos a "0" antes de hacer as.numeric
    temp_col[is.na(temp_col) | temp_col == "" | temp_col == " "] <- "0"
    df[[col]] <- as.numeric(temp_col)
    # Si queda algún NA tras la conversión, lo forzamos a 0
    df[[col]][is.na(df[[col]])] <- 0
  }
  return(df)
}

# Aplicamos la limpieza exacta
datos <- limpiar_columna_exacta(variables, c("air_quality_Nitrogen_dioxide", "air_quality_Sulphur_dioxide"))

# Mantenemos la totalidad absoluta de las filas del dataset original
datos_limpios <- datos

n_total <- nrow(datos_limpios)
cat("Tamaño muestral total depurado (n) = ", n_total, "\n")
## Tamaño muestral total depurado (n) =  141703

3.2. Extracto de tabla

# Extraemos las primeras 10 observaciones del dataset depurado
extraer_diez_filas <- head(data.frame(
  `NO2 (Causa - ug/m3)` = datos_limpios$air_quality_Nitrogen_dioxide,
  `SO2 (Efecto - ug/m3)` = datos_limpios$air_quality_Sulphur_dioxide,
  check.names = FALSE
), 10)

# Agregamos la fila del total al final 
tabla_con_total <- rbind(
  extraer_diez_filas,
  c(n_total, n_total)
)

rownames(tabla_con_total)[11] <- "Total (n)"

kable(
  tabla_con_total, align = "c", booktabs = TRUE,
  caption = "Tabla N.1: Pares de valores de $NO_2$ y $SO_2$, Estudio Meteorológico Global 2024-2026"
) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE, position = "center") %>%
  row_spec(0, bold = TRUE, color = "white", background = "#1F4E79") %>%
  row_spec(11, bold = TRUE, color = "black", background = "#E2EFDA") %>%
  footnote(
    general = paste("Las filas 1-10 representan un extracto de tus datos. La última fila detalla de manera dinámica el tamaño muestral completo (n =", n_total, ") incluyendo los valores cero."),
    general_title = "Nota: ", 
    footnote_as_chunk = TRUE
  )
Tabla N.1: Pares de valores de \(NO_2\) y \(SO_2\), Estudio Meteorológico Global 2024-2026
NO2 (Causa - ug/m3) SO2 (Efecto - ug/m3)
1 1.1 0.2
2 0.9 0.1
3 65.1 13.4
4 1.6 0.2
5 72.7 31.5
6 0.2 0.2
7 8.3 1.3
8 1.0 0.3
9 15.1 1.0
10 5.1 4.1
Total (n) 141703.0 141703.0
Nota: Las filas 1-10 representan un extracto de tus datos. La última fila detalla de manera dinámica el tamaño muestral completo (n = 141703 ) incluyendo los valores cero.

4. Gráfica de Dispersión

4.1. Gráfica de dispersión original

# Guardamos los vectores limpios para utilizarlos en los cálculos y gráficos
x_real <- datos_limpios$air_quality_Nitrogen_dioxide
y_real <- datos_limpios$air_quality_Sulphur_dioxide

ggplot(datos_limpios, aes(x = air_quality_Nitrogen_dioxide, y = air_quality_Sulphur_dioxide)) +
  geom_point(alpha = 0.2, color = "#2E8B57", size = 1) +
  # Fijamos la vista para que inicie estrictamente desde 0 en ambos ejes
  coord_cartesian(xlim = c(0, NA), ylim = c(0, NA)) +
  theme_minimal() +
  labs(
    title =   "Gráfico N°1:Relación entre Dióxido de Azufre (SO2) frente al Dióxido de Nitrógeno 
    (NO2)  analizado del Período 2024-2026 ",
    subtitle = paste("Datos originales sin procesar | n =", n_total, "registros"),
    x = "Dióxido de Nitrógeno - NO2 (µg/m³)",
    y = "Dióxido de Azufre - SO2 (µg/m³)"
  ) +
  theme(
    plot.title = element_text(face = "bold", size = 12, hjust = 0.5),
    plot.subtitle = element_text(size = 10, hjust = 0.5),
    panel.border = element_rect(color = "black", fill = NA, linewidth = 1),
    axis.ticks = element_line(color = "black"),
    axis.title.y = element_text(margin = margin(r = 15)),
    axis.title.x = element_text(margin = margin(t = 10))
  )

4.2. Tratamiento de datos

Agrupamos el NO2 en 100 percentiles utilizando ntile() Incluimos todos los datos (incluidos ceros) para calcular las medias representativas

datos_percentiles <- datos_limpios %>%
  mutate(percentil = ntile(air_quality_Nitrogen_dioxide, 100)) %>%
  group_by(percentil) %>%
  summarise(
    NO2_Promedio = mean(air_quality_Nitrogen_dioxide, na.rm = TRUE),
    SO2_Promedio = mean(air_quality_Sulphur_dioxide, na.rm = TRUE)
  )

# Asignamos los datos tratados para el modelo potencial
x_tratado <- datos_percentiles$NO2_Promedio
y_tratado <- datos_percentiles$SO2_Promedio

cat("Se redujo el ruido dividiendo las 141,703 observaciones en 100 percentiles representativos.\n")
## Se redujo el ruido dividiendo las 141,703 observaciones en 100 percentiles representativos.

4.3. Gráfica de Dispersión simplificada

plot(
  x_tratado, y_tratado, type = "p", pch = 16, col = "darkgreen",
  main = "Gráfico N°2: Promedio de SO2 por Percentiles de NO2 en las 
  capitales del mundo de Mayo 2024 hasta 2026",
  xlab = "NO2 Promedio (µg/m³)", ylab = "SO2 Promedio (µg/m³)"
)

5.Conjetura

Observamos que al aumentar el \(NO_2\), el \(SO_2\) también se incrementa de forma progresiva. Conjeturamos que un Modelo Potencial (\(Y = a \cdot X^b\)) explicará adecuadamente este crecimiento entre ambos contaminantes.

6. Cálculos de Parámetros

Ajustamos la regresión linealizada aplicando logaritmos: ln(Y) = ln(a) + b * ln(X) Agregamos +0.0001 infinitesimal únicamente para que ln(0) se pueda calcular sin perder ningún registro

regresion_linealizada <- lm(log(y_tratado + 0.0001) ~ log(x_tratado + 0.0001))

# Extraemos los coeficientes del modelo potencial original
b_coef <- regresion_linealizada$coefficients[2]
a_coef <- exp(regresion_linealizada$coefficients[1])

a_val <- round(a_coef, 4)
b_val <- round(b_coef, 4)

# Mostramos el cuadro matemático de la ecuación obtenida
plot(1:10, 1:10, type = "n", xlab = "", ylab = "", axes = FALSE, main = "Ecuación del Modelo Matemático")
rect(1, 3, 10, 8, col = "#F2F4F7", border = "#1F4E79", lwd = 3)
text(5.5, 6.2, labels = "Modelo de Regresión Potencial (Y = a * X^b)", cex = 1.2, font = 2, col = "#1F4E79")
text(5.5, 4.5, labels = paste0("SO2 = ", a_val, " * (NO2)^(", b_val, ")"), cex = 1.1, font = 4, col = "red")

                                          Donde:

\(Y\) es la concentración de Dióxido de Azufre (\(\text{SO}_2\) en \(\mu\text{g/m}^3\)) — variable #dependiente.

\(X\) es la concentración de Dióxido de Nitrógeno (\(\text{NO}_2\) en \(\mu\text{g/m}^3\)) — variable independiente.

\(a\) es la constante de escala que representa el nivel base teórico de \(\text{SO}_2\) en la atmósfera.

\(b\) Exponente del modelo, el cual se conjetura que será positivo (\(b > 0\)), indicando una relación directamente proporcional y no lineal entre ambos contaminantes.

7. Sobreponer la realidad con el modelo

# Visualizamos la curva ajustada del modelo potencial sobre los datos tratados

plot(
  x_tratado, y_tratado, type = "p", pch = 16, col = "darkgreen",
  main = "Gráfico N°3: Modelo Potencial :NO2 y SO2 para el análisis meteorológico
  en capitales del mundo
  Periodo: Mayo 2024 a Mayo 2026",
  xlab = "NO2 Promedio (µg/m³)", ylab = "SO2 Promedio (µg/m³)"
)

x_seq <- seq(min(x_tratado), max(x_tratado), length.out = 1000)
y_pred_seq <- a_coef * ((x_seq + 0.0001) ^ b_coef)

lines(x_seq, y_pred_seq, col = "red", lwd = 3)
legend("topleft", legend = paste0("Modelo Potencial (y = ", a_val, "*x^", b_val, ")"), 
       col = "red", lty = 1, lwd = 3, bty = "n")

8. Test de bondad

# Calculamos Pearson y R² asegurando que no arroje NA
r_potencial <- cor(log(y_tratado + 0.0001), log(x_tratado + 0.0001), use = "complete.obs")
r2_potencial <- r_potencial^2

cat("Coeficiente de Correlación de Pearson (r):", round(r_potencial, 4), "\n")
## Coeficiente de Correlación de Pearson (r): 0.9715
cat("Coeficiente de Determinación (R²):", round(r2_potencial, 4), "\n")
## Coeficiente de Determinación (R²): 0.9439

9. Restricciones

# Dominio [X]: Dióxido de Nitrógeno (µg/m³) -> D = {x ∈ ℝ | x ≥ 0}
# Rango [Y]: Dióxido de Azufre (µg/m³) -> R = {y ∈ ℝ | y ≥ 0}

Tanto el \(NO_2\) (causa) como el \(SO_2\) (efecto) representan la cantidad de un gas en el aire, por lo que físicamente es imposible tener concentraciones negativas (\(X \ge 0\) y \(Y \ge 0\)). Al evaluar nuestro modelo potencial, vemos que para cualquier valor de \(NO_2\) positivo, el resultado de \(SO_2\) siempre será un número positivo, respetando totalmente la realidad física.

10. Estimaciones

¿Qué cantidad de Dióxido de Azufre (\(SO_2\)) se espera cuando la concentración de Dióxido de Nitrógeno (\(NO_2\)) sea de \(60\ \mu g/m^3\)?

# Cálculos
SO2_60 <- round(a_coef * ((60 + 0.0001)^b_coef), 4)

SO2_20 <- a_coef * ((20 + 0.0001)^b_coef)
SO2_60_real <- a_coef * ((60 + 0.0001)^b_coef)

incremento_porcentaje <- ((SO2_60_real - SO2_20) / SO2_20) * 100

# Visualización en cuadros estéticos
par(mar = c(0.5, 0.5, 0.5, 0.5))

plot(0, 0, type = "n", xlim = c(0, 10), ylim = c(0, 10), axes = FALSE, xlab = "", ylab = "")

# PREGUNTA DE CANTIDAD 
rect(0.2, 5.2, 9.8, 9.8, col = "#EBF5FB", border = "#1F4E79", lwd = 2)
text(
  x = 5, y = 7.5,
  labels = paste0(
    "1. Pregunta de Cantidad ( Mayo en Quito - Sector El Trébol)\n\n",
    "¿Cuál es la Evaluación de Contaminantes de SO2 esperada (gas irritante) cuando\n",
    "los buses a diésel elevan el NO2 a 60 µg/m³ en hora pico?\n\n",
    "Resultado: ", SO2_60, " µg/m³"
  ),
  cex = 0.95, col = "#1F4E79", font = 2
)

# PREGUNTA DE PORCENTAJE
rect(0.2, 0.2, 9.8, 4.9, col = "#E8F8F5", border = "#117A65", lwd = 2)
text(
  x = 5, y = 2.55,
  labels = paste0(
    "2. Pregunta de Porcentaje (Impacto de la Congestión Vehicular)\n\n",
    "¿En qué porcentaje aumenta el SO2 esperado al pasar de un tráfico\n",
    "fluido (20 µg/m³ de NO2) a una congestión severa (60 µg/m³ de NO2)?\n\n",
    "Resultado: +", round(incremento_porcentaje, 2), " %"
  ),
  cex = 1.05, col = "#117A65", font = 2
)

11. Conclusiones

Entre el Dióxido de Nitrógeno (\(NO_2\)) y el Dióxido de Azufre (\(SO_2\)) existe una relación de tipo potencial representada por la ecuación \(Y = 1.4011 \cdot X^{0.733}\), demostrando que el incremento de \(NO_2\) impulsa de forma directa la concentración de \(SO_2\) en la atmósfera. Con un coeficiente de correlación de Pearson mayor a \(0.70\), se confirma un ajuste sólido entre ambos contaminantes . El modelo respeta las restricciones ambientales al aplicarse \([0, \infty)\ \mu g/m^3\), garantizando estimaciones coherentes con la realidad física.