datos_originales <- read_csv2("Oil__Gas____Other_Regulated_Wells__Beginning_1860.csv")
dims <- dim(datos_originales)
cat("Dimensiones del dataset original:", dims[1], "filas y", dims[2], "columnas.\n")## Dimensiones del dataset original: 47407 filas y 55 columnas.
datatable(
head(datos_originales, 50),
caption = htmltools::tags$caption(
style = 'caption-side: top; text-align: center; font-weight: bold; color: #1b365d; font-size: 15px;',
'Tabla 1: Visualización interactiva y estilizada de las primeras observaciones del dataset original'
),
options = list(
pageLength = 5,
autoWidth = TRUE,
scrollX = TRUE,
dom = 'Bfrtip',
language = list(url = '//cdn.datatables.net/plug-ins/1.10.25/i18n/Spanish.json')
),
class = 'cell-border stripe hover compact'
)variables_requeridas <- c(
"True Vertical Depth, ft",
"Proposed Depth",
"Elevation",
"Surface Longitude",
"Surface Latitude",
"County",
"Region",
"Well Type",
"Original Well Type",
"Objective Formation",
"Producing Formation",
"Slant",
"Spacing Acres"
)
datos_proc <- datos_originales %>%
select(any_of(variables_requeridas))
names(datos_proc) <- c(
"True_Vertical_Depth",
"Proposed_Depth",
"Elevation",
"Surface_Longitude",
"Surface_Latitude",
"County",
"Region",
"Well_Type",
"Original_Well_Type",
"Objective_Formation",
"Producing_Formation",
"Slant",
"Spacing_Acres"
)
datos_proc <- datos_proc %>% distinct()
numeric_vars <- c("True_Vertical_Depth", "Proposed_Depth", "Elevation", "Surface_Longitude", "Surface_Latitude", "Spacing_Acres")
for(v in numeric_vars) {
if(v %in% names(datos_proc)) {
datos_proc[[v]] <- as.numeric(as.character(datos_proc[[v]]))
}
}
cat_vars <- c("County", "Region", "Well_Type", "Original_Well_Type", "Objective_Formation", "Producing_Formation", "Slant")
for(v in cat_vars) {
if(v %in% names(datos_proc)) {
datos_proc[[v]] <- addNA(as.factor(datos_proc[[v]]))
levels(datos_proc[[v]])[is.na(levels(datos_proc[[v]]))] <- "Desconocido"
}
}
datos_limpios <- datos_proc %>%
filter(!is.na(True_Vertical_Depth))
for(v in numeric_vars) {
if(v %in% names(datos_limpios) && sum(is.na(datos_limpios[[v]])) > 0) {
datos_limpios[[v]][is.na(datos_limpios[[v]])] <- median(datos_limpios[[v]], na.rm = TRUE)
}
}
cat("Dimensiones finales del dataset limpio:", nrow(datos_limpios), "filas y", ncol(datos_limpios), "columnas.\n")## Dimensiones finales del dataset limpio: 46365 filas y 11 columnas.
set.seed(123)
if(nrow(datos_limpios) > 10) {
train_index <- createDataPartition(datos_limpios$True_Vertical_Depth, p = 0.70, list = FALSE)
train_set <- datos_limpios[train_index, ]
test_set <- datos_limpios[-train_index, ]
} else {
train_index <- sample(1:nrow(datos_limpios), size = 0.7 * nrow(datos_limpios))
train_set <- datos_limpios[train_index, ]
test_set <- datos_limpios[-train_index, ]
}
partition_summary <- data.frame(
Conjunto = c("Entrenamiento (Train)", "Prueba (Test)", "Total"),
Observaciones = c(nrow(train_set), nrow(test_set), nrow(datos_limpios)),
Porcentaje = c("70%", "30%", "100%")
)
partition_summary %>%
tabla_formato("Tabla 2: Resumen de la división del dataset")| Conjunto | Observaciones | Porcentaje |
|---|---|---|
| Entrenamiento (Train) | 32457 | 70% |
| Prueba (Test) | 13908 | 30% |
| Total | 46365 | 100% |
## Ranger result
##
## Call:
## ranger(formula = True_Vertical_Depth ~ ., data = train_set, num.trees = 500, mtry = mtry_opt, importance = "impurity", sample.fraction = 1, seed = 123)
##
## Type: Regression
## Number of trees: 500
## Sample size: 32457
## Number of independent variables: 10
## Mtry: 3
## Target node size: 5
## Variable importance mode: impurity
## Splitrule: variance
## OOB prediction error (MSE): 416766.3
## R squared (OOB): 0.8136499
## Error OOB (Prediction error / MSE aproximado): 416766.3
importance_df <- data.frame(
Variable = names(rf_model_ranger$variable.importance),
IncNodePurity = as.numeric(rf_model_ranger$variable.importance)
) %>% arrange(desc(IncNodePurity))
head(importance_df, 10) %>%
tabla_formato("Tabla 3: Top 10 Variables más influyentes")| Variable | IncNodePurity |
|---|---|
| County | 18339092467 |
| Elevation | 10325499181 |
| Well_Type | 10108900839 |
| Original_Well_Type | 8889413454 |
| Proposed_Depth | 8397100213 |
| Region | 2133786874 |
| Objective_Formation | 1902914164 |
| Surface_Latitude | 1589563748 |
| Producing_Formation | 1065163132 |
| Surface_Longitude | 0 |
ggplot(head(importance_df, 10), aes(x = reorder(Variable, IncNodePurity), y = IncNodePurity)) +
geom_point(color = "steelblue", size = 3) +
geom_segment(aes(x = Variable, xend = Variable, y = 0, yend = IncNodePurity), color = "steelblue", size = 1) +
scale_x_discrete() +
scale_y_continuous(labels = label_number(big.mark = ",")) +
coord_flip() +
theme_minimal() +
labs(title = "Top 10 Variables de Importancia", x = "Variables", y = "Importancia")predictions <- predict(rf_model_ranger, data = test_set)$predictions
results_df <- data.frame(
Valor_Real = test_set$True_Vertical_Depth,
Valor_Predicho = predictions
) %>%
mutate(
Error_Absoluto = abs(Valor_Real - Valor_Predicho),
Error_Relativo_Porc = ifelse(Valor_Real != 0, (Error_Absoluto / abs(Valor_Real)) * 100, 0)
)
head(results_df, 10) %>%
tabla_formato("Tabla 4: Comparación entre el Valor Real y el Predicho")| Valor_Real | Valor_Predicho | Error_Absoluto | Error_Relativo_Porc |
|---|---|---|---|
| 2006 | 2423.9495 | 417.94954 | 20.834972 |
| 0 | 692.5764 | 692.57639 | 0.000000 |
| 6321 | 4751.1313 | 1569.86869 | 24.835765 |
| 928 | 863.7021 | 64.29795 | 6.928658 |
| 979 | 775.5399 | 203.46014 | 20.782445 |
| 1473 | 1665.7382 | 192.73823 | 13.084741 |
| 1608 | 1562.3738 | 45.62616 | 2.837448 |
| 5293 | 4917.5158 | 375.48424 | 7.093978 |
| 0 | 820.5711 | 820.57115 | 0.000000 |
| 1842 | 1682.2695 | 159.73053 | 8.671582 |
ggplot(results_df, aes(x = Valor_Real, y = Valor_Predicho)) +
geom_point(alpha = 0.4, color = "darkblue") +
geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed", size = 1) +
theme_minimal() +
labs(title = "Valores Observados y Predichos", x = "Valor Real (ft)", y = "Valor Predicho (ft)")results_df$Index <- 1:nrow(results_df)
n_muestras <- min(30, nrow(results_df))
ggplot(results_df[1:n_muestras, ], aes(x = Index)) +
geom_line(aes(y = Valor_Real, color = "Real"), size = 1) +
geom_line(aes(y = Valor_Predicho, color = "Predicho"), size = 1, linetype = "dashed") +
theme_minimal() +
labs(title = "Comparación Directa: Real y Predicho", x = "Índice de Pozo", y = "Profundidad (ft)", color = "Leyenda") +
scale_color_manual(values = c("Real" = "darkblue", "Predicho" = "coral3"))y_true <- test_set$True_Vertical_Depth
y_pred <- predictions
mse_val <- mse(y_true, y_pred)
rmse_val <- rmse(y_true, y_pred)
mae_val <- mae(y_true, y_pred)
r2_val <- cor(y_true, y_pred)^2
pearson_corr <- cor(y_true, y_pred)
residuals_vec <- y_true - y_pred## Correlación de Pearson: 0.9012644
resumen_residuos <- data.frame(
Métrica_Estadística = c("Mínimo", "1er Cuartil", "Mediana", "Media", "3er Cuartil", "Máximo", "Desviación Estándar"),
Valor_ft = c(
min(residuals_vec, na.rm = TRUE),
quantile(residuals_vec, 0.25, na.rm = TRUE),
median(residuals_vec, na.rm = TRUE),
mean(residuals_vec, na.rm = TRUE),
quantile(residuals_vec, 0.75, na.rm = TRUE),
max(residuals_vec, na.rm = TRUE),
sd(residuals_vec, na.rm = TRUE)
)
)
resumen_residuos %>%
tabla_formato("Tabla 5: Análisis estadístico de los residuos del modelo")| Métrica_Estadística | Valor_ft |
|---|---|
| Mínimo | -7427.767290 |
| 1er Cuartil | -166.395098 |
| Mediana | -4.793049 |
| Media | 5.329854 |
| 3er Cuartil | 255.071845 |
| Máximo | 9746.930113 |
| Desviación Estándar | 647.852597 |
res_df <- data.frame(Residuo = residuals_vec)
ggplot(res_df, aes(x = Residuo)) +
geom_histogram(bins = 40, fill = "skyblue", color = "black", alpha = 0.7) +
theme_minimal() +
labs(title = "Histograma de Residuos", x = "Residuo (ft)", y = "Densidad")ggplot(res_df, aes(sample = Residuo)) +
stat_qq(color = "darkblue", alpha = 0.5) +
stat_qq_line(color = "red", size = 1) +
theme_minimal() +
labs(title = "Gráfico Q-Q Normal de los Residuos", x = "Cuantiles Teóricos", y = "Cuantiles Muestrales")Nota analítica: Si bien el modelo presenta un excelente ajuste global (\(R^2\) de ~0.81), el gráfico Q-Q evidencia colas pesadas en los residuos, lo que indica la presencia de ciertos pozos con errores atípicos de predicción debido a la alta variabilidad geológica.
El modelo de Random Forest funcionó muy bien para calcular la profundidad real de los pozos petroleros utilizando la información histórica disponible. Logró capturar con bastante precisión los factores clave del subsuelo y ofreció resultados muy cercanos a la realidad, aunque en ciertos pozos específicos se encontraron diferencias mayores debido a las complicaciones naturales del terreno y la geología de la zona.
Autor: Jennifer Cordones | Machine Learning de Regresión (Random Forest) — Oil, Gas & Other Regulated Wells - NY State