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'
),
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(
"Well Type",
"True Vertical Depth, ft",
"Proposed Depth",
"Elevation",
"Surface Longitude",
"Surface Latitude",
"County",
"Region",
"Original Well Type",
"Objective Formation",
"Producing Formation",
"Slant",
"Spacing Acres"
)
datos_proc <- datos_originales %>%
select(any_of(variables_requeridas))
names(datos_proc) <- c(
"Well_Type",
"True_Vertical_Depth",
"Proposed_Depth",
"Elevation",
"Surface_Longitude",
"Surface_Latitude",
"County",
"Region",
"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", "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(Well_Type))
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)
}
}
datos_limpios$Well_Type <- make.names(datos_limpios$Well_Type)
datos_limpios$Well_Type <- as.factor(datos_limpios$Well_Type)
cat("Dimensiones finales del dataset limpio:", nrow(datos_limpios), "filas y", ncol(datos_limpios), "columnas.\n")## Dimensiones finales del dataset limpio: 46370 filas y 11 columnas.
set.seed(123)
if(nrow(datos_limpios) > 10) {
train_index <- createDataPartition(datos_limpios$Well_Type, 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 estratificada del dataset")| Conjunto | Observaciones | Porcentaje |
|---|---|---|
| Entrenamiento (Train) | 32469 | 70% |
| Prueba (Test) | 13901 | 30% |
| Total | 46370 | 100% |
## Ranger result
##
## Call:
## ranger(formula = Well_Type ~ ., data = train_set, num.trees = 500, mtry = mtry_opt, importance = "impurity", probability = FALSE, num.threads = 0, seed = 123)
##
## Type: Classification
## Number of trees: 500
## Sample size: 32469
## Number of independent variables: 10
## Mtry: 3
## Target node size: 1
## Variable importance mode: impurity
## Splitrule: gini
## OOB prediction error: 21.04 %
cat("Tasa de Error OOB (Classification Error):", round(rf_model_ranger$prediction.error * 100, 4), "%\n")## Tasa de Error OOB (Classification Error): 21.0447 %
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 en la Clasificación")| Variable | IncNodePurity |
|---|---|
| Original_Well_Type | 4171.09860 |
| Elevation | 4022.57214 |
| True_Vertical_Depth | 3543.71893 |
| Proposed_Depth | 2840.38178 |
| County | 1005.31169 |
| Region | 994.03113 |
| Surface_Latitude | 542.00678 |
| Producing_Formation | 154.55103 |
| Objective_Formation | 59.32067 |
| Surface_Longitude | 0.00000 |
predictions <- predict(rf_model_ranger, data = test_set, num.threads = 0)$predictions
results_df <- data.frame(
Valor_Real = test_set$Well_Type,
Valor_Predicho = predictions
)
cat("Primeras predicciones generadas exitosamente para el conjunto de prueba.\n")## Primeras predicciones generadas exitosamente para el conjunto de prueba.
conf_matrix <- confusionMatrix(as.factor(results_df$Valor_Predicho), as.factor(results_df$Valor_Real))
accuracy_val <- conf_matrix$overall['Accuracy']
kappa_val <- conf_matrix$overall['Kappa']## Accuracy Global: 78.75 %
## Índice Kappa de Cohen: 0.7124553
cm_table <- as.data.frame(conf_matrix$table)
colnames(cm_table) <- c("Predichos", "Referencia", "Frecuencia")
ggplot(cm_table, aes(x = Referencia, y = Predichos, fill = Frecuencia)) +
geom_tile(color = "white") +
scale_fill_viridis_c(option = "viridis", direction = -1) +
geom_text(aes(label = Frecuencia), color = "white", size = 4, fontface = "bold") +
theme_minimal() +
labs(
title = "Matriz de Confusión - Random Forest",
subtitle = "Comparación entre la categoría real y la categoría estimada",
x = "Categoría Registrada en Campo (Referencia)",
y = "Categoría Estimada por el Modelo",
fill = "Frecuencia"
) +
theme(
axis.text.x = element_text(angle = 45, hjust = 1, face = "bold"),
axis.text.y = element_text(face = "bold"),
plot.title = element_text(face = "bold", size = 14),
panel.grid.major = element_blank(),
legend.position = "right"
)ggplot(datos_limpios, aes(x = Well_Type, fill = Well_Type)) +
geom_bar(color = "black", alpha = 0.7) +
scale_fill_viridis_d(option = "viridis") +
theme_minimal() +
labs(title = "Distribución del Tipo de Pozo", x = "Tipo de Pozo", y = "Frecuencia") +
theme(axis.text.x = element_text(angle = 45, hjust = 1), legend.position = "none")ggplot(head(importance_df, 10), aes(x = reorder(Variable, IncNodePurity), y = IncNodePurity)) +
geom_point(color = "#440154", size = 3) +
geom_segment(aes(x = Variable, xend = Variable, y = 0, yend = IncNodePurity), color = "#440154", size = 1) +
coord_flip() +
theme_minimal() +
labs(title = "Top 10 Variables de Importancia (Clasificación)", x = "Variables", y = "Importancia (Gini Impurity)")El modelo de Random Forest demostró ser una herramienta muy confiable para clasificar el tipo de pozo petrolero, destacando que la profundidad, las características de la formación y la ubicación geográfica son los factores que más determinan el resultado. Gracias a esto, el modelo logró un rendimiento general alto al acertar consistentemente con los datos reales de campo. No obstante, se identificó un desbalance en los registros históricos de algunas categorías menos frecuentes, lo que sugiere que equilibrar los datos en futuros trabajos podría optimizar aún más las predicciones.
Autor: Jennifer Cordones | Machine Learning de Clasificación (Random Forest) — Oil, Gas & Other Regulated Wells - NY State