# Modelo Log Normal
# Varianble Continua
#Autor: Ariana Viteri
#Fecha:11/06/2026

0.- Carga de Librerias

library(gt)
## Warning: package 'gt' was built under R version 4.5.3
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.5.3
## 
## 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

1.- Carga de Datos

# ===== CARGA DE DATOS =====
datos <- read.csv("~/semestre 3 y 4/Estadistica/Datos Cambiados.csv",
                  header = TRUE, dec = ".", sep = ",")

2.- Variable Aleatoria

#----------------------------------------
# Selección de variable
ozono<- datos$O3

# NOTA: Asumimos que la columna se llama 'O3'
ozono <- datos$O3[datos$O3 != "-"]

# Conversión a numérico
ozono <- as.numeric(ozono)

3.- Extraer TDF y GDF

# 1. Calculamos el rango y definimos la amplitud necesaria para 10 intervalos
min_oz <- floor(min(ozono, na.rm = TRUE))
max_oz <- ceiling(max(ozono, na.rm = TRUE))

# Redondeamos la amplitud hacia arriba para asegurar que cubra el rango en 10 pasos
A_ajustada <- ceiling((max_oz - min_oz) / 10)

# 2. Definimos los cortes (breaks) forzando los 10 intervalos
mis_breaks <- seq(from = min_oz, by = A_ajustada, length.out = 11)

# 3. Generamos el histograma con esos breaks
histo_ozono <- hist(ozono, breaks = mis_breaks, plot = FALSE)

# 4. Extraemos límites y calculamos estadísticas
Li2 <- histo_ozono$breaks[1:(length(histo_ozono$breaks)-1)]
Ls2 <- histo_ozono$breaks[2:length(histo_ozono$breaks)]

MC2 <- (Li2 + Ls2) / 2

ni2 <- histo_ozono$counts
hi2 <- (ni2 / sum(ni2)) * 100

Ni2_asc <- cumsum(ni2)
Hi2_asc <- cumsum(hi2)

Ni2_desc <- rev(cumsum(rev(ni2)))
Hi2_desc <- rev(cumsum(rev(hi2)))

# 5. Creamos los intervalos como texto
Intervalo2 <- paste0("[", Li2, " - ", Ls2, ")")

Intervalo2[length(Intervalo2)] <- paste0(
  "[",
  Li2[length(Li2)],
  " - ",
  Ls2[length(Ls2)],
  "]"
)

# 6. Construcción de la tabla
TDF_ozono_final <- data.frame(
  Intervalo = Intervalo2,
  MC = MC2,
  ni = ni2,
  hi = round(hi2, 2),
  Ni_asc = Ni2_asc,
  Hi_asc = round(Hi2_asc, 2),
  Ni_desc = Ni2_desc,
  Hi_desc = round(Hi2_desc, 2)
)

# 7. Totales
totaless <- data.frame(
  Intervalo = "Totales",
  MC = "-",
  ni = sum(ni2),
  hi = sum(hi2),
  Ni_asc = "-",
  Hi_asc = "-",
  Ni_desc = "-",
  Hi_desc = "-"
)

TDF_ozono_final <- rbind(TDF_ozono_final, totaless)

# 8. Tabla con gt()
TDF_ozono_final %>%
  gt() %>%
  tab_header(
    title = md("*Tabla Nro. 1*"),
    subtitle = md("*Distribución de frecuencia simplificada de concentración de Ozono, estudio calidad del aire en India 2015-2020*")
  ) %>%
  tab_source_note(
    source_note = md("Autor: Grupo 1 <br> Fuente: https://www.kaggle.com/datasets/rohanrao/air-quality-data-in-india")
  ) %>%
  tab_style(
    style = cell_borders(sides = c("left", "right"), color = "black", weight = px(2)),
    locations = cells_body()
  ) %>%
  tab_style(
    style = cell_borders(sides = c("left", "right"), color = "black", weight = px(2)),
    locations = cells_column_labels()
  ) %>%
  tab_options(
    table.border.top.color = "black",
    table.border.bottom.color = "black",
    table.border.top.style = "solid",
    table.border.bottom.style = "solid",
    column_labels.border.top.color = "black",
    column_labels.border.bottom.color = "black",
    column_labels.border.bottom.width = px(2),
    row.striping.include_table_body = TRUE,
    heading.border.bottom.color = "black",
    heading.border.bottom.width = px(2),
    table_body.hlines.color = "gray",
    table_body.border.bottom.color = "black"
  )
Tabla Nro. 1
Distribución de frecuencia simplificada de concentración de Ozono, estudio calidad del aire en India 2015-2020
Intervalo MC ni hi Ni_asc Hi_asc Ni_desc Hi_desc
[0 - 26) 13 10183 39.92 10183 39.92 25509 100
[26 - 52) 39 10801 42.34 20984 82.26 15326 60.08
[52 - 78) 65 3493 13.69 24477 95.95 4525 17.74
[78 - 104) 91 759 2.98 25236 98.93 1032 4.05
[104 - 130) 117 200 0.78 25436 99.71 273 1.07
[130 - 156) 143 53 0.21 25489 99.92 73 0.29
[156 - 182) 169 16 0.06 25505 99.98 20 0.08
[182 - 208) 195 3 0.01 25508 100 4 0.02
[208 - 234) 221 0 0.00 25508 100 1 0
[234 - 260] 247 1 0.00 25509 100 1 0
Totales - 25509 100.00 - - - -
Autor: Grupo 1
Fuente: https://www.kaggle.com/datasets/rohanrao/air-quality-data-in-india
# Histograma que genera r studio porcentual
plot(histo_ozono,
     freq = FALSE,
     main = "Gráfica Nro. 1\nDistribución porcentual de concentración de Ozono\nen el estudio calidad del aire en India de 2015-2020",
     xlab = "Ozono (µg/m³)",
     ylab = "Porcentaje ",
     xaxt = "n",
     cex.main = 1,
     ylim = c(0, max(hi2)))

# Cuadrícula
abline(v = mis_breaks, col = "gray70", lty = 2, lwd = 0.8)
abline(h = axTicks(2), col = "gray70", lty = 2, lwd = 0.8)

# Barras porcentuales
rect(histo_ozono$breaks[-length(histo_ozono$breaks)],
     0,
     histo_ozono$breaks[-1],
     hi2, #hi2 es hi simplificado 
     col = "darkseagreen3",
     border = "black")

# Eje X personalizado
axis(1,
     at = mis_breaks,
     labels = round(mis_breaks, 0),
     las = 1,
     cex.axis = 0.9)

TDF Y GDF ajustado

# Recurrimos a un ajuste de tabla para ignorar los outlers que estan en los intervalos superiores y no    afecten en nuestro modelo
# ==========================================
# TABLA Nro. 2 (Intervalos hasta 130)
# ==========================================

# Mantener solo valores entre 0 y 130
ozono <- ozono[ozono <= 130]

# Definir límites manuales
mis_breaks <- seq(0, 130, by = 10)

# Crear histograma sin graficar
histo_ozono <- hist(
  ozono,
  breaks = mis_breaks,
  plot = FALSE,
  right = FALSE
)

# Límites inferiores y superiores
Li2 <- histo_ozono$breaks[-length(histo_ozono$breaks)]
Ls2 <- histo_ozono$breaks[-1]

# Marca de clase
MC2 <- (Li2 + Ls2) / 2

# Frecuencias
ni2 <- histo_ozono$counts
hi2 <- (ni2 / sum(ni2)) * 100

Ni2_asc <- cumsum(ni2)
Hi2_asc <- cumsum(hi2)

Ni2_desc <- rev(cumsum(rev(ni2)))
Hi2_desc <- rev(cumsum(rev(hi2)))

# Intervalos
Intervalo2 <- paste0("[", Li2, " - ", Ls2, ")")

Intervalo2[length(Intervalo2)] <- paste0(
  "[",
  Li2[length(Li2)],
  " - ",
  Ls2[length(Ls2)],
  "]"
)

# Construcción de la tabla
TDF_ozono_final <- data.frame(
  Intervalo = Intervalo2,
  MC = MC2,
  ni = ni2,
  hi = round(hi2, 2),
  Ni_asc = Ni2_asc,
  Hi_asc = round(Hi2_asc, 2),
  Ni_desc = Ni2_desc,
  Hi_desc = round(Hi2_desc, 2)
)

# Totales
totaless <- data.frame(
  Intervalo = "Totales",
  MC = "-",
  ni = sum(ni2),
  hi = round(sum(hi2), 2),
  Ni_asc = "-",
  Hi_asc = "-",
  Ni_desc = "-",
  Hi_desc = "-"
)

TDF_ozono_final <- rbind(TDF_ozono_final, totaless)

# Tabla con gt()
TDF_ozono_final %>%
  gt() %>%
  tab_header(
    title = md("*Tabla Nro. 2*"),
    subtitle = md("*Distribución de frecuencia específica de concentración de Ozono, estudio calidad del aire en India 2015-2020*")
  ) %>%
  tab_source_note(
    source_note = md("Autor: Grupo 1 <br> Fuente: https://www.kaggle.com/datasets/rohanrao/air-quality-data-in-india")
  ) %>%
  tab_style(
    style = cell_borders(
      sides = c("left", "right"),
      color = "black",
      weight = px(2)
    ),
    locations = cells_body()
  ) %>%
  tab_style(
    style = cell_borders(
      sides = c("left", "right"),
      color = "black",
      weight = px(2)
    ),
    locations = cells_column_labels()
  ) %>%
  tab_options(
    table.border.top.color = "black",
    table.border.bottom.color = "black",
    table.border.top.style = "solid",
    table.border.bottom.style = "solid",
    column_labels.border.top.color = "black",
    column_labels.border.bottom.color = "black",
    column_labels.border.bottom.width = px(2),
    row.striping.include_table_body = TRUE,
    heading.border.bottom.color = "black",
    heading.border.bottom.width = px(2),
    table_body.hlines.color = "gray",
    table_body.border.bottom.color = "black"
  )
Tabla Nro. 2
Distribución de frecuencia específica de concentración de Ozono, estudio calidad del aire en India 2015-2020
Intervalo MC ni hi Ni_asc Hi_asc Ni_desc Hi_desc
[0 - 10) 5 2310 9.08 2310 9.08 25436 100
[10 - 20) 15 4708 18.51 7018 27.59 23126 90.92
[20 - 30) 25 5323 20.93 12341 48.52 18418 72.41
[30 - 40) 35 4683 18.41 17024 66.93 13095 51.48
[40 - 50) 45 3430 13.48 20454 80.41 8412 33.07
[50 - 60) 55 2218 8.72 22672 89.13 4982 19.59
[60 - 70) 65 1229 4.83 23901 93.97 2764 10.87
[70 - 80) 75 669 2.63 24570 96.6 1535 6.03
[80 - 90) 85 365 1.43 24935 98.03 866 3.4
[90 - 100) 95 236 0.93 25171 98.96 501 1.97
[100 - 110) 105 137 0.54 25308 99.5 265 1.04
[110 - 120) 115 87 0.34 25395 99.84 128 0.5
[120 - 130] 125 41 0.16 25436 100 41 0.16
Totales - 25436 100.00 - - - -
Autor: Grupo 1
Fuente: https://www.kaggle.com/datasets/rohanrao/air-quality-data-in-india
# Gráfica específica
plot(histo_ozono,
     freq = FALSE,
     main = "Gráfica Nro. 2\nDistribución porcentual específica de concentración de Ozono\nen el estudio calidad del aire en India de 2015-2020",
     xlab = "Ozono (µg/m³)",
     ylab = "Porcentaje",
     xaxt = "n",
     yaxt = "n",
     cex.main = 1,
     ylim = c(0, max(hi2) * 1.1))

# Cuadrícula
abline(v = mis_breaks, col = "gray70", lty = 2, lwd = 0.8)
abline(h = pretty(c(0, max(hi2))), col = "gray70", lty = 2, lwd = 0.8)

# Barras porcentuales
rect(
  histo_ozono$breaks[-length(histo_ozono$breaks)],
  0,
  histo_ozono$breaks[-1],
  hi2,
  col = "darkseagreen3",
  border = "black"
)

# Eje X
axis(1,
     at = mis_breaks,
     labels = mis_breaks,
     las = 1,
     cex.axis = 0.9)

# Eje Y en porcentaje
axis(2,
     at = pretty(c(0, max(hi2))),
     labels = round(pretty(c(0, max(hi2))), 1),
     las = 1)
box()

5.- Conjetura

#Que la concentración de ozono con intervalos especificos, sigue un modelo de probabilidad log-normal,   ya que la variable en su histograma presenta barras asimétricas con una mayor concentración de observaciones en intervalos bajos y una cola larga hacia valores altos, lo que indica una distribución sesgada    positiva.

6.- Gráfica de sobreposición

# Calculo probabilidad
#--------------------------------------
# Parámetros lognormales
mu <- mean(log(ozono))
sigma <- sd(log(ozono))

# Histograma porcentual (CORREGIDO: ahora sí usando hist())
hist(ozono,
     breaks = mis_breaks,
     freq = FALSE,
     col = NA,
     border = NA,
     main = "Gráfica Nro 3\nComparación de la Realidad y el Modelo Log-normal
          de la Concentración de Ozono en India 2015–2020",
     xlab = "Ozono (µg/m³)",
     ylab = "Densidad de probabilidad",
     xaxt = "n",
     yaxt = "n",
     ylim = c(0, max(hi2) * 1.25))

# Cuadrícula
abline(v = mis_breaks, col = "gray80", lty = 2)
abline(h = pretty(c(0, max(hi2))), col = "gray80", lty = 2)

# Barras (se mantienen igual)
rect(
  histo_ozono$breaks[-length(histo_ozono$breaks)],
  0,
  histo_ozono$breaks[-1],
  hi2,
  col = "lightblue1",
  border = "black"
)

# Curva lognormal convertida a porcentaje
x <- seq(min(ozono), max(ozono), length = 1000)

y <- dlnorm(x,
            meanlog = mu,
            sdlog = sigma) * 10 * 100

lines(x, y,
      col = "darkblue",
      lwd = 3)

# Ejes
axis(1,
     at = mis_breaks,
     labels = mis_breaks)

axis(2,
     at = pretty(c(0, max(hi2))),
     las = 1)
box()

7.-Test de Bondad

#--------------------------------------
# Teste de Person
#--------------------------------------
fo <- ni2
N <- length(ozono)

fe <- N * (plnorm(Ls2, mu, sigma) -
             plnorm(Li2, mu, sigma))

Coef_Pearson <- cor(fo, fe) * 100

Coef_Pearson
## [1] 93.96538
#-----------------------------------------
# Test Chi-cuadrado 
#--------------------------------------
# Total de datos
N <- sum(ni2)

# Frecuencia relativa observada
fo <- ni2 / N

# Número de intervalos
k <- length(fo)

# Probabilidades teóricas por intervalo (log-normal)
P <- c(0)

for (i in 1:k) {
  P[i] <- plnorm(Ls2[i], mu, sigma) - plnorm(Li2[i], mu, sigma)
}

fe <- P  # probabilidades esperadas

# Chi-cuadrado
Chi_Calculado <- sum((fo - fe)^2 / fe)

# Grados de libertad
gl <- k - 1

# Valor crítico (α = 0.05)
Chi_Critico <- qchisq(0.95, df = gl)

# Resultados
Chi_Calculado
## [1] 0.07827287
Chi_Critico
## [1] 21.02607
# Decisión
if (Chi_Calculado < Chi_Critico) {
  print("Evalua : El modelo log-normal es adecuado.")
} else {
  print("Se rechaza : El modelo log-normal no es adecuado.")
}
## [1] "Evalua : El modelo log-normal es adecuado."

8.-Calculo de probabilidad

# Calculo de probabilidad
#¿Cuál es la probabilidad de que la concentración de ozono  no supere los 30 µg/m³?
#--------------------------------------
# PROBABILIDAD: P(Ozono ≤ 30)

# Cálculo directo
prob_30 <- plnorm(30, mu, sigma) * 100
prob_30
## [1] 55.01148
#--------------------------------------
# DEMOSTRACIÓN GRÁFICA

# Rango completo de la distribución (usa tu data real)
x <- seq(min(ozono, na.rm = TRUE),
         max(ozono, na.rm = TRUE),
         by = 0.01)

y <- dlnorm(x, mu, sigma)
y <- y * 100 * A_ajustada

plot(x, y,
     col = "skyblue3",
     lwd = 2,
     type = "l",
     xlim = c(0, 130),
     ylim = c(0, max(y)),
     main = "Gráfica N°6: Cálculo de probabilidad",
     ylab = "Densidad de probabilidad",
     xlab = "Ozono (µg/m³)",
     xaxt = "n")

# Eje X
axis(1, at = seq(0, 130, by = 10), las = 1)


#--------------------------------------
# ÁREA DE PROBABILIDAD P(X ≤ 30)

x_section <- seq(0, 30, by = 0.01)
y_section <- dlnorm(x_section, mu, sigma)

# escalar a porcentaje también
y_section <- y_section * 100 * A_ajustada

lines(x_section, y_section, col = "darkgreen", lwd = 2)

polygon(c(x_section, rev(x_section)),
        c(y_section, rep(0, length(y_section))),
        col = rgb(0, 0.6, 0, 0.35),
        border = NA)

#--------------------------------------
# LEYENDA

legend("topright",
       legend = c("Modelo log-normal", "Area de probabilidad"),
       col = c("skyblue3", "darkgreen"),
       lwd = 2,
       bty = "n")
grid()

9 .- Conclusión

#--------------------------------------
# 10. INTERVALO DE CONFIANZA

# Media, desviación y tamaño
media <- mean(ozono, na.rm = TRUE)
sigma <- sd(ozono, na.rm = TRUE)
n <- length(ozono)

# Valor crítico t (más correcto que usar 2 fijo)
t_critico <- qt(0.975, df = n - 1)

# Error estándar
error <- t_critico * (sigma / sqrt(n))

# Límites del intervalo
limite_inferior <- round(media - error, 2)
limite_superior <- round(media + error, 2)

# Tabla final (dinámica, no texto fijo)
tabla_intervalo <- data.frame(
  Intervalo = paste0("P [", limite_inferior, " < μ < ", limite_superior, "] = 95%")
)

# Tabla gt
tabla_intervalo %>%
  gt() %>%
  tab_header(
    title = md("*Tabla Nro. 3*"),
    subtitle = md("Intervalo de confianza de la concentración de ozono, estudio calidad del aire en India 2015-2020")
  ) %>%
  tab_source_note(
    source_note = md("Autor: Grupo 1\nFuente: https://www.kaggle.com/datasets/rohanrao/air-quality-data-in-india")
  ) %>%
  tab_options(
    table.border.top.color = "black",
    table.border.bottom.color = "black",
    table.border.top.style = "solid",
    table.border.bottom.style = "solid",
    column_labels.border.top.color = "black",
    column_labels.border.bottom.color = "black",
    column_labels.border.bottom.width = px(2),
    row.striping.include_table_body = TRUE,
    heading.border.bottom.color = "black",
    heading.border.bottom.width = px(2),
    table_body.hlines.color = "gray",
    table_body.border.bottom.color = "black"
  )
Tabla Nro. 3
Intervalo de confianza de la concentración de ozono, estudio calidad del aire en India 2015-2020
Intervalo
P [33.91 < μ < 34.42] = 95%
Autor: Grupo 1 Fuente: https://www.kaggle.com/datasets/rohanrao/air-quality-data-in-india

En conclusión:

#La variable concentración de ozono en (µg/m³) se explica mediante un modelo log-normal con parámetros μ = 3.31 y σ = 0.80. Podemos afirmar con un 95% de confianza que la media de esta variable se encuentra aproximadamente entre 34.22 y 34.76 µg/m³, con una media observada de 34.49 µg/m³ y una desviación estándar de 21.69 µg/m³. Además, se evidencia una alta dispersión en los datos, lo que es consistente con una distribución asimétrica positiva característica de variables ambientales como el ozono.