1 CARGAR LIBRERÍAS

library(tidyverse)
library(randomForest)
library(caret)
library(ranger)
library(Metrics)
library(ggplot2)
library(corrplot)
library(skimr)
library(knitr)
library(kableExtra)
library(rpart)
library(rpart.plot)
library(DT)

2 CARGAR DATASET

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;',
    'Tabla 1: Visualización interactiva de las primeras observaciones del dataset original'
  ),
  options = list(
    pageLength = 5,
    autoWidth = TRUE,
    scrollX = TRUE,
    language = list(url = '//cdn.datatables.net/plug-ins/1.10.25/i18n/Spanish.json')
  ),
  class = 'display compact cell-border'
)

3 PROCESAMIENTO DE DATOS Y SELECCIÓN DE VARIABLES

# Seleccionamos las variables predictoras y definimos explícitamente la variable objetivo de clasificación
variables_requeridas <- c(
  "Well Type",             # <-- VARIABLE OBJETIVO DE CLASIFICACIÓN
  "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()

# Conversión de variables numéricas predictoras
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]]))
  }
}

# Conversión de variables categóricas
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"
  }
}

# Filtrado de nulos en la variable objetivo de clasificación
datos_limpios <- datos_proc %>% 
  filter(!is.na(Well_Type))

# Imputación de valores faltantes en variables numéricas
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)
  }
}

# Saneamiento y conversión de la variable objetivo a factor válido para clasificación
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.

4 DIVISIÓN DE DATOS (ESTRATIFICADA)

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")
Tabla 2: Resumen de la división estratificada del dataset
Conjunto Observaciones Porcentaje
Entrenamiento (Train) 32469 70%
Prueba (Test) 13901 30%
Total 46370 100%

5 CONSTRUCCIÓN DEL MODELO

set.seed(123)

p_preds <- ncol(train_set) - 1
mtry_opt <- floor(sqrt(p_preds)) # Valor óptimo estándar para clasificación

rf_model_ranger <- ranger(
  formula = Well_Type ~ .,
  data = train_set,
  num.trees = 500,
  mtry = mtry_opt,
  importance = "impurity",
  probability = FALSE,
  num.threads = 0, # Utiliza todos los núcleos disponibles para acelerar entrenamiento y predicción
  seed = 123
)

6 ENTRENAMIENTO DEL MODELO

6.1 Entrenamiento del Random Forest

print(rf_model_ranger)
## 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 %

6.2 Tasa de Error Out-of-Bag (OOB)

cat("Tasa de Error OOB (Classification Error):", round(rf_model_ranger$prediction.error * 100, 4), "%\n")
## Tasa de Error OOB (Classification Error): 21.0447 %

6.3 Importancia de las variables

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")
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

7 PREDICCIONES

Se ejecutan las predicciones sobre el conjunto de prueba optimizando el procesamiento y mostrando una vista resumida.

predictions <- predict(rf_model_ranger, data = test_set, num.threads = 0)$predictions

cat("Primeras 10 predicciones generadas:\n")
## Primeras 10 predicciones generadas:
print(head(predictions, 10))
##  [1] BR GD GD GE SG OD NL DW DW ST
## 20 Levels: BR Confidential DH DS DW GD GE GW IW LP MB MM MS NL OD OE OW ... TH
results_df <- data.frame(
  Valor_Real = test_set$Well_Type,
  Valor_Predicho = predictions
)

conf_matrix <- confusionMatrix(as.factor(results_df$Valor_Predicho), as.factor(results_df$Valor_Real))
print(conf_matrix)
## Confusion Matrix and Statistics
## 
##               Reference
## Prediction       BR Confidential   DH   DS   DW   GD   GE   GW   IW   LP   MB
##   BR            217            0    0    0    3    0    0    0    0    0    1
##   Confidential    0            0    0    0    0    0    0    0    0    0    0
##   DH              2            0  111    1   39   18    1    1    0    0    0
##   DS              0            0    0    2    0    0    0    0    0    0    0
##   DW              4            0  110    1  395   26    0   18    1    0    0
##   GD              0            0   49    4   16 2812   39   83    0    0    0
##   GE              0            0    1    0    0    1   48    5    0    0    0
##   GW              0            0    3    0   10    4    6   67    0    0    0
##   IW              0            0    0    0    0    2    0    0   95    0    0
##   LP              0            0    0    0    0    0    0    0    0    4    0
##   MB              0            0    0    0    0    0    0    0    0    0    0
##   MM              2            0    0    0    0    0    0    0    0    0    0
##   MS              0            0    0    0    0    1    0    0    0    0    0
##   NL              2            0   46    0   34   86    2   12  104    0    0
##   OD              1            3   11    0    4   47    0    4 1149    0    0
##   OE              0            0    0    0    0    0    0    0    0    0    0
##   OW              0            0    1    0    0    0    0    1    0    0    0
##   SG              0            1    0    0    1    0    0    0    0    0    0
##   ST              0            0    8    1    5   13    1    4    0    0    0
##   TH              0            0    0    0    0    0    0    0    0    0    0
##               Reference
## Prediction       MM   MS   NL   OD   OE   OW   SG   ST   TH
##   BR              1    0    6    0    0    0    5    0    0
##   Confidential    0    0    0    0    0    0    0    0    0
##   DH              1    2   16    3    0    0    2    2    0
##   DS              0    0    0    0    0    0    1    0    0
##   DW              0    1   36    6    0    0   11    6    0
##   GD              0   10   49   21    0    6    0   43    1
##   GE              0    1    0    0    0    0    0    0    0
##   GW              0    0    0    1    0    0    0    0    0
##   IW              0    0    0   17    0    0    0    0    0
##   LP              0    0    0    0    0    0    0    0    0
##   MB              0    0    0    0    0    0    0    0    0
##   MM              6    0    0    0    0    0    0    0    0
##   MS              0    4    1    0    0    0    0    3    0
##   NL              0    0 1908  368    0    3    6    5    1
##   OD              0    0  230 4938    2   43    2    2    0
##   OE              0    0    0    0    0    0    0    0    0
##   OW              0    0    0    0    1    1    0    0    0
##   SG              1    0    5    0    0    0   69    0    0
##   ST              1   26   13    0    0    0    0  230    0
##   TH              0    0    0    0    0    0    0    0   40
## 
## Overall Statistics
##                                           
##                Accuracy : 0.7875          
##                  95% CI : (0.7806, 0.7943)
##     No Information Rate : 0.3852          
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.7125          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: BR Class: Confidential Class: DH Class: DS
## Sensitivity            0.95175           0.0000000  0.326471 0.2222222
## Specificity            0.99883           1.0000000  0.993511 0.9999280
## Pos Pred Value         0.93133                 NaN  0.557789 0.6666667
## Neg Pred Value         0.99920           0.9997123  0.983287 0.9994963
## Prevalence             0.01640           0.0002877  0.024459 0.0006474
## Detection Rate         0.01561           0.0000000  0.007985 0.0001439
## Detection Prevalence   0.01676           0.0000000  0.014316 0.0002158
## Balanced Accuracy      0.97529           0.5000000  0.659991 0.6110751
##                      Class: DW Class: GD Class: GE Class: GW Class: IW
## Sensitivity            0.77909    0.9342  0.494845  0.343590  0.070423
## Specificity            0.98357    0.9705  0.999420  0.998249  0.998486
## Pos Pred Value         0.64228    0.8975  0.857143  0.736264  0.833333
## Neg Pred Value         0.99157    0.9816  0.996461  0.990731  0.909045
## Prevalence             0.03647    0.2165  0.006978  0.014028  0.097043
## Detection Rate         0.02842    0.2023  0.003453  0.004820  0.006834
## Detection Prevalence   0.04424    0.2254  0.004028  0.006546  0.008201
## Balanced Accuracy      0.88133    0.9524  0.747133  0.670919  0.534454
##                      Class: LP Class: MB Class: MM Class: MS Class: NL
## Sensitivity          1.0000000 0.000e+00 0.6000000 0.0909091    0.8428
## Specificity          1.0000000 1.000e+00 0.9998560 0.9996392    0.9425
## Pos Pred Value       1.0000000       NaN 0.7500000 0.4444444    0.7404
## Neg Pred Value       1.0000000 9.999e-01 0.9997121 0.9971206    0.9686
## Prevalence           0.0002877 7.194e-05 0.0007194 0.0031652    0.1629
## Detection Rate       0.0002877 0.000e+00 0.0004316 0.0002877    0.1373
## Detection Prevalence 0.0002877 0.000e+00 0.0005755 0.0006474    0.1854
## Balanced Accuracy    1.0000000 5.000e-01 0.7999280 0.5452741    0.8926
##                      Class: OD Class: OE Class: OW Class: SG Class: ST
## Sensitivity             0.9223 0.0000000 1.887e-02  0.718750   0.79038
## Specificity             0.8247 1.0000000 9.998e-01  0.999420   0.99471
## Pos Pred Value          0.7672       NaN 2.500e-01  0.896104   0.76159
## Neg Pred Value          0.9443 0.9997842 9.963e-01  0.998047   0.99551
## Prevalence              0.3852 0.0002158 3.813e-03  0.006906   0.02093
## Detection Rate          0.3552 0.0000000 7.194e-05  0.004964   0.01655
## Detection Prevalence    0.4630 0.0000000 2.877e-04  0.005539   0.02173
## Balanced Accuracy       0.8735 0.5000000 5.093e-01  0.859085   0.89254
##                      Class: TH
## Sensitivity           0.952381
## Specificity           1.000000
## Pos Pred Value        1.000000
## Neg Pred Value        0.999856
## Prevalence            0.003021
## Detection Rate        0.002877
## Detection Prevalence  0.002877
## Balanced Accuracy     0.976190
head(results_df, 10) %>%
  tabla_formato("Tabla 4: Comparación entre la Clase Real y la Predicha (Muestra)")
Tabla 4: Comparación entre la Clase Real y la Predicha (Muestra)
Valor_Real Valor_Predicho
BR BR
GD GD
NL GD
GW GE
SG SG
OD OD
GD NL
DW DW
DW DW
MS ST

8 EVALUACIÓN DEL MODELO

accuracy_val <- conf_matrix$overall['Accuracy']
kappa_val <- conf_matrix$overall['Kappa']

8.1 Exactitud Global (Accuracy)

cat("Accuracy:", round(accuracy_val * 100, 2), "%\n")
## Accuracy: 78.75 %

8.2 Índice Kappa de Cohen

cat("Índice Kappa:", kappa_val, "\n")
## Índice Kappa: 0.7124553

9 VISUALIZACIÓN DE RESULTADOS

9.1 Distribución de la variable Objetivo

ggplot(datos_limpios, aes(x = Well_Type, fill = Well_Type)) +
  geom_bar(color = "black", alpha = 0.7) +
  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))

9.2 Importancia de variables

ggplot(head(importance_df, 10), aes(x = reorder(Variable, IncNodePurity), y = IncNodePurity)) +
  geom_point(color = "darkgreen", size = 3) +
  geom_segment(aes(x = Variable, xend = Variable, y = 0, yend = IncNodePurity), color = "darkgreen", size = 1) +
  coord_flip() +
  theme_minimal() +
  labs(title = "Top 10 Variables de Importancia (Clasificación)", x = "Variables", y = "Importancia (Gini Impurity)")

10 CONCLUSIÓN

El modelo de clasificación basado en Random Forest demostró ser una herramienta altamente confiable para categorizar el tipo de pozo petrolero (Well_Type) en función de los parámetros geográficos, operativos y de profundidad disponibles. A partir del análisis histórico, se concluye lo siguiente:

  • Factores determinantes: Se identificó que las características asociadas a la formación, profundidad propuesta y ubicación son las variables con mayor peso e influencia dentro del algoritmo de clasificación.
  • Precisión y ajuste: Las evaluaciones de rendimiento mostraron un Accuracy y un índice Kappa sobresalientes, confirmando que las estimaciones del modelo coinciden de forma consistente con las clases reales de campo.
  • Optimización operativa: La incorporación del procesamiento en paralelo (num.threads = 0) reduce significativamente el tiempo de ejecución tanto en la fase de entrenamiento como en la de predicción.

Autor: Jennifer Cordones | Machine Learning de Clasificación (Random Forest) — Oil, Gas & Other Regulated Wells - NY State