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

White 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                        1585 Operario_B             23637
2                        1736 Operario_B             20780
3                         699 Operario_C             25957
4                        1287 Operario_A             30543
5                        1030 Operario_B             21416
6                         521 Operario_A             14806
  Pronostico_Fecha_Prox_Mant Falla_Presentada Costo_Reparacion_USD
1                 2026-02-08  Error_operativo                  222
2                 2026-01-07 Desgaste_natural                  309
3                 2026-04-11 Desgaste_natural                  509
4                 2026-01-16   Fallo_mecanico                  874
5                 2026-03-19  Error_operativo                  730
6                 2026-01-11 Desgaste_natural                  656
  Vibracion_mm_s Temperatura_C  Familia_Equipo
1     0.05813752      71.41377           Bomba
2     3.46649108      89.65324           Bomba
3     2.08511605      55.67731 Motor_Electrico
4     1.11788199      66.62509           Bomba
5     1.47416706      58.64618      Ventilador
6     4.98664790      60.21693           Bomba

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]  76   2 127 196 187  89 191  32  45 133  85 177 103  24 198  18
 [17]  33   6  40   9 124  65 145 125 109  50  73 194  78  93  44 162
 [33]  16 171 135 119 132 131  63 168  69 155  35 157 156  82 174  79
 [49]  70  96 154 110 185 182 175  56  74 166 143 102  39 186  59 101
 [65] 163  29  30  87  28 183 130 189 113  13 176  36 100 172 150  77
 [81]  37 111   7 165  62 161 158 139  68  48 147 116  11 180   8  60
 [97]  66 200 170  51 160 123  57   4  31  61  41  25  21 120  17  88
[113]  94  84 142  47  71 107 137   3  46 178  80 179 152  75 122 199
[129] 121 184 181  43  81  23 126  34  97 190  19  26

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: 82.14%
Confusion matrix:
                  Desgaste_natural Error_operativo Fallo_electronico
Desgaste_natural                 4              10                13
Error_operativo                  8               4                20
Fallo_electronico                8              15                15
Fallo_mecanico                   2              14                 8
                  Fallo_mecanico class.error
Desgaste_natural               5   0.8750000
Error_operativo                7   0.8974359
Fallo_electronico              5   0.6511628
Fallo_mecanico                 2   0.9230769

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                          6               4                 6
  Compresor                      1               4                 3
  Motor_Electrico                6               0                 5
  Ventilador                     5               8                 5
                 Predicciones
Datos_Reales      Fallo_mecanico
  Bomba                        1
  Compresor                    2
  Motor_Electrico              3
  Ventilador                   1
# 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.2666667 

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       0.60685756       -1.536925
Operario                         -0.41953877       -1.538000
Piezas_Producidas                -0.56501110       -2.823134
Familia_Equipo                   -2.47097250       -1.419639
Costo_Reparacion_USD              0.09730541       -1.535871
Vibracion_mm_s                   -2.48393986       -1.444552
Temperatura_C                    -1.56708797       -3.034960
                            Fallo_electronico Fallo_mecanico
Tiempo_Funcionamiento_Horas        -1.0503329      1.1879474
Operario                           -0.6295352     -1.5651636
Piezas_Producidas                  -2.3828743     -2.6606697
Familia_Equipo                      1.7244015     -1.9240614
Costo_Reparacion_USD                5.0821205      0.8868473
Vibracion_mm_s                      0.1537464     -1.3725841
Temperatura_C                      -2.0682968      0.7320836
                            MeanDecreaseAccuracy MeanDecreaseGini
Tiempo_Funcionamiento_Horas           -0.5433701        18.881259
Operario                              -1.7293070         5.858783
Piezas_Producidas                     -4.1163745        16.521316
Familia_Equipo                        -1.4939224         8.373440
Costo_Reparacion_USD                   2.1662740        19.234126
Vibracion_mm_s                        -2.4058142        16.918831
Temperatura_C                         -3.4401144        17.168891
# 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.09730541       -1.535871
2 Tiempo_Funcionamiento_Horas       0.60685756       -1.536925
3               Temperatura_C      -1.56708797       -3.034960
4              Vibracion_mm_s      -2.48393986       -1.444552
5           Piezas_Producidas      -0.56501110       -2.823134
6              Familia_Equipo      -2.47097250       -1.419639
7                    Operario      -0.41953877       -1.538000
  Fallo_electronico Fallo_mecanico MeanDecreaseAccuracy
1         5.0821205      0.8868473            2.1662740
2        -1.0503329      1.1879474           -0.5433701
3        -2.0682968      0.7320836           -3.4401144
4         0.1537464     -1.3725841           -2.4058142
5        -2.3828743     -2.6606697           -4.1163745
6         1.7244015     -1.9240614           -1.4939224
7        -0.6295352     -1.5651636           -1.7293070
  MeanDecreaseGini
1        19.234126
2        18.881259
3        17.168891
4        16.918831
5        16.521316
6         8.373440
7         5.858783

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, "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 = {Random Forest en Mantenimiento Crítico},
  journal = {Themys: Ciencia Abierta y Reproductible},
  year = {2026},
  doi = {10.5281/zenodo.17663213},
  volume = {1},
  issue = {1},
  issn = {ISSN en trámite}
}