Una empresa plantea la necesidad de comprender las dinámicas de rotación de sus empleados, determinando que factores influyen en la desición de renunciar y, de esta forma, generar estrategias que permitan retener el talento en las posiciones claves de la compañía.
En el informe que se desarrollará en el presente documento, se aplicará un modelo de regresión logística que tiene por objetivo identificar las variables más relevantes que inciden en la decisión de renuncia de los colaboradores, y sobre éstas, tomar medidas proactivas para la retención del talento.
La metodología que se llevará a cabo para desarrollar el modelo se relaciona a continuación:
# Cargar el dataset
options(repos = c(CRAN = "https://cran.rstudio.com/"))
library(paqueteMODELOS)
data("rotacion")
Inicialmente para el desarrollo del proyecto, vamos a utilizar la base datos denominada: “rotación” que ha sido suministrada por la organización y, en la que han recopilado y registrado los datos históricos sobre el empleo de sus colaboradores, incluyendo variables como antigüedad en el cargo actual, nivel de satisfacción laboral, el salario actual, edad, entre otros factores relevantes, sobre los cuales desarrollaremos el modelo.
En esta etapa consultaremos información disponible en la Web que hayan estudiado casos de rotación de personal y con los resultados obtenidos seleccionaremos las variables que en nuestro juicio y apoyados en los estudios consultados, seleccionaremos las variables que entendemos influyen en la rotación de personal en la empresa.
Examinando la información disponible sobre casos de estudio de rotación de personal en fuentes de Internet, encontramos un caso de estudio titulado: “Análisis de las causas de la rotación de personal en el área Comercial de una Gran Superficie”, que se desarrolla por medio de entrevistas a ex empleados de una empresa de grandes superficies y se indaga sobre los motivos por los cuales renunciaron a la compañía , en el estudio el autor identificó 13 factores que inciden en la rotación de personal:
En otro estudio titulado: “Análisis de las causas de rotación de personal de la empresa Holcrest S.A.S” el autor desarrolla el estudio bajo una metodología de tipo cuantitativo descriptivo por medio de una encuesta a los empleados de la compañía, en el estudio el autor concluye que las variables que más impactan en la rotación de la planta de personal en la empresa son:
Revisando artículos de medios periodísticos reconocidos, La República en un articulo del 19 de noviembre de 2021, titulado: “ La rotación en empresas aumenta hoy porque trabajadores busca más flexibilidad”, menciona que las principales causas de la rotación de personal son:
El tiempo en un artículo del 06 de Marzo de 2024, titulado: “ La rotación de personal, un desafío que impacta a las empresas colombianas”, indica que las causas de rotación de personal están asociadas a:
Realizando un comparativo entre las cuatro fuentes de información, identificamos que los estudios coinciden con las siguientes causas atribuibles a la rotación de personal:
Preliminarmente, desde un juicio a priori y apoyados en los estudios consultados en Internet, identificamos que las variables categóricas y numéricas que están más estrechamente relacionadas con la rotación de personal en la empresa son las siguientes:
Se estima que el ingreso de los colaboradores esté relacionado inversamente con la rotación, debido a que los colaboradores con salarios más bajos tienden a buscar trabajos mejor remunerados, lo que aumenta la probabilidad de renunciar. Por el contrario, los colaboradores con salarios más altos tienen menor probabilidad de rotar.
Se proyecta que la edad esté negativamente relacionada con la rotación, debido a que los colaboradores de mayor edad son más estables, mientras que los colaboradores más jóvenes son más propensos a cambiar de empleo.
Se espera una relación negativa entre los años de experiencia y la rotación, porqué los colaboradores más experimentados son menos propensos a salir de la empresa, por otro lado, los que tienen menos años de experiencia pueden cambiar de trabajo.
Se prevé una relación positiva entre las horas extras y la rotación, debido a que, las horas extras puede ser un indicativo de sobre carga laboral, que puede motivar a los colaboradores a buscar mejores alternativas laborales.
Se estima una relación negativa entre el equilibrio trabajo-vida y la rotación, desequilibrios entre el horario laboral y no laboral, puede motivar a los colaboradores a renunciar.
Se espera que la satisfacción con el ambiente laboral esté inversamente relacionada con la rotación, un ambiente de trabajo tóxico aumenta la intención de abandono por parte de los colaboradores.
dimensiones <- dim(rotacion)
cat(paste0("Número de filas: ", dimensiones[1], "\n",
"Número de columnas:", dimensiones[2], "\n"))
## Número de filas: 1470
## Número de columnas:24
library(DT)
#Visualizar la estructura del dataset
datatable(rotacion,
options = list(
scrollX = TRUE,
autoWidth = TRUE,
columnDefs = list(
list(className = "dt-center", targets = "_all"),
list(width = '100px', targets = c(0,1,2)),
list(width = '150px', targets = c(3,4,5))
),
pageLength = 10,
lengthMenu = c(5, 10, 20, 50),
dom = 'Bfrtip'
),
class = "compact stripe hover",
rownames = FALSE)
En esta primera etapa de exploración, visualizamos que el dataset se compone de 24 columnas y 1.470 filas. Cada fila corresponde al registro de información único por empleado, por otro lado, las 24 columnas contienen información socio económica, geográfica y laboral que ha recopilado históricamente la organización.
library(DT)
# Visualizar diccionario de datos
diccionario <- data.frame(
Variable = c("Rotación", "Edad", "Viaje de Negocios", "Departamento", "Distancia_Casa",
"Educación", "Campo_Educación", "Satisfacción_Ambiental", "Genero", "Cargo",
"Satisfacción_Laboral", "Estado_Civil", "Ingreso_Mensual", "Trabajos_Anteriores",
"Horas_Extra", "Porcentaje_Aumento_Salarial", "Rendimiento_Laboral", "Años_Experiencia",
"Capacitaciones", "Equilibrio_Trabajo_Vida", "Antigüedad", "Antigüedad_Cargo",
"Años_Última_Promoción", "Años_Acargo_Con_Mismo_Jefe"),
Descripcion = c("Indica si el empleado ha dejado la empresa ('Sí'/'No')",
"Edad del empleado en años",
"Frecuencia con la que el empleado viaja por negocios",
"Departamento en el que trabaja el empleado",
"Distancia en kilómetros desde casa hasta el trabajo",
"Nivel de educación alcanzado",
"Campo de estudio del empleado",
"Nivel de satisfacción con el ambiente laboral",
"Género del empleado ('M'/'F')",
"Puesto o cargo dentro de la empresa",
"Nivel de satisfacción laboral",
"Estado civil del empleado ('Soltero', 'Casado', etc.)",
"Ingreso mensual del empleado en dólares",
"Número de trabajos previos antes de este empleo",
"Indica si el empleado trabaja horas extra ('Sí'/'No')",
"Porcentaje de aumento salarial recibido",
"Rendimiento laboral del empleado",
"Número de años de experiencia laboral",
"Número de capacitaciones recibidas",
"Balance entre trabajo y vida personal",
"Antigüedad en la empresa en años",
"Antigüedad en el cargo actual en años",
"Años desde la última promoción",
"Años a cargo con el mismo jefe"),
stringsAsFactors = FALSE
)
library(shiny)
datatable(diccionario,
options = list(
scrollX = TRUE,
autoWidth = FALSE,
pageLength = 7,
columnDefs = list(
list(targets = 0, width = "20%", className = "dt-left"),
list(targets = 1, width = "80%", className = "dt-left")
)
),
class = "compact stripe hover",
rownames = FALSE) %>%
formatStyle(
columns = c("Variable", "Descripcion"),
textAlign = "left"
) %>%
htmlwidgets::prependContent(
tags$style(HTML("
table.dataTable th, table.dataTable td {
text-align: left !important;
white-space: nowrap;
}
"))
)
En el diccionario de datos visualizamos la descripción y significado de cada variable, observamos también, que el dataset se compone de ocho variables categóricas nominales como: rotación, viaje de negocios, Departamento, Campo_Educación, Género, Cargo, Estado_Civil, Horas_Extra, en cuanto a las variables categóricas ordinales encontramos cinco: Educación, Satisfacción_Ambiental, Satisfacción_Laboral, Rendimiento_Laboral, Equilibrio_Trabajo_Vida.
En cuanto a las variables numéricas, identificamos siete variables discretas: Trabajos_Anteriores, Capacitaciones, Años_Experiencia, Antigüedad, Antigüedad_Cargo, Años_Última_Promoción, Años_Acargo_Con_Mismo_Jefe, por último tenemos cuatro variables numéricas continuas: Edad, Distancia_Casa, Ingreso_Mensual, Porcentaje_Aumento_Salarial.
# Cargar librerías necesarias
library(DT)
# Crear un diccionario de datos
diccionario_datos <- data.frame(
Variable = c("Rendimiento_laboral", "Distancia_casa", "Educacion",
"Satisfaccion_ambiental", "Satisfaccion_laboral",
"Trabajos_anteriores", "Equilibrio_trabajo_vida"),
Descripcion = c("Nivel de rendimiento en el trabajo",
"Kilómetros de distancia desde la casa",
"Nivel educativo alcanzado",
"Grado de satisfacción con el ambiente de trabajo",
"Grado de satisfacción con el trabajo",
"Número de trabajos antes de ingresar a la empresa",
"Equilibrio entre vida laboral y personal"),
Valores = c("1=Bajo, 2=Medio, 3=Alto, 4=Muy alto",
"Distancia en kilómetros",
"1=Primaria, 2=Secundaria, 3=Técnico/Tecnólogo, 4=Pregrado, 5=Posgrado",
"1=Muy insatisfecho, 2=Insatisfecho, 3=Satisfecho, 4=Muy satisfecho",
"1=Muy insatisfecho, 2=Insatisfecho, 3=Satisfecho, 4=Muy satisfecho",
"Cantidad numérica",
"1=Muy bajo, 2=Bajo, 3=Medio, 4=Alto"),
stringsAsFactors = FALSE
)
# Mostrar el diccionario de datos en formato tabla interactiva
datatable(diccionario_datos,
options = list(
scrollX = TRUE,
autoWidth = TRUE,
pageLength = 7
),
class = "compact stripe hover",
rownames = FALSE)
Observamos en la tabla, que las variables Rendimiento_laboral, Educación, Satisfacción_ambiental, Satisfacción_laboral, Equilibrio_trabajo_vida,han sido codificadas previamente con números en la base de datos.
# Visualizar tipo de variables
library(dplyr)
library(tibble)
library(DT)
estructura_rotacion <- tibble(
Variable = names(rotacion),
Tipo = sapply(rotacion, \(x) class(x)[1])
) %>% arrange(Tipo)
datatable(estructura_rotacion, options = list(
dom = 't',
pageLength = nrow(estructura_rotacion),
scrollX = TRUE
))
Una vez visualizamos en la tabla el tipo de datos, identificamos variables categóricas que se definen como tipo numero por su contenido,sin embargo, como mencionamos en el paso anterior, en la base de datos previamente se codificaron con números,por lo tanto, es necesario definir estas variables como categóricas.
# Definir los tipos de variables categóricas y numericas
library(dplyr)
library(DT)
variables_categoricas <- c("Rotación", "Viaje_de_Negocios", "Departamento",
"Campo_Educación", "Genero", "Cargo",
"Estado_Civil", "Horas_Extra",
"Educación", "Satisfacción_Ambiental",
"Satisfacción_Laboral", "Rendimiento_Laboral",
"Equilibrio_Trabajo_Vida")
variables_numericas <- c("Edad", "Distancia_Casa", "Ingreso_Mensual",
"Porcentaje_Aumento_Salarial", "Trabajos_Anteriores",
"Capacitaciones", "Años_Experiencia", "Antigüedad",
"Antigüedad_Cargo", "Años_Última_Promoción",
"Años_Acargo_Con_Mismo_Jefe")
estructura_variables <- data.frame(
Variable = c(variables_categoricas, variables_numericas),
Tipo = c(rep("Categorica", length(variables_categoricas)),
rep("Numerica", length(variables_numericas)))
)
datatable(estructura_variables,
options = list(
scrollX = TRUE,
scrollY = "400px",
autoWidth = TRUE,
pageLength = nrow(estructura_variables),
dom = 'Bfrtip'
),
class = "compact stripe hover",
rownames = FALSE) %>%
formatStyle(
columns = names(estructura_variables),
textAlign = "left"
) %>%
htmlwidgets::prependContent(
tags$style(HTML("
.datatables {
width: 100% !important; /* Ocupa todo el ancho disponible */
margin-left: 0px !important; /* Asegura que la tabla esté pegada a la izquierda */
text-align: left !important; /* Alineación del texto */
}
table.dataTable {
width: 100% !important; /* Hace que la tabla se expanda completamente */
}
.dataTables_scrollBody {
overflow-y: auto !important; /* Activa el desplazamiento vertical */
}
"))
)
La definición de las variables según su categoría es indispensable, ya que con la definición correcta, se garantiza que el modelo identifique el tipo de variable y la procese e interprete de forma adecuada evitando errores y resultados no confiables.
Continuando con la exploración de los datos, en este caso específicamente para las variables numéricas, vamos a visualizar como es su distribución, mínimos y máximos, promedios e identificar si se presentan datos atípicos.
# Generar grafico de cajas
library(plotly)
library(reshape2)
library(htmltools)
library(dplyr)
variables_numericas <- c("Edad", "Distancia_Casa", "Ingreso_Mensual",
"Porcentaje_aumento_salarial", "Trabajos_Anteriores",
"Capacitaciones", "Años_Experiencia", "Antigüedad",
"Antigüedad_Cargo", "Años_ultima_promoción",
"Años_acargo_con_mismo_jefe")
rotacion_melt <- melt(rotacion, measure.vars = variables_numericas)
grupos <- list(
c("Edad", "Distancia_Casa", "Ingreso_Mensual"),
c("Porcentaje_aumento_salarial", "Trabajos_Anteriores", "Capacitaciones"),
c("Años_Experiencia", "Antigüedad", "Antigüedad_Cargo", "Años_ultima_promoción", "Años_acargo_con_mismo_jefe")
)
browsable(tagList(
lapply(grupos, function(g) {
subplot(
lapply(g, function(v) {
plot_ly(rotacion_melt[rotacion_melt$variable == v, ],
y = ~value, type = "box", name = v) %>%
layout(
xaxis = list(
title = "",
tickangle = -45,
tickfont = list(size = 10)
),
yaxis = list(title = "Valores"),
legend = list(
font = list(size = 10),
x = 1.1,
y = 1
)
)
}),
nrows = 1, shareX = TRUE, titleX = TRUE
)
})
))
Preliminarmente, realizando un análisis descriptivo de las variables numéricas obtenemos la siguiente información:
En cuanto a la edad de los empleados observamos que oscilan entre los 18 y 60 años, con una media de 36, la mayoría de los empleados se ubican entre los 30 y 43 años, con lo cual, podemos inferir que la nomina presenta un equilibro entre población joven y adulta en términos de edad, por otro lado no observamos valores atípicos, y es coherente que la edad mínima sea de 18 años, que es la edad mínima legal para trabajar.
La distancia del trabajo al hogar presenta desplazamientos para algunos empleados desde 1 a 29 km, con una distancia promedio de 9 Km, observamos en la distribución de los datos que, la mayoría de los empleados viven entre 1 a 14 Km de distancia; sin embargo, la mayoría en este grupo tiene desplazamientos superiores a los 7 km, no se visualizan valores atípicos.
El nivel de ingresos se ubica en un rango salarial aproximado desde los 1.000 hasta los 20.000 USD, con un promedio aproximado de 6.500 USD, la mayoría de los colaboradores devengan salarios entre los 2.900 hasta 8.300 USD, observamos que la mitad de los empleados perciben hasta 5.000 USD o menos, sin embargo, contrastando la mediana con la media, identificamos que esta última es mayor, por lo que podemos inferir que algunos empleados tienen salarios muy elevados que, es coherente con las asignaciones salariales de los cargos claves en la alta dirección.
Revisando el aumento porcentual del salario, el aumento minino es de 11% y el máximo del 25%, la mayoría de los empleados obtuvieron aumentos entre el 12% al 18%, así mismo, la mitad de los empleados percibieron aumentos hasta el 14%, en promedio el aumento es del 15%, no observamos valores atípicos, y de acuerdo con la desviación estándar (3,66), la dispersión es relativamente baja, por otro lado, es normal visualizar aumentos significativamente altos en comparación con la media, se puede inferir que son posiciones estratégicas para la compañía.
Analizando la variable trabajos anteriores, identificamos que, la mayoría de los empleados cuenta con experiencia previa, como mínimo han trabajado en dos organizaciones antes de ingresar a la compañía; sin embargo, observamos empleados cuya primer experiencia laboral la están desarrollando en la compañía, por otro lado, se observa que hay empleados con una trayectoria laboral dinámica, se identifican valores atípicos con hasta 9 empleos previo al ingreso a la empresa, este dato es importante porque sobre estos podemos inferir que son empleados que rotan constantemente.
Validando las capacitaciones impartidas a los colaboradores, en su mayoría han realizado hasta 3 capacitaciones, también observamos que algunos empleados no han tenido capacitaciones y otros han recibido más capacitaciones que otros, observamos valores atípicos.
La variable experiencia presenta una variabilidad considerable, observamos empleados sin experiencia y otros hasta con 40 años de experiencia, en su mayoría los colaboradores tienen entre 6 y 15 años de experiencia, se observan valores atípicos. En cuanto a la antigüedad con la compañía, obtenemos que, la mitad de los empleados tiene hasta 5 años, la mayoría de los empleados tienen entre 3 y 9 años de antigüedad, por otro lado, identificamos empleados con un antigüedad considerable desde 19 a los 40 años, observamos también valores atípicos.
visualizando la variable antigüedad en el cargo, obtenemos que en su mayoría los empleados han permanecido en su posición entre 2 y 7 años, encontramos empleados que han permanecido en su puesto actual desde 7 a 18 años, ahora bien, observando la variable años desde la ultima promoción, la mayoría de los empleados han sido ascendidos hace un año; sin embargo, hay otros que desde hace 8 a 15 años no han sido promovidos, las variables indicadas anteriormente presentan datos atípicos.
Para finalizar, en cuanto a la variable años a cargo con el mismo Jefe, la mayoría ha trabajado con el mismo jefe desde los 2 a 7 años, algunos colaboradores han estado a cargo del mismo jefe desde 7 a 14 años, así mismo se observan valores atípicos.
En esta parte del análisis, nos enfocaremos en la exploración de las variables cualitativas
# Definir las variables categóricas a analizar con el nombre correcto
variables_categoricas <- c("Rotación", "Viaje de Negocios", "Departamento",
"Campo_Educación", "Genero", "Cargo",
"Estado_Civil", "Horas_Extra",
"Educación", "Satisfacción_Ambiental",
"Satisfación_Laboral", "Rendimiento_Laboral",
"Equilibrio_Trabajo_Vida")
variables_categoricas_presentes <- intersect(names(rotacion), variables_categoricas)
diccionario_reemplazo <- list(
"Rendimiento_Laboral" = c("1" = "Bajo", "2" = "Medio", "3" = "Alto", "4" = "Muy Alto"),
"Educación" = c("1" = "Primaria", "2" = "Secundaria", "3" = "Técnico/Tecnólogo", "4" = "Pregrado", "5" = "Posgrado"),
"Satisfacción_Ambiental" = c("1" = "Muy Insatisfecho", "2" = "Insatisfecho", "3" = "Satisfecho", "4" = "Muy Satisfecho"),
"Satisfación_Laboral" = c("1" = "Muy Insatisfecho", "2" = "Insatisfecho", "3" = "Satisfecho", "4" = "Muy Satisfecho"),
"Equilibrio_Trabajo_Vida" = c("1" = "Muy Bajo", "2" = "Bajo", "3" = "Medio", "4" = "Alto")
)
rotacion_mod <- rotacion
for (var in names(diccionario_reemplazo)) {
if (var %in% names(rotacion_mod)) {
rotacion_mod <- rotacion_mod %>%
mutate(!!sym(var) := recode(as.character(.data[[var]]), !!!diccionario_reemplazo[[var]]))
}
}
resumen_categoricas <- list()
for (var in variables_categoricas_presentes) {
tabla <- rotacion_mod %>%
mutate(across(all_of(var), as.character)) %>%
group_by(.data[[var]]) %>%
summarise(Frecuencia = n(), .groups = "drop") %>%
mutate(Porcentaje = round((Frecuencia / sum(Frecuencia)) * 100, 2),
Variable = var) %>%
rename(Categoria = 1)
resumen_categoricas[[var]] <- tabla
}
resumen_categoricas_df <- bind_rows(resumen_categoricas) %>%
select(Variable, Categoria, Frecuencia, Porcentaje)
datatable(resumen_categoricas_df,
options = list(
scrollX = TRUE,
autoWidth = TRUE,
pageLength = 10,
lengthMenu = c(5, 10, 20, 50),
dom = 'Bfrtip'
),
class = "compact stripe hover",
rownames = FALSE) %>%
formatRound(columns = "Porcentaje", digits = 2)
# Cargar librerías necesarias
library(dplyr)
library(plotly)
# Diccionario de reemplazo para variables categóricas codificadas
diccionario_reemplazo <- list(
"Rendimiento_Laboral" = c("3" = "Alto", "4" = "Muy Alto")
)
# Variables a graficar
variables_categoricas <- c("Rotación", "Genero", "Horas_Extra", "Rendimiento_Laboral")
# Verificar que las variables estén en el dataset
variables_presentes <- intersect(names(rotacion), variables_categoricas)
# Aplicar reemplazos en las variables categóricas
rotacion_mod <- rotacion
for (var in names(diccionario_reemplazo)) {
if (var %in% names(rotacion_mod)) {
rotacion_mod[[var]] <- recode(as.character(rotacion_mod[[var]]), !!!diccionario_reemplazo[[var]])
}
}
# Definir colores para cada gráfica
colores_plotly <- c("#1f77b4", "#ff7f0e", "#2ca02c", "#d62728")
# Crear gráficas individuales
graficos_barras <- lapply(seq_along(variables_presentes), function(i) {
var <- variables_presentes[i]
datos_frecuencia <- rotacion_mod %>%
count(.data[[var]]) %>%
rename(Categoria = 1, Frecuencia = n)
plot_ly(datos_frecuencia,
x = ~Categoria,
y = ~Frecuencia,
type = "bar",
name = var,
marker = list(color = colores_plotly[i])) %>%
layout(
title = list(text = var, x = 0.5, y = 0.9, font = list(size = 14)),
xaxis = list(title = "", tickangle = 0, tickfont = list(size = 10)),
yaxis = list(title = "Frecuencia"),
margin = list(t = 50, b = 100) # Reducimos margen inferior para evitar exceso de espacio
)
})
# Renderizar el gráfico como un grid de 2 filas x 2 columnas
subplot(
graficos_barras[[1]], graficos_barras[[2]],
graficos_barras[[3]], graficos_barras[[4]],
nrows = 2,
shareX = FALSE,
titleX = TRUE
) %>%
layout(
title = "Distribución de Variables Categóricas",
showlegend = TRUE,
margin = list(t = 50, l = 50, r = 50, b = 100), # Ajuste de márgenes
height = 580, # Ajustamos la altura para no sobrepasar el render
width = 900 # Limitamos el ancho
)
# Agregar un margen inferior para asegurar que el texto sea visible
cat("<br><br><br>")
## <br><br><br>
Observamos en este primer grupo de variables que, el 84% de los empleados han conservado su empleo, mientras que el 16% restante ha renunciado, para el ejercicio es interesante examinar las variables de este segmento para determinar patrones que nos permitan identificar la causa de la renuncia.
En cuanto al género se identifica que la fuerza laboral en su mayoría predomina los hombres con el 60% y el 40% mujeres, lo que denota un desequilibrio en la equidad de género, y podría ser un determinante de rotación en el género femenino por falta de oportunidades.
El 28% de los empleados trabaja horas extras, mientras que el 72% restante no lo hace, esta variable puede ser un detonante en términos de rotación, posiblemente quienes se extienden en la jornada laboral pueden tener intenciones de renunciar.
Por último, observamos que la fuerza laboral es productiva ubicándose en las categorías alta con el 85% y muy alta con el 15% del rendimiento laboral.
# Cargar librerías necesarias
library(dplyr)
library(plotly)
# Diccionario de reemplazo de valores codificados por categorías
diccionario_reemplazo <- list(
"Satisfación_Laboral" = c("1" = "Muy Insatisfecho", "2" = "Insatisfecho", "3" = "Satisfecho", "4" ="Muy Satisfecho"),
"Equilibrio_Trabajo_Vida" = c("1" = "Muy Bajo", "2" = "Bajo", "3" = "Medio", "4" = "Alto")
)
# Definir las variables categóricas a graficar
variables_categoricas <- c("Viaje de Negocios", "Satisfación_Laboral", "Departamento", "Equilibrio_Trabajo_Vida")
# Filtrar solo las variables presentes en el dataset
variables_presentes <- intersect(names(rotacion), variables_categoricas)
# Aplicar reemplazo de valores en variables codificadas
rotacion_mod <- rotacion
for (var in names(diccionario_reemplazo)) {
if (var %in% names(rotacion_mod)) {
rotacion_mod[[var]] <- recode(as.character(rotacion_mod[[var]]), !!!diccionario_reemplazo[[var]])
}
}
# Definir la paleta de colores para cada gráfico
colores_plotly <- c("#1f77b4", "#ff7f0e", "#2ca02c", "#d62728")
# Crear lista para almacenar gráficos
graficos_barras <- lapply(seq_along(variables_presentes), function(i) {
var <- variables_presentes[i]
datos_frecuencia <- rotacion_mod %>%
count(.data[[var]]) %>%
rename(Categoria = 1, Frecuencia = n)
plot_ly(datos_frecuencia,
x = ~Categoria,
y = ~Frecuencia,
type = "bar",
name = var,
marker = list(color = colores_plotly[i])) %>%
layout(title = list(text = var, x = 0.5, y = 0.9, font = list(size = 14)),
xaxis = list(title = "", tickangle =-7, tickfont = list(size = 10)),
yaxis = list(title = "Frecuencia"),
margin = list(t = 50, b = 80))
})
# Organizar los gráficos en una cuadrícula de 2 filas y 2 columnas
subplot(graficos_barras[[1]], graficos_barras[[2]],
graficos_barras[[3]], graficos_barras[[4]],
nrows = 2, shareX = FALSE, titleX = TRUE) %>%
layout(title = "Distribución de Variables Categóricas",
showlegend = TRUE,
margin = list(t = 50, l = 50, r = 50, b = 80),
height = 500,
width = 900)
En este segundo grupo, observamos que en la variable viaje de negocios, predominan los empleados que raramente viajan con el 70 %, seguido de los viajeros frecuentes con el 19% y en último lugar los que nunca viajan con el 11%, los viajeros frecuentes posiblemente pueden presentar motivaciones para renunciar, debido a que los continuos desplazamientos pueden ser agotadores.
La distribución de acuerdo con los departamentos de la empresa se concentra en su mayoría en el área de I y D con el 65%, en segundo lugar, se encuentran el área de Ventas con el 30%, y en último lugar el área de Recursos Humanos con 5%, podemos inferir que el objeto social de la compañía se centra en servicios de Investigación y Desarrollo.
En cuanto a la satisfacción laboral, obtenemos que las clases mayoritarias son muy satisfecho con el 30% y satisfecho con el 30%, por otro lado, insatisfechos con el 19% y muy insatisfechos con el 21%, observamos que si bien, la mayoría de los empleados están satisfechos (60%), se presenta un porcentaje considerable de empleados no satisfechos (40%), una participación importante teniendo en cuenta que el grado de satisfacción puede ser determinante para la renuncia de un empleado y esta proporción puede ser una señal de alerta para la compañía dado que, si se materializara la renuncia de este segmento de empleados, podría poner en aprietos la continuidad de los procesos de la organización.
Por último, la mayoría de los colaboradores considera que el equilibrio trabajo vida es medio con el 61%, seguido por bajo con un 23% y alto con 10% y en ultimo lugar muy bajo con 6%, observamos que bajo, muy bajo y medio tienen una participación del 90%, y si contrastamos con la satisfacción laboral es posible que exista una relación entre estas variables, es decir, que el grado de insatisfacción este explicado por el equilibrio trabajo y vida.
# Cargar librerías necesarias
library(dplyr)
library(plotly)
# Diccionario de reemplazo de valores codificados por categorías
diccionario_reemplazo <- list(
"Educación" = c("1" = "Primaria", "2" = "Secundaria", "3" = "Técnico/Tecnólogo", "4" = "Pregrado", "5" = "Posgrado"),
"Satisfacción_Ambiental" = c("1" = "Muy Insatisfecho", "2" = "Insatisfecho", "3" = "Satisfecho", "4" = "Muy Satisfecho")
)
# Definir las variables categóricas a graficar
variables_categoricas <- c("Educación", "Campo_Educación")
# Filtrar solo las variables presentes en el dataset
variables_presentes <- intersect(names(rotacion), variables_categoricas)
# Aplicar reemplazo de valores en variables codificadas
rotacion_mod <- rotacion
for (var in names(diccionario_reemplazo)) {
if (var %in% names(rotacion_mod)) {
rotacion_mod[[var]] <- recode(as.character(rotacion_mod[[var]]), !!!diccionario_reemplazo[[var]])
}
}
# Definir la paleta de colores para cada gráfico
colores_plotly <- c("#9467bd", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd")
# Crear lista para almacenar gráficos
graficos_barras <- lapply(seq_along(variables_presentes), function(i) {
var <- variables_presentes[i]
datos_frecuencia <- rotacion_mod %>%
count(.data[[var]]) %>%
rename(Categoria = 1, Frecuencia = n)
plot_ly(datos_frecuencia,
x = ~Categoria,
y = ~Frecuencia,
type = "bar",
name = var,
marker = list(color = colores_plotly[i])) %>%
layout(title = list(text = var, x = 0.5, y = 0.9, font = list(size = 14)),
xaxis = list(title = "", tickangle = 0, tickfont = list(size = 10)),
yaxis = list(title = "Frecuencia"),
margin = list(t = 50, b = 80))
})
# Organizar los gráficos en una cuadrícula de 1 columna y 5 filas
subplot(graficos_barras[[1]], graficos_barras[[2]],
nrows = 2, shareX = FALSE, titleX = TRUE) %>%
layout(title = "Distribución de Variables Categóricas",
showlegend = TRUE,
margin = list(t = 50, l = 50, r = 50, b = 80),
height = 500, # Ajuste de altura para mejor visualización
width = 900) # Ajuste de ancho
En este grupo, identificamos que, aproximadamente el 70% de los colaboradores no alcanzaron el pregrado, y el 30% restante tienen pregrado y postgrado, esta diferenciación posiblemente explique la brecha entre salarios de los empleados.
Analizando la variable campo de educación, obtenemos que predominan ciencias con el 41% y salud con el 32%, que en conjunto suman el 73%, podríamos inferir que es una empresa de la industria de la salud, por otro lado, el restante tiene una participación del 27% con perfiles de cargos administrativos.
# Cargar librerías necesarias
library(dplyr)
library(plotly)
# Diccionario de reemplazo de valores codificados por categorías
diccionario_reemplazo <- list(
"Educación" = c("1" = "Primaria", "2" = "Secundaria", "3" = "Técnico/Tecnólogo", "4" = "Pregrado", "5" = "Posgrado"),
"Satisfacción_Ambiental" = c("1" = "Muy Insatisfecho", "2" = "Insatisfecho", "3" = "Satisfecho", "4" = "Muy Satisfecho")
)
# Definir las variables categóricas a graficar
variables_categoricas <- c("Satisfacción_Ambiental", "Estado_Civil")
# Filtrar solo las variables presentes en el dataset
variables_presentes <- intersect(names(rotacion), variables_categoricas)
# Aplicar reemplazo de valores en variables codificadas
rotacion_mod <- rotacion
for (var in names(diccionario_reemplazo)) {
if (var %in% names(rotacion_mod)) {
rotacion_mod[[var]] <- recode(as.character(rotacion_mod[[var]]), !!!diccionario_reemplazo[[var]])
}
}
# Definir la paleta de colores para cada gráfico
colores_plotly <- c("#d62728", "#2ca02c", "#2ca02c", "#d62728", "#9467bd")
# Crear lista para almacenar gráficos
graficos_barras <- lapply(seq_along(variables_presentes), function(i) {
var <- variables_presentes[i]
datos_frecuencia <- rotacion_mod %>%
count(.data[[var]]) %>%
rename(Categoria = 1, Frecuencia = n)
plot_ly(datos_frecuencia,
x = ~Categoria,
y = ~Frecuencia,
type = "bar",
name = var,
marker = list(color = colores_plotly[i])) %>%
layout(title = list(text = var, x = 0.5, y = 0.9, font = list(size = 14)),
xaxis = list(title = "", tickangle = 0, tickfont = list(size = 10)),
yaxis = list(title = "Frecuencia"),
margin = list(t = 50, b = 80))
})
# Organizar los gráficos en una cuadrícula de 1 columna y 5 filas
subplot(graficos_barras[[1]], graficos_barras[[2]],
nrows = 2, shareX = FALSE, titleX = TRUE) %>%
layout(title = "Distribución de Variables Categóricas",
showlegend = TRUE,
margin = list(t = 50, l = 50, r = 50, b = 80),
height = 500, # Ajuste de altura para mejor visualización
width = 900) # Ajuste de ancho
En cuanto a la satisfacción ambiental, la mayoría de los colaboradores, están satisfechos y muy satisfechos con el 60%, en comparación con muy insatisfecho e insatisfecho con el 40%, que es coherente con la medición de la variable satisfacción laboral, es decir que, la percepción de insatisfacción laboral se replica para la insatisfacción ambiental.
El 46% de los colaboradores son casados, el 32% solteros y el 22% divorciados, esta variable es importante en términos de retención de personal, podríamos inferir que los empleados casados y divorciados pueden tener hijos, en ese sentido, esta variable es significativa al momento de tomar la decisión de renunciar.
# Cargar librerías necesarias
library(dplyr)
library(plotly)
# Diccionario de reemplazo de valores codificados por categorías
diccionario_reemplazo <- list(
"Educación" = c("1" = "Primaria", "2" = "Secundaria", "3" = "Técnico/Tecnólogo", "4" = "Pregrado", "5" = "Posgrado"),
"Satisfacción_Ambiental" = c("1" = "Muy Insatisfecho", "2" = "Insatisfecho", "3" = "Satisfecho", "4" = "Muy Satisfecho")
)
# Definir las variables categóricas a graficar
variables_categoricas <- c("Campo_Educación")
# Filtrar solo las variables presentes en el dataset
variables_presentes <- intersect(names(rotacion), variables_categoricas)
# Aplicar reemplazo de valores en variables codificadas
rotacion_mod <- rotacion
for (var in names(diccionario_reemplazo)) {
if (var %in% names(rotacion_mod)) {
rotacion_mod[[var]] <- recode(as.character(rotacion_mod[[var]]), !!!diccionario_reemplazo[[var]])
}
}
# Definir la paleta de colores para cada gráfico
colores_plotly <- c("#2ca02c", "#2ca02c", "#2ca02c", "#d62728", "#9467bd")
# Crear lista para almacenar gráficos
graficos_barras <- lapply(seq_along(variables_presentes), function(i) {
var <- variables_presentes[i]
datos_frecuencia <- rotacion_mod %>%
count(.data[[var]]) %>%
rename(Categoria = 1, Frecuencia = n)
plot_ly(datos_frecuencia,
x = ~Categoria,
y = ~Frecuencia,
type = "bar",
name = var,
marker = list(color = colores_plotly[i])) %>%
layout(title = list(text = var, x = 0.5, y = 0.9, font = list(size = 14)),
xaxis = list(title = "", tickangle = 0, tickfont = list(size = 10)),
yaxis = list(title = "Frecuencia"),
margin = list(t = 50, b = 80))
})
# Organizar los gráficos en una cuadrícula de 1 columna y 5 filas
subplot(graficos_barras[[1]],
nrows = 1, shareX = FALSE, titleX = TRUE) %>%
layout(title = "Distribución de Variables Categóricas",
showlegend = TRUE,
margin = list(t = 50, l = 50, r = 50, b = 80),
height = 500, # Ajuste de altura para mejor visualización
width = 900) # Ajuste de ancho
Las posiciones en las que se distribuyen los colaboradores son ejecutivo de ventas con 22.18%, Investigador Científico con 19,86%, Técnico de laboratorio con 17.62%, Director Manufactura 9.86%, Representante de Salud 8.91%, Gerente 6.94%, Representante de Ventas 5.65%, Director de Investigación 5.44% y Recursos Humanos 3.54, por los cargos, podríamos afirmar como lo habíamos mencionado anteriormente, que es una empresa que comercializa servicios de salud.
En esta etapa de nuestra metodología, con el objetivo de mejorar la calidad de los datos, identificaremos si en la base de datos existen datos nulos o faltantes, filas duplicadas y caracteres extraños que no correspondan al formato de la variable, en el evento de detectar estas casuísticas analizaremos la mejor estrategia para su tratamiento.
# Cargar librerías necesarias
library(dplyr)
library(DT)
# Función para detectar caracteres extraños (no alfanuméricos)
contar_caracteres_extranos <- function(columna) {
sum(grepl("[^A-Za-z0-9ÁÉÍÓÚáéíóúÑñ\\s]", columna, perl = TRUE), na.rm = TRUE)
}
# Crear la tabla resumen
tabla_resumen <- data.frame(
Variable = names(rotacion),
Campos_Nulos = sapply(rotacion, function(x) sum(is.na(x))),
Porcentaje_Nulos = sapply(rotacion, function(x) round(mean(is.na(x)) * 100, 2)),
Campos_Vacios = sapply(rotacion, function(x) sum(x == "", na.rm = TRUE)),
Porcentaje_Vacios = sapply(rotacion, function(x) round(mean(x == "", na.rm = TRUE) * 100, 2)),
Campos_Caracteres_Extraños = sapply(rotacion, function(x) contar_caracteres_extranos(as.character(x))),
Porcentaje_Caracteres_Extraños = sapply(rotacion, function(x) round(mean(grepl("[^A-Za-z0-9ÁÉÍÓÚáéíóúÑñ\\s]", as.character(x), perl = TRUE), na.rm = TRUE) * 100, 2))
)
# Mostrar solo la tabla sin título
datatable(tabla_resumen,
options = list(
scrollX = TRUE,
autoWidth = TRUE,
pageLength = 10,
dom = 'Bfrtip'
),
class = "compact stripe hover",
rownames = FALSE) %>%
formatRound(columns = c("Porcentaje_Nulos", "Porcentaje_Vacios", "Porcentaje_Caracteres_Extraños"), digits = 2)
Una vez generado el resumen de la calidad de los datos, obtenemos que la totalidad de las variables no contienen datos nulos ni faltantes, sin embargo, la variable viaje de negocios tiene 150 caracteres extraños equivalente al 10% y la variable cargo 1.368 equivalente al 93% de los datos, por lo tanto vamos a generar una tabla que nos permita visualizar estos caracteres y verificarlos.
# Cargar librerías necesarias
library(dplyr)
library(DT)
library(stringr)
# Definir las variables a analizar
variables_seleccionadas <- c("Viaje de Negocios", "Cargo")
# Función para detectar caracteres extraños en una columna y extraerlos
extraer_caracteres_extranos <- function(columna) {
valores_extranos <- columna[str_detect(columna, "[^A-Za-z0-9ÁÉÍÓÚáéíóúÑñ\\s]")]
valores_extranos <- unique(valores_extranos[valores_extranos != ""]) # Remover duplicados y valores vacíos
if (length(valores_extranos) == 0) return("Ninguno") else return(paste(valores_extranos, collapse = ", "))
}
# Filtrar solo las variables seleccionadas y convertir a carácter
rotacion_filtrada <- rotacion %>%
select(all_of(variables_seleccionadas)) %>%
mutate(across(everything(), as.character))
# Crear la tabla con caracteres extraños detectados
tabla_caracteres_extranos <- data.frame(
Variable = variables_seleccionadas,
Caracteres_Extraños_Identificados = sapply(rotacion_filtrada, extraer_caracteres_extranos)
)
# Mostrar la tabla en formato TB con DataTable
datatable(tabla_caracteres_extranos,
options = list(
scrollX = TRUE,
autoWidth = TRUE,
pageLength = 5,
dom = 'Bfrtip'
),
class = "compact stripe hover",
rownames = FALSE)
Extrayendo los caracteres extraños, visualizamos que el guion bajo (_), se ha incluido como un carácter erróneo; sin embargo, así fue nombrado en la tabla de datos y es correcto, por lo tanto, sobre este no aplicaremos ningún tratamiento.
A continuación, verificaremos si se presentan filas duplicadas.
# Cargar librerías necesarias
library(dplyr)
library(DT)
# Identificar filas duplicadas
filas_duplicadas <- rotacion %>%
group_by(across(everything())) %>%
filter(n() > 1) %>%
ungroup()
# Contar el número total de filas duplicadas
num_duplicados <- nrow(filas_duplicadas)
# Mostrar mensaje si no hay filas duplicadas
if (num_duplicados == 0) {
cat(" No se encontraron filas duplicadas en el dataset.\n")
} else {
# Mostrar tabla de filas duplicadas en formato TB
browsable(
tagList(
tags$h3(" Filas Duplicadas Identificadas",
style = "font-size: 16px; font-weight: bold; text-align: left; margin-bottom: 10px; color: #000;"),
datatable(filas_duplicadas,
options = list(scrollX = TRUE, pageLength = 10),
class = "compact stripe hover")
)
)
}
## No se encontraron filas duplicadas en el dataset.
En esta etapa, analizaremos como son las relaciones de las variables numéricas y categóricas con la variable objetivo rotación, el objetivo es identificar que variables presentan correlaciones significativas para incluirlas en el modelo.
También nos apoyaremos en el criterio experto, para lo cual, se consultará fuentes de información en Internet que hayan abordado casos de estudio sobre rotación de personal e identificaremos que variables explican la rotación en estos documentos, posteriormente con los datos de nuestra base, realizaremos un análisis de correlación Biserial para las variables numéricas y, para las variables categóricas una prueba Chi-cuadrado y correlación de Sperman, una vez obtengamos los resultados contrastamos los datos de los estudios y seleccionaremos las variables.
El análisis se enfoca en las variables numéricas y tiene por objetivo identificar las relaciones que se puedan presentar entre la variables objetivo y las variables predictoras.
# Cargar librerías necesarias
library(ltm) # Librería para biserial.cor()
library(dplyr)
library(DT)
# Convertir la variable "Rotación" en binaria (1 = "Si", 0 = "No")
rotacion_mod <- rotacion %>%
mutate(`Rotación` = ifelse(`Rotación` %in% c("Si", "Sí", "YES", "yes"), 1, 0))
# Definir las variables numéricas asegurando que los nombres coincidan con el dataset
variables_numericas <- c("Edad", "Distancia_Casa", "Ingreso_Mensual",
"Porcentaje_aumento_salarial", "Trabajos_Anteriores",
"Capacitaciones", "Años_Experiencia", "Antigüedad",
"Antigüedad_Cargo", "Años_ultima_promoción",
"Años_acargo_con_mismo_jefe")
# Crear una lista para almacenar los resultados
resultados <- data.frame(Variable = character(),
Correlacion_Biserial = numeric(),
P_Valor = numeric(),
stringsAsFactors = FALSE)
# Calcular la correlación biserial y el p-valor para cada variable numérica
for (var in variables_numericas) {
if (var %in% names(rotacion_mod)) {
# Calcular correlación biserial
corr_test <- biserial.cor(rotacion_mod[[var]], rotacion_mod$`Rotación`, use = "complete.obs")
# Extraer el p-valor usando la función cor.test
p_valor <- cor.test(rotacion_mod[[var]], rotacion_mod$`Rotación`)$p.value
# Guardar resultados en la lista
resultados <- rbind(resultados,
data.frame(Variable = var,
Correlacion_Biserial = round(corr_test, 3),
P_Valor = round(p_valor, 5)))
}
}
# Mostrar la tabla de resultados
datatable(resultados,
options = list(
scrollX = TRUE,
autoWidth = TRUE,
pageLength = 10,
dom = 'Bfrtip'),
class = "compact stripe hover",
rownames = FALSE) %>%
formatRound(columns = c("Correlacion_Biserial", "P_Valor"), digits = 3)
library(plotly)
# Ordenar los datos por correlación para un gráfico más limpio
resultados <- resultados[order(resultados$Correlacion_Biserial), ]
# Gráfico de barras horizontal con plotly
fig <- plot_ly(
data = resultados,
x = ~Correlacion_Biserial,
y = ~reorder(Variable, Correlacion_Biserial),
type = 'bar',
orientation = 'h',
text = ~round(Correlacion_Biserial, 2),
textposition = 'auto',
marker = list(color = ~Correlacion_Biserial,
colorscale = 'Viridis', # Puedes cambiar a 'Plotly', 'Cividis', etc.
showscale = FALSE)
)
fig <- fig %>% layout(
title = "Correlación Biserial con la Variable Rotación",
xaxis = list(title = "Coeficiente de Correlación Biserial"),
yaxis = list(title = list(text = "Variable", standoff = 20)),
margin = list(l = 160), # Para evitar que se corten las etiquetas
plot_bgcolor = 'rgba(0,0,0,0)',
paper_bgcolor = 'rgba(0,0,0,0)'
)
fig
El coeficiente de correlación Biserial toma valores entre -1 y 1, valores cercanos a 1, indican que existe una fuerte correlación positiva con la variable objetivo,es decir que, existe mayor probabilidad de rotar, mientras que valores cercanos a -1 indican lo contrario, es decir, una fuerte correlación negativa con la variables objetivo,en otras palabras más probabilidad de no rotar, por otro lado, valores cercanos a 0, indican que la relación es débil o no existe.
El P-valor nos indica si la correlación es estadísticamente significativa, de acuerdo con la siguiente regla general:
Validando los resultados, obtenemos que los coeficientes no presentan un grado fuerte de correlación con la variable objetivo porque no son cercanos a -1 o 1, sin embargo, identificamos algunos con un grado débil de correlación positiva como es el caso del ingreso mensual (0.160) , Edad (0.159), años experiencia (0.171), años a cargo con el mismo jefe (0.156), antigüedad cargo (0.161), antigüedad (0.134), es decir, que a medida que los valores de las variables aumentan la probabilidad de rotación aumenta, así mismo, el P-valor menor a 0.05 de estas variables nos indica que existe relación de estas variables con la rotación.
De acuerdo con el coeficiente de correlación en la variable distancia casa, existe una relación negativa muy débil(-0.078) nos indica que a mayor distancia de la casa al trabajo, la probabilidad de renuncia es menor, para el resto de variables, sus coeficientes no parecen estar directamente relacionados con la rotación como es el caso de trabajos anteriores (-0.043), porcentaje aumento salarial (0.013) y años última promoción (0.033).
A continuación, examinaremos si se presenta correlación entre la variable objetivo y categóricas, emplearemos la prueba Chi-cuadrado para las variables categóricas nominales Género, horas extra, estado civil, departamento, cargo, viaje de negocios y campo de educación.
# Cargar librerías necesarias
library(dplyr)
library(DT)
# Variables a analizar
variables_categoricas <- c("Genero", "Horas_Extra", "Estado_Civil",
"Departamento", "Cargo", "Viaje de Negocios", "Campo_Educación")
rotacion$Rotación <- as.factor(rotacion$Rotación)
chi_resultados <- data.frame(
Variable = character(),
Estadistico_Chi = numeric(),
Valor_p = numeric(),
stringsAsFactors = FALSE
)
# Calcular prueba de Chi-cuadrado para cada variable
for (var in variables_categoricas) {
if (var %in% names(rotacion)) {
tabla <- table(rotacion[[var]], rotacion$Rotación)
prueba <- chisq.test(tabla)
chi_resultados <- rbind(chi_resultados, data.frame(
Variable = var,
Estadistico_Chi = round(prueba$statistic, 3),
Valor_p = round(prueba$p.value, 5)
))
}
}
# Mostrar tabla de resultados
datatable(chi_resultados,
options = list(
scrollX = TRUE,
pageLength = 10,
dom = 'Bfrtip'
),
rownames = FALSE,
class = "compact stripe hover") %>%
formatRound(columns = c("Estadistico_Chi", "Valor_p"), digits = 3)
# Cargar librerías necesarias
library(plotly)
library(dplyr)
# Crear tabla con resultados
chi_data <- data.frame(
Variable = c("Genero", "Horas_Extra", "Estado_Civil", "Departamento",
"Cargo", "Viaje de Negocios", "Campo_Educación"),
Estadistico_Chi = c(1.117, 87.564, 46.164, 10.796, 86.190, 24.182, 16.025),
Valor_p = c(0.291, 0.000, 0.000, 0.005, 0.000, 0.000, 0.007)
)
# Clasificar significancia
chi_data <- chi_data %>%
mutate(Significativo = ifelse(Valor_p < 0.05, "Sí", "No"))
# Crear gráfico interactivo con colores por defecto de Plotly
plot_ly(chi_data,
x = ~Estadistico_Chi,
y = ~reorder(Variable, Estadistico_Chi),
type = 'bar',
orientation = 'h',
color = ~Significativo, # Usa la paleta por defecto de Plotly
text = ~paste("p =", round(Valor_p, 3)),
hoverinfo = 'text+y') %>%
layout(title = "Chi-cuadrado: Asociación entre variables categóricas y Rotación",
xaxis = list(title = "Estadístico Chi²"),
yaxis = list(title = ""),
legend = list(title = list(text = "¿Significativo?")),
margin = list(l = 120, r = 20, b = 60, t = 60))
Estadístico Chi² mide que tan diferentes son las frecuencias observadas respecto a las que se esperan si las variables fueran independientes, un valor alto indica una mayor diferencia entre las frecuencias y por lo tanto, mayor evidencia de asociación, en caso contrario un valor menor del estadístico indica menor evidencia de asociación.
El P-valor nos indica si la correlación es estadísticamente significativa, de acuerdo con la siguiente regla general:
De acuerdo con los resultados de la prueba, En la tabla y la gráfica evidenciamos que las variables horas extra (87, 564) , estado civil (46,164), departamento (10,796), cargo (86,190), viaje de negocios ( 24,182) y campo de educación (16,025), según el estadístico, presentan una relación con la variable rotación, así mismo, los valores observados de P-valor para estas seis variables son menores a 0.05, es decir, que podemos afirmar que estas variables son estadísticamente significativas y por lo tanto, están asociadas con la rotación de personal; por otro lado, la variable genero no presenta una relación estadísticamente significativa con la rotación de personal por su P-valor mayor a 0.05.
Para finalizar el análisis, utilizaremos la correlación de Spearman para examinar relaciones entre la variable objetivo y las categóricas ordinales, Educación, Satisfacción Laboral, Satisfacción Ambiental, Rendimiento Laboral, Equilibrio Trabajo Vida.
# Cargar librerías necesarias
library(dplyr)
library(DT)
# Asegúrate de que la variable Rotación esté codificada como numérica binaria (0 = No, 1 = Sí)
rotacion_mod <- rotacion %>%
mutate(Rotación = ifelse(Rotación %in% c("Sí", "Si", "YES", "yes"), 1, 0))
# Variables ordinales a analizar
variables_ordinales <- c("Educación", "Satisfacción_Laboral", "Satisfacción_Ambiental",
"Rendimiento_Laboral", "Equilibrio_Trabajo_Vida")
# Crear dataframe para guardar resultados
spearman_resultados <- data.frame(
Variable = character(),
Rho_Spearman = numeric(),
Valor_p = numeric(),
stringsAsFactors = FALSE
)
# Calcular correlación de Spearman para cada variable
for (var in variables_ordinales) {
if (var %in% names(rotacion_mod)) {
resultado <- cor.test(rotacion_mod[[var]], rotacion_mod$Rotación, method = "spearman")
spearman_resultados <- rbind(spearman_resultados, data.frame(
Variable = var,
Rho_Spearman = round(resultado$estimate, 3),
Valor_p = round(resultado$p.value, 5)
))
}
}
# Mostrar resultados en tabla interactiva
datatable(spearman_resultados,
options = list(scrollX = TRUE, pageLength = 10, dom = 'Bfrtip'),
rownames = FALSE,
class = "compact stripe hover") %>%
formatRound(columns = c("Rho_Spearman", "Valor_p"), digits = 3)
# Cargar librerías necesarias
library(plotly)
library(dplyr)
# Crear manualmente la tabla de resultados
spearman_data <- data.frame(
Variable = c("Educación", "Equilibrio_Trabajo_Vida",
"Rendimiento_Laboral", "Satisfacción_Ambiental"),
Rho_Spearman = c(-0.030, -0.052, 0.003, -0.096),
Valor_p = c(0.245, 0.046, 0.912, 0.000)
)
# Clasificar significancia
spearman_data <- spearman_data %>%
mutate(Significativo = ifelse(Valor_p < 0.05, "Sí", "No"))
# Crear gráfico interactivo con colores nativos de plotly
plot_ly(spearman_data,
x = ~Rho_Spearman,
y = ~reorder(Variable, Rho_Spearman),
type = 'bar',
orientation = 'h',
color = ~Significativo,
text = ~paste("ρ =", Rho_Spearman, "<br>p =", Valor_p),
hoverinfo = 'text+y') %>%
layout(title = "Correlación de Spearman con Rotación",
xaxis = list(title = "Coeficiente ρ (Spearman)", zeroline = TRUE),
yaxis = list(title = ""),
legend = list(title = list(text = "¿Significativo?")),
margin = list(l = 100, r = 20, b = 60, t = 60))
El coeficiente de Spearman toma valores entre -1 y 1, valores cercanos a 1 indican una fuerte relación positiva con la variable objetivo, es decir, a medida que una variable aumenta, también lo hace la otra, en el contexto de rotación, esto puede interpretarse como una mayor permanencia en la empresa.
Por el contrario, valores cercanos a -1 indican una fuerte relación negativa, lo cual sugiere que a medida que una variable aumenta, la variable objetivo tiende a disminuir. En este caso, podría asociarse con una mayor propensión a rotar.
Valores cercanos a 0 indican que no existe una relación clara entre ambas variables
El P-valor nos indica si la correlación es estadísticamente significativa, de acuerdo con la siguiente regla general:
En la tabla y la gráfica observamos que las variables Equilibrio Trabajo Vida (-0,052) y Satisfacción Ambiental (-0,096) presentan una correlación negativa de acuerdo con el coeficiente de Sperman y estadísticamente significativa según el P-valor menor 0.05 en ambos casos indicando que entre menos percepción de satisfacción ambiental y equilibrio trabajo y vida se asocia con una mayor probabilidad de rotación.
Las variables educación (-0,030) y rendimiento laboral (0.003) presentan correlaciones débiles negativas y casi nulas, por otro lado, tampoco son estadísticamente significativas de acuerdo con sus p-valor que en ambos casos son mayores a 0.05, por lo tanto no hay relación.
Al contrastar las hipótesis planteadas de las tres variables categóricas y numéricas al inicio del ejercicio, con los resultados obtenidos en el análisis de correlación biserial, Chi cuadrado y Spearman, concluimos lo siguiente:
Las variables horas extra, equilibrio trabajo-vida y satisfacción ambiental, confirman las hipótesis porque se relacionan con mayor probabilidad de renuncia de acuerdo con los resultados en el análisis bivariado.
Por el contrario, las variables ingreso mensual, edad y años de experiencia, que intuitivamente y acudiendo al sentido común, en la hipótesis inicial afirmamos que reducen la rotación, en los resultados del análisis arrojaron una correlación positiva y estadísticamente significativa con la variable de rotación, contradiciendo la hipótesis preliminar, es decir que en el caso particular de la empresa, los colaboradores con mayores ingresos, más edad o mayor experiencia son más propensos a rotar.
A continuación, seleccionamos y verificamos las variables que serán procesadas en el modelo.
# Cargar librería necesaria
library(DT)
# Clasificación de las variables según tu selección
variables_numericas <- c("Ingreso_Mensual", "Edad", "Años_Experiencia")
variables_categoricas <- c("Horas_Extra", "Satisfacción_Ambiental", "Equilibrio_Trabajo_Vida")
# Unir todas las variables seleccionadas
variables_modelo <- c(variables_numericas, variables_categoricas)
# Crear la tabla con tipo de variable
tabla_verificacion <- data.frame(
Variable = variables_modelo,
Tipo = ifelse(variables_modelo %in% variables_numericas, "Numérica", "Categórica"),
stringsAsFactors = FALSE
)
# Mostrar como tabla interactiva
datatable(tabla_verificacion,
options = list(scrollX = TRUE, pageLength = 10, dom = 't'),
class = "compact stripe hover",
rownames = FALSE)
Continuamos con la evaluación de colinealidad, el objetivo es verificar si las variables numéricas ingreso mensual, edad y años de experiencia presentan una correlación fuerte entre ellas.
# Cargar librerías necesarias
library(car)
library(dplyr)
library(DT)
# Asegurar que Rotación esté codificada como 0/1
rotacion_mod <- rotacion %>%
mutate(Rotación = ifelse(Rotación %in% c("Sí", "Si", "YES", "yes"), 1, 0))
# Seleccionar solo las variables necesarias y eliminar NA
datos_modelo <- rotacion_mod %>%
dplyr::select(Rotación, Ingreso_Mensual, Edad, Años_Experiencia) %>%
na.omit()
# Crear modelo lineal auxiliar (solo para VIF, no para predicción)
modelo_vif <- lm(Rotación ~ Ingreso_Mensual + Edad + Años_Experiencia, data = datos_modelo)
# Calcular VIF
valores_vif <- vif(modelo_vif)
# Armar tabla con interpretación
tabla_vif <- data.frame(
Variable = names(valores_vif),
VIF = round(valores_vif, 2),
Nivel_Colinealidad = ifelse(valores_vif > 10, "Alta",
ifelse(valores_vif > 5, "Moderada", "Baja"))
)
# Mostrar tabla interactiva
datatable(tabla_vif,
options = list(scrollX = TRUE, pageLength = 10, dom = 't'),
class = "compact stripe hover",
rownames = FALSE)
Verificando los resultados obtenidos, concluimos que las tres variables no presentan multicolinealidad, por lo tanto no son redundantes entre ellas y se pueden aplicar en el modelo.
En el paso siguiente, se codifican las variables categóricas horas extra, satisfacción ambiental y equilibrio trabajo vida.
# Partimos del dataset 'rotacion_mod' con Rotación ya codificada como 0/1
# Recodificar variables categóricas
rotacion_codificada <- rotacion_mod %>%
mutate(
Horas_Extra = factor(Horas_Extra, levels = c("No", "Si")),
Satisfacción_Ambiental = ordered(Satisfacción_Ambiental,
levels = c("1", "2", "3", "4"),
labels = c("Muy Insatisfecho", "Insatisfecho", "Satisfecho", "Muy Satisfecho")),
Equilibrio_Trabajo_Vida = ordered(Equilibrio_Trabajo_Vida,
levels = c("1", "2", "3", "4"),
labels = c("Muy Bajo", "Bajo", "Medio", "Alto"))
)
# Verificar codificación de las variables
str(rotacion_codificada[, c("Horas_Extra", "Satisfacción_Ambiental", "Equilibrio_Trabajo_Vida")])
## tibble [1,470 × 3] (S3: tbl_df/tbl/data.frame)
## $ Horas_Extra : Factor w/ 2 levels "No","Si": 2 1 2 2 1 1 2 1 1 1 ...
## $ Satisfacción_Ambiental : Ord.factor w/ 4 levels "Muy Insatisfecho"<..: 2 3 4 4 1 4 3 4 4 3 ...
## $ Equilibrio_Trabajo_Vida: Ord.factor w/ 4 levels "Muy Bajo"<"Bajo"<..: 1 3 3 3 3 2 2 3 3 2 ...
# También podrías usar table() para ver los valores únicos por variable
lapply(rotacion_codificada[, c("Horas_Extra", "Satisfacción_Ambiental", "Equilibrio_Trabajo_Vida")], table)
## $Horas_Extra
##
## No Si
## 1054 416
##
## $Satisfacción_Ambiental
##
## Muy Insatisfecho Insatisfecho Satisfecho Muy Satisfecho
## 284 287 453 446
##
## $Equilibrio_Trabajo_Vida
##
## Muy Bajo Bajo Medio Alto
## 80 344 893 153
Verificamos si existe desbalance de datos entre las clases rota y no rota.
# Cargar librería necesaria
library(dplyr)
library(DT)
# Contar frecuencias y porcentajes de la variable objetivo
tabla_rotacion <- rotacion_mod %>%
mutate(Rotación = ifelse(Rotación == 1, "Si", "No")) %>%
count(Rotación) %>%
mutate(
Porcentaje = round(n / sum(n) * 100, 2)
) %>%
rename(Frecuencia = n)
# Mostrar en tabla interactiva
datatable(tabla_rotacion,
options = list(scrollX = TRUE, pageLength = 10, dom = 't'),
class = "compact stripe hover",
rownames = FALSE)
Observamos que la variable objetivo esta desbalanceada, con una clase predominante que no rota del 83.88% y una minoritaria que si rota del 16.12%, sin embargo, previo al balanceo de clases, dividimos la base de datos en entrenamiento y prueba (70/30) para aseguramos que el balanceo de datos solo se aplique sobre los datos de entrenamiento, evitando introducir sesgos al modelo.
# Cargar librerías necesarias
library(caret)
library(dplyr)
# Establecer semilla para reproducibilidad
set.seed(123)
# Crear índices para partición estratificada 70% entrenamiento
indices_entrenamiento <- createDataPartition(rotacion_codificada$Rotación, p = 0.7, list = FALSE)
# Dividir en entrenamiento y prueba
datos_entrenamiento <- rotacion_codificada[indices_entrenamiento, ]
datos_prueba <- rotacion_codificada[-indices_entrenamiento, ]
# Verificar proporciones de clases en ambos conjuntos
tabla_clases <- data.frame(
Conjunto = c("Entrenamiento", "Prueba"),
No = c(sum(datos_entrenamiento$Rotación == 0), sum(datos_prueba$Rotación == 0)),
Sí = c(sum(datos_entrenamiento$Rotación == 1), sum(datos_prueba$Rotación == 1))
)
# Calcular proporciones
tabla_clases$Total <- tabla_clases$No + tabla_clases$Sí
tabla_clases$Porcentaje_Sí <- round(tabla_clases$Sí / tabla_clases$Total * 100, 2)
# Mostrar tabla
library(DT)
datatable(tabla_clases,
options = list(scrollX = TRUE, dom = 't'),
class = "compact stripe hover",
rownames = FALSE)
Se ha dividido el conjunto de datos en entrenamiento (1.029) y prueba (441).
Ahora procedemos con el balanceo de clases, para este caso igualaremos la clase minoritaria a la mayoritaria en el conjunto de datos de entrenamiento, usando Smote para crear datos sintéticos, y verificaremos el balanceo de datos.
# Cargar librerías necesarias
library(smotefamily)
library(dplyr)
library(DT)
# Paso 1: Seleccionar variables relevantes
variables_modelo <- c("Rotación", "Ingreso_Mensual", "Edad", "Años_Experiencia",
"Horas_Extra", "Satisfacción_Ambiental", "Equilibrio_Trabajo_Vida")
datos_modelo_smote <- datos_entrenamiento[, variables_modelo]
# Paso 2: Convertir categóricas a numéricas
datos_modelo_smote$Horas_Extra <- as.numeric(as.factor(datos_modelo_smote$Horas_Extra))
datos_modelo_smote$Satisfacción_Ambiental <- as.numeric(as.factor(datos_modelo_smote$Satisfacción_Ambiental))
datos_modelo_smote$Equilibrio_Trabajo_Vida <- as.numeric(as.factor(datos_modelo_smote$Equilibrio_Trabajo_Vida))
datos_modelo_smote$Rotación <- as.numeric(as.character(datos_modelo_smote$Rotación))
# Paso 3: Separar X e Y
X <- datos_modelo_smote[, -1]
Y <- datos_modelo_smote$Rotación
# Paso 4: Calcular balanceo exacto
tabla_clases <- table(Y)
n_mayoritaria <- max(tabla_clases)
n_minoritaria <- min(tabla_clases)
n_sinteticos_necesarios <- n_mayoritaria - n_minoritaria
dup_size <- ceiling(n_sinteticos_necesarios / n_minoritaria)
# Paso 5: Aplicar SMOTE
smote_resultado <- SMOTE(X, Y, K = 5, dup_size = dup_size)
datos_balanceados <- smote_resultado$data
colnames(datos_balanceados)[ncol(datos_balanceados)] <- "Rotación"
# Paso 6: Ajustar para dejar misma cantidad exacta por clase
datos_balanceados <- datos_balanceados %>%
group_by(Rotación) %>%
slice_head(n = n_mayoritaria) %>%
ungroup()
# Paso 7: Crear tabla resumen
tabla_final <- datos_balanceados %>%
group_by(Rotación) %>%
summarise(Frecuencia = n()) %>%
mutate(Porcentaje = round(Frecuencia / sum(Frecuencia) * 100, 2))
# Paso 8: Mostrar como tabla tipo TB
datatable(tabla_final,
options = list(scrollX = TRUE, pageLength = 5, dom = 't'),
class = "compact stripe hover",
rownames = FALSE)
Como se visualiza los datos están balanceados y listos para entrena el modelo.
Ejecución de modelo.
# Asegurar que Rotación sea factor
datos_balanceados$Rotación <- as.factor(datos_balanceados$Rotación)
# Ajustar el modelo de regresión logística
modelo_logistico <- glm(Rotación ~ Ingreso_Mensual + Edad + Años_Experiencia +
Horas_Extra + Satisfacción_Ambiental + Equilibrio_Trabajo_Vida,
data = datos_balanceados, family = binomial)
# Ver resumen del modelo
summary(modelo_logistico)
##
## Call:
## glm(formula = Rotación ~ Ingreso_Mensual + Edad + Años_Experiencia +
## Horas_Extra + Satisfacción_Ambiental + Equilibrio_Trabajo_Vida,
## family = binomial, data = datos_balanceados)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.17593 -0.95377 0.07935 0.92830 2.36762
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 1.703e+00 4.006e-01 4.251 2.13e-05 ***
## Ingreso_Mensual -7.434e-05 2.104e-05 -3.534 0.000410 ***
## Edad -4.436e-02 9.234e-03 -4.803 1.56e-06 ***
## Años_Experiencia -1.665e-02 1.486e-02 -1.120 0.262620
## Horas_Extra 1.533e+00 1.233e-01 12.432 < 2e-16 ***
## Satisfacción_Ambiental -3.612e-01 5.324e-02 -6.784 1.17e-11 ***
## Equilibrio_Trabajo_Vida -2.632e-01 7.638e-02 -3.447 0.000568 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 2403.8 on 1733 degrees of freedom
## Residual deviance: 1989.9 on 1727 degrees of freedom
## AIC: 2003.9
##
## Number of Fisher Scoring iterations: 4
Una vez se ejecuta el modelo de regresión logística en donde la variable respuesta es rotación (1 = sí, 0 = no) incluyendo las covariables: ingreso mensual, edad, años de experiencia, horas extra, satisfacción ambiental y equilibrio trabajo-vida, en donde los coeficientes estimados y el signo indican la probabilidad de que un colaborar renuncie.
Las variables con coeficientes positivos indican que el aumento en esa variable aumenta la probabilidad de rotación, mientras que, un coeficiente negativo indica que el aumento en esa variable reduce la probabilidad de renuncia.
A continuación preparamos el conjunto de prueba para aplicar las metricas de evaluación y posteriormente realizar las predicciones:
# Convertir Rotación a numérico binario (0 = no rota, 1 = sí rota), si es factor
datos_prueba$Rotación <- as.numeric(as.character(datos_prueba$Rotación))
# Convertir Horas_Extra a numérico (como en el entrenamiento balanceado)
datos_prueba$Horas_Extra <- as.numeric(as.factor(datos_prueba$Horas_Extra))
# Convertir Satisfacción_Ambiental a numérico codificado (ya lo está, reforzamos)
datos_prueba$Satisfacción_Ambiental <- as.numeric(as.factor(datos_prueba$Satisfacción_Ambiental))
# Convertir Equilibrio_Trabajo_Vida a numérico codificado (ya lo está, reforzamos)
datos_prueba$Equilibrio_Trabajo_Vida <- as.numeric(as.factor(datos_prueba$Equilibrio_Trabajo_Vida))
# Verificación de estructuras
str(datos_prueba[, c("Rotación", "Horas_Extra", "Satisfacción_Ambiental", "Equilibrio_Trabajo_Vida")])
## tibble [441 × 4] (S3: tbl_df/tbl/data.frame)
## $ Rotación : num [1:441] 1 0 0 1 0 1 0 1 0 0 ...
## $ Horas_Extra : num [1:441] 2 2 1 2 1 1 1 2 1 1 ...
## $ Satisfacción_Ambiental : num [1:441] 4 3 2 3 1 3 1 2 2 4 ...
## $ Equilibrio_Trabajo_Vida: num [1:441] 3 2 3 3 2 3 3 3 2 3 ...
Evaluaremos el desempeño de la predicción del modelo para lo cual examinaremos la matriz de confusión y se generaran métricas como accurancy, sensibilidad, especificidad, y analizaremos la curva AUC ROC.
# Paso 1: Predecir probabilidades con el modelo entrenado
probabilidades <- predict(modelo_logistico, newdata = datos_prueba, type = "response")
# Paso 2: Clasificar con umbral 0.5
predicciones <- ifelse(probabilidades >= 0.5, 1, 0)
# Paso 3: Convertir a factor
predicciones_factor <- as.factor(predicciones)
real_factor <- as.factor(datos_prueba$Rotación)
# Paso 4: Evaluar con matriz de confusión
library(caret)
library(DT)
library(dplyr)
confusion <- confusionMatrix(predicciones_factor, real_factor, positive = "1")
# -------- Matriz de confusión en tabla ordenada --------
matriz_conf <- as.table(confusion$table)
matriz_df <- as.data.frame.matrix(matriz_conf)
tabla_confusion <- tibble::tibble(
`Clase Real` = rownames(matriz_df),
`Predicho: No rota (0)` = matriz_df$`0`,
`Predicho: Sí rota (1)` = matriz_df$`1`
)
# Mostrar tabla ordenada - Matriz de Confusión
datatable(tabla_confusion,
caption = "Matriz de Confusión - Predicción de Rotación",
options = list(dom = 't', pageLength = 5),
rownames = FALSE,
class = "compact stripe hover")
# -------- Métricas clave del modelo en tabla ordenada --------
metricas <- data.frame(
Métrica = c("Accuracy (Exactitud)",
"Sensibilidad (Recall - clase 1)",
"Especificidad",
"Valor Predictivo Positivo (Precision)",
"Valor Predictivo Negativo",
"Kappa",
"Balanced Accuracy"),
Valor = c(
round(confusion$overall["Accuracy"], 3),
round(confusion$byClass["Sensitivity"], 3),
round(confusion$byClass["Specificity"], 3),
round(confusion$byClass["Pos Pred Value"], 3),
round(confusion$byClass["Neg Pred Value"], 3),
round(confusion$overall["Kappa"], 3),
round(confusion$byClass["Balanced Accuracy"], 3)
)
)
# Mostrar tabla ordenada - Métricas del Modelo
datatable(metricas,
caption = "Métricas de Evaluación del Modelo de Regresión Logística",
options = list(dom = 't', pageLength = 10),
rownames = FALSE,
class = "compact stripe hover")
library(DT)
# Crear el dataframe con resultados completos
resultados <- data.frame(
ID = 1:nrow(datos_prueba),
Probabilidad_Rotación = round(probabilidades, 3),
Predicción = predicciones,
Real = datos_prueba$Rotación
)
# Ordenar por mayor probabilidad de rotación
resultados_ordenados <- resultados[order(-resultados$Probabilidad_Rotación), ]
# Mostrar como tabla interactiva ordenada
datatable(resultados_ordenados,
caption = "Resultados de Predicción - Rotación de Personal",
options = list(
pageLength = 10,
scrollX = TRUE
),
rownames = FALSE,
class = "compact stripe hover")
Con los resultados obtenidos, la matriz de confusión permite evaluar el rendimiento del modelo al comparar las predicciones con los resultados reales de rotación de personal. A continuación, se detallan los significados de cada uno:
Verdaderos negativos (TN = 247): Son las estimaciones los que el modelo predijo que el empleado no rotaría y, efectivamente, el empleado no roto. Estas predicciones fueron correctas e indican la capacidad del modelo para identificar empleados que no van a rotar.
Falsos negativos (FN = 22): Son las estimaciones los que el modelo predijo que el empleado no rotaría, pero sí renunció.
Falsos positivos (FP = 119): Representan las estimaciones en los que el modelo predijo que el empleado sí rotaría, pero en realidad se mantuvo en la empresa.
Verdaderos positivos (TP = 53): El modelo predijo correctamente que el empleado sí renunciaría y efectivamente rotó.
En cuanto al resultado de las métricas, concluimos lo siguiente:
Accuracy (Exactitud) = 68.03% El modelo es aceptable, logró clasificar correctamente el 68% de los casos totales,
Sensibilidad (Recall para la clase “sí rota”) = 70.67% El modelo tiene la capacidad de detectar correctamente la rotación, identificando 7 de cada 10 casos reales de rotación, esta métrica es importante en nuestro caso de estudio porque detecta a los empelados que van a rotar.
Especificidad = 67.49% El modelo tiene la capacidad de detectar correctamente 2 de cada 3 de los empleados que permanecen en la empresa.
Valor Predictivo Positivo (Precisión) = 30.81% De todos los empleados que el modelo predijo como casos de rotación, solo el 31% efectivamente renunciaron.
Valor Predictivo Negativo = 91.82% El modelo presenta una alta capacidad para predecir que un empleado no rotará. Esta métrica indica que el modelo es confiable al identificar a quienes no representan riesgo de rotación
Balanced Accuracy = 69.08% Indica que el modelo presenta un rendimiento aceptable en las dos clases.
# Cargar librerías necesarias
library(pROC)
library(plotly)
# Calcular el objeto ROC
roc_obj <- roc(real_factor, probabilidades)
# Extraer puntos de la curva
roc_df <- data.frame(
FPR = 1 - roc_obj$specificities,
TPR = roc_obj$sensitivities
)
# Calcular el AUC
auc_valor <- round(auc(roc_obj), 3)
# Graficar curva ROC con Plotly
plot_ly(roc_df, x = ~FPR, y = ~TPR, type = 'scatter', mode = 'lines',
line = list(width = 3, color = 'blue'),
name = paste("Curva ROC (AUC =", auc_valor, ")")) %>%
add_trace(x = c(0, 1), y = c(0, 1),
mode = 'lines', line = list(dash = 'dash', color = 'gray'),
showlegend = FALSE) %>%
layout(title = "Curva ROC - Modelo de Rotación",
xaxis = list(title = "Tasa de Falsos Positivos (1 - Especificidad)"),
yaxis = list(title = "Tasa de Verdaderos Positivos (Sensibilidad)"),
showlegend = TRUE)
La curva ROC representa el equilibrio entre la sensibilidad (tasa de verdaderos positivos) y la especificidad (1 - tasa de falsos positivos), visualizando la curva del modelo está por encima de la línea diagonal que nos indica que el modelo diferencian entre empelados que rotan y que no rota, por ende un mejor capacidad predictiva.
Ahora bien, el valor resultante del AUC es 0.731 mayor a 0.5 lo que indica que el modelo puede identificar correctamente entre un empleado que rotará y uno que no, en aproximadamente el 73.1% de los casos seleccionados aleatoriamente, por lo tanto se concluye que el modelo presenta una buena capacidad predictiva.
A continuación simulamos unos unos datos de un empelado hipotético y ejecutaremos el modelo y validaremos sus resultados:
# Crear el nuevo individuo
nuevo_empleado <- data.frame(
Ingreso_Mensual = 4500,
Edad = 30,
Años_Experiencia = 5,
Horas_Extra = 1,
Satisfacción_Ambiental = 2,
Equilibrio_Trabajo_Vida = 2
)
# Predecir la probabilidad de rotación
prob_rotacion <- predict(modelo_logistico, newdata = nuevo_empleado, type = "response")
# Mostrar resultado
paste0("Probabilidad estimada de rotación: ", round(prob_rotacion * 100, 2), "%")
## [1] "Probabilidad estimada de rotación: 55.94%"
Ejecutando el modelo obtenemos que este colaborador hipotético presentan 56% de probabilidad de rotación, ahora bien, en términos de estrategia, generaría cortes diferenciados por departamentos bajo los cuales voy a intervenir al empleado, es decir, el caso de la empresa de estudio, asignaría un corte del 50% a los empleados del departamento de ventas, 75% al departamento de I y D, por último 90% al departamento de recursos humanos.
Los cortes asignados reflejan el costo beneficio de la rotación de empleados claves en la generación de ingresos de la empresa, en el caso de los empleados del área de ventas son los que participan activamente generando ingresos, por lo tanto, el corte definido 50% para generar la alarma permitirá anticiparse y retener el talento, porque una rotación frecuente en esta área puede impactar los ingresos de la compañía. Los empleados del área de I Y D, que si bien, participan en la generación de valor, no están directamente relacionados con la generación de ingresos y su rotación puede ser aceptable, es decir, la empresa no ve afectados su ingresos mientras consigue remplazar la vacante. En ultimo lugar con el 90% de probabilidades de rotación se encuentra recursos humanos por tratarse de una área de soporte, que no esta directamente relacionada con la generación de valor e ingresos de la empresa.
Una vez definidos los cortes de acuerdo con lo criterios definidos, suponiendo que nuestro empleado hipotético pertenece al área de ventas, dado que la probabilidad de rotar es mayor a la definida en el corte para esta área, aplicaría un protocolo de retención, que consiste en analizar los posibles motivos de una renuncia, si nos fijamos en los datos del empleado, visualizamos que trabaja horas extras, no esta satisfecho con el ambiente laboral y su equilibrio trabajo y vida es bajo, se podría examinar la carga laboral, dado que, el trabajo excesivo explica el deficiente equilibrio trabajo y vida, por ende su insatisfacción laboral, posiblemente, se pueda dotar al empleado con mejores herramientas que permitan realizar sus tareas más rápido, brindado formación y proponiendo esquemas de teletrabajo que eliminen los desplazamientos al lugar de trabajo permitiendo que este tiempo se destine a la calidad de vida del empleado, posiblemente, estás estrategias pueden influir para que el empleado permanezca en la empresa.
El análisis de datos permitió identificar los principales elementos que influyen en la rotación del personal. Entre los más relevantes están el exceso de horas extra, un ambiente laboral poco satisfactorio y un bajo equilibrio entre la vida personal y el trabajo. Estos aspectos aumentan significativamente la probabilidad de que un empleado renuncie.
Con base en estos hallazgos, se recomienda implementar estrategias enfocadas en mejorar el clima laboral, ofrecer más flexibilidad, reducir la carga excesiva de trabajo y personalizar las estrategias de retención según el perfil del empleado, algunas estrategias que se pueden implementar son las siguientes:
Mejorar el clima laboral:
Ofrecer mayor flexibilidad:
Reducir la carga laboral excesiva:
Personalizar estrategias de retención:
Aplicar herramientas predictivas: