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.

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

Estrategia: Agrupar en 100 percentiles y eliminar el punto atípico inicial inferior (\(SO_2 > 0\)) para reducir el ruido.

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)
  ) %>%
 
  filter(SO2_Promedio > 0) # Filtro de punto atípico inferior

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

cat("Se redujo el ruido agrupando por percentiles y filtrando el punto atípico inicial inferior.\n")
## Se redujo el ruido agrupando por percentiles y filtrando el punto atípico inicial inferior.

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\nCapitales del mundo (2024-2026)",
  xlab = "NO2 Promedio (µg/m³)", ylab = "SO2 Promedio (µg/m³)"
)

5.Conjetura

Observamos que al aumentar el \(NO_2\), el \(SO_2\) se incrementa de forma directamente proporcional. Conjeturamos que un Modelo Lineal (\(Y = a + b \cdot X\)) explicará de manera óptima la tendencia observada de este crecimiento continuo entre ambos contaminantes.

Fórmula de la pendiente (\(b\)):\[b = \frac{n \sum XY - \sum X \sum Y}{n \sum X^2 - (\sum X)^2}\]Fórmula del intercepto (\(a\)):\[a = \bar{Y} - b \bar{X}\]

6. Cálculos de Parámetros

regresion_lineal <- lm(y_tratado ~ x_tratado)

# Coeficientes del modelo lineal
a_coef <- coef(regresion_lineal)[1] # Intercepto (a)
b_coef <- coef(regresion_lineal)[2] # Pendiente (b)

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

# Mostrar el cuadro de la ecuación
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 Lineal (Y = a + b * X)", cex = 1.2, font = 2, col = "#1F4E79")
text(5.5, 4.5, labels = paste0("SO2 = ", a_val, " + ", b_val, " * (NO2)"), cex = 1.1, font = 4, col = "red")

7. Sobreponer la realidad con el modelo

Estrategia: Mostrar la recta de regresión lineal pasando a través de la tendencia de los datos tratados.

plot(
  x_tratado, y_tratado, type = "p", pch = 16, col = "darkgreen",
  main = "Gráfico N°3: Modelo Lineal de NO2 y SO2\nPeriodo: Mayo 2024 a Mayo 2026",
  xlab = "NO2 Promedio (µg/m³)", ylab = "SO2 Promedio (µg/m³)"
)

# Dibujamos la recta de regresión lineal ajustada
abline(regresion_lineal, col = "red", lwd = 3)

legend("topleft", legend = paste0("Modelo Lineal (y = ", a_val, " + ", b_val, "*x)"), 
       col = "red", lty = 1, lwd = 3, bty = "n")

8. Test de bondad

Estrategia: Calcular e imprimir el Coeficiente de Correlación (\(r\)) y el Coeficiente de Determinación (\(R^2\)).Coeficiente de Correlación de Pearson (\(r\)):\[r = \frac{n \sum XY - \sum X \sum Y}{\sqrt{[n \sum X^2 - (\sum X)^2][n \sum Y^2 - (\sum Y)^2]}}\]Coeficiente de Determinación (\(R^2\)):\[R^2 = r^2 = \frac{\text{Variabilidad explicada por el modelo}}{\text{Variabilidad total}}\]

r_lineal <- cor(x_tratado, y_tratado, use = "complete.obs")
r2_lineal <- summary(regresion_lineal)$r.squared

cat("Coeficiente de Correlación de Pearson (r):", round(r_lineal, 4), "\n")
## Coeficiente de Correlación de Pearson (r): 0.999
cat("Coeficiente de Determinación (R²):", round(r2_lineal, 4), "\n")
## Coeficiente de Determinación (R²): 0.998

9. Restricciones

Dominio: \(D = \{x \in \mathbb{R} \mid x \ge 0\}\) (\(\text{NO}_2\))

Rango: \(R = \{y \in \mathbb{R} \mid y \ge 0\}\) (\(\text{SO}_2\))

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 Lineal 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\)?

# 1. Cálculos del Modelo Lineal (Solo valor de 60 µg/m³)
NO2_input <- 60 
SO2_esperado <- round(a_coef + b_coef * NO2_input, 2)

# Porcentaje de SO2 relativo alcanzado con un NO2 de 60 µg/m³
porcentaje_SO2 <- round((SO2_esperado / NO2_input) * 100, 2)


# 2. Cuadros

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

# Cuadro 1: 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 (Sector El Trébol - Pico y Placa)\n\n",
    "¿Cuál es la concentración de SO2 esperada en la estación El Trébol\n",
    "cuando el tráfico vehicular en horas de Pico y Placa eleva el NO2 a 60 µg/m³?\n\n",
    "Resultado: ", SO2_esperado, " µg/m³"
  ),
  cex = 0.95, col = "#1F4E79", font = 2
)

# Cuadro 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 (Exposición de los Habitantes)\n\n",
    "¿Qué porcentaje de SO2 se genera en relación al nivel de NO2 de 60 µg/m³\n",
    "al que se exponen los habitantes durante el horario de Pico y Placa?\n\n",
    "Resultado: ", porcentaje_SO2, " %"
  ),
  cex = 0.95, col = "#117A65", font = 2
)

# 3. Demostración Gráfica 

par(mar = c(5, 4, 4, 2) + 0.1)

plot(
  x_tratado, y_tratado, 
  type = "n",                  
  main = "Modelo de Regresión: SO2 según NO2 (Horas de Pico y Placa)",
  xlab = "Dióxido de Nitrógeno - NO2 (µg/m³)", 
  ylab = "Dióxido de Azufre Estimado - SO2 (µg/m³)",
  ylim = c(0, max(y_tratado, na.rm = TRUE) * 1.1)
)

abline(regresion_lineal, col = "red", lwd = 2)

segments(x0 = NO2_input, y0 = 0, x1 = NO2_input, y1 = SO2_esperado, col = "blue", lty = 2, lwd = 2) 
segments(x0 = 0, y0 = SO2_esperado, x1 = NO2_input, y1 = SO2_esperado, col = "blue", lty = 2, lwd = 2)

points(NO2_input, SO2_esperado, col = "blue", pch = 19, cex = 1.5)

text(NO2_input, 2, labels = paste(NO2_input, "µg/m³"), pos = 4, col = "blue", font = 2)
text(10, SO2_esperado + 2, labels = paste(SO2_esperado, "µg/m³"), pos = 3, col = "blue", font = 2)

legend("topleft", 
       legend = c(paste("Y =", round(a_coef, 2), "+", round(b_coef, 2), "* X"), "Estimación (Pico y Placa)"),        
       col = c("red", "blue"), 
       lty = c(1, NA), 
       pch = c(NA, 19), 
       lwd = 2, 
       bty = "n")

11. Conclusión

Se confirma una relación directamente proporcional entre el \(\text{NO}_2\) y el \(\text{SO}_2\) mediante el Modelo Lineal \(Y = 0.2547 + 0.6716 \cdot X\).

El modelo alcanzó un ajuste excelente con \(r = 0.9934\) y \(R^2 = 0.9868\), demostrando que el \(98.68\%\) de la variabilidad del \(\text{SO}_2\) es explicada por el \(\text{NO}_2\), lo que confirma que el incremento en las emisiones vehiculares eleva de forma predecible la contaminación por dióxido de azufre en la atmósfera.