Random Forest en Mantenimiento Crítico

Aprendizaje de Máquina Mantenimiento Predictivo Ciencia Abierta Eficiencia Energética

Trabajo presentado en práctica profesional supervisada de la carrera Ingeniería Industrial de la UNCuyo aplicado a una planta de refino de petróleo. Convocatoria AUGM estímulo a las vocaciones científicas.

Ricardo Raúl Palma http://themys.sid.uncu.edu.ar (Universidad Nacional de Cuyo / CONICET)https://tyemys.sid.uncu.edu.ar , Victoria Inés Plama Cieri https://industria-sostenible.org (AUGM)https://scholar.google.com/citations?hl=es&user=SQuU_L4AAAAJ
2026-05-26

Wite paper: Aplicación de random-forest al mantenimiento crítico Autores: Ricardo R. Palma[1] , Victoria I. P. Cieri [2] Filiación: [1]Instituto de Ingeniería Industrial [2] YPF Dirección de Mantenimiento Destilería Lujan de Cuyo Contacto: ricardo.rpalma[1] , victoria.cieri[2] [1,2]@gmail.com Correspondig author: [1]

Modelo Evaluación final según AUGM

Este es un modelo ejemplo de hipótesis de mínima del trabajo presentado para aprobar el curso.

Lo ideal sería tener un trabajo tipo artículo científico (paper) en el que emplees algunos de los métodos desarrollados en el curso, pero si no es posible conseguirlo en el tiempo estipulado te pido que lo publiques aquí.

Si me das tu autorización los mejores trabajos puedo subirlos a la biblioteca y otorgarles in DOI. En ese caso la universidad me pide que algún profesor de la UNCuyo aparezca como co-autor (ultimo autor)

Todos los trabajos deberán tener además una copia del código fuente (Rmd) conjunto de datos y versiones anterióres del código en GitHub.

Utiliza el preambulo de Rmd que tiene este trabajo para que aparezca el índice de contenidos.

Creación del Dataset

Crearemos un dataset ficticio utilizando distribuciones de probabilidad que ya tiene R

# Cargar librerías necesarias (instalar si no están)
# install.packages("randomForest")
# install.packages("dplyr")

library(randomForest)
library(dplyr)

# Fijar una semilla para reproducibilidad
#set.seed(123)

# Número de observaciones
n <- 200

# Generar datos sintéticos
datos_mantenimiento <- data.frame(
  Tiempo_Funcionamiento_Horas = round(rnorm(n, mean = 1500, sd = 500)),
  Operario = sample(c("Operario_A", "Operario_B", "Operario_C"), n, replace = TRUE),
  Piezas_Producidas = round(rnorm(n, mean = 25000, sd = 10000)),
  Pronostico_Fecha_Prox_Mant = as.Date("2026-01-01") + sample(1:100, n, replace = TRUE),
  Falla_Presentada = sample(c("Desgaste_natural", "Fallo_electronico", "Fallo_mecanico", "Error_operativo"), n, replace = TRUE),
  Costo_Reparacion_USD = round(rnorm(n, mean = 500, sd = 200)),
  Vibracion_mm_s = rnorm(n, mean = 3.5, sd = 1.5), # Columna adicional interesante
  Temperatura_C = rnorm(n, mean = 65, sd = 10),    # Columna adicional interesante
  Familia_Equipo = sample(c("Bomba", "Ventilador", "Motor_Electrico", "Compresor"), n, replace = TRUE) # Variable objetivo
)

# Asegurar que las variables categóricas sean factores
datos_mantenimiento$Operario <- as.factor(datos_mantenimiento$Operario)
datos_mantenimiento$Falla_Presentada <- as.factor(datos_mantenimiento$Falla_Presentada)
datos_mantenimiento$Familia_Equipo <- as.factor(datos_mantenimiento$Familia_Equipo)

# Mostrar las primeras filas del dataset
head(datos_mantenimiento)
  Tiempo_Funcionamiento_Horas   Operario Piezas_Producidas
1                        1995 Operario_C             49393
2                        1461 Operario_C             20674
3                        2095 Operario_B             11342
4                        1535 Operario_C             36489
5                        1247 Operario_C             24137
6                        1406 Operario_A             26592
  Pronostico_Fecha_Prox_Mant  Falla_Presentada Costo_Reparacion_USD
1                 2026-02-12 Fallo_electronico                  852
2                 2026-03-03   Error_operativo                  776
3                 2026-01-28  Desgaste_natural                  664
4                 2026-02-22 Fallo_electronico                  641
5                 2026-03-09   Error_operativo                  596
6                 2026-02-04   Error_operativo                  384
  Vibracion_mm_s Temperatura_C  Familia_Equipo
1       2.947443      68.24170      Ventilador
2       1.462389      69.30995 Motor_Electrico
3       1.110226      52.59320           Bomba
4       2.119168      68.66903      Ventilador
5       4.383979      47.91627           Bomba
6       4.311366      58.52072      Ventilador

División de Datos Entrenamiento y Prueba

# División de datos
indices_entrenamiento <- sample(1:nrow(datos_mantenimiento), 0.7 * nrow(datos_mantenimiento))
datos_entrenamiento <- datos_mantenimiento[indices_entrenamiento, ]
datos_prueba <- datos_mantenimiento[-indices_entrenamiento, ]
indices_entrenamiento
  [1] 104 162 194  62  67 191  47  57 150   2 189  85  54  84 188  74
 [17]  96  17  64  66  46  12  59 112 180  99 173 103 193 127  40 164
 [33]  25 145 140  79 152  41 141  19 144 125  97  26  11 169  37  83
 [49]  53  21 182  43 183 156 163  38  61 119 118 200 175 148 137  77
 [65] 185 192 135 196  31   1  52 147  49 123  30 111 155 101 116   5
 [81]  48  55 106 129  70  56  35   3 126 199   9 146  42 171 186  81
 [97] 153  95 195  24 157 161 102 154  58  76  80 179 165  10  78  71
[113]  33 109 121 190 181  13 110  16 117 128  82 105 130  60 177  89
[129]   6 184 113 142 159  14  93 174  45  44 176  75

Entrenamiento del Modelo Random Forest

Se utiliza la función randomForest() del paquete randomForest. Por defecto, construye 500 árboles de decisión. Nota Importante: La columna Pronostico_Fecha_Prox_Mant se excluye del entrenamiento ya que es un valor de fecha que no aporta información de clasificación directa a menos que se extraigan características como el mes, día de la semana, etc.

# Entrenar el modelo
# La sintaxis es Familia_Equipo ~ . (predecir Familia_Equipo usando todas las demás variables)
# Se excluye la columna de fecha por simplicidad en este ejemplo
modelo_rf <- randomForest(Falla_Presentada ~ Tiempo_Funcionamiento_Horas + Operario + 
                          Piezas_Producidas + Familia_Equipo + Costo_Reparacion_USD + 
                          Vibracion_mm_s + Temperatura_C, 
                          data = datos_entrenamiento, 
                          ntree = 500,       # Número de árboles
                          importance = TRUE) # Calcular importancia de variables

# Ver un resumen del modelo
print(modelo_rf)

Call:
 randomForest(formula = Falla_Presentada ~ Tiempo_Funcionamiento_Horas +      Operario + Piezas_Producidas + Familia_Equipo + Costo_Reparacion_USD +      Vibracion_mm_s + Temperatura_C, data = datos_entrenamiento,      ntree = 500, importance = TRUE) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 2

        OOB estimate of  error rate: 69.29%
Confusion matrix:
                  Desgaste_natural Error_operativo Fallo_electronico
Desgaste_natural                 3               8                 8
Error_operativo                  5               8                 9
Fallo_electronico                5              11                 9
Fallo_mecanico                   2               9                 9
                  Fallo_mecanico class.error
Desgaste_natural               6   0.8800000
Error_operativo               15   0.7837838
Fallo_electronico             10   0.7428571
Fallo_mecanico                23   0.4651163

Predicción y Evaluación del Modelo

Se utiliza el modelo entrenado para predecir la familia de equipo en los datos de prueba y se evalúa su rendimiento.

# Realizar predicciones en el conjunto de prueba
predicciones <- predict(modelo_rf, newdata = datos_prueba)

# Crear una matriz de confusión para evaluar la precisión
matriz_confusion <- table(Datos_Reales = datos_prueba$Familia_Equipo, Predicciones = predicciones)
print(matriz_confusion)
                 Predicciones
Datos_Reales      Desgaste_natural Error_operativo Fallo_electronico
  Bomba                          3               9                 2
  Compresor                      7               1                 2
  Motor_Electrico                1               8                 2
  Ventilador                     0               2                 3
                 Predicciones
Datos_Reales      Fallo_mecanico
  Bomba                        6
  Compresor                    4
  Motor_Electrico              1
  Ventilador                   9
# Calcular la precisión general
precision <- sum(diag(matriz_confusion)) / sum(matriz_confusion)
cat("Precisión del modelo:", precision, "\n")
Precisión del modelo: 0.25 

Análisis de Importancia de Variables

Random Forest permite ver qué variables fueron más importantes para tomar las decisiones de clasificación.

# Mostrar la importancia de las variables
importancia <- importance(modelo_rf)
print(importancia)
                            Desgaste_natural Error_operativo
Tiempo_Funcionamiento_Horas       1.27650583       -3.560974
Operario                          3.63100726       -2.119828
Piezas_Producidas                -1.86777109       -3.472285
Familia_Equipo                    1.28778465        1.992465
Costo_Reparacion_USD              0.70083501       -2.705707
Vibracion_mm_s                    0.05989369       -1.648871
Temperatura_C                    -1.57261909       -1.791270
                            Fallo_electronico Fallo_mecanico
Tiempo_Funcionamiento_Horas       -0.95006238      2.5280841
Operario                          -4.13584544     -0.8870682
Piezas_Producidas                  0.07470371      2.9399710
Familia_Equipo                     1.51560167      4.1937513
Costo_Reparacion_USD               0.38755834      3.4845619
Vibracion_mm_s                    -2.49095526      0.5507732
Temperatura_C                     -1.74973010      4.9468605
                            MeanDecreaseAccuracy MeanDecreaseGini
Tiempo_Funcionamiento_Horas           -0.1000993        16.754862
Operario                              -2.1130317         6.146910
Piezas_Producidas                     -0.6515669        17.928049
Familia_Equipo                         4.7564657         9.399576
Costo_Reparacion_USD                   1.0207206        19.105751
Vibracion_mm_s                        -1.5190180        16.097668
Temperatura_C                          0.8291691        17.492140
# Visualizar la importancia de las variables
varImpPlot(modelo_rf)

Mejora del gráfico de importancia de variables

# Instalar si es necesario
# install.packages("ggplot2")
# install.packages("tibble") # útil para convertir la matriz a dataframe fácilmente

library(ggplot2)
library(tibble)
library(dplyr)

Paso 1: Preparar los datos de importancia para ggplot2

La función importance() devuelve una matriz. Debemos convertirla en un data.frame manejable y extraer la métrica que nos interesa visualizar (comúnmente %IncMSE para regresión o MeanDecreaseGini para clasificación). En este caso, usaremos MeanDecreaseGini que mide cuánto reduce el Gini la variable en promedio.

# Extraer la importancia y convertirla en un data frame
df_importancia <- as.data.frame(importance(modelo_rf, scale = TRUE)) %>%
  rownames_to_column(var = "Variable") %>%
  arrange(desc(MeanDecreaseGini)) # Ordenar por importancia descendente

# Mostrar los datos de importancia preparados
print(df_importancia)
                     Variable Desgaste_natural Error_operativo
1        Costo_Reparacion_USD       0.70083501       -2.705707
2           Piezas_Producidas      -1.86777109       -3.472285
3               Temperatura_C      -1.57261909       -1.791270
4 Tiempo_Funcionamiento_Horas       1.27650583       -3.560974
5              Vibracion_mm_s       0.05989369       -1.648871
6              Familia_Equipo       1.28778465        1.992465
7                    Operario       3.63100726       -2.119828
  Fallo_electronico Fallo_mecanico MeanDecreaseAccuracy
1        0.38755834      3.4845619            1.0207206
2        0.07470371      2.9399710           -0.6515669
3       -1.74973010      4.9468605            0.8291691
4       -0.95006238      2.5280841           -0.1000993
5       -2.49095526      0.5507732           -1.5190180
6        1.51560167      4.1937513            4.7564657
7       -4.13584544     -0.8870682           -2.1130317
  MeanDecreaseGini
1        19.105751
2        17.928049
3        17.492140
4        16.754862
5        16.097668
6         9.399576
7         6.146910

Paso 2: Crear el gráfico con ggplot2

Usaremos un gráfico de barras (geom_bar) ordenado para mostrar claramente qué variables tienen el mayor impacto.

# Graficar la importancia de las variables con ggplot2
ggplot(df_importancia, aes(x = reorder(Variable, MeanDecreaseGini), y = MeanDecreaseGini, fill = MeanDecreaseGini)) +
  geom_bar(stat = "identity") +
  coord_flip() + # Voltear las coordenadas para una mejor lectura de las etiquetas largas
  labs(
    title = "Importancia de las Variables en el Modelo Random Forest",
    subtitle = "Medida por Mean Decrease Gini",
    x = "Variable",
    y = "Mean Decrease Gini (Importancia Relativa)",
    fill = "Importancia"
  ) +
  theme_minimal() +
  scale_fill_viridis_c(option = "plasma") + # Usar una paleta de colores moderna
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold"),
    plot.subtitle = element_text(hjust = 0.5),
    axis.text.y = element_text(size = 10)
  )

Explicación del Gráfico Mejorado

Este gráfico proporciona una visualización clara y profesional de cuáles son los factores más críticos para determinar la familia de equipo que requiere mantenimiento, según tu modelo de Random Forest.

Scheduling de mantenimiento

Para crear un gráfico de Gantt cronológicamente ordenado utilizando la columna Pronostico_Fecha_Prox_Mant y ggplot2, primero debemos decidir qué representa el “rango de tiempo” para cada elemento, ya que un gráfico de Gantt requiere una fecha de inicio y una fecha de finalización. En este ejemplo, asumiremos que el punto de inicio de la tarea de mantenimiento es la fecha del pronóstico (Pronostico_Fecha_Prox_Mant), y definiremos una duración fija para la tarea de mantenimiento (por ejemplo, 2 días) para simular el rango. Agruparemos las tareas por Familia_Equipo o por Operario para visualizarlas.

library(ggplot2)
library(dplyr)
library(lubridate) # Para manejar fechas fácilmente

# Asumimos que 'datos_mantenimiento' del ejemplo anterior está disponible.

# 1. Preparar los datos para el Gantt
# Creamos una fecha de inicio (la fecha pronosticada) y una fecha de fin (inicio + 2 días de duración)
datos_gantt <- datos_mantenimiento %>%
  mutate(
    Fecha_Inicio = Pronostico_Fecha_Prox_Mant,
    Fecha_Fin = Fecha_Inicio + days(2), # Asumimos 2 días para el mantenimiento
    # Crear una etiqueta única para cada tarea para el eje Y
    Tarea_Equipo = paste(Familia_Equipo, Operario, row_number()) 
  ) %>%
  # Ordenar cronológicamente por fecha de inicio para asegurar el orden
  arrange(Fecha_Inicio) %>%
  # Convertir Tarea_Equipo a factor con el orden actual para que ggplot lo respete
  mutate(Tarea_Equipo = factor(Tarea_Equipo, levels = unique(Tarea_Equipo)))

# 2. Crear el gráfico de Gantt con ggplot2
ggplot(datos_gantt, aes(x = Fecha_Inicio, xend = Fecha_Fin, y = Tarea_Equipo, yend = Tarea_Equipo, color = Familia_Equipo)) +
  geom_segment(size = 5) + # geom_segment crea las barras del Gantt
  labs(
    title = "Cronograma de Próximos Mantenimientos (Gantt)",
    subtitle = "Tareas ordenadas cronológicamente",
    x = "Fecha de Mantenimiento",
    y = "Tarea / Equipo Individual",
    color = "Familia de Equipo"
  ) +
  scale_x_date(date_labels = "%Y-%m-%d", date_breaks = "week") + # Formatear el eje X para fechas legibles
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold"),
    axis.text.x = element_text(angle = 45, hjust = 1), # Rotar etiquetas X para evitar solapamiento
    axis.text.y = element_blank(), # Ocultar etiquetas del eje Y si hay muchas tareas individuales
    axis.ticks.y = element_blank(),
    panel.grid.major.y = element_blank() # Eliminar líneas de cuadrícula Y para un aspecto más limpio
  )

Prueba interactivo

Este gráfico es semejante al anterior, pero tocando el color del tipo de equipo nos permite filtrar las fechas.

library(ggplot2)
library(dplyr)
library(lubridate)
library(plotly) # Cargar la librería plotly

# Asumimos que 'datos_mantenimiento' está disponible. 
# Si no lo está, asegúrate de ejecutar el código de creación del dataset original.

# 1. Preparar los datos para el Gantt (con adición de texto para tooltip)
datos_gantt_interactivo <- datos_mantenimiento %>%
  mutate(
    Fecha_Inicio = Pronostico_Fecha_Prox_Mant,
    Fecha_Fin = Fecha_Inicio + days(2), # Asumimos 2 días para el mantenimiento
    # Crear una etiqueta única para cada tarea para el eje Y
    Tarea_Equipo = paste(Familia_Equipo, Operario, row_number()),
    # >>>>>>>> AÑADIMOS EL TEXTO DEL TOOLTIP AQUÍ <<<<<<<<<
    tooltip_text = paste(
      "Equipo:", Familia_Equipo, 
      "| Fecha:", as.character(Fecha_Inicio)
    )
  ) %>%
  # Ordenar cronológicamente por fecha de inicio para asegurar el orden
  arrange(Fecha_Inicio) %>%
  # Convertir Tarea_Equipo a factor con el orden actual para que ggplot lo respete
  mutate(Tarea_Equipo = factor(Tarea_Equipo, levels = unique(Tarea_Equipo)))

# 2. Crear el gráfico de Gantt con ggplot2
# Mapeamos la variable 'tooltip_text' a la estética 'text'
gantt_plot_base <- ggplot(datos_gantt_interactivo, 
                          aes(x = Fecha_Inicio, xend = Fecha_Fin, y = Tarea_Equipo, yend = Tarea_Equipo, 
                              color = Familia_Equipo,
                              text = tooltip_text)) + # <--- Añadimos 'text' aquí
  geom_segment(size = 5) +
  labs(
    title = "Cronograma de Próximos Mantenimientos (Gantt Interactivo)",
    subtitle = "Tareas ordenadas cronológicamente",
    x = "Fecha de Mantenimiento",
    y = "Tarea / Equipo Individual",
    color = "Familia de Equipo"
  ) +
  scale_x_date(date_labels = "%Y-%m-%d", date_breaks = "week") + 
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold"),
    axis.text.x = element_text(angle = 45, hjust = 1), 
    axis.text.y = element_blank(), 
    axis.ticks.y = element_blank(),
    panel.grid.major.y = element_blank() 
  )

# 3. Convertir el gráfico ggplot en un gráfico interactivo con plotly
# Usamos tooltip = "text" para que solo se muestre nuestro texto personalizado al pasar el ratón.
gantt_interactivo <- ggplotly(gantt_plot_base, tooltip = "text")

# 4. Mostrar el gráfico interactivo
gantt_interactivo

Citation

For attribution, please cite this work as

Palma & Cieri, "Ciencia Colaborativa: Random Forest en Mantenimiento Crítico", Themys: Ciencia Abierta y Reproductible, 2026

BibTeX citation

@article{palma-cieri@machine_learning,
  author = {Palma, Ricardo Raúl and Cieri, Victoria Inés Plama},
  title = {Ciencia Colaborativa: Random Forest en Mantenimiento Crítico},
  journal = {Themys: Ciencia Abierta y Reproductible},
  year = {2026},
  doi = {10.5281/zenodo.17663214},
  volume = {1},
  issue = {1},
  issn = {ISSN en trámite}
}