datos <- read_csv("oil_and_gas_leases_data___2_.csv", show_col_types = FALSE)
# Rellenamiento de celdas faltantes (NA) con la media de cada variable numérica de interés
if (any(is.na(datos$YEARS_ACTIVE))) {
datos$YEARS_ACTIVE[is.na(datos$YEARS_ACTIVE)] <- mean(datos$YEARS_ACTIVE, na.rm = TRUE)
}
if (any(is.na(datos$CUMULATIVE_PRODUCTION))) {
datos$CUMULATIVE_PRODUCTION[is.na(datos$CUMULATIVE_PRODUCTION)] <- mean(datos$CUMULATIVE_PRODUCTION, na.rm = TRUE)
}
# Se descartan pozos con valores no positivos (no tienen sentido físico y
# además impiden aplicar logaritmos en los modelos exponencial/potencial)
datos <- datos %>%
filter(YEARS_ACTIVE > 0, CUMULATIVE_PRODUCTION > 0)
str(datos[, c("YEARS_ACTIVE", "CUMULATIVE_PRODUCTION")])## tibble [47,757 × 2] (S3: tbl_df/tbl/data.frame)
## $ YEARS_ACTIVE : num [1:47757] 55 55 47 20 28 55 20 48 48 55 ...
## $ CUMULATIVE_PRODUCTION: num [1:47757] 47225 275063 82624 7544 681006 ...
Variable causa (X): YEARS_ACTIVE — años
que el pozo ha permanecido activo.
Variable efecto (Y):
CUMULATIVE_PRODUCTION — producción acumulada del
pozo (barriles/pies³ equivalentes).
Justificación: la producción acumulada es, por
definición, la suma de la producción a lo largo del tiempo de vida del
pozo. Por lo tanto el tiempo de actividad (YEARS_ACTIVE) es
la variable que antecede y explica la magnitud de lo acumulado
(efecto), y no al revés: el pozo no produce más años porque acumuló más,
sino que acumula más precisamente porque lleva más años en producción.
Esto satisface la relación causa → efecto exigida.
max_x <- max(datos$YEARS_ACTIVE)
min_x <- min(datos$YEARS_ACTIVE)
max_y <- max(datos$CUMULATIVE_PRODUCTION)
min_y <- min(datos$CUMULATIVE_PRODUCTION)
resumen_general <- data.frame(
Variable = c("YEARS_ACTIVE (X)", "CUMULATIVE_PRODUCTION (Y)"),
Minimo = c(min_x, min_y),
Maximo = c(max_x, max_y),
Rango = c(max_x - min_x, max_y - min_y),
Media = c(mean(datos$YEARS_ACTIVE), mean(datos$CUMULATIVE_PRODUCTION))
)
kable(resumen_general, digits = 2, caption = "Resumen general de las variables")| Variable | Minimo | Maximo | Rango | Media |
|---|---|---|---|---|
| YEARS_ACTIVE (X) | 1 | 89 | 88 | 24.3 |
| CUMULATIVE_PRODUCTION (Y) | 1 | 985283 | 985282 | 144072.2 |
Se toma el valor máximo de YEARS_ACTIVE (89 años) y se
divide el eje en tres tercios iguales, de modo que cada
parte agrupe pozos “jóvenes”, “maduros” y “muy antiguos”. Esto también
permite visualizar mejor la nube de puntos, que al graficarse completa
se satura por la gran cantidad de observaciones (47757 pozos).
limite1 <- max_x / 3
limite2 <- 2 * max_x / 3
datos <- datos %>%
mutate(parte = case_when(
YEARS_ACTIVE <= limite1 ~ "Parte 1",
YEARS_ACTIVE <= limite2 ~ "Parte 2",
TRUE ~ "Parte 3"
))
parte1 <- datos %>% filter(parte == "Parte 1")
parte2 <- datos %>% filter(parte == "Parte 2")
parte3 <- datos %>% filter(parte == "Parte 3")
kable(data.frame(
Parte = c("Parte 1", "Parte 2", "Parte 3"),
Rango_X = c(paste0("[", round(min_x,1), " - ", round(limite1,1), "]"),
paste0("(", round(limite1,1), " - ", round(limite2,1), "]"),
paste0("(", round(limite2,1), " - ", round(max_x,1), "]")),
n = c(nrow(parte1), nrow(parte2), nrow(parte3))
), caption = "Pozos por parte")| Parte | Rango_X | n |
|---|---|---|
| Parte 1 | [1 - 29.7] | 31814 |
| Parte 2 | (29.7 - 59.3] | 13200 |
| Parte 3 | (59.3 - 89] | 2743 |
Con miles de pozos por parte, la nube de puntos es puro ruido. Por
eso se agrupa YEARS_ACTIVE en clases (regla de Sturges:
k = 1 + 3.322*log10(n)), se calcula la
amplitud de cada clase (rango/k) y se
obtiene el par (x̄, ȳ) -promedio de cada variable- por
clase. Estas parejas (x̄, ȳ), todas del mismo tamaño de muestra por
clase, son las que se usan para ajustar los modelos.
construir_tabla <- function(sub_datos) {
n <- nrow(sub_datos)
k <- ceiling(1 + 3.322 * log10(n)) # número de clases (Sturges)
minx <- min(sub_datos$YEARS_ACTIVE)
maxx <- max(sub_datos$YEARS_ACTIVE)
amplitud <- (maxx - minx) / k
cortes <- seq(minx, maxx, length.out = k + 1)
sub_datos$clase <- cut(sub_datos$YEARS_ACTIVE, breaks = cortes, include.lowest = TRUE)
tabla <- sub_datos %>%
group_by(clase) %>%
summarise(
n_pozos = n(),
x_medio = mean(YEARS_ACTIVE),
y_medio = mean(CUMULATIVE_PRODUCTION),
.groups = "drop"
) %>%
filter(!is.na(x_medio)) %>%
arrange(x_medio)
attr(tabla, "k") <- k
attr(tabla, "amplitud") <- amplitud
tabla
}
tabla1 <- construir_tabla(parte1)
tabla2 <- construir_tabla(parte2)
tabla3 <- construir_tabla(parte3)kable(tabla1, digits = 2, caption = paste0("Parte 1 · k = ", attr(tabla1,"k"),
" clases · amplitud = ", round(attr(tabla1,"amplitud"), 2)))| clase | n_pozos | x_medio | y_medio |
|---|---|---|---|
| [1,2.75] | 3269 | 1.33 | 10987.78 |
| (2.75,4.5] | 2149 | 3.47 | 29459.15 |
| (4.5,6.25] | 1939 | 5.52 | 39367.34 |
| (6.25,8] | 2023 | 7.50 | 46118.11 |
| (8,9.75] | 960 | 9.00 | 50316.09 |
| (9.75,11.5] | 2999 | 10.57 | 74746.10 |
| (11.5,13.2] | 2936 | 12.48 | 75794.74 |
| (13.2,15] | 2839 | 14.50 | 81603.45 |
| (15,16.8] | 1240 | 16.00 | 93328.99 |
| (16.8,18.5] | 3222 | 17.47 | 115858.88 |
| (18.5,20.2] | 2670 | 19.40 | 136701.51 |
| (20.2,22] | 1615 | 21.45 | 144098.86 |
| (22,23.8] | 584 | 23.00 | 159312.19 |
| (23.8,25.5] | 1116 | 24.46 | 164375.88 |
| (25.5,27.2] | 969 | 26.53 | 195726.17 |
| (27.2,29] | 1284 | 28.50 | 295431.82 |
kable(tabla2, digits = 2, caption = paste0("Parte 2 · k = ", attr(tabla2,"k"),
" clases · amplitud = ", round(attr(tabla2,"amplitud"), 2)))| clase | n_pozos | x_medio | y_medio |
|---|---|---|---|
| [30,31.9] | 1421 | 30.48 | 371985.1 |
| (31.9,33.9] | 1099 | 32.53 | 265403.8 |
| (33.9,35.8] | 1159 | 34.54 | 243750.0 |
| (35.8,37.7] | 975 | 36.52 | 250284.0 |
| (37.7,39.7] | 922 | 38.51 | 201286.9 |
| (39.7,41.6] | 1274 | 40.51 | 166132.3 |
| (41.6,43.5] | 1222 | 42.53 | 160773.2 |
| (43.5,45.5] | 1296 | 44.45 | 166921.6 |
| (45.5,47.4] | 775 | 46.44 | 220442.7 |
| (47.4,49.3] | 592 | 48.44 | 209613.0 |
| (49.3,51.3] | 458 | 50.41 | 196486.0 |
| (51.3,53.2] | 370 | 52.50 | 199175.3 |
| (53.2,55.1] | 915 | 54.78 | 175533.1 |
| (55.1,57.1] | 371 | 56.56 | 291887.7 |
| (57.1,59] | 351 | 58.47 | 254173.2 |
kable(tabla3, digits = 2, caption = paste0("Parte 3 · k = ", attr(tabla3,"k"),
" clases · amplitud = ", round(attr(tabla3,"amplitud"), 2)))| clase | n_pozos | x_medio | y_medio |
|---|---|---|---|
| [60,62.2] | 1049 | 60.67 | 292429.7 |
| (62.2,64.5] | 154 | 63.45 | 299179.7 |
| (64.5,66.7] | 150 | 65.55 | 305899.6 |
| (66.7,68.9] | 197 | 67.63 | 380550.7 |
| (68.9,71.2] | 237 | 70.08 | 324237.0 |
| (71.2,73.4] | 179 | 72.41 | 379722.6 |
| (73.4,75.6] | 150 | 74.45 | 382004.9 |
| (75.6,77.8] | 126 | 76.45 | 383540.5 |
| (77.8,80.1] | 117 | 78.98 | 427609.9 |
| (80.1,82.3] | 90 | 81.54 | 485647.9 |
| (82.3,84.5] | 99 | 83.46 | 506358.8 |
| (84.5,86.8] | 89 | 85.46 | 568473.9 |
| (86.8,89] | 106 | 88.03 | 571222.5 |
Primero la nube de puntos completa (pozo por pozo), coloreada por parte, y luego cada parte por separado ya con los pares (x̄, ȳ) que se usarán en la regresión.
colores <- c("Parte 1" = "#2E86AB", "Parte 2" = "#E67E22", "Parte 3" = "#C0392B")
plot(datos$YEARS_ACTIVE, datos$CUMULATIVE_PRODUCTION,
col = colores[datos$parte], pch = 16, cex = 0.4,
xlab = "Años activo (X)", ylab = "Producción acumulada (Y)",
main = "Nube de puntos completa - dividida en 3 partes")
abline(v = c(limite1, limite2), lty = 2, col = "gray40")
legend("topright", legend = names(colores), col = colores, pch = 16, bty = "n")par(mfrow = c(1, 3))
plot(tabla1$x_medio, tabla1$y_medio, pch = 19, col = "#2E86AB",
xlab = "X medio (años)", ylab = "Y medio (producción)", main = "Parte 1")
plot(tabla2$x_medio, tabla2$y_medio, pch = 19, col = "#E67E22",
xlab = "X medio (años)", ylab = "Y medio (producción)", main = "Parte 2")
plot(tabla3$x_medio, tabla3$y_medio, pch = 19, col = "#C0392B",
xlab = "X medio (años)", ylab = "Y medio (producción)", main = "Parte 3")Parte 1 (pozos jóvenes, 0 - 29.7 años): la nube crece rápido al inicio y luego se suaviza — forma típica de curva de declinación de producción (comportamiento hiperbólico/potencial de Arps). Se conjetura un modelo potencial: \(y = a \cdot x^{b}\).
Parte 2 (pozos maduros, 29.7 - 59.3 años): la nube no es monótona: sube, se estabiliza y vuelve a subir (efecto de reacondicionamientos/reactivaciones a mitad de vida del pozo). Ningún modelo monótono (log, exp, potencial) puede seguir esa curvatura, así que se conjetura obligatoriamente un modelo polinómico de grado 2: \(y = a + bx + cx^{2}\).
Parte 3 (pozos muy antiguos, 59.3 - 89 años): el crecimiento se acelera de forma marcada al final (pozos centenarios acumulan de forma compuesta). Se conjetura un modelo exponencial: \(y = a \cdot e^{bx}\).
modelo1 <- lm(log(y_medio) ~ log(x_medio), data = tabla1)
a1 <- exp(coef(modelo1)[1])
b1 <- coef(modelo1)[2]
cat("y =", round(a1, 3), "* x^", round(b1, 3))## y = 7404.869 * x^ 0.972
##
## Call:
## lm(formula = log(y_medio) ~ log(x_medio), data = tabla1)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.21980 -0.12876 0.00091 0.04676 0.42970
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 8.90989 0.13400 66.49 < 2e-16 ***
## log(x_medio) 0.97213 0.05143 18.90 2.31e-11 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.1662 on 14 degrees of freedom
## Multiple R-squared: 0.9623, Adjusted R-squared: 0.9596
## F-statistic: 357.2 on 1 and 14 DF, p-value: 2.311e-11
modelo2 <- lm(y_medio ~ x_medio + I(x_medio^2), data = tabla2)
a2 <- coef(modelo2)[1]; b2 <- coef(modelo2)[2]; c2 <- coef(modelo2)[3]
cat("y =", round(a2,2), "+", round(b2,2), "*x +", round(c2,4), "*x^2")## y = 1563103 + -60369.54 *x + 656.0594 *x^2
##
## Call:
## lm(formula = y_medio ~ x_medio + I(x_medio^2), data = tabla2)
##
## Residuals:
## Min 1Q Median 3Q Max
## -49278 -21779 -9006 24140 45994
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1563103.5 242185.0 6.454 3.14e-05 ***
## x_medio -60369.5 11158.7 -5.410 0.000157 ***
## I(x_medio^2) 656.1 124.8 5.257 0.000202 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 32200 on 12 degrees of freedom
## Multiple R-squared: 0.7252, Adjusted R-squared: 0.6794
## F-statistic: 15.83 on 2 and 12 DF, p-value: 0.000431
modelo3 <- lm(log(y_medio) ~ x_medio, data = tabla3)
a3 <- exp(coef(modelo3)[1])
b3 <- coef(modelo3)[2]
cat("y =", round(a3, 3), "* e^(", round(b3, 4), "* x )")## y = 58647.76 * e^( 0.0257 * x )
##
## Call:
## lm(formula = log(y_medio) ~ x_medio, data = tabla3)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.091705 -0.040116 0.006166 0.017453 0.131209
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 10.979305 0.160172 68.55 7.91e-16 ***
## x_medio 0.025710 0.002137 12.03 1.13e-07 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.06487 on 11 degrees of freedom
## Multiple R-squared: 0.9294, Adjusted R-squared: 0.9229
## F-statistic: 144.7 on 1 and 11 DF, p-value: 1.133e-07
par(mfrow = c(1, 3))
plot(tabla1$x_medio, tabla1$y_medio, col = "#2E86AB", pch = 16,
xlab = "X (años)", ylab = "Y (producción)", main = "Parte 1: Potencial")
curve(a1 * x^b1, add = TRUE, col = "black", lwd = 2)
plot(tabla2$x_medio, tabla2$y_medio, col = "#E67E22", pch = 16,
xlab = "X (años)", ylab = "Y (producción)", main = "Parte 2: Polinómico")
curve(a2 + b2*x + c2*x^2, add = TRUE, col = "black", lwd = 2)
plot(tabla3$x_medio, tabla3$y_medio, col = "#C0392B", pch = 16,
xlab = "X (años)", ylab = "Y (producción)", main = "Parte 3: Exponencial")
curve(a3 * exp(b3 * x), add = TRUE, col = "black", lwd = 2)Para los modelos monótonos (potencial y exponencial) se prueba el coeficiente de Pearson sobre la escala en que el modelo es lineal (logarítmica). Para el modelo polinómico, al no ser lineal en una sola escala, se usa el coeficiente de correlación múltiple \(R = \sqrt{R^2}\), que cumple el mismo rol de “bondad de ajuste” entre 0 y 1.
# Parte 1: potencial -> se prueba log(y) vs log(x)
r1 <- cor.test(log(tabla1$x_medio), log(tabla1$y_medio))
# Parte 2: polinómico -> R multiple = sqrt(R^2) del modelo
R2_2 <- summary(modelo2)$r.squared
r2 <- sqrt(R2_2)
# Parte 3: exponencial -> se prueba x vs log(y)
r3 <- cor.test(tabla3$x_medio, log(tabla3$y_medio))
tabla_pearson <- data.frame(
Parte = c("Parte 1 (Potencial)", "Parte 2 (Polinómico)", "Parte 3 (Exponencial)"),
r_o_R = c(r1$estimate, r2, r3$estimate),
R2 = c(r1$estimate^2, R2_2, r3$estimate^2),
Supera_0.7 = c(abs(r1$estimate) > 0.7, r2 > 0.7, abs(r3$estimate) > 0.7),
p_valor_modelo = c(
pf(summary(modelo1)$fstatistic[1], summary(modelo1)$fstatistic[2], summary(modelo1)$fstatistic[3], lower.tail = FALSE),
pf(summary(modelo2)$fstatistic[1], summary(modelo2)$fstatistic[2], summary(modelo2)$fstatistic[3], lower.tail = FALSE),
pf(summary(modelo3)$fstatistic[1], summary(modelo3)$fstatistic[2], summary(modelo3)$fstatistic[3], lower.tail = FALSE)
)
)
kable(tabla_pearson, digits = 4, caption = "Test de bondad de ajuste por parte (umbral |r| o R > 0.7)")| Parte | r_o_R | R2 | Supera_0.7 | p_valor_modelo |
|---|---|---|---|---|
| Parte 1 (Potencial) | 0.9810 | 0.9623 | TRUE | 0e+00 |
| Parte 2 (Polinómico) | 0.8516 | 0.7252 | TRUE | 4e-04 |
| Parte 3 (Exponencial) | 0.9640 | 0.9294 | TRUE | 0e+00 |
Las tres partes superan el umbral de |r| > 0.7 (o
R > 0.7 en el caso del polinómico) y además el
estadístico F de cada modelo es significativo (p-valor < 0.05), por
lo que los tres modelos se aceptan.
# Un ejemplo de estimación puntual dentro del rango de cada parte
x_est1 <- round(mean(range(tabla1$x_medio)), 1)
x_est2 <- round(mean(range(tabla2$x_medio)), 1)
x_est3 <- round(mean(range(tabla3$x_medio)), 1)
y_est1 <- a1 * x_est1^b1
y_est2 <- a2 + b2*x_est2 + c2*x_est2^2
y_est3 <- a3 * exp(b3 * x_est3)
kable(data.frame(
Parte = c("Parte 1", "Parte 2", "Parte 3"),
X_estimado = c(x_est1, x_est2, x_est3),
Y_estimado = round(c(y_est1, y_est2, y_est3), 1)
), caption = "Estimaciones puntuales de producción acumulada")| Parte | X_estimado | Y_estimado |
|---|---|---|
| Parte 1 | 14.9 | 102331.3 |
| Parte 2 | 44.5 | 175820.7 |
| Parte 3 | 74.3 | 396144.2 |