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.
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]
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 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
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
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
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
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)
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
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, "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}
}