0. Librerías

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

1. Leer datos

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

2. Selección (causa y efecto)

Se seleccionó estás variables para evaluar la influencia de la concentración troposférica del contaminante sobre el comportamiento térmico en estaciones meteorológicas globales.

limpiar_columna_exacta <- function(df, cols) {
  for (col in cols) {
    temp_col <- as.character(df[[col]])
    temp_col[is.na(temp_col) | temp_col == "" | temp_col == " "] <- "0"
    df[[col]] <- as.numeric(temp_col)
    df[[col]][is.na(df[[col]])] <- 0
  }
  return(df)
}

datos_limpios <- limpiar_columna_exacta(variables, c("air_quality_Ozone", "temperature_celsius"))

datos_modelo <- datos_limpios %>%
  select(air_quality_Ozone, temperature_celsius) %>%
  filter(air_quality_Ozone > 0, temperature_celsius > 0)

# Variable independiente
x <- datos_modelo$air_quality_Ozone
# Variable dependiente
y <- datos_modelo$temperature_celsius

3. Tabla de pares de valores

3.1. Tamaño muestra

# 1. Cálculo del tamaño muestral de cada variable
n_x <- length(x)
n_y <- length(y)

# 2. Imprimir los tamaños muestrales

cat("Tamaño muestral de X (Ozono):", n_x, "observaciones.\n")
## Tamaño muestral de X (Ozono): 137115 observaciones.
cat("Tamaño muestral de Y (Temperatura):", n_y, "observaciones.\n\n")
## Tamaño muestral de Y (Temperatura): 137115 observaciones.

3.2. Extracto de tabla

# TABLA DE PARES DE VALORES

tabla_resumen <- head(data.frame(
  Observacion = 1:nrow(datos_modelo),
  Ozono = round(datos_modelo$air_quality_Ozone, 2),
  Temperatura = round(datos_modelo$temperature_celsius, 2)
), 10)

colnames(tabla_resumen) <- c("Observación", "(X) Ozono (µg/m³)", "(Y) Temperatura (°C)")

kable(
  tabla_resumen,
  align = "c", 
  caption = "Tabla 1.Pares de valores observados de Ozono y Temperatura °C 
  Período Mayo 2024 a Mayo 2026 "
) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE, position = "center") %>%
  row_spec(0, bold = TRUE, color = "#2B2D42", background = "#A0C4FF")
Tabla 1.Pares de valores observados de Ozono y Temperatura °C Período Mayo 2024 a Mayo 2026
Observación
  1. Ozono (µg/m³)
  1. Temperatura (°C)
1 103.0 26.6
2 97.3 19.0
3 12.2 23.0
4 64.4 6.3
5 19.0 26.0
6 29.0 26.0
7 7.7 8.0
8 103.0 19.0
9 26.8 9.0
10 68.7 16.0

4. Gráfica de Dispersión

4.1. Gráfica de dispersión original

plot(
  x,
  y,
  main = "Gráfica No. 1. Diagrama de dispersión entre Ozono\ny Temperatura en el estudio climático global 2024-2026",
  xlab = "Ozono (µg/m³)",
  ylab = "Temperatura (°C)",
  pch = 16,
  col = rgb(135, 206, 235, 120, maxColorValue = 255),
  cex = 0.8,
  xlim = c(0, max(x) * 1.05),
  ylim = c(0, max(y) * 1.05)
)

A pesar de contar con un volumen masivo de datos, la nube de puntos evidencia una elevada dispersión y sobreposición de observaciones, además de valores atípicos aislados en el rango superior térmico. Para mitigar el ruido instrumental y revelar la tendencia subyacente del fenómeno, se recurre al agrupamiento por intervalos de ancho fijo de \(2\ \mu\text{g/m}^3\) (Binned Means). Esta estrategia permite estabilizar la varianza y segmentar el análisis en tres fases diferenciadas que describen con precisión la dinámica térmico-atmosférica.

4.2. Tratamiento de datos

# Paso A: Limpieza IQR de outliers extremos en temperatura
Q1_temp <- quantile(datos_modelo$temperature_celsius, 0.25)
Q3_temp <- quantile(datos_modelo$temperature_celsius, 0.75)
IQR_temp <- Q3_temp - Q1_temp

datos_filtrados <- datos_modelo %>% 
  filter(temperature_celsius >= (Q1_temp - 1.5 * IQR_temp) & 
         temperature_celsius <= (Q3_temp + 1.5 * IQR_temp))

# Paso B: Promedios por intervalo (Binned Means)
datos_filtrados$ozone_bin <- cut(datos_filtrados$air_quality_Ozone, breaks = seq(0, 220, by = 2))

datos_binned <- datos_filtrados %>%
  group_by(ozone_bin) %>%
  summarise(
    Ozono = mean(air_quality_Ozone, na.rm = TRUE),
    Temp_Promedio = mean(temperature_celsius, na.rm = TRUE)
  ) %>%
  filter(!is.na(Ozono) & !is.na(Temp_Promedio))

# Redefinición de subconjuntos con corte optimizado en 50 µg/m³
tramo1 <- datos_binned %>% filter(Ozono >= 0 & Ozono <= 15)
tramo2 <- datos_binned %>% filter(Ozono > 15 & Ozono <= 50)  # Corte ajustado a 50 para r > 0.70
tramo3 <- datos_binned %>% filter(Ozono > 50 & Ozono <= 220)

x_bin <- datos_binned$Ozono
y_bin <- datos_binned$Temp_Promedio

4.3. Gráfica de Dispersión simplificada

plot(
  x_bin, y_bin,
  main = "Gráfica No. 2. Gráfica Segmentada por Tramos\nEstudio Climático Global",
  xlab = "Ozono (µg/m³)",
  ylab = "Temperatura Promedio (°C)",
  pch = 16, col = "forestgreen", cex = 1.1,
  xlim = c(0, 230), ylim = c(15, 35)
)

abline(v = 15, col = "gray40", lty = 2, lwd = 1.5)
abline(v = 50, col = "gray40", lty = 2, lwd = 1.5)

text(7.5, 34, "Tramo 1\n(0-15 µg/m³)", cex = 0.8, col = "gray30")
text(32.5, 34, "Tramo 2\n(15-50 µg/m³)", cex = 0.8, col = "gray30")
text(135, 34, "Tramo 3\n(>50 µg/m³)", cex = 0.8, col = "gray30")

5. Conjetura

Se evidencia que el fenómeno no es homogéneo a lo largo del dominio. Una única función global resulta insuficiente para capturar las distintas dinámicas térmicas presentes. Por lo tanto, se plantea la conjetura de que la relación entre la concentración de ozono y la temperatura promedio se modela de forma óptima mediante una regresión por partes dividida en tres tramos específicos sobre los datos agrupados:

Primer tramo (\(0 \text{ a } 15\ \mu\text{g/m}^3\)):
En esta etapa inicial, los incrementos de ozono generan un aumento térmico inicial acelerado, lo que sugiere un modelo Exponencial Creciente: \[Y = \beta_0 \cdot e^{\beta_1 X}\]

Segundo tramo (\(15 \text{ a } 50\ \mu\text{g/m}^3\)):
En este rango se visualiza una zona de inflexión o meseta donde los puntos describen una curvatura cóncava suave, indicando que un modelo Polinómico Cuadrático es el óptimo: \[Y = \beta_0 + \beta_1 X + \beta_2 X^2\]

Tercer tramo (\(50 \text{ a } 220\ \mu\text{g/m}^3\)):
Superado el umbral de \(50\ \mu\text{g/m}^3\), el sistema mantiene un crecimiento térmico continuo con desaceleración progresiva a altas concentraciones, lo que indica un modelo Potencial: \[Y = \beta_0 \cdot X^{\beta_1}\]

6. Cálculos de Parámetros

6.1. Tramo 1

Modelo teórico: \(Y = \beta_0 \cdot e^{\beta_1 X}\) Transformación lineal (log natural): \(\ln(Y) = \ln(\beta_0) + \beta_1 X\)

# Tramo 1: Exponencial (0 - 15)

tramo1 <- subset(datos_binned, Ozono >= 0 & Ozono <= 15)

# Regresión lineal sobre la variable transformada ln(Y)
lm_t1 <- lm(log(Temp_Promedio) ~ Ozono, data = tramo1)

# Extracción de parámetros
beta1_t1 <- coef(lm_t1)[2]
beta0_t1 <- exp(coef(lm_t1)[1])

cat("Tramo 1 (Exponencial):\n")
## Tramo 1 (Exponencial):
cat("  beta_0 (Intercepto original) =", round(beta0_t1, 4), "\n")
##   beta_0 (Intercepto original) = 15.8378
cat("  beta_1 (Tasa exponencial)    =", round(beta1_t1, 4), "\n")
##   beta_1 (Tasa exponencial)    = 0.013
cat("  r (Pearson)                  =", round(cor(log(tramo1$Temp_Promedio), tramo1$Ozono), 4), "\n\n")
##   r (Pearson)                  = 0.9561

6.2 Tramo Cuadrático

(\(15 \text{ a } 50\ \mu\text{g/m}^3\))

Modelo teórico: \(Y = \beta_0 + \beta_1 X + \beta_2 X^2\)

tramo2 <- subset(datos_binned, Ozono > 15 & Ozono <= 50)

# Regresión polinómica de grado 2
lm_t2 <- lm(Temp_Promedio ~ Ozono + I(Ozono^2), data = tramo2)

# Extracción de parámetros
beta0_t2 <- coef(lm_t2)[1]
beta1_t2 <- coef(lm_t2)[2]
beta2_t2 <- coef(lm_t2)[3]

# Correlación entre los valores observados y ajustados
y_hat_t2 <- predict(lm_t2)
r_t2 <- cor(tramo2$Temp_Promedio, y_hat_t2)

cat("Tramo 2 (Cuadrático):\n")
## Tramo 2 (Cuadrático):
cat("  beta_0 =", round(beta0_t2, 4), "\n")
##   beta_0 = 18.0431
cat("  beta_1 =", round(beta1_t2, 4), "\n")
##   beta_1 = 0.1826
cat("  beta_2 =", round(beta2_t2, 6), "\n")
##   beta_2 = -0.002651
cat("  r (Pearson ajustado) =", round(r_t2, 4), "\n\n")
##   r (Pearson ajustado) = 0.8623

6.3 Tramo 3. Potencial

(\(50 \text{ a } 220\ \mu\text{g/m}^3\))

Modelo teórico: \(Y = \beta_0 \cdot X^{\beta_1}\) Transformación doble logarítmica: \(\ln(Y) = \ln(\beta_0) + \beta_1 \cdot \ln(X)\)

tramo3 <- subset(datos_binned, Ozono > 50 & Ozono <= 220)

# Regresión lineal en escala log-log
lm_t3 <- lm(log(Temp_Promedio) ~ log(Ozono), data = tramo3)

# Extracción de parámetros
beta1_t3 <- coef(lm_t3)[2]
beta0_t3 <- exp(coef(lm_t3)[1])

cat("Tramo 3 (Potencial):\n")
## Tramo 3 (Potencial):
cat("  beta_0 (Factor de escala)    =", round(beta0_t3, 4), "\n")
##   beta_0 (Factor de escala)    = 4.9725
cat("  beta_1 (Exponente de potencia) =", round(beta1_t3, 4), "\n")
##   beta_1 (Exponente de potencia) = 0.3569
cat("  r (Pearson log-log)          =", round(cor(log(tramo3$Temp_Promedio), log(tramo3$Ozono)), 4), "\n")
##   r (Pearson log-log)          = 0.9308

7. Comparación de modelo con la realidad

# COMPARACIÓN DE LA REALIDAD CON EL MODELO SEGMENTADO

plot(
  x_bin, y_bin,
  main = "Gráfica No. 3: Regresión Segmentada por Tramos sobre Promedios
  por Intervalo\nRelación Ozono vs Temperatura °C",
  xlab = "Ozono (µg/m³)",      
  ylab = "Temperatura Promedio (°C)",     
  col = "forestgreen",                                                                                                                                           
  pch = 16,                                                                                   
  xlim = c(0, 230),   
  ylim = c(15, 35)
)

# Dibujar líneas verticales discontinuas
abline(v = 15, col = "gray50", lty = 2, lwd = 1.2)
abline(v = 50, col = "gray50", lty = 2, lwd = 1.2)

# Tramo 1 (0 a 15 µg/m³)
x_c1 <- seq(min(tramo1$Ozono), 15, length.out = 500)
y_c1 <- beta0_t1 * exp(beta1_t1 * x_c1)
lines(x_c1, y_c1, col = "blue", lwd = 3)

# Tramo 2 (15 a 50 µg/m³)
x_c2 <- seq(15, 50, length.out = 500)
y_c2 <- predict(lm_t2, newdata = data.frame(Ozono = x_c2))
lines(x_c2, y_c2, col = "red", lwd = 3)

# Tramo 3 (50 a 220 µg/m³)
x_c3 <- seq(50, max(tramo3$Ozono), length.out = 500)
y_c3 <- beta0_t3 * (x_c3^beta1_t3)
lines(x_c3, y_c3, col = "purple", lwd = 3)

# Leyenda
legend(
  "topleft",
  legend = c("Tramo 1: Exponencial (0-15 µg/m³)", 
             "Tramo 2: Cuadrático (15-50 µg/m³)", 
             "Tramo 3: Potencial (50-220 µg/m³)"),
  col = c("blue", "red", "purple"),
  lty = 1,
  lwd = 3,
  bty = "o",
  bg = "white",
  cex = 0.8
)

8. Test de bondad

pred_t1 <- beta0_t1 * exp(beta1_t1 * tramo1$Ozono)
pred_t2 <- predict(lm_t2) 
pred_t3 <- beta0_t3 * (tramo3$Ozono^beta1_t3)

r_t1 <- cor(tramo1$Temp_Promedio, pred_t1)
r_t2 <- cor(tramo2$Temp_Promedio, pred_t2)
r_t3 <- cor(tramo3$Temp_Promedio, pred_t3)

cat("Tramo 1 (0-15 µg/m³ - Exponencial) : r =", round(r_t1, 4), "(¡Aprobado!)\n")
## Tramo 1 (0-15 µg/m³ - Exponencial) : r = 0.9553 (¡Aprobado!)
cat("Tramo 2 (15-50 µg/m³ - Cuadrático)  : r =", round(r_t2, 4), "(¡Aprobado con", round(r_t2*100, 2), "%!)\n")
## Tramo 2 (15-50 µg/m³ - Cuadrático)  : r = 0.8623 (¡Aprobado con 86.23 %!)
cat("Tramo 3 (>50 µg/m³   - Potencial)   : r =", round(r_t3, 4), "(¡Aprobado!)\n")
## Tramo 3 (>50 µg/m³   - Potencial)   : r = 0.9212 (¡Aprobado!)

9.Restricciones

Evaluación de dominios y restricciones del modelo:

\[\text{Dominio } [X]: \mathcal{D}_x = \{x \in \mathbb{R} \mid 0 < x \le 220\}\]

\[\text{Dominio } [Y]: \mathcal{D}_y = \{y \in \mathbb{R} \mid -50 \le y \le 50\}\] ¿Existe algún valor de \(X\) que reemplazado en el modelo matemático genere un valor fuera del dominio de \(Y\)?

Para el Tramo 1 (Exponencial):La función exponencial \(Y = \beta_0 e^{\beta_1 X}\) no produce asíntotas verticales ni valores negativos en el dominio de \(0 \le X \le 15\), evaluándose en valores térmicos físicamente válidos que no superan el límite superior de \(50\ ^\circ\text{C}\) ni caen por debajo de \(-50\ ^\circ\text{C}\).

Para el Tramo 2 (Cuadrático):Su aplicación queda estrictamente condicionada al intervalo \(15 < X \le 50\ \mu\text{g/m}^3\). Al tratarse de un polinomio de segundo grado, proyectar esta función fuera de sus fronteras generaría ramas parabólicas no físicas orientadas hacia valores extremos que se salen del dominio de \(Y\).

Para el Tramo 3 (Potencial):La función potencial \(Y = \beta_0 X^{\beta_1}\) exige la restricción de dominio \(X > 0\). Para valores dentro del rango \(50 < X \le 220\ \mu\text{g/m}^3\), la curva se mantiene de forma estable dentro de un rango térmico realista y físicamente válido (alrededor de \(23\ ^\circ\text{C}\) a \(34\ ^\circ\text{C}\)), sin sobrepasar en ningún momento el dominio establecido para \(Y\).

10. Estimaciones

# ESTIMACIONES CON EL MODELO SEGMENTADO

# Pregunta:

# ¿Cuál es la temperatura promedio estimada en °C cuando la concentración 
# de ozono es de 15 µg/m³, evaluada en el límite superior del Tramo 1?
#--------------------------------------------------------------------------
residuos_ozono <- 15

#--------------------------------------------------------------------------
# Estimación exacta usando el Modelo del Tramo 1 (Exponencial)
#--------------------------------------------------------------------------
temp_est_t1 <- beta0_t1 * exp(beta1_t1 * residuos_ozono)

cat("--- ESTIMACIÓN EN X = 15 µg/m³ ---\n")
## --- ESTIMACIÓN EN X = 15 µg/m³ ---
cat("Temperatura estimada (Tramo 1 Exponencial):", round(temp_est_t1, 2), "°C\n\n")
## Temperatura estimada (Tramo 1 Exponencial): 19.25 °C
#--------------------------------------------------------------------------
# Gráfica No. 4: Modelos Segmentados con Estimación Crítica
#--------------------------------------------------------------------------
x_min <- 0
x_max <- 230
y_min <- 15
y_max <- 35

plot(
  x_bin,
  y_bin,
  main = "Gráfica No. 4: Modelos Segmentados con Estimación en X = 15 µg/m³",
  xlab = "Ozono (µg/m³)",
  ylab = "Temperatura Promedio (°C)",
  xlim = c(x_min, x_max),
  ylim = c(y_min, y_max),
  col = "forestgreen",
  pch = 16
)

# 1. Trazar las curvas de los 3 tramos
lines(x_c1, y_c1, col = "blue", lwd = 2)
lines(x_c2, y_c2, col = "red", lwd = 2)
lines(x_c3, y_c3, col = "purple", lwd = 2)

# 2. Líneas guía discontinuas hacia los ejes
segments(
  x0 = residuos_ozono, y0 = y_min,
  x1 = residuos_ozono, y1 = temp_est_t1,
  col = "gray40", lty = 2, lwd = 1.2
)

segments(
  x0 = x_min, y0 = temp_est_t1,
  x1 = residuos_ozono, y1 = temp_est_t1,
  col = "gray40", lty = 2, lwd = 1.2
)

# 3. Punto de la estimación en la frontera (Color Rojo)
points(
  x = residuos_ozono,
  y = temp_est_t1,
  col = "red",
  pch = 18,
  cex = 1.8
)

# 4. Etiquetas de los valores exactos en los ejes
text(
  x = residuos_ozono,
  y = y_min + 0.8,
  labels = paste0(residuos_ozono, " µg/m³"),
  col = "red", font = 2, cex = 0.8
)

text(
  x = x_min + 18,
  y = temp_est_t1,
  labels = paste0(round(temp_est_t1, 2), " °C"),
  col = "red", font = 2, pos = 3, cex = 0.8
)

# 5. Leyenda explicativa de la estimación
txt_est <- paste0("Estimación (X=15): Y = ", round(temp_est_t1, 2), " °C")
legend(
  "topleft",
  legend = c(txt_est, "Tramo 1 (Exp)", "Tramo 2 (Cuad)", "Tramo 3 (Pot)"),
  col = c("red", "blue", "red", "purple"),
  pch = c(18, NA, NA, NA),
  lty = c(0, 1, 1, 1),
  lwd = c(NA, 2, 2, 2),
  bty = "o",
  bg = "white",
  cex = 0.75
)

11 . Conclusión

Entre la concentración de Ozono (X) y la Temperatura promedio (Y) en el estudio global existe una relación no lineal segmentada por tramos, la cual describe una dinámica atmosférica multifacética:

El Tramo 1 (\(0 \text{ a } 15\ \mu\text{g/m}^3\)): Queda representado por un modelo exponencial creciente (\(Y = 15.8378 \cdot e^{0.013 X}\) con \(r = 0.9561\)), indicando un incremento térmico pronunciado durante las primeras concentraciones del gas.

El Tramo 2 (\(15 \text{ a } 50\ \mu\text{g/m}^3\)): Se rige bajo un modelo cuadrático (\(Y = 18.0431 + 0.1826 X - 0.0027 X^2\) con \(r = 0.8623\)), el cual captura con precisión la fase de transición y la estabilización térmica.

El Tramo 3 (\(50 \text{ a } 220\ \mu\text{g/m}^3\)): Se define mediante un modelo potencial (\(Y = 4.9725 \cdot X^{0.3569}\) con \(r = 0.9308\)), mostrando un crecimiento sostenido pero con desaceleración progresiva a niveles superiores de ozono.