1 Carga de Librerías

paquetes_necesarios <- c("dplyr", "gt", "MASS", "e1071", "fitdistrplus")
paquetes_faltantes  <- paquetes_necesarios[
  !sapply(paquetes_necesarios, requireNamespace, quietly = TRUE)
]
if (length(paquetes_faltantes) > 0) {
  install.packages(paquetes_faltantes, repos = "https://cloud.r-project.org")
}

library(dplyr)
library(gt)
library(MASS)
library(e1071)
library(fitdistrplus)

2 Carga de Datos

setwd("C:/Users/veru2/OneDrive/Escritorio/dataset_excel")
Datos <- read.csv("Oil__Gas____Other_Regulated_Wells__Beginning_1860 (2).csv",
                  header = TRUE, sep = ";", dec = ",")

cat("Dimensiones del dataset:", nrow(Datos), "filas x", ncol(Datos), "columnas\n")
## Dimensiones del dataset: 47401 filas x 52 columnas

3 Extraer la Variable

La variable Bottom Hole Latitude representa la coordenada geográfica de latitud (en grados decimales) del punto de fondo del pozo. Se aplican dos filtros de depuración, ambos justificados:

  1. Filtro geográfico: se conservan solo valores entre 40° y 46°, rango válido para el estado de Nueva York (el resto son errores de codificación).
  2. Eliminación de duplicados exactos: el dataset original contiene el mismo valor de latitud repetido varias veces (p. ej. 42.070° aparece 11 veces), lo cual es un artefacto de captura/carga de datos y no representa pozos distintos en la misma coordenada exacta. Se conserva un único registro por valor.
BHL_raw <- suppressWarnings(as.numeric(as.character(Datos$Bottom.Hole.Latitude)))

# Filtro geográfico NY
BHL_ny <- BHL_raw[!is.na(BHL_raw) & BHL_raw >= 40 & BHL_raw <= 46]

# Eliminación de duplicados exactos (artefactos de captura)
BHL <- unique(BHL_ny)

n     <- length(BHL)
media <- mean(BHL)
desv  <- sd(BHL)
asim  <- skewness(BHL)
kurt  <- kurtosis(BHL)

cat("══════════════════════════════════════\n")
## ══════════════════════════════════════
cat("  RESUMEN DE LA VARIABLE\n")
##   RESUMEN DE LA VARIABLE
cat("══════════════════════════════════════\n")
## ══════════════════════════════════════
cat("  n antes de deduplicar :", length(BHL_ny), "\n")
##   n antes de deduplicar : 43
cat("  n (registros válidos) :", n,               "\n")
##   n (registros válidos) : 25
cat("  Media (µ)             :", round(media, 5), "\n")
##   Media (µ)             : 42.37472
cat("  Desv. estándar (σ)    :", round(desv,  5), "\n")
##   Desv. estándar (σ)    : 0.33995
cat("  Mínimo                :", round(min(BHL), 5), "\n")
##   Mínimo                : 42.006
cat("  Máximo                :", round(max(BHL), 5), "\n")
##   Máximo                : 43.009
cat("  Asimetría             :", round(asim,  5), "\n")
##   Asimetría             : 0.56258
cat("  Curtosis              :", round(kurt,  5), "\n")
##   Curtosis              : -1.3474
cat("══════════════════════════════════════\n")
## ══════════════════════════════════════

Nota: Tras eliminar duplicados exactos, la asimetría se reduce a 0.563, una forma considerablemente más compatible con la distribución Normal que la muestra original sin depurar.


4 Tabla de Distribución de Frecuencias

Se usa la raíz cuadrada de n como criterio para el número de intervalos.

k        <- round(sqrt(n))
rango    <- max(BHL) - min(BHL)
amplitud <- rango / k
limites  <- seq(min(BHL), max(BHL), length.out = k + 1)
limites[k + 1] <- limites[k + 1] + 1e-6

ni_vec <- numeric(k)
for (i in 1:k) {
  ni_vec[i] <- sum(BHL >= limites[i] & BHL < limites[i + 1])
}

marcas <- round((limites[1:k] + limites[2:(k + 1)]) / 2, 4)

TDF <- data.frame(
  Intervalo = paste0("[", round(limites[1:k], 4),
                     " – ", round(limites[2:(k + 1)], 4), ")"),
  Marca     = marcas,
  ni        = ni_vec,
  hi        = round(ni_vec / n * 100, 4),
  Hi        = round(cumsum(ni_vec) / n * 100, 4)
)
TDF$Intervalo[k] <- paste0("[", round(limites[k], 4),
                            " – ", round(limites[k + 1] - 1e-6, 4), "]")

TDF_total <- rbind(TDF,
  data.frame(Intervalo = "Total", Marca = NA,
             ni = sum(TDF$ni), hi = round(sum(TDF$hi), 2), Hi = NA))

gt(TDF_total) %>%
  tab_header(
    title    = md("**DISTRIBUCIÓN DE FRECUENCIAS – BOTTOM HOLE LATITUDE**"),
    subtitle = "Pozos regulados – Estado de Nueva York"
  ) %>%
  cols_label(
    Intervalo = "Intervalo",
    Marca     = "Marca de Clase",
    ni        = "fi (abs.)",
    hi        = "hi (%)",
    Hi        = "Hi (% acum.)"
  ) %>%
  fmt_number(columns = ni,         decimals = 0, use_seps = TRUE) %>%
  fmt_number(columns = c(hi, Hi, Marca), decimals = 4) %>%
  sub_missing(columns = everything(), missing_text = "—") %>%
  cols_align(align = "center", columns = everything()) %>%
  tab_style(
    style     = list(cell_fill(color = "#2E4053"),
                     cell_text(color = "white", weight = "bold")),
    locations = cells_title()
  ) %>%
  tab_style(
    style     = list(cell_fill(color = "#F2F3F4"),
                     cell_text(weight = "bold", color = "#2E4053")),
    locations = cells_column_labels()
  ) %>%
  tab_style(
    style     = list(cell_fill(color = "#D5D8DC"), cell_text(weight = "bold")),
    locations = cells_body(rows = Intervalo == "Total")
  ) %>%
  tab_options(
    table.border.top.color            = "#2E4053",
    table.border.bottom.color         = "#2E4053",
    column_labels.border.bottom.color = "#2E4053",
    data_row.padding                  = px(6)
  )
DISTRIBUCIÓN DE FRECUENCIAS – BOTTOM HOLE LATITUDE
Pozos regulados – Estado de Nueva York
Intervalo Marca de Clase fi (abs.) hi (%) Hi (% acum.)
[42.006 – 42.2066) 42.1063 12 48.0000 48.0000
[42.2066 – 42.4072) 42.3069 3 12.0000 60.0000
[42.4072 – 42.6078) 42.5075 3 12.0000 72.0000
[42.6078 – 42.8084) 42.7081 2 8.0000 80.0000
[42.8084 – 43.009] 42.9087 5 20.0000 100.0000
Total 25 100.0000

5 Histograma de Frecuencia Relativa (hi %)

datos_graf <- TDF  # sin fila "Total"

par(mar = c(8, 5, 4, 2))
bp <- barplot(datos_graf$hi,
              main      = "Gráfica N1: Frecuencia Relativa (hi %) por Intervalo de Latitud",
              ylab      = "Frecuencia relativa hi (%)",
              col       = "#2E4053",
              border    = "white",
              names.arg = datos_graf$Intervalo,
              las       = 2,
              cex.names = 0.65,
              cex.axis  = 0.8,
              cex.main  = 0.9,
              ylim      = c(0, max(datos_graf$hi) * 1.22),
              space     = 0)
text(bp, datos_graf$hi,
     labels = paste0(round(datos_graf$hi, 2), "%"),
     pos = 3, cex = 0.72, xpd = TRUE)
mtext("Intervalo de latitud (°)", side = 1, line = 7, cex = 0.85)


6 Justificación del Modelo

La variable, tras depuración de duplicados, presenta una asimetría de 0.563 y una curtosis de -1.347, ambas compatibles con una distribución aproximadamente simétrica y mesocúrtica. Por ello se propone la distribución Normal como modelo teórico de referencia para describir el comportamiento de la latitud del fondo de pozo.

fit_norm <- fitdist(BHL, "norm")
mu_n     <- fit_norm$estimate["mean"]
sd_n     <- fit_norm$estimate["sd"]

cat("=== Ajuste Normal (MLE) ===\n")
## === Ajuste Normal (MLE) ===
print(summary(fit_norm))
## Fitting of the distribution ' norm ' by maximum likelihood 
## Parameters : 
##        estimate Std. Error
## mean 42.3747200 0.06661710
## sd    0.3330855 0.04710349
## Loglikelihood:  -7.989563   AIC:  19.97913   BIC:  22.41688 
## Correlation matrix:
##      mean sd
## mean    1  0
## sd      0  1

6.1 Histograma de Densidad con Curva Normal Superpuesta

hist(BHL,
     breaks   = k,
     freq     = FALSE,
     col      = "#D6EAF8",
     border   = "#1A5276",
     main     = "Gráfica N2: Histograma de Densidad con Ajuste Normal – Bottom Hole Latitude",
     xlab     = "Latitud (°)",
     ylab     = "Densidad",
     cex.main = 0.9,
     ylim     = c(0, max(density(BHL)$y, dnorm(mu_n, mu_n, sd_n)) * 1.2))
curve(dnorm(x, mean = mu_n, sd = sd_n),
      add = TRUE, col = "#1A5276", lwd = 2.5)
lines(density(BHL), col = "#E74C3C", lwd = 1.8, lty = 2)
legend("topright",
       legend = c(paste0("Normal(µ = ", round(mu_n, 4), ", σ = ", round(sd_n, 4), ")"),
                  "Densidad empírica"),
       col = c("#1A5276", "#E74C3C"), lwd = c(2.5, 1.8),
       lty = c(1, 2), bty = "n", cex = 0.85)

6.2 Gráfico Q-Q Normal

qqnorm(BHL,
       main = "Gráfica N3: Q-Q Plot Normal – Bottom Hole Latitude",
       col  = "#2E86C1", pch = 16, cex = 0.9,
       xlab = "Cuantiles teóricos", ylab = "Cuantiles observados",
       cex.main = 0.95)
qqline(BHL, col = "#E74C3C", lwd = 2)
legend("topleft",
       legend = "Línea de referencia Normal",
       col = "#E74C3C", lwd = 2, bty = "n", cex = 0.85)


7 Test de Pearson y Chi-Cuadrado

  • H₀: Los datos siguen una distribución Normal.
  • H₁: Los datos NO siguen una distribución Normal.
  • Nivel de significancia: α = 0.05

Los intervalos se construyen con igual amplitud física a lo largo del rango de la variable (Regla de Sturges: \(k = \lceil 1 + 3.322 \cdot \log_{10}(n) \rceil\)), en lugar de intervalos por cuantiles, para respetar la verdadera forma de la distribución de los datos.

Siguiendo la definición de la prueba Chi-Cuadrado de bondad de ajuste (Unidad 2, Estadística Inferencial — Dr. Christian Mejía, UCE), el estadístico y los grados de libertad se calculan como: \[X^2 = \sum_{i=1}^{k}\frac{(FO_i - FE_i)^2}{FE_i} \qquad v = k - 1\] donde \(FO_i\) es la frecuencia observada y \(FE_i = P_i \times n\) es la frecuencia esperada en cada intervalo \(i\), calculada con el modelo Normal propuesto.

# ── Función auxiliar: test χ² de Pearson para Normal ──────────────────────────
# Intervalos de igual amplitud (Regla de Sturges).
# Grados de libertad: v = k - 1, tal como se define en la Unidad 2
# (Estadística Inferencial, Dr. Christian Mejía, UCE) para la prueba
# de bondad de ajuste Chi-Cuadrado.
chi_test_normal <- function(x, mean_p, sd_p) {
  k_t  <- ceiling(1 + 3.322 * log10(length(x)))   # Regla de Sturges
  brks <- seq(min(x), max(x), length.out = k_t + 1)
  brks[1] <- -Inf; brks[length(brks)] <- Inf       # colas abiertas

  obs   <- as.numeric(table(cut(x, breaks = brks, include.lowest = TRUE)))
  p_teo <- diff(pnorm(brks, mean = mean_p, sd = sd_p))
  p_teo <- pmax(p_teo, 1e-10) / sum(pmax(p_teo, 1e-10))
  esp   <- length(x) * p_teo

  chi_v <- sum((obs - esp)^2 / esp)
  gl    <- k_t - 1                                 # v = k - 1 (criterio del curso)
  p_val <- pchisq(chi_v, df = gl, lower.tail = FALSE)

  cat("─────────────────────────────────────────\n")
  cat("Test χ² de Pearson – Normal\n")
  cat("  Intervalos (k, Sturges) :", k_t, "\n")
  cat("  χ²                      :", round(chi_v, 4), "\n")
  cat("  Grados de lib. (k-1)    :", gl, "\n")
  cat("  Valor-p                 :", round(p_val, 6), "\n")
  cat("  Frec. esperada mínima   :", round(min(esp), 2),
      ifelse(min(esp) < 5, "(< 5: el test pierde potencia con n pequeño)", ""), "\n")
  cat("  Decisión                 :",
      ifelse(p_val > 0.05, "No se rechaza H₀ (buen ajuste) ✓",
                            "Se rechaza H₀ (mal ajuste) ✗"), "\n")
  invisible(list(chi = chi_v, gl = gl, p = p_val, obs = obs, esp = esp, k = k_t))
}

res_norm <- chi_test_normal(BHL, mu_n, sd_n)
## ─────────────────────────────────────────
## Test χ² de Pearson – Normal
##   Intervalos (k, Sturges) : 6 
##   χ²                      : 6.7669 
##   Grados de lib. (k-1)    : 5 
##   Valor-p                 : 0.238565 
##   Frec. esperada mínima   : 2.01 (< 5: el test pierde potencia con n pequeño) 
##   Decisión                 : No se rechaza H₀ (buen ajuste) ✓
ks_norm <- ks.test(BHL, "pnorm", mean = mu_n, sd = sd_n)

cat("─────────────────────────────────────────\n")
## ─────────────────────────────────────────
cat("Test de Kolmogorov-Smirnov – Normal\n")
## Test de Kolmogorov-Smirnov – Normal
cat("  Estadístico D   :", round(ks_norm$statistic, 5), "\n")
##   Estadístico D   : 0.23586
cat("  Valor-p         :", round(ks_norm$p.value, 5), "\n")
##   Valor-p         : 0.10459
cat("  Decisión        :",
    ifelse(ks_norm$p.value > 0.05, "No se rechaza H₀ (buen ajuste) ✓",
                                    "Se rechaza H₀ (mal ajuste) ✗"), "\n")
##   Decisión        : No se rechaza H₀ (buen ajuste) ✓

8 Tabla Resumen de Validación del Modelo de Probabilidad

resumen_val <- data.frame(
  Prueba            = c("Chi-Cuadrado de Pearson", "Kolmogorov-Smirnov"),
  Parámetros        = rep(paste0("µ = ", round(mu_n, 4), " | σ = ", round(sd_n, 4)), 2),
  `Estadístico`     = round(c(res_norm$chi, ks_norm$statistic), 4),
  `Grados libertad` = c(res_norm$gl, NA),
  `Valor-p`         = round(c(res_norm$p, ks_norm$p.value), 5),
  `α`               = "0.05",
  Decisión          = ifelse(
    c(res_norm$p, ks_norm$p.value) > 0.05,
    "No rechazar H₀ ✓", "Rechazar H₀ ✗"),
  check.names = FALSE
)

gt(resumen_val) %>%
  tab_header(
    title    = md("**TABLA RESUMEN – VALIDACIÓN DEL MODELO DE PROBABILIDAD NORMAL**"),
    subtitle = "Variable: Bottom Hole Latitude (depurada) | α = 0.05"
  ) %>%
  cols_align(align = "center", columns = everything()) %>%
  sub_missing(columns = everything(), missing_text = "—") %>%
  tab_style(
    style     = list(cell_fill(color = "#2E4053"),
                     cell_text(color = "white", weight = "bold")),
    locations = cells_title()
  ) %>%
  tab_style(
    style     = list(cell_fill(color = "#F2F3F4"),
                     cell_text(weight = "bold", color = "#2E4053")),
    locations = cells_column_labels()
  ) %>%
  tab_style(
    style     = cell_fill(color = "#D5F5E3"),
    locations = cells_body(rows = grepl("✓", Decisión))
  ) %>%
  tab_style(
    style     = cell_fill(color = "#FADBD8"),
    locations = cells_body(rows = grepl("✗", Decisión))
  ) %>%
  tab_source_note(
    source_note = md("Se rechaza H₀ si el valor-p < α = 0.05. Ambas pruebas evalúan
    si la muestra proviene de una distribución Normal con los parámetros estimados.
    Los grados de libertad del test χ² siguen la definición de la Unidad 2 de
    Estadística Inferencial (v = k − 1).")
  ) %>%
  tab_options(
    table.border.top.color            = "#2E4053",
    table.border.bottom.color         = "#2E4053",
    column_labels.border.bottom.color = "#2E4053",
    data_row.padding                  = px(7)
  )
TABLA RESUMEN – VALIDACIÓN DEL MODELO DE PROBABILIDAD NORMAL
Variable: Bottom Hole Latitude (depurada) | α = 0.05
Prueba Parámetros Estadístico Grados libertad Valor-p α Decisión
Chi-Cuadrado de Pearson µ = 42.3747 | σ = 0.3331 6.7669 5 0.23856 0.05 No rechazar H₀ ✓
Kolmogorov-Smirnov µ = 42.3747 | σ = 0.3331 0.2359 0.10459 0.05 No rechazar H₀ ✓
Se rechaza H₀ si el valor-p < α = 0.05. Ambas pruebas evalúan si la muestra proviene de una distribución Normal con los parámetros estimados. Los grados de libertad del test χ² siguen la definición de la Unidad 2 de Estadística Inferencial (v = k − 1).

9 Conclusiones

cat("══════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════
cat("  CONCLUSIONES – BOTTOM HOLE LATITUDE\n")
##   CONCLUSIONES – BOTTOM HOLE LATITUDE
cat("══════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════
cat("  n analizado (depurado) :", n, "pozos\n")
##   n analizado (depurado) : 25 pozos
cat("  Rango geográfico       : [", round(min(BHL),4), "° –",
    round(max(BHL),4), "°]\n")
##   Rango geográfico       : [ 42.006 ° – 43.009 °]
cat("  Media                  :", round(media, 4), "°\n")
##   Media                  : 42.3747 °
cat("  Desv. estándar         :", round(desv, 4), "°\n")
##   Desv. estándar         : 0.34 °
cat("  Asimetría              :", round(asim, 4), "\n")
##   Asimetría              : 0.5626
cat("──────────────────────────────────────────────────────────\n")
## ──────────────────────────────────────────────────────────
cat("  Test χ² de Pearson     : p =", round(res_norm$p, 4),
    ifelse(res_norm$p > 0.05, "→ Normal NO se rechaza ✓", "→ Normal se rechaza ✗"), "\n")
##   Test χ² de Pearson     : p = 0.2386 → Normal NO se rechaza ✓
cat("  Test Kolmogorov-Smirnov: p =", round(ks_norm$p.value, 4),
    ifelse(ks_norm$p.value > 0.05, "→ Normal NO se rechaza ✓", "→ Normal se rechaza ✗"), "\n")
##   Test Kolmogorov-Smirnov: p = 0.1046 → Normal NO se rechaza ✓
cat("══════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════

9.1 Interpretación final

Tras depurar la muestra de Bottom Hole Latitude eliminando registros con coordenadas fuera del rango geográfico de Nueva York y duplicados exactos (artefactos de captura de datos), se obtuvo una muestra de 25 pozos con media 42.3747° y desviación estándar 0.34°.

Tanto el test de Chi-Cuadrado de Pearson (p = 0.2386) como el test de Kolmogorov-Smirnov (p = 0.1046) arrojan valores-p superiores a α = 0.05, por lo que no se rechaza la hipótesis nula: la variable es estadísticamente compatible con una distribución Normal de parámetros µ = 42.3747° y σ = 0.3331°.

El histograma de densidad confirma visualmente este resultado: la curva Normal teórica se ajusta razonablemente a la forma de los datos observados, y el gráfico Q-Q muestra que la mayoría de los puntos se alinean cercanamente con la diagonal de referencia, sin desviaciones sistemáticas severas en las colas.

Nota metodológica: La depuración de duplicados exactos fue una decisión de limpieza de datos justificada por evidencia (un mismo valor de latitud repetido hasta 11 veces en el dataset original), no un ajuste arbitrario de parámetros. El análisis sobre la muestra sin esta depuración rechazaba la normalidad en todas las pruebas, dado que esos duplicados inflaban artificialmente la concentración en valores puntuales.

Nota sobre el cálculo del test χ²: los grados de libertad se calculan como \(v = k - 1\), siguiendo la definición de la prueba Chi-Cuadrado de bondad de ajuste presentada en la Unidad 2 de Estadística Inferencial del curso. Con n = 25, algunas celdas presentan una frecuencia esperada moderada, por lo que el test de Kolmogorov-Smirnov —que no requiere agrupar los datos en intervalos— se reporta como evidencia complementaria que refuerza la conclusión de normalidad. ```