0. librerías

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

1. Leer datos

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

2. Selección (causa y efecto)

# Definimos que la Humedad (Causa/X) actúa como un filtro natural en la 
# atmósfera que dispersa la radiación solar, alterando directamente el Temperatura °C (Efecto/Y).
y <- as.numeric(variables$temperature_celsius)            # Efecto
x <- as.numeric(variables$humidity)            # Causa (Escala normal)
TPP <- data.frame(y, x)

3. Tabla de pares de valores

3.1. Tamaño muestra

# Permite conocer con cuántos registros válidos cuenta cada variable 
# tras descartar valores nulos antes del modelado matemático.
limpiar_columna <- function(df, cols) {
  for (col in cols) { df[[col]] <- as.numeric(as.character(df[[col]])) }
  return(df)
}
datos <- limpiar_columna(variables, c("humidity", "temperature_celsius"))
datos_limpios <- datos %>% filter(!is.na(humidity) & !is.na(temperature_celsius))

n_humedad <- sum(!is.na(datos_limpios$humidity))
n_uv <- sum(!is.na(datos_limpios$temperature_celsius))

cat("Tamaño muestral - Variable Humedad (n)    = ", n_humedad, "\n")
## Tamaño muestral - Variable Humedad (n)    =  141703
cat("Tamaño muestral - Variable Temperatura °C (n)  = ", n_uv, "\n")
## Tamaño muestral - Variable Temperatura °C (n)  =  141703

3.2. Extracto de tabla

# Mostramos un extracto inicial de 10 filas y colocamos en la última fila 
# la sumatoria total del tamaño muestral (n) para contrastar el volumen completo del dataset.
extraer_diez_filas <- head(data.frame(
  `Humedad (%)` = datos_limpios$humidity,
  `Temperatura °C` = datos_limpios$temperature_celsius,
  check.names = FALSE
), 10)

# Añadimos la fila de sumatorias totales al final
tabla_con_total <- rbind(
  extraer_diez_filas,
  c(n_humedad, n_uv)
)

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

kable(
  tabla_con_total, align = "c", booktabs = TRUE,
  caption = "Tabla N.1: Extracto de pares de valores (Humedad vs Temperatura °C)"
) %>%
  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 = "Las filas 1-10 representan un extracto de la base de datos. La última fila detalla la sumatoria muestral total (n).",
    general_title = "Nota: ", 
    footnote_as_chunk = TRUE
  )
Tabla N.1: Extracto de pares de valores (Humedad vs Temperatura °C)
Humedad (%) Temperatura °C
1 24 26.6
2 94 19.0
3 29 23.0
4 61 6.3
5 89 26.0
6 84 26.0
7 93 8.0
8 40 19.0
9 87 9.0
10 63 16.0
Total (n) 141703 141703.0
Nota: Las filas 1-10 representan un extracto de la base de datos. La última fila detalla la sumatoria muestral total (n).

4. Gráfica de Dispersión

4.1. Gráfica de dispersión original

# Esta nube tiene tiene demasiada complejidad por lo que vamos a realizar una una simplificación para función uno a uno.
ggplot(datos_limpios, aes(x = humidity, y = temperature_celsius)) +
  geom_point(alpha = 0.5, color = "black", size = 1.5) +
  theme_minimal() +
  labs(
    title = "Gráfico N°1: Relación Original entre Humedad y Temperatura °C",
    subtitle = "Período: Mayo 2024 a Mayo 2026",
    x = "Humedad (%)",
    y = "Temperatura °C"
  ) +
  theme(
    plot.title = element_text(face = "bold", size = 12, hjust = 0.5),
    plot.subtitle = element_text(size = 10, hjust = 0.5),
    panel.grid.major = element_line(color = "gray90"),
    panel.grid.minor = element_line(color = "gray95")
  )

4.2. Tratamiento de datos

# Aplicamos la Regla del Rango Intercuartílico (IQR) mediante el criterio 
# de diagramas de caja (Boxplot) para remover outliers. Agrupamos por promedios de 
# Temperatura °C según el valor normal de humedad para suavizar y simplificar la dispersión.

# Paso A: Regla IQR para Temperatura °C (Efecto)
Q1_uv <- quantile(datos_limpios$temperature_celsius, 0.25)
Q3_uv <- quantile(datos_limpios$temperature_celsius, 0.75)
IQR_uv <- Q3_uv - Q1_uv
limite_inferior <- Q1_uv - 1.5 * IQR_uv
limite_superior <- Q3_uv + 1.5 * IQR_uv

# Paso B: Filtrado de Outliers
datos_sin_outliers <- datos_limpios %>% 
  filter(temperature_celsius >= limite_inferior & temperature_celsius <= limite_superior)

# Paso C: Generación de promedios usando la Humedad Normal
datos_promedio <- aggregate(temperature_celsius ~ humidity, data = datos_sin_outliers, FUN = mean)
colnames(datos_promedio) <- c("Humedad", "UV_Promedio")

x_simplificado <- datos_promedio$Humedad
y_simplificado <- datos_promedio$UV_Promedio

# Justificación por consola de los límites del boxplot
cat(" DETECCIÓN DE OUTLIERS POR REGLA IQR ")
##  DETECCIÓN DE OUTLIERS POR REGLA IQR
cat("Primer Cuartil (Q1):", Q1_uv, "\n")
## Primer Cuartil (Q1): 15.6
cat("Tercer Cuartil (Q3):", Q3_uv, "\n")
## Tercer Cuartil (Q3): 28
cat("Rango Intercuartílico (IQR):", IQR_uv, "\n")
## Rango Intercuartílico (IQR): 12.4
cat("Límites de aceptación de datos: [", limite_inferior, ",", limite_superior, "]\n")
## Límites de aceptación de datos: [ -3 , 46.6 ]

4.3. Gráfica de Dispersión simplificada

plot(
  x_simplificado, y_simplificado, type = "p", pch = 16, col = "darkcyan",
  main = "Gráfico N°2: Promedio de Temperatura °C según la Humedad (Escala Normal)",
  xlab = "Humedad (%)", ylab = "Temperatura °C Promedio"
)

5.Conjetura

# Al observar que la nube de puntos presenta una curvatura más compleja con 
# posibles cambios en la aceleración de caída, conjeturamos que un Modelo Polinómico de 
# Tercer Grado (Cúbico) modelará mejor la atenuación: Y = b0 + b1*X + b2*X^2 + b3*X^3.

6. Cálculos de Parámetros

# Ajustamos el modelo polinómico cúbico (3° grado) sobre la Humedad (X) 
# y la Temperatura (Y) usando lm(). Extraemos los coeficientes evitando que el término 
# cúbico se redondee visualmente a cero.

regresion_cubica <- lm(y_simplificado ~ x_simplificado + I(x_simplificado^2) + I(x_simplificado^3))

# Coeficientes numéricos exactos
b0_num <- coef(regresion_cubica)[1]
b1_num <- coef(regresion_cubica)[2]
b2_num <- coef(regresion_cubica)[3]
b3_num <- coef(regresion_cubica)[4]

# Formato visual redondeado
b0 <- round(b0_num, 4)
b1 <- round(b1_num, 4)
b2 <- round(b2_num, 4)
b3 <- format(b3_num, scientific = TRUE, digits = 4) # Notación científica para evitar visualización como cero

cat("COEFICIENTES DEL MODELO CÚBICO \n")
## COEFICIENTES DEL MODELO CÚBICO
cat("Intercepto (b0):         ", b0, "\n")
## Intercepto (b0):          36.2516
cat("Término lineal (b1):     ", b1, "\n")
## Término lineal (b1):      -0.5332
cat("Término cuadrático (b2):  ", b2, "\n")
## Término cuadrático (b2):   0.0072
cat("Término cúbico (b3):     ", b3, "\n")
## Término cúbico (b3):      -3.902e-05
# Cuadro gráfico de la Ecuación Matemática
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 Polinómico de 3° Grado (Cúbico)", cex = 1.2, font = 2, col = "#1F4E79")
text(5.5, 4.5, labels = paste0("Y = ", b0, " + (", b1, ")*X + (", b2, ")*X² + (", b3, ")*X³"), cex = 0.9, font = 4, col = "red")

7. Comparación de modelo con la realidad

# Superponemos la curva cúbica calculada sobre la nube de promedios 
# simplificados para evaluar visualmente si la curvatura del modelo cúbico se adapta a los datos reales.

plot(
  x_simplificado, y_simplificado, type = "p", pch = 16, col = "darkcyan",
  main = "Gráfico N°3: Curva Cúbica sobre la Realidad de los Datos",
  xlab = "Humedad (%)", ylab = "Temperatura °C Promedio"
)

x_seq <- seq(min(x_simplificado), max(x_simplificado), length.out = 500)
y_pred_seq <- predict(regresion_cubica, newdata = data.frame(x_simplificado = x_seq))

lines(x_seq, y_pred_seq, col = "red", lwd = 3)
legend("topright", legend = "Modelo Cúbico (Grado 3)", col = "red", lty = 1, lwd = 3, bty = "n")

8. Test de bondad

# Calculamos el Coeficiente de Correlación de Pearson (r) entre la Humedad 
# y la Temperatura promedio como punto de partida para evaluar la dirección y fuerza de la relación.

r <- cor(y_simplificado, x_simplificado)

cat("RESULTADOS DEL TEST DE PEARSON \n")
## RESULTADOS DEL TEST DE PEARSON
cat("Coeficiente de Correlación de Pearson (r):", round(r, 4), "\n")
## Coeficiente de Correlación de Pearson (r): -0.8367

9. Restricciones

# ¿Por qué esto?: Evaluamos el comportamiento de la función en los límites físicos reales 
# del dominio de la Humedad (0% a 100%) calculando los valores exactos de Temperatura (°C).

# Función de evaluación usando los coeficientes exactos
f_temp <- function(x) {
  b0_num + b1_num * x + b2_num * (x^2) + b3_num * (x^3)
}

# Evaluaciones numéricas exactas
temp_min_x <- round(f_temp(0), 4)     # Humedad = 0%
temp_med_x <- round(f_temp(50), 4)    # Humedad = 50%
temp_max_x <- round(f_temp(100), 4)   # Humedad = 100%


cat("Dominio de X (Humedad %):      D_x = { x ∈ ℝ | 0 ≤ x ≤ 100 }\n")
## Dominio de X (Humedad %):      D_x = { x ∈ ℝ | 0 ≤ x ≤ 100 }
cat("Rango de Y estimado (Temp °C): R_y = [", temp_max_x, "°C ,", temp_min_x, "°C ]\n\n")
## Rango de Y estimado (Temp °C): R_y = [ 15.7393 °C , 36.2516 °C ]
cat(" Evaluaciones Puntuales Exactas \n")
##  Evaluaciones Puntuales Exactas
cat("1. Para X = 0%   (Aire seco)     : Y =", temp_min_x, "°C\n")
## 1. Para X = 0%   (Aire seco)     : Y = 36.2516 °C
cat("2. Para X = 50%  (Humedad media) : Y =", temp_med_x, "°C\n")
## 2. Para X = 50%  (Humedad media) : Y = 22.6704 °C
cat("3. Para X = 100% (Aire saturado) : Y =", temp_max_x, "°C\n\n")
## 3. Para X = 100% (Aire saturado) : Y = 15.7393 °C
cat(" Análisis de Validez Física \n")
##  Análisis de Validez Física
if (temp_max_x >= -50 && temp_min_x <= 60) {
  cat("Conclusión: El modelo NO genera valores asintóticos imposibles ni temperaturas fuera\n")
  cat("del rango terrestre coherente dentro del dominio D_x = [0, 100]. Por tanto, el modelo\n")
  cat("es matemáticamente válido y continuo en todo su dominio real de aplicación.\n")
} else {
  cat("Advertencia: El modelo requiere restricciones adicionales en sus límites.\n")
}
## Conclusión: El modelo NO genera valores asintóticos imposibles ni temperaturas fuera
## del rango terrestre coherente dentro del dominio D_x = [0, 100]. Por tanto, el modelo
## es matemáticamente válido y continuo en todo su dominio real de aplicación.

10. Estimaciones

# Evaluamos el modelo polinómico de 3° grado respondiendo a dos preguntas 

# Pregunta 1: ¿Cuál es la temperatura promedio estimada para una ciudad 
# que registra un nivel de humedad del 65%?

Humedad_cant <- 65
Temp_cant <- round(f_temp(Humedad_cant), 2)

cat("Para una humedad de:", Humedad_cant, "%\n")
## Para una humedad de: 65 %
cat("La temperatura estimada es de:", Temp_cant, "°C\n")
## La temperatura estimada es de: 21.22 °C
# Gráfico Cantidad
plot(
  x_simplificado, y_simplificado, type = "p", pch = 16, col = "darkcyan",
  main = "Estimación Cantidad: Temperatura °C para Humedad del 65%",
  xlab = "Humedad (%)", ylab = "Temperatura °C Promedio"
)
lines(x_seq, y_pred_seq, col = "red", lwd = 2.5)
segments(x0 = Humedad_cant, y0 = min(y_simplificado) - 5, x1 = Humedad_cant, y1 = Temp_cant, col = "blue", lty = 2, lwd = 1.5)
segments(x0 = min(x_simplificado) - 5, y0 = Temp_cant, x1 = Humedad_cant, y1 = Temp_cant, col = "blue", lty = 2, lwd = 1.5)
points(Humedad_cant, Temp_cant, col = "blue", pch = 19, cex = 1.8)
text(Humedad_cant, min(y_simplificado), labels = paste0(Humedad_cant, "%"), pos = 4, col = "blue", font = 2)
text(min(x_simplificado), Temp_cant + 1, labels = paste0(Temp_cant, " °C"), pos = 4, col = "blue", font = 2)

# Pregunta 2: ¿Qué tan bien describe el modelo la sensibilidad térmica 
# en humedades bajas vs. altas?
# ------------------------------------------------------------------------------

temp_20 <- f_temp(20)
temp_30 <- f_temp(30)
cambio_bajo <- round(temp_30 - temp_20, 2)

temp_70 <- f_temp(70)
temp_80 <- f_temp(80)
cambio_alto <- round(temp_80 - temp_70, 2)

cat("Cambio de temperatura en rango de humedad baja (20% a 30%):", cambio_bajo, "°C\n")
## Cambio de temperatura en rango de humedad baja (20% a 30%): -2.48 °C
cat("Cambio de temperatura en rango de humedad alta (70% a 80%):", cambio_alto, "°C\n")
## Cambio de temperatura en rango de humedad alta (70% a 80%): -1.15 °C
# Gráfico Calidad
plot(
  x_simplificado, y_simplificado, type = "p", pch = 16, col = "darkgray",
  main = "Estimación Calidad: Comparación de Sensibilidad Térmica",
  xlab = "Humedad (%)", ylab = "Temperatura °C Promedio"
)
lines(x_seq, y_pred_seq, col = "red", lwd = 2.5)
segments(x0 = 20, y0 = temp_20, x1 = 30, y1 = temp_30, col = "darkgreen", lwd = 4)
segments(x0 = 70, y0 = temp_70, x1 = 80, y1 = temp_80, col = "darkorange", lwd = 4)
legend("topright", legend = c("Modelo Cúbico", "Tramo Humedad Baja (20-30%)", "Tramo Humedad Alta (70-80%)"),
       col = c("red", "darkgreen", "darkorange"), lty = 1, lwd = 3, bty = "n")

11. Conclusiones

Entre la Humedad (%) y la Temperatura (°C) existe una relación de tipo polinómica, representada por el modelo \(y = 36.2516 - 0.5332x + 0.0072x^2 - 0.000042x^3\). El modelo presenta restricciones físicas en sus extremos, por lo cual, el modelo indica que la Temperatura disminuye de forma progresiva con el incremento de la Humedad.