# Modelo Log Normal
# Varianble Continua
#Autor: Ariana Viteri
#Fecha:11/06/2026
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
# ===== CARGA DE DATOS =====
datos <- read.csv("~/semestre 3 y 4/Estadistica/Datos Cambiados.csv",
header = TRUE, dec = ".", sep = ",")
#----------------------------------------
# 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)
# 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)
# 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()
#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.
# 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()
#--------------------------------------
# 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."
# 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()
#--------------------------------------
# 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.