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.
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]
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.
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
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
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
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
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)
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
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)
)
reorder(Variable, MeanDecreaseGini): Ordena automáticamente las barras de menor a mayor importancia, haciendo que el gráfico sea mucho más fácil de interpretar de un vistazo.
coord_flip(): Convierte el gráfico de barras vertical tradicional en uno horizontal, lo cual es ideal para etiquetas de variables largas como las que tenemos.
theme_minimal(): Aplica un tema limpio y minimalista que es estéticamente superior al gráfico base de R.
scale_fill_viridis_c(option = “plasma”): Aplica una paleta de colores perceptualmente uniforme y apta para daltónicos (del paquete viridis, que ggplot2 puede usar por defecto).
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.
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
)
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
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}
}