En una organización, se busca comprender y prever los factores que influyen en la rotación de empleados entre distintos cargos. La empresa ha recopilado datos históricos sobre el empleo de sus trabajadores, incluyendo variables como la antigüedad en el cargo actual, el nivel de satisfacción laboral, el salario actual, edad y otros factores relevantes. La gerencia planea desarrollar un modelo de regresión logística que permita estimar la probabilidad de que un empleado cambie de cargo en el próximo período y determinar cuales factores indicen en mayor proporción a estos cambios.
Con esta información, la empresa podrá tomar medidas proactivas para retener a su talento clave, identificar áreas de mejora en la gestión de recursos humanos y fomentar un ambiente laboral más estable y tranquilo. La predicción de la probabilidad de rotación de empleados ayudará a la empresa a tomar decisiones estratégicas informadas y a mantener un equipo de trabajo comprometido y satisfecho en sus roles actuales.
Rows: 1,470
Columns: 24
$ Rotación <chr> "Si", "No", "Si", "No", "No", "No", "No", …
$ Edad <dbl> 41, 49, 37, 33, 27, 32, 59, 30, 38, 36, 35…
$ `Viaje de Negocios` <chr> "Raramente", "Frecuentemente", "Raramente"…
$ Departamento <chr> "Ventas", "IyD", "IyD", "IyD", "IyD", "IyD…
$ Distancia_Casa <dbl> 1, 8, 2, 3, 2, 2, 3, 24, 23, 27, 16, 15, 2…
$ Educación <dbl> 2, 1, 2, 4, 1, 2, 3, 1, 3, 3, 3, 2, 1, 2, …
$ Campo_Educación <chr> "Ciencias", "Ciencias", "Otra", "Ciencias"…
$ Satisfacción_Ambiental <dbl> 2, 3, 4, 4, 1, 4, 3, 4, 4, 3, 1, 4, 1, 2, …
$ Genero <chr> "F", "M", "M", "F", "M", "M", "F", "M", "M…
$ Cargo <chr> "Ejecutivo_Ventas", "Investigador_Cientifi…
$ Satisfación_Laboral <dbl> 4, 2, 3, 3, 2, 4, 1, 3, 3, 3, 2, 3, 3, 4, …
$ Estado_Civil <chr> "Soltero", "Casado", "Soltero", "Casado", …
$ Ingreso_Mensual <dbl> 5993, 5130, 2090, 2909, 3468, 3068, 2670, …
$ Trabajos_Anteriores <dbl> 8, 1, 6, 1, 9, 0, 4, 1, 0, 6, 0, 0, 1, 0, …
$ Horas_Extra <chr> "Si", "No", "Si", "Si", "No", "No", "Si", …
$ Porcentaje_aumento_salarial <dbl> 11, 23, 15, 11, 12, 13, 20, 22, 21, 13, 13…
$ Rendimiento_Laboral <dbl> 3, 4, 3, 3, 3, 3, 4, 4, 4, 3, 3, 3, 3, 3, …
$ Años_Experiencia <dbl> 8, 10, 7, 8, 6, 8, 12, 1, 10, 17, 6, 10, 5…
$ Capacitaciones <dbl> 0, 3, 3, 3, 3, 2, 3, 2, 2, 3, 5, 3, 1, 2, …
$ Equilibrio_Trabajo_Vida <dbl> 1, 3, 3, 3, 3, 2, 2, 3, 3, 2, 3, 3, 2, 3, …
$ Antigüedad <dbl> 6, 10, 0, 8, 2, 7, 1, 1, 9, 7, 5, 9, 5, 2,…
$ Antigüedad_Cargo <dbl> 4, 7, 0, 7, 2, 7, 0, 0, 7, 7, 4, 5, 2, 2, …
$ Años_ultima_promoción <dbl> 0, 1, 0, 3, 2, 3, 0, 0, 1, 7, 0, 0, 4, 1, …
$ Años_acargo_con_mismo_jefe <dbl> 5, 7, 0, 0, 2, 6, 0, 0, 8, 7, 3, 8, 3, 2, …
Antes de comenzar con el análisis de los datos, es importante organizar y limpiar los nombres de las variables. Esto se hace porque algunos nombres contienen espacios, tildes o están escritos de forma inconsistente, lo cual puede generar errores al momento de trabajar con el código o construir modelos.
Por ejemplo, hay variables como “Viaje de Negocios” o “Antigüedad”, que contienen espacios y caracteres especiales. Aunque son fáciles de leer para una persona, pueden ser problemáticas para el análisis en R. Para solucionar esto, se utilizó una función que transforma todos los nombres de las variables a un formato más sencillo:
Esto permite trabajar de forma más ordenada, evitar errores y facilitar la construcción de modelos más adelante.
[1] "rotacion" "edad"
[3] "viaje_de_negocios" "departamento"
[5] "distancia_casa" "educacion"
[7] "campo_educacion" "satisfaccion_ambiental"
[9] "genero" "cargo"
[11] "satisfacion_laboral" "estado_civil"
[13] "ingreso_mensual" "trabajos_anteriores"
[15] "horas_extra" "porcentaje_aumento_salarial"
[17] "rendimiento_laboral" "anos_experiencia"
[19] "capacitaciones" "equilibrio_trabajo_vida"
[21] "antiguedad" "antiguedad_cargo"
[23] "anos_ultima_promocion" "anos_acargo_con_mismo_jefe"
Una vez los nombres de las variables han sido limpiados y estandarizados, es importante construir una tabla descriptiva que permita entender el significado de cada una. Esta tabla funciona como una guía que explica, de manera sencilla, qué representa cada variable, qué tipo de dato es y cómo podría relacionarse con la rotación de empleados.
Este paso es fundamental, ya que permite interpretar correctamente los resultados del análisis y facilita la comunicación de los hallazgos a personas que no tienen conocimientos técnicos.
tabla_variables <- tribble(
~VARIABLE, ~TIPO_VARIABLE, ~DESCRIPCIÓN, ~RELACIÓN_CON_ROTACIÓN,
"rotacion", "Categórica binaria", "Indica si el empleado cambia o no de cargo", "Variable respuesta del estudio",
"edad", "Cuantitativa", "Edad del empleado en años", "Puede influir en la decisión de cambio",
"viaje_de_negocios", "Categórica", "Frecuencia de viajes laborales", "Viajes frecuentes pueden generar desgaste",
"departamento", "Categórica", "Área de trabajo del empleado", "Algunos departamentos pueden tener mayor movilidad",
"distancia_casa", "Cuantitativa", "Distancia entre casa y trabajo", "Trayectos largos pueden afectar estabilidad",
"educacion", "Cuantitativa discreta", "Nivel educativo del empleado", "Puede influir en oportunidades de cambio",
"campo_educacion", "Categórica", "Área de formación académica", "Algunos campos pueden tener mayor movilidad",
"satisfaccion_ambiental", "Cuantitativa discreta", "Nivel de satisfacción con el ambiente laboral", "Baja satisfacción podría aumentar la rotación",
"genero", "Categórica", "Género del empleado", "Puede explorarse como factor asociado",
"cargo", "Categórica", "Puesto actual del empleado", "Algunos cargos pueden tener más rotación",
"satisfaccion_laboral", "Cuantitativa discreta", "Nivel de satisfacción con el trabajo", "Baja satisfacción podría asociarse a rotación",
"estado_civil", "Categórica", "Situación civil del empleado", "Podría influir en decisiones laborales",
"ingreso_mensual", "Cuantitativa", "Salario mensual del empleado", "Salarios bajos podrían aumentar rotación",
"trabajos_anteriores", "Cuantitativa discreta", "Número de empleos previos", "Mayor historial laboral puede reflejar movilidad",
"horas_extra", "Categórica", "Indica si trabaja horas extra", "Las horas extra pueden aumentar desgaste",
"porcentaje_aumento_salarial", "Cuantitativa", "Porcentaje de aumento salarial recibido", "Aumentos bajos podrían generar inconformidad",
"rendimiento_laboral", "Cuantitativa discreta", "Calificación del desempeño", "Puede influir en cambios o permanencia",
"anos_experiencia", "Cuantitativa", "Años totales de experiencia laboral", "Puede asociarse con estabilidad o ascenso",
"capacitaciones", "Cuantitativa discreta", "Cantidad de capacitaciones recibidas", "Mayor formación puede influir en movilidad",
"equilibrio_trabajo_vida", "Cuantitativa discreta", "Percepción del balance trabajo-vida", "Desequilibrio puede aumentar rotación",
"antiguedad", "Cuantitativa", "Tiempo total en la empresa", "Menor antigüedad puede asociarse con mayor cambio",
"antiguedad_cargo", "Cuantitativa", "Tiempo en el cargo actual", "Puede reflejar estabilidad o reciente movimiento",
"anos_ultima_promocion", "Cuantitativa", "Tiempo desde la última promoción", "Mucho tiempo sin promoción puede desmotivar",
"anos_acargo_con_mismo_jefe", "Cuantitativa", "Tiempo con el mismo jefe", "Puede influir según la estabilidad del liderazgo"
)
tabla_variables %>%
kable(
caption = "Tabla descriptiva de variables del dataset rotacion",
align = "l"
) %>%
kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed")
)| VARIABLE | TIPO_VARIABLE | DESCRIPCIÓN | RELACIÓN_CON_ROTACIÓN |
|---|---|---|---|
| rotacion | Categórica binaria | Indica si el empleado cambia o no de cargo | Variable respuesta del estudio |
| edad | Cuantitativa | Edad del empleado en años | Puede influir en la decisión de cambio |
| viaje_de_negocios | Categórica | Frecuencia de viajes laborales | Viajes frecuentes pueden generar desgaste |
| departamento | Categórica | Área de trabajo del empleado | Algunos departamentos pueden tener mayor movilidad |
| distancia_casa | Cuantitativa | Distancia entre casa y trabajo | Trayectos largos pueden afectar estabilidad |
| educacion | Cuantitativa discreta | Nivel educativo del empleado | Puede influir en oportunidades de cambio |
| campo_educacion | Categórica | Área de formación académica | Algunos campos pueden tener mayor movilidad |
| satisfaccion_ambiental | Cuantitativa discreta | Nivel de satisfacción con el ambiente laboral | Baja satisfacción podría aumentar la rotación |
| genero | Categórica | Género del empleado | Puede explorarse como factor asociado |
| cargo | Categórica | Puesto actual del empleado | Algunos cargos pueden tener más rotación |
| satisfaccion_laboral | Cuantitativa discreta | Nivel de satisfacción con el trabajo | Baja satisfacción podría asociarse a rotación |
| estado_civil | Categórica | Situación civil del empleado | Podría influir en decisiones laborales |
| ingreso_mensual | Cuantitativa | Salario mensual del empleado | Salarios bajos podrían aumentar rotación |
| trabajos_anteriores | Cuantitativa discreta | Número de empleos previos | Mayor historial laboral puede reflejar movilidad |
| horas_extra | Categórica | Indica si trabaja horas extra | Las horas extra pueden aumentar desgaste |
| porcentaje_aumento_salarial | Cuantitativa | Porcentaje de aumento salarial recibido | Aumentos bajos podrían generar inconformidad |
| rendimiento_laboral | Cuantitativa discreta | Calificación del desempeño | Puede influir en cambios o permanencia |
| anos_experiencia | Cuantitativa | Años totales de experiencia laboral | Puede asociarse con estabilidad o ascenso |
| capacitaciones | Cuantitativa discreta | Cantidad de capacitaciones recibidas | Mayor formación puede influir en movilidad |
| equilibrio_trabajo_vida | Cuantitativa discreta | Percepción del balance trabajo-vida | Desequilibrio puede aumentar rotación |
| antiguedad | Cuantitativa | Tiempo total en la empresa | Menor antigüedad puede asociarse con mayor cambio |
| antiguedad_cargo | Cuantitativa | Tiempo en el cargo actual | Puede reflejar estabilidad o reciente movimiento |
| anos_ultima_promocion | Cuantitativa | Tiempo desde la última promoción | Mucho tiempo sin promoción puede desmotivar |
| anos_acargo_con_mismo_jefe | Cuantitativa | Tiempo con el mismo jefe | Puede influir según la estabilidad del liderazgo |
Para comenzar el análisis exploratorio, se visualizan las primeras filas del conjunto de datos. Esto permite tener una idea general de cómo están organizadas las variables y qué tipo de información contiene cada una. Es como observar una pequeña muestra del dataset antes de analizarlo en profundidad.
En este paso se analiza el tamaño del conjunto de datos, es decir, cuántas filas y columnas tiene. Las filas representan el número de empleados registrados, mientras que las columnas corresponden a las variables disponibles para cada uno. Conocer estas dimensiones ayuda a entender la cantidad de información con la que se cuenta para el análisis.
Dimensiones: 1470 24
Número de Filas: 1470
Cantidad de Columnas: 24
El dataset cuenta con 1470 empleados y 24 variables, lo que proporciona una base suficiente para realizar análisis estadísticos.
A continuación, se revisa la estructura general del conjunto de datos. Esto permite identificar el tipo de cada variable, es decir, si es numérica o categórica. Este paso es importante porque el tipo de variable determina qué tipo de análisis se puede realizar posteriormente.
Rows: 1,470
Columns: 24
$ rotacion <chr> "Si", "No", "Si", "No", "No", "No", "No", …
$ edad <dbl> 41, 49, 37, 33, 27, 32, 59, 30, 38, 36, 35…
$ viaje_de_negocios <chr> "Raramente", "Frecuentemente", "Raramente"…
$ departamento <chr> "Ventas", "IyD", "IyD", "IyD", "IyD", "IyD…
$ distancia_casa <dbl> 1, 8, 2, 3, 2, 2, 3, 24, 23, 27, 16, 15, 2…
$ educacion <dbl> 2, 1, 2, 4, 1, 2, 3, 1, 3, 3, 3, 2, 1, 2, …
$ campo_educacion <chr> "Ciencias", "Ciencias", "Otra", "Ciencias"…
$ satisfaccion_ambiental <dbl> 2, 3, 4, 4, 1, 4, 3, 4, 4, 3, 1, 4, 1, 2, …
$ genero <chr> "F", "M", "M", "F", "M", "M", "F", "M", "M…
$ cargo <chr> "Ejecutivo_Ventas", "Investigador_Cientifi…
$ satisfacion_laboral <dbl> 4, 2, 3, 3, 2, 4, 1, 3, 3, 3, 2, 3, 3, 4, …
$ estado_civil <chr> "Soltero", "Casado", "Soltero", "Casado", …
$ ingreso_mensual <dbl> 5993, 5130, 2090, 2909, 3468, 3068, 2670, …
$ trabajos_anteriores <dbl> 8, 1, 6, 1, 9, 0, 4, 1, 0, 6, 0, 0, 1, 0, …
$ horas_extra <chr> "Si", "No", "Si", "Si", "No", "No", "Si", …
$ porcentaje_aumento_salarial <dbl> 11, 23, 15, 11, 12, 13, 20, 22, 21, 13, 13…
$ rendimiento_laboral <dbl> 3, 4, 3, 3, 3, 3, 4, 4, 4, 3, 3, 3, 3, 3, …
$ anos_experiencia <dbl> 8, 10, 7, 8, 6, 8, 12, 1, 10, 17, 6, 10, 5…
$ capacitaciones <dbl> 0, 3, 3, 3, 3, 2, 3, 2, 2, 3, 5, 3, 1, 2, …
$ equilibrio_trabajo_vida <dbl> 1, 3, 3, 3, 3, 2, 2, 3, 3, 2, 3, 3, 2, 3, …
$ antiguedad <dbl> 6, 10, 0, 8, 2, 7, 1, 1, 9, 7, 5, 9, 5, 2,…
$ antiguedad_cargo <dbl> 4, 7, 0, 7, 2, 7, 0, 0, 7, 7, 4, 5, 2, 2, …
$ anos_ultima_promocion <dbl> 0, 1, 0, 3, 2, 3, 0, 0, 1, 7, 0, 0, 4, 1, …
$ anos_acargo_con_mismo_jefe <dbl> 5, 7, 0, 0, 2, 6, 0, 0, 8, 7, 3, 8, 3, 2, …
Se presenta un resumen estadístico de las variables, el cual incluye información como valores mínimos, máximos, promedios y medianas en el caso de variables numéricas. Para las variables categóricas, se muestran las frecuencias de cada categoría. Este resumen permite identificar posibles valores atípicos, rangos de datos y comportamientos generales.
rotacion edad viaje_de_negocios departamento
Length:1470 Min. :18.00 Length:1470 Length:1470
Class :character 1st Qu.:30.00 Class :character Class :character
Mode :character Median :36.00 Mode :character Mode :character
Mean :36.92
3rd Qu.:43.00
Max. :60.00
distancia_casa educacion campo_educacion satisfaccion_ambiental
Min. : 1.000 Min. :1.000 Length:1470 Min. :1.000
1st Qu.: 2.000 1st Qu.:2.000 Class :character 1st Qu.:2.000
Median : 7.000 Median :3.000 Mode :character Median :3.000
Mean : 9.193 Mean :2.913 Mean :2.722
3rd Qu.:14.000 3rd Qu.:4.000 3rd Qu.:4.000
Max. :29.000 Max. :5.000 Max. :4.000
genero cargo satisfacion_laboral estado_civil
Length:1470 Length:1470 Min. :1.000 Length:1470
Class :character Class :character 1st Qu.:2.000 Class :character
Mode :character Mode :character Median :3.000 Mode :character
Mean :2.729
3rd Qu.:4.000
Max. :4.000
ingreso_mensual trabajos_anteriores horas_extra
Min. : 1009 Min. :0.000 Length:1470
1st Qu.: 2911 1st Qu.:1.000 Class :character
Median : 4919 Median :2.000 Mode :character
Mean : 6503 Mean :2.693
3rd Qu.: 8379 3rd Qu.:4.000
Max. :19999 Max. :9.000
porcentaje_aumento_salarial rendimiento_laboral anos_experiencia
Min. :11.00 Min. :3.000 Min. : 0.00
1st Qu.:12.00 1st Qu.:3.000 1st Qu.: 6.00
Median :14.00 Median :3.000 Median :10.00
Mean :15.21 Mean :3.154 Mean :11.28
3rd Qu.:18.00 3rd Qu.:3.000 3rd Qu.:15.00
Max. :25.00 Max. :4.000 Max. :40.00
capacitaciones equilibrio_trabajo_vida antiguedad antiguedad_cargo
Min. :0.000 Min. :1.000 Min. : 0.000 Min. : 0.000
1st Qu.:2.000 1st Qu.:2.000 1st Qu.: 3.000 1st Qu.: 2.000
Median :3.000 Median :3.000 Median : 5.000 Median : 3.000
Mean :2.799 Mean :2.761 Mean : 7.008 Mean : 4.229
3rd Qu.:3.000 3rd Qu.:3.000 3rd Qu.: 9.000 3rd Qu.: 7.000
Max. :6.000 Max. :4.000 Max. :40.000 Max. :18.000
anos_ultima_promocion anos_acargo_con_mismo_jefe
Min. : 0.000 Min. : 0.000
1st Qu.: 0.000 1st Qu.: 2.000
Median : 1.000 Median : 3.000
Mean : 2.188 Mean : 4.123
3rd Qu.: 3.000 3rd Qu.: 7.000
Max. :15.000 Max. :17.000
En este paso se realiza una revisión más detallada de la estructura
del conjunto de datos utilizando la función str(). Esta
función permite observar de forma compacta el tipo de cada variable, así
como algunos valores de ejemplo. A diferencia de otras funciones,
str() muestra la información de manera más técnica, lo que
resulta útil para confirmar que los datos están correctamente
organizados.
Este análisis ayuda a verificar que las variables categóricas y numéricas estén correctamente definidas, lo cual es fundamental antes de continuar con análisis más avanzados o la construcción de modelos.
tibble [1,470 × 24] (S3: tbl_df/tbl/data.frame)
$ rotacion : chr [1:1470] "Si" "No" "Si" "No" ...
$ edad : num [1:1470] 41 49 37 33 27 32 59 30 38 36 ...
$ viaje_de_negocios : chr [1:1470] "Raramente" "Frecuentemente" "Raramente" "Frecuentemente" ...
$ departamento : chr [1:1470] "Ventas" "IyD" "IyD" "IyD" ...
$ distancia_casa : num [1:1470] 1 8 2 3 2 2 3 24 23 27 ...
$ educacion : num [1:1470] 2 1 2 4 1 2 3 1 3 3 ...
$ campo_educacion : chr [1:1470] "Ciencias" "Ciencias" "Otra" "Ciencias" ...
$ satisfaccion_ambiental : num [1:1470] 2 3 4 4 1 4 3 4 4 3 ...
$ genero : chr [1:1470] "F" "M" "M" "F" ...
$ cargo : chr [1:1470] "Ejecutivo_Ventas" "Investigador_Cientifico" "Tecnico_Laboratorio" "Investigador_Cientifico" ...
$ satisfacion_laboral : num [1:1470] 4 2 3 3 2 4 1 3 3 3 ...
$ estado_civil : chr [1:1470] "Soltero" "Casado" "Soltero" "Casado" ...
$ ingreso_mensual : num [1:1470] 5993 5130 2090 2909 3468 ...
$ trabajos_anteriores : num [1:1470] 8 1 6 1 9 0 4 1 0 6 ...
$ horas_extra : chr [1:1470] "Si" "No" "Si" "Si" ...
$ porcentaje_aumento_salarial: num [1:1470] 11 23 15 11 12 13 20 22 21 13 ...
$ rendimiento_laboral : num [1:1470] 3 4 3 3 3 3 4 4 4 3 ...
$ anos_experiencia : num [1:1470] 8 10 7 8 6 8 12 1 10 17 ...
$ capacitaciones : num [1:1470] 0 3 3 3 3 2 3 2 2 3 ...
$ equilibrio_trabajo_vida : num [1:1470] 1 3 3 3 3 2 2 3 3 2 ...
$ antiguedad : num [1:1470] 6 10 0 8 2 7 1 1 9 7 ...
$ antiguedad_cargo : num [1:1470] 4 7 0 7 2 7 0 0 7 7 ...
$ anos_ultima_promocion : num [1:1470] 0 1 0 3 2 3 0 0 1 7 ...
$ anos_acargo_con_mismo_jefe : num [1:1470] 5 7 0 0 2 6 0 0 8 7 ...
En este paso se analiza si existen valores faltantes dentro del conjunto de datos. Los valores faltantes, también conocidos como NA, representan información que no fue registrada o que se perdió. Identificar estos valores es importante, ya que pueden afectar los resultados del análisis y la precisión de los modelos. Si no se manejan correctamente, podrían generar conclusiones incorrectas.
Primero, se calcula el número total de valores faltantes en todo el dataset. Luego, se realiza un análisis más detallado por cada variable, mostrando cuántos datos faltan y qué porcentaje representan.
Esto permite decidir si es necesario limpiar, eliminar o completar la información antes de continuar con el análisis.
[1] 0
# NA por variable
faltantes_var <- data.frame(
variable = names(df),
faltantes = sapply(df, function(x) sum(is.na(x))),
porcentaje = round(sapply(df, function(x) mean(is.na(x)) * 100), 2)
)
faltantes_varEn este paso se analiza si existen valores vacíos dentro del conjunto de datos. Los valores vacíos, representados como cadenas ““, indican que la información no fue registrada, aunque no estén marcados como NA. Identificar estos vacíos es importante porque, al igual que los NA, pueden afectar el análisis, generar resultados sesgados o afectar la precisión de los modelos. Primero, se calcula el número total de valores vacíos en cada variable. Luego, se muestra un análisis detallado por variable, indicando cuántos valores vacíos hay y qué porcentaje representan del total.
Este análisis permite decidir si es necesario limpiar, transformar o completar estos valores antes de continuar con el análisis, asegurando que los resultados sean más confiables.
vacios_var <- data.frame(
variable = names(df),
vacios = sapply(df, function(x) if(is.character(x) | is.factor(x)) sum(x == "") else 0),
porcentaje = round(sapply(df, function(x) if(is.character(x) | is.factor(x)) mean(x == "") * 100 else 0), 2)
)
vacios_varEn este paso se verifica si existen registros duplicados en el conjunto de datos. Un registro duplicado es una fila que aparece más de una vez con exactamente la misma información en todas las columnas. Detectar y eliminar duplicados es importante porque pueden sesgar el análisis y afectar la validez de los resultados, ya que algunos empleados podrían contarse más de una vez.
Primero se calcula cuántos registros duplicados hay en total y, si existen, se muestran para poder decidir cómo manejarlos.
[1] 0
En este paso se realiza la limpieza y preparación de las variables de texto para facilitar el análisis. Primero, se eliminan espacios en blanco extras al inicio, final y entre palabras en las variables de texto. Esto evita errores por diferencias en espacios que podrían hacer que una misma categoría se registre como distinta.
Luego, se convierten todas las variables de texto en factores, que es
el tipo adecuado para trabajar con variables categóricas en análisis
estadístico y modelado. Finalmente, se asegura que la variable objetivo
rotacion esté ordenada correctamente con niveles
"No" y "Si". Además, se crea una variable
binaria numérica (rotacion_bin) con valores 0 y 1 para
facilitar modelos que requieran variables numéricas.
Después, se revisan los niveles de todas las variables categóricas para detectar posibles errores de escritura, categorías poco frecuentes o inconsistencias que podrían afectar el análisis.
# Limpiar espacios en variables de texto
df <- df |>
mutate(across(where(is.character), ~ str_squish(str_trim(.x))))
# Convertir variables de texto a factor
df <- df |>
mutate(across(where(is.character), as.factor))
# Ajustar variable objetivo 'rotacion' y crear variable binaria
df <- df |>
mutate(
rotacion = factor(rotacion, levels = c("No", "Si")),
rotacion_bin = ifelse(rotacion == "Si", 1, 0)
)
# Revisar la estructura para verificar cambios
str(df)tibble [1,470 × 25] (S3: tbl_df/tbl/data.frame)
$ rotacion : Factor w/ 2 levels "No","Si": 2 1 2 1 1 1 1 1 1 1 ...
$ edad : num [1:1470] 41 49 37 33 27 32 59 30 38 36 ...
$ viaje_de_negocios : Factor w/ 3 levels "Frecuentemente",..: 3 1 3 1 3 1 3 3 1 3 ...
$ departamento : Factor w/ 3 levels "IyD","RH","Ventas": 3 1 1 1 1 1 1 1 1 1 ...
$ distancia_casa : num [1:1470] 1 8 2 3 2 2 3 24 23 27 ...
$ educacion : num [1:1470] 2 1 2 4 1 2 3 1 3 3 ...
$ campo_educacion : Factor w/ 6 levels "Ciencias","Humanidades",..: 1 1 4 1 5 1 5 1 1 5 ...
$ satisfaccion_ambiental : num [1:1470] 2 3 4 4 1 4 3 4 4 3 ...
$ genero : Factor w/ 2 levels "F","M": 1 2 2 1 2 2 1 2 2 2 ...
$ cargo : Factor w/ 9 levels "Director_Investigación",..: 3 5 9 5 9 9 9 9 2 7 ...
$ satisfacion_laboral : num [1:1470] 4 2 3 3 2 4 1 3 3 3 ...
$ estado_civil : Factor w/ 3 levels "Casado","Divorciado",..: 3 1 3 1 1 3 1 2 3 1 ...
$ ingreso_mensual : num [1:1470] 5993 5130 2090 2909 3468 ...
$ trabajos_anteriores : num [1:1470] 8 1 6 1 9 0 4 1 0 6 ...
$ horas_extra : Factor w/ 2 levels "No","Si": 2 1 2 2 1 1 2 1 1 1 ...
$ porcentaje_aumento_salarial: num [1:1470] 11 23 15 11 12 13 20 22 21 13 ...
$ rendimiento_laboral : num [1:1470] 3 4 3 3 3 3 4 4 4 3 ...
$ anos_experiencia : num [1:1470] 8 10 7 8 6 8 12 1 10 17 ...
$ capacitaciones : num [1:1470] 0 3 3 3 3 2 3 2 2 3 ...
$ equilibrio_trabajo_vida : num [1:1470] 1 3 3 3 3 2 2 3 3 2 ...
$ antiguedad : num [1:1470] 6 10 0 8 2 7 1 1 9 7 ...
$ antiguedad_cargo : num [1:1470] 4 7 0 7 2 7 0 0 7 7 ...
$ anos_ultima_promocion : num [1:1470] 0 1 0 3 2 3 0 0 1 7 ...
$ anos_acargo_con_mismo_jefe : num [1:1470] 5 7 0 0 2 6 0 0 8 7 ...
$ rotacion_bin : num [1:1470] 1 0 1 0 0 0 0 0 0 0 ...
# Revisar niveles y frecuencias de variables categóricas
variables_categoricas <- names(df)[sapply(df, is.factor)]
for (v in variables_categoricas) {
cat("\n=============================\n")
cat("Variable:", v, "\n")
print(table(df[[v]], useNA = "ifany"))
}
=============================
Variable: rotacion
No Si
1233 237
=============================
Variable: viaje_de_negocios
Frecuentemente No_Viaja Raramente
277 150 1043
=============================
Variable: departamento
IyD RH Ventas
961 63 446
=============================
Variable: campo_educacion
Ciencias Humanidades Mercadeo Otra Salud Tecnicos
606 27 159 82 464 132
=============================
Variable: genero
F M
588 882
=============================
Variable: cargo
Director_Investigación Director_Manofactura Ejecutivo_Ventas
80 145 326
Gerente Investigador_Cientifico Recursos_Humanos
102 292 52
Representante_Salud Representante_Ventas Tecnico_Laboratorio
131 83 259
=============================
Variable: estado_civil
Casado Divorciado Soltero
673 327 470
=============================
Variable: horas_extra
No Si
1054 416
En este paso se verifican las variables cuantitativas para asegurarnos de que no tengan valores imposibles o sospechosos.
Detectar valores fuera de estos rangos ayuda a identificar errores de registro, inconsistencias o datos que necesitan limpieza antes de hacer análisis o modelos predictivos.
# Validar valores fuera de rangos razonables
chequeo_rangos <- data.frame(
variable = c("edad", "educacion", "satisfaccion_ambiental", "satisfacion_laboral",
"porcentaje_aumento_salarial", "rendimiento_laboral",
"capacitaciones", "equilibrio_trabajo_vida"),
fuera_rango = c(
sum(df$edad < 18 | df$edad > 70, na.rm = TRUE),
sum(df$educacion < 1 | df$educacion > 5, na.rm = TRUE),
sum(df$satisfaccion_ambiental < 1 | df$satisfaccion_ambiental > 4, na.rm = TRUE),
sum(df$satisfacion_laboral < 1 | df$satisfacion_laboral > 4, na.rm = TRUE),
sum(df$porcentaje_aumento_salarial < 0, na.rm = TRUE),
sum(df$rendimiento_laboral < 1 | df$rendimiento_laboral > 4, na.rm = TRUE),
sum(df$capacitaciones < 0, na.rm = TRUE),
sum(df$equilibrio_trabajo_vida < 1 | df$equilibrio_trabajo_vida > 4, na.rm = TRUE)
)
)
chequeo_rangosEn este paso buscamos valores que se alejan demasiado del resto de los datos, llamados outliers o valores atípicos.
#Identificar variables numéricas
variables_cuantitativas <- names(df)[sapply(df, is.numeric)]
variables_cuantitativas [1] "edad" "distancia_casa"
[3] "educacion" "satisfaccion_ambiental"
[5] "satisfacion_laboral" "ingreso_mensual"
[7] "trabajos_anteriores" "porcentaje_aumento_salarial"
[9] "rendimiento_laboral" "anos_experiencia"
[11] "capacitaciones" "equilibrio_trabajo_vida"
[13] "antiguedad" "antiguedad_cargo"
[15] "anos_ultima_promocion" "anos_acargo_con_mismo_jefe"
[17] "rotacion_bin"
variables_cuantitativas <- setdiff(variables_cuantitativas, "rotacion_bin")
#Resumen de outliers con regla IQR
calcular_outliers <- function(x) {
q1 <- quantile(x, 0.25, na.rm = TRUE)
q3 <- quantile(x, 0.75, na.rm = TRUE)
iqr <- q3 - q1
li <- q1 - 1.5 * iqr
ls <- q3 + 1.5 * iqr
sum(x < li | x > ls, na.rm = TRUE)
}
resumen_outliers <- data.frame(
variable = variables_cuantitativas,
n_outliers = sapply(df[variables_cuantitativas], calcular_outliers)
)
resumen_outliersEn este paso usamos gráficos para entender mejor los datos y detectar casos extremos de manera visual:
#Gráfico de outliers por variable
ggplot(resumen_outliers, aes(x = reorder(variable, n_outliers), y = n_outliers)) +
geom_col() +
coord_flip() +
labs(title = "Cantidad de valores atípicos por variable",
x = "Variable", y = "Número de outliers")#Boxplots para todas las variables cuantitativas
df |>
select(all_of(variables_cuantitativas)) |>
pivot_longer(cols = everything(), names_to = "variable", values_to = "valor") |>
ggplot(aes(x = variable, y = valor)) +
geom_boxplot() +
coord_flip() +
labs(title = "Boxplots de variables cuantitativas",
x = "Variable", y = "Valor")En este paso creamos una tabla resumen que nos da una visión general de todas las variables del dataset:
tabla_descriptiva <- data.frame(
variable = names(df),
tipo = sapply(df, function(x) class(x)[1]),
valores_unicos = sapply(df, function(x) length(unique(x))),
faltantes = sapply(df, function(x) sum(is.na(x))),
porcentaje_faltantes = round(sapply(df, function(x) mean(is.na(x)) * 100), 2)
)
tabla_descriptiva |>
kable(caption = "Tabla descriptiva general del dataset") |>
kable_styling(full_width = FALSE)| variable | tipo | valores_unicos | faltantes | porcentaje_faltantes | |
|---|---|---|---|---|---|
| rotacion | rotacion | factor | 2 | 0 | 0 |
| edad | edad | numeric | 43 | 0 | 0 |
| viaje_de_negocios | viaje_de_negocios | factor | 3 | 0 | 0 |
| departamento | departamento | factor | 3 | 0 | 0 |
| distancia_casa | distancia_casa | numeric | 29 | 0 | 0 |
| educacion | educacion | numeric | 5 | 0 | 0 |
| campo_educacion | campo_educacion | factor | 6 | 0 | 0 |
| satisfaccion_ambiental | satisfaccion_ambiental | numeric | 4 | 0 | 0 |
| genero | genero | factor | 2 | 0 | 0 |
| cargo | cargo | factor | 9 | 0 | 0 |
| satisfacion_laboral | satisfacion_laboral | numeric | 4 | 0 | 0 |
| estado_civil | estado_civil | factor | 3 | 0 | 0 |
| ingreso_mensual | ingreso_mensual | numeric | 1349 | 0 | 0 |
| trabajos_anteriores | trabajos_anteriores | numeric | 10 | 0 | 0 |
| horas_extra | horas_extra | factor | 2 | 0 | 0 |
| porcentaje_aumento_salarial | porcentaje_aumento_salarial | numeric | 15 | 0 | 0 |
| rendimiento_laboral | rendimiento_laboral | numeric | 2 | 0 | 0 |
| anos_experiencia | anos_experiencia | numeric | 40 | 0 | 0 |
| capacitaciones | capacitaciones | numeric | 7 | 0 | 0 |
| equilibrio_trabajo_vida | equilibrio_trabajo_vida | numeric | 4 | 0 | 0 |
| antiguedad | antiguedad | numeric | 37 | 0 | 0 |
| antiguedad_cargo | antiguedad_cargo | numeric | 19 | 0 | 0 |
| anos_ultima_promocion | anos_ultima_promocion | numeric | 16 | 0 | 0 |
| anos_acargo_con_mismo_jefe | anos_acargo_con_mismo_jefe | numeric | 18 | 0 | 0 |
| rotacion_bin | rotacion_bin | numeric | 2 | 0 | 0 |
En este paso se separan las variables en dos tipos según su naturaleza:
# Identificar variables categóricas
variables_categoricas <- names(df)[sapply(df, is.factor)]
# Identificar variables cuantitativas
variables_cuantitativas <- names(df)[sapply(df, is.numeric)]
variables_cuantitativas <- setdiff(variables_cuantitativas, "rotacion_bin")
# Revisar resultados
cat("Variables Categoricas:")Variables Categoricas:
[1] "rotacion" "viaje_de_negocios" "departamento"
[4] "campo_educacion" "genero" "cargo"
[7] "estado_civil" "horas_extra"
Variables Cuantitativas:
[1] "edad" "distancia_casa"
[3] "educacion" "satisfaccion_ambiental"
[5] "satisfacion_laboral" "ingreso_mensual"
[7] "trabajos_anteriores" "porcentaje_aumento_salarial"
[9] "rendimiento_laboral" "anos_experiencia"
[11] "capacitaciones" "equilibrio_trabajo_vida"
[13] "antiguedad" "antiguedad_cargo"
[15] "anos_ultima_promocion" "anos_acargo_con_mismo_jefe"
En este paso se estudia cada variable categórica de forma individual para entender su distribución y frecuencia de valores:
for (v in variables_categoricas) {
cat("\n=============================\n")
cat("Variable:", v, "\n")
# Tabla de frecuencia y porcentaje
tab <- as.data.frame(table(df[[v]], useNA = "ifany"))
names(tab) <- c("categoria", "frecuencia")
tab$porcentaje <- round(tab$frecuencia / sum(tab$frecuencia) * 100, 2)
print(tab)
# Gráfico de barras
print(
ggplot(tab, aes(x = reorder(categoria, frecuencia), y = frecuencia)) +
geom_col(fill = "skyblue") +
coord_flip() +
labs(title = paste("Distribución de", v),
x = v, y = "Frecuencia")
)
}
=============================
Variable: rotacion
categoria frecuencia porcentaje
1 No 1233 83.88
2 Si 237 16.12
=============================
Variable: viaje_de_negocios
categoria frecuencia porcentaje
1 Frecuentemente 277 18.84
2 No_Viaja 150 10.20
3 Raramente 1043 70.95
=============================
Variable: departamento
categoria frecuencia porcentaje
1 IyD 961 65.37
2 RH 63 4.29
3 Ventas 446 30.34
=============================
Variable: campo_educacion
categoria frecuencia porcentaje
1 Ciencias 606 41.22
2 Humanidades 27 1.84
3 Mercadeo 159 10.82
4 Otra 82 5.58
5 Salud 464 31.56
6 Tecnicos 132 8.98
=============================
Variable: genero
categoria frecuencia porcentaje
1 F 588 40
2 M 882 60
=============================
Variable: cargo
categoria frecuencia porcentaje
1 Director_Investigación 80 5.44
2 Director_Manofactura 145 9.86
3 Ejecutivo_Ventas 326 22.18
4 Gerente 102 6.94
5 Investigador_Cientifico 292 19.86
6 Recursos_Humanos 52 3.54
7 Representante_Salud 131 8.91
8 Representante_Ventas 83 5.65
9 Tecnico_Laboratorio 259 17.62
=============================
Variable: estado_civil
categoria frecuencia porcentaje
1 Casado 673 45.78
2 Divorciado 327 22.24
3 Soltero 470 31.97
=============================
Variable: horas_extra
categoria frecuencia porcentaje
1 No 1054 71.7
2 Si 416 28.3
En este paso se estudia cada variable numérica de forma individual para entender su distribución, tendencia central y dispersión:
for (v in variables_cuantitativas) {
cat("\n=============================\n")
cat("Variable:", v, "\n")
# Resumen estadístico
print(summary(df[[v]]))
# Histograma
print(
ggplot(df, aes(x = .data[[v]])) +
geom_histogram(bins = 30, fill = "lightgreen", color = "black") +
labs(title = paste("Histograma de", v),
x = v, y = "Frecuencia")
)
# Boxplot
print(
ggplot(df, aes(y = .data[[v]])) +
geom_boxplot(fill = "salmon") +
labs(title = paste("Boxplot de", v),
y = v)
)
}
=============================
Variable: edad
Min. 1st Qu. Median Mean 3rd Qu. Max.
18.00 30.00 36.00 36.92 43.00 60.00
=============================
Variable: distancia_casa
Min. 1st Qu. Median Mean 3rd Qu. Max.
1.000 2.000 7.000 9.193 14.000 29.000
=============================
Variable: educacion
Min. 1st Qu. Median Mean 3rd Qu. Max.
1.000 2.000 3.000 2.913 4.000 5.000
=============================
Variable: satisfaccion_ambiental
Min. 1st Qu. Median Mean 3rd Qu. Max.
1.000 2.000 3.000 2.722 4.000 4.000
=============================
Variable: satisfacion_laboral
Min. 1st Qu. Median Mean 3rd Qu. Max.
1.000 2.000 3.000 2.729 4.000 4.000
=============================
Variable: ingreso_mensual
Min. 1st Qu. Median Mean 3rd Qu. Max.
1009 2911 4919 6503 8379 19999
=============================
Variable: trabajos_anteriores
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.000 1.000 2.000 2.693 4.000 9.000
=============================
Variable: porcentaje_aumento_salarial
Min. 1st Qu. Median Mean 3rd Qu. Max.
11.00 12.00 14.00 15.21 18.00 25.00
=============================
Variable: rendimiento_laboral
Min. 1st Qu. Median Mean 3rd Qu. Max.
3.000 3.000 3.000 3.154 3.000 4.000
=============================
Variable: anos_experiencia
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.00 6.00 10.00 11.28 15.00 40.00
=============================
Variable: capacitaciones
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.000 2.000 3.000 2.799 3.000 6.000
=============================
Variable: equilibrio_trabajo_vida
Min. 1st Qu. Median Mean 3rd Qu. Max.
1.000 2.000 3.000 2.761 3.000 4.000
=============================
Variable: antiguedad
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.000 3.000 5.000 7.008 9.000 40.000
=============================
Variable: antiguedad_cargo
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.000 2.000 3.000 4.229 7.000 18.000
=============================
Variable: anos_ultima_promocion
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.000 0.000 1.000 2.188 3.000 15.000
=============================
Variable: anos_acargo_con_mismo_jefe
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.000 2.000 3.000 4.123 7.000 17.000
No Si
1233 237
No Si
83.87755 16.12245
# Gráfico de barras
ggplot(df, aes(x = rotacion)) +
geom_bar(fill = "steelblue") +
labs(title = "Distribución de la variable Rotación",
x = "Rotación", y = "Frecuencia")# Correlación entre variables cuantitativas
matriz_cor <- cor(df[variables_cuantitativas], use = "pairwise.complete.obs")
round(matriz_cor, 2) edad distancia_casa educacion
edad 1.00 0.00 0.21
distancia_casa 0.00 1.00 0.02
educacion 0.21 0.02 1.00
satisfaccion_ambiental 0.01 -0.02 -0.03
satisfacion_laboral 0.00 0.00 -0.01
ingreso_mensual 0.50 -0.02 0.09
trabajos_anteriores 0.30 -0.03 0.13
porcentaje_aumento_salarial 0.00 0.04 -0.01
rendimiento_laboral 0.00 0.03 -0.02
anos_experiencia 0.68 0.00 0.15
capacitaciones -0.02 -0.04 -0.03
equilibrio_trabajo_vida -0.02 -0.03 0.01
antiguedad 0.31 0.01 0.07
antiguedad_cargo 0.21 0.02 0.06
anos_ultima_promocion 0.22 0.01 0.05
anos_acargo_con_mismo_jefe 0.20 0.01 0.07
satisfaccion_ambiental satisfacion_laboral
edad 0.01 0.00
distancia_casa -0.02 0.00
educacion -0.03 -0.01
satisfaccion_ambiental 1.00 -0.01
satisfacion_laboral -0.01 1.00
ingreso_mensual -0.01 -0.01
trabajos_anteriores 0.01 -0.06
porcentaje_aumento_salarial -0.03 0.02
rendimiento_laboral -0.03 0.00
anos_experiencia 0.00 -0.02
capacitaciones -0.02 -0.01
equilibrio_trabajo_vida 0.03 -0.02
antiguedad 0.00 0.00
antiguedad_cargo 0.02 0.00
anos_ultima_promocion 0.02 -0.02
anos_acargo_con_mismo_jefe 0.00 -0.03
ingreso_mensual trabajos_anteriores
edad 0.50 0.30
distancia_casa -0.02 -0.03
educacion 0.09 0.13
satisfaccion_ambiental -0.01 0.01
satisfacion_laboral -0.01 -0.06
ingreso_mensual 1.00 0.15
trabajos_anteriores 0.15 1.00
porcentaje_aumento_salarial -0.03 -0.01
rendimiento_laboral -0.02 -0.01
anos_experiencia 0.77 0.24
capacitaciones -0.02 -0.07
equilibrio_trabajo_vida 0.03 -0.01
antiguedad 0.51 -0.12
antiguedad_cargo 0.36 -0.09
anos_ultima_promocion 0.34 -0.04
anos_acargo_con_mismo_jefe 0.34 -0.11
porcentaje_aumento_salarial rendimiento_laboral
edad 0.00 0.00
distancia_casa 0.04 0.03
educacion -0.01 -0.02
satisfaccion_ambiental -0.03 -0.03
satisfacion_laboral 0.02 0.00
ingreso_mensual -0.03 -0.02
trabajos_anteriores -0.01 -0.01
porcentaje_aumento_salarial 1.00 0.77
rendimiento_laboral 0.77 1.00
anos_experiencia -0.02 0.01
capacitaciones -0.01 -0.02
equilibrio_trabajo_vida 0.00 0.00
antiguedad -0.04 0.00
antiguedad_cargo 0.00 0.03
anos_ultima_promocion -0.02 0.02
anos_acargo_con_mismo_jefe -0.01 0.02
anos_experiencia capacitaciones
edad 0.68 -0.02
distancia_casa 0.00 -0.04
educacion 0.15 -0.03
satisfaccion_ambiental 0.00 -0.02
satisfacion_laboral -0.02 -0.01
ingreso_mensual 0.77 -0.02
trabajos_anteriores 0.24 -0.07
porcentaje_aumento_salarial -0.02 -0.01
rendimiento_laboral 0.01 -0.02
anos_experiencia 1.00 -0.04
capacitaciones -0.04 1.00
equilibrio_trabajo_vida 0.00 0.03
antiguedad 0.63 0.00
antiguedad_cargo 0.46 -0.01
anos_ultima_promocion 0.40 0.00
anos_acargo_con_mismo_jefe 0.46 0.00
equilibrio_trabajo_vida antiguedad antiguedad_cargo
edad -0.02 0.31 0.21
distancia_casa -0.03 0.01 0.02
educacion 0.01 0.07 0.06
satisfaccion_ambiental 0.03 0.00 0.02
satisfacion_laboral -0.02 0.00 0.00
ingreso_mensual 0.03 0.51 0.36
trabajos_anteriores -0.01 -0.12 -0.09
porcentaje_aumento_salarial 0.00 -0.04 0.00
rendimiento_laboral 0.00 0.00 0.03
anos_experiencia 0.00 0.63 0.46
capacitaciones 0.03 0.00 -0.01
equilibrio_trabajo_vida 1.00 0.01 0.05
antiguedad 0.01 1.00 0.76
antiguedad_cargo 0.05 0.76 1.00
anos_ultima_promocion 0.01 0.62 0.55
anos_acargo_con_mismo_jefe 0.00 0.77 0.71
anos_ultima_promocion anos_acargo_con_mismo_jefe
edad 0.22 0.20
distancia_casa 0.01 0.01
educacion 0.05 0.07
satisfaccion_ambiental 0.02 0.00
satisfacion_laboral -0.02 -0.03
ingreso_mensual 0.34 0.34
trabajos_anteriores -0.04 -0.11
porcentaje_aumento_salarial -0.02 -0.01
rendimiento_laboral 0.02 0.02
anos_experiencia 0.40 0.46
capacitaciones 0.00 0.00
equilibrio_trabajo_vida 0.01 0.00
antiguedad 0.62 0.77
antiguedad_cargo 0.55 0.71
anos_ultima_promocion 1.00 0.51
anos_acargo_con_mismo_jefe 0.51 1.00
# Convertir a formato largo para ggplot
cor_df <- as.data.frame(as.table(matriz_cor))
# Mapa de calor de correlación
ggplot(cor_df, aes(Var1, Var2, fill = Freq)) +
geom_tile() +
scale_fill_gradient2(low = "blue", mid = "white", high = "red", midpoint = 0) +
labs(title = "Mapa de correlación entre variables cuantitativas",
x = "", y = "", fill = "Correlación") +
theme(axis.text.x = element_text(angle = 90, hjust = 1))Para esta actividad, estas 6 variables porque son intuitivas y tienen sentido para la rotación:
CATEGÓRICAS
CUANTITATIVAS
Justificación e hipótesis de cada variable
Hipótesis: los empleados que trabajan horas extra tienen una mayor probabilidad de rotación que aquellos que no trabajan horas extra.
Hipótesis: los empleados que viajan con mayor frecuencia por trabajo tienen una mayor probabilidad de rotación que aquellos que viajan rara vez o no viajan.
Hipótesis: los empleados solteros presentan una mayor probabilidad de rotación que los empleados casados.
Hipótesis: a mayor satisfacción laboral, menor probabilidad de rotación.
Hipótesis: a mayor ingreso mensual, menor probabilidad de rotación.
Hipótesis: a mayor antigüedad en el cargo, menor probabilidad de rotación.
# Crear la tabla resumen
tabla_variables_hipotesis <- tribble(
~Variable, ~Tipo, ~Justificacion, ~Hipotesis_esperada,
"Horas_Extra", "Categórica", "La sobrecarga laboral puede generar desgaste y deseo de cambio", "Quienes hacen horas extra tienen mayor rotación",
"Viaje_de_Negocios", "Categórica", "Viajar con frecuencia puede generar cansancio e inestabilidad", "Quienes viajan más tienen mayor rotación",
"Estado_Civil", "Categórica", "Las condiciones personales pueden influir en la estabilidad laboral", "Los solteros tendrían mayor rotación",
"Satisfacion_Laboral","Cuantitativa", "La satisfacción con el trabajo influye en la permanencia", "A mayor satisfacción, menor rotación",
"Ingreso_Mensual", "Cuantitativa", "El salario influye en la motivación y permanencia", "A mayor ingreso, menor rotación",
"Antiguedad_Cargo", "Cuantitativa", "El tiempo en el cargo refleja estabilidad y adaptación", "A mayor antigüedad, menor rotación"
)
# Mostrar la tabla bonita
tabla_variables_hipotesis %>%
kable(caption = "Tabla de variables con justificación e hipótesis esperada") %>%
kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed"))| Variable | Tipo | Justificacion | Hipotesis_esperada |
|---|---|---|---|
| Horas_Extra | Categórica | La sobrecarga laboral puede generar desgaste y deseo de cambio | Quienes hacen horas extra tienen mayor rotación |
| Viaje_de_Negocios | Categórica | Viajar con frecuencia puede generar cansancio e inestabilidad | Quienes viajan más tienen mayor rotación |
| Estado_Civil | Categórica | Las condiciones personales pueden influir en la estabilidad laboral | Los solteros tendrían mayor rotación |
| Satisfacion_Laboral | Cuantitativa | La satisfacción con el trabajo influye en la permanencia | A mayor satisfacción, menor rotación |
| Ingreso_Mensual | Cuantitativa | El salario influye en la motivación y permanencia | A mayor ingreso, menor rotación |
| Antiguedad_Cargo | Cuantitativa | El tiempo en el cargo refleja estabilidad y adaptación | A mayor antigüedad, menor rotación |
En este paso, además de limpiar los duplicados y organizar la variable “rotación” como antes, seleccionamos solo las columnas que nos interesan para el análisis y el modelado. Esto hace que nuestro dataset sea más manejable y contenga solo la información relevante. Las columnas elegidas incluyen la variable de interés (rotacion), su versión numérica (rotacion_bin), variables categóricas que podrían influir en la rotación (horas_extra, viaje_de_negocios, estado_civil) y variables numéricas importantes (satisfacion_laboral, ingreso_mensual, antiguedad_cargo).
Esto prepara un dataset limpio y enfocado, listo para construir modelos estadísticos o de machine learning.
## **PREPARACIÓN FINAL DEL DATASET CON VARIABLES SELECCIONADAS**
# Variables que decidimos usar para el modelo
variables_seleccionadas <- c(
"rotacion", "rotacion_bin", # Variable respuesta
"horas_extra", "viaje_de_negocios", "estado_civil", # Categóricas
"satisfacion_laboral", "ingreso_mensual", "antiguedad_cargo" # Cuantitativas
)
# Crear dataset final limpio solo con variables seleccionadas
df_limpio <- df |>
select(all_of(variables_seleccionadas)) |> # Selección de columnas
distinct() |> # Eliminar duplicados
mutate(
rotacion = factor(rotacion, levels = c("No", "Si")),
rotacion_bin = ifelse(rotacion == "Si", 1, 0)
)Antes de comenzar a construir modelos, es importante revisar que el dataset final esté correctamente preparado. En esta verificación se comprueba:
Dimesión del nuevo dataset: 1470 8
Cantidad de valores faltantes: 0
Cantidad de valores duplicados: 0
tibble [1,470 × 8] (S3: tbl_df/tbl/data.frame)
$ rotacion : Factor w/ 2 levels "No","Si": 2 1 2 1 1 1 1 1 1 1 ...
$ rotacion_bin : num [1:1470] 1 0 1 0 0 0 0 0 0 0 ...
$ horas_extra : Factor w/ 2 levels "No","Si": 2 1 2 2 1 1 2 1 1 1 ...
$ viaje_de_negocios : Factor w/ 3 levels "Frecuentemente",..: 3 1 3 1 3 1 3 3 1 3 ...
$ estado_civil : Factor w/ 3 levels "Casado","Divorciado",..: 3 1 3 1 1 3 1 2 3 1 ...
$ satisfacion_laboral: num [1:1470] 4 2 3 3 2 4 1 3 3 3 ...
$ ingreso_mensual : num [1:1470] 5993 5130 2090 2909 3468 ...
$ antiguedad_cargo : num [1:1470] 4 7 0 7 2 7 0 0 7 7 ...
En esta sección se examinan individualmente las variables numéricas seleccionadas para entender cómo se distribuyen sus valores. Para esto, se generan histogramas que muestran la frecuencia con la que aparecen diferentes rangos de valores en cada variable.
Este análisis nos ayuda a visualizar patrones como concentraciones de datos, posibles sesgos, y la presencia de valores extremos o atípicos. Entender la distribución de cada variable es fundamental para tomar decisiones informadas en pasos posteriores del análisis y modelado.
# Selección de variables cuantitativas para análisis univariado
data2 <- df_limpio[, c("satisfacion_laboral", "ingreso_mensual", "antiguedad_cargo")]
# Configurar gráfica 2 filas y 2 columnas
par(mfrow = c(2, 2))
# Histograma para satisfacción laboral
hist(data2$satisfacion_laboral,
main = "Histograma de Satisfacción Laboral",
xlab = "Nivel de satisfacción laboral",
ylab = "Frecuencia",
col = "lightblue", border = "white")
# Histograma para ingreso mensual
hist(data2$ingreso_mensual,
main = "Histograma de Ingreso Mensual",
xlab = "Ingreso mensual",
ylab = "Frecuencia",
col = "lightgreen", border = "white")
# Histograma para antigüedad en el cargo
hist(data2$antiguedad_cargo,
main = "Histograma de Antigüedad en Cargo",
xlab = "Años en el cargo",
ylab = "Frecuencia",
col = "lightcoral", border = "white")
# Resetear layout gráfico
par(mfrow = c(1, 1))En esta sección se analizan una por una las variables categóricas seleccionadas para entender cómo se distribuyen sus diferentes categorías o grupos dentro del conjunto de datos. Se muestra la frecuencia absoluta y el porcentaje de cada categoría, además de gráficos de barras que visualizan estas proporciones.
Este análisis permite identificar cuáles son las categorías más comunes, detectar posibles desequilibrios o rarezas en los datos, y es útil para comprender mejor el contexto y comportamiento de cada variable antes de explorar relaciones con otras variables o construir modelos.
# Variables categóricas seleccionadas para análisis univariado
vars_cat_sel <- c("horas_extra", "viaje_de_negocios", "estado_civil")
# Análisis univariado para variables categóricas
for (v in vars_cat_sel) {
cat("\n=============================\n")
cat("Variable:", v, "\n")
# Tabla de frecuencias y porcentaje
tab <- as.data.frame(table(df[[v]], useNA = "ifany"))
names(tab) <- c("categoria", "frecuencia")
tab$porcentaje <- round(tab$frecuencia / sum(tab$frecuencia) * 100, 2)
print(tab)
# Gráfico de barras con proporciones
print(
ggplot(tab, aes(x = reorder(categoria, frecuencia), y = frecuencia)) +
geom_col(fill = "steelblue") +
coord_flip() +
labs(title = paste("Distribución de", v),
x = v, y = "Frecuencia") +
theme_minimal()
)
}
=============================
Variable: horas_extra
categoria frecuencia porcentaje
1 No 1054 71.7
2 Si 416 28.3
=============================
Variable: viaje_de_negocios
categoria frecuencia porcentaje
1 Frecuentemente 277 18.84
2 No_Viaja 150 10.20
3 Raramente 1043 70.95
=============================
Variable: estado_civil
categoria frecuencia porcentaje
1 Casado 673 45.78
2 Divorciado 327 22.24
3 Soltero 470 31.97
En esta sección estamos comparando cada variable categórica con la rotación de los empleados. Para cada variable:
## **ANÁLISIS BIVARIADO: VARIABLES CATEGÓRICAS vs ROTACIÓN**
# Variables categóricas seleccionadas
vars_cat_sel <- c("horas_extra", "viaje_de_negocios", "estado_civil")
for (v in vars_cat_sel) {
cat("\n=============================\n")
cat("Variable:", v, "\n")
# Tabla de contingencia
tab <- table(df_limpio[[v]], df_limpio$rotacion)
print(tab)
# Porcentajes por fila
prop_fila <- round(prop.table(tab, 1) * 100, 2)
print(prop_fila)
# Test Chi-cuadrado para asociación
print(chisq.test(tab))
# Gráfico de proporciones
print(
ggplot(df_limpio, aes(x = .data[[v]], fill = rotacion)) +
geom_bar(position = "fill") +
scale_y_continuous(labels = function(x) paste0(x * 100, "%")) +
labs(title = paste("Rotación según", v),
x = v, y = "Proporción") +
theme_minimal()
)
}
=============================
Variable: horas_extra
No Si
No 944 110
Si 289 127
No Si
No 89.56 10.44
Si 69.47 30.53
Pearson's Chi-squared test with Yates' continuity correction
data: tab
X-squared = 87.564, df = 1, p-value < 2.2e-16
=============================
Variable: viaje_de_negocios
No Si
Frecuentemente 208 69
No_Viaja 138 12
Raramente 887 156
No Si
Frecuentemente 75.09 24.91
No_Viaja 92.00 8.00
Raramente 85.04 14.96
Pearson's Chi-squared test
data: tab
X-squared = 24.182, df = 2, p-value = 5.609e-06
=============================
Variable: estado_civil
No Si
Casado 589 84
Divorciado 294 33
Soltero 350 120
No Si
Casado 87.52 12.48
Divorciado 89.91 10.09
Soltero 74.47 25.53
Pearson's Chi-squared test
data: tab
X-squared = 46.164, df = 2, p-value = 9.456e-11
En esta sección estamos comparando las variables numéricas con la rotación de los empleados. Para cada variable:
## **ANÁLISIS BIVARIADO: VARIABLES CUANTITATIVAS vs ROTACIÓN**
vars_num_sel <- c("satisfacion_laboral", "ingreso_mensual", "antiguedad_cargo")
for (v in vars_num_sel) {
cat("\n=============================\n")
cat("Variable:", v, "\n")
# Resumen estadístico por grupo de rotación
resumen <- df_limpio |>
group_by(rotacion) |>
summarise(
n = n(),
media = mean(.data[[v]], na.rm = TRUE),
mediana = median(.data[[v]], na.rm = TRUE),
sd = sd(.data[[v]], na.rm = TRUE)
)
print(resumen)
# Boxplot para comparar distribución según rotación
print(
ggplot(df_limpio, aes(x = rotacion, y = .data[[v]])) +
geom_boxplot(fill = "#69b3a2", alpha = 0.7) +
labs(title = paste(v, "según Rotación"),
x = "Rotación", y = v) +
theme_minimal()
)
}
=============================
Variable: satisfacion_laboral
# A tibble: 2 × 5
rotacion n media mediana sd
<fct> <int> <dbl> <dbl> <dbl>
1 No 1233 2.78 3 1.09
2 Si 237 2.47 3 1.12
=============================
Variable: ingreso_mensual
# A tibble: 2 × 5
rotacion n media mediana sd
<fct> <int> <dbl> <dbl> <dbl>
1 No 1233 6833. 5204 4818.
2 Si 237 4787. 3202 3640.
=============================
Variable: antiguedad_cargo
# A tibble: 2 × 5
rotacion n media mediana sd
<fct> <int> <dbl> <dbl> <dbl>
1 No 1233 4.48 3 3.65
2 Si 237 2.90 2 3.17
En esta sección:
## **SIGNO DEL COEFICIENTE EN ANÁLISIS BIVARIADO**
resultado_uni <- function(var, data) {
formula_uni <- as.formula(paste("rotacion_bin ~", var))
modelo <- glm(formula_uni, data = data, family = binomial)
coefs <- summary(modelo)$coefficients
salida <- data.frame(
variable = var,
termino = rownames(coefs),
beta = coefs[, 1],
error_std = coefs[, 2],
z = coefs[, 3],
p_value = coefs[, 4],
odds_ratio = exp(coefs[, 1])
)
rownames(salida) <- NULL
salida
}
variables_seleccionadas <- c("horas_extra", "viaje_de_negocios", "estado_civil",
"satisfacion_laboral", "ingreso_mensual", "antiguedad_cargo")
# Usar dataset limpio
resultados_bivariados <- do.call(rbind, lapply(variables_seleccionadas, resultado_uni, data = df_limpio))
resultados_bivariadosEn esta sección se realiza un análisis univariado de las variables categóricas seleccionadas para el estudio. Se construyen tablas de frecuencias y porcentajes para cada categoría, lo que permite ver cómo se distribuyen los empleados según sus características (por ejemplo, si hacen horas extra, su estado civil o la frecuencia de viajes de negocios).
Además, se generan gráficos de barras para cada variable, mostrando de manera visual la cantidad de empleados en cada categoría. Esto ayuda a identificar rápidamente categorías predominantes o poco frecuentes, y sirve como referencia para entender cómo estas variables podrían relacionarse con la rotación de empleados.
vars_cat <- c("rotacion", "horas_extra", "viaje_de_negocios", "estado_civil")
for (v in vars_cat) {
cat("\n============================\n")
cat("Variable:", v, "\n")
tabla <- as.data.frame(table(df_limpio[[v]]))
colnames(tabla) <- c("categoria", "frecuencia")
tabla$porcentaje <- round(100 * tabla$frecuencia / sum(tabla$frecuencia), 2)
print(tabla)
print(
ggplot(tabla, aes(x = reorder(categoria, frecuencia), y = frecuencia)) +
geom_col() +
coord_flip() +
labs(
title = paste("Distribución de", v),
x = "Categoría",
y = "Frecuencia"
)
)
}
============================
Variable: rotacion
categoria frecuencia porcentaje
1 No 1233 83.88
2 Si 237 16.12
============================
Variable: horas_extra
categoria frecuencia porcentaje
1 No 1054 71.7
2 Si 416 28.3
============================
Variable: viaje_de_negocios
categoria frecuencia porcentaje
1 Frecuentemente 277 18.84
2 No_Viaja 150 10.20
3 Raramente 1043 70.95
============================
Variable: estado_civil
categoria frecuencia porcentaje
1 Casado 673 45.78
2 Divorciado 327 22.24
3 Soltero 470 31.97
En esta sección se analizan las variables numéricas seleccionadas para obtener un panorama general de sus valores. Se calculan medidas estadísticas como:
vars_num <- c("satisfacion_laboral", "ingreso_mensual", "antiguedad_cargo")
resumen_num <- data.frame(
variable = vars_num,
media = sapply(df_limpio[vars_num], mean, na.rm = TRUE),
mediana = sapply(df_limpio[vars_num], median, na.rm = TRUE),
desviacion = sapply(df_limpio[vars_num], sd, na.rm = TRUE),
minimo = sapply(df_limpio[vars_num], min, na.rm = TRUE),
maximo = sapply(df_limpio[vars_num], max, na.rm = TRUE)
)
resumen_num %>%
kable(caption = "Resumen descriptivo de variables cuantitativas") %>%
kable_styling(full_width = FALSE)| variable | media | mediana | desviacion | minimo | maximo | |
|---|---|---|---|---|---|---|
| satisfacion_laboral | satisfacion_laboral | 2.728571 | 3 | 1.102846 | 1 | 4 |
| ingreso_mensual | ingreso_mensual | 6502.931293 | 4919 | 4707.956783 | 1009 | 19999 |
| antiguedad_cargo | antiguedad_cargo | 4.229252 | 3 | 3.623137 | 0 | 18 |
En esta sección se utilizan gráficos para explorar las variables numéricas seleccionadas de manera más visual:
for (v in vars_num) {
print(
ggplot(df_limpio, aes(x = .data[[v]])) +
geom_histogram(bins = 30) +
labs(
title = paste("Histograma de", v),
x = v,
y = "Frecuencia"
)
)
}#COEFICIENTE DE VARIABLES SELECCIONADAS CON REGRESIÓN LOGÍSTICA SIMPLE Este paso es muy importante porque la actividad pide interpretar el signo del coeficiente. En esta sección se realiza un análisis bivariado usando regresión logística para cada variable seleccionada respecto a la variable objetivo rotacion_bin (0 = No, 1 = Sí).
El objetivo principal es interpretar el signo del coeficiente (beta) de cada variable:
Además, se calcula el odds ratio, que transforma el coeficiente logarítmico en una medida más intuitiva de riesgo o probabilidad relativa. Esto permite entender cómo cada característica individual afecta la rotación de empleados antes de construir modelos más complejos.
resultado_uni <- function(var, data) {
formula_uni <- as.formula(paste("rotacion_bin ~", var))
modelo <- glm(formula_uni, data = data, family = binomial)
salida <- tidy(modelo) %>%
mutate(odds_ratio = exp(estimate),
variable = var)
return(salida)
}
variables_seleccionadas <- c(
"horas_extra", "viaje_de_negocios", "estado_civil",
"satisfacion_laboral", "ingreso_mensual", "antiguedad_cargo"
)
resultados_bivariados <- do.call(
rbind,
lapply(variables_seleccionadas, resultado_uni, data = df_limpio)
)
resultados_bivariadosEn esta sección se construye un modelo de regresión logística multivariado para predecir la probabilidad de rotación (rotacion_bin). Aquí se incluyen todas las variables seleccionadas simultáneamente: horas_extra, viaje_de_negocios, estado_civil, satisfacion_laboral, ingreso_mensual y antiguedad_cargo.
El objetivo es evaluar el efecto conjunto de estas variables sobre la rotación, controlando por la influencia de las demás. Los coeficientes resultantes indican cómo cambia la probabilidad de rotación cuando una variable aumenta, manteniendo las demás constantes.
Con este paso podemos identificar qué factores tienen mayor impacto en la decisión de cambiar de cargo, y así interpretar los resultados de manera más completa que en los análisis bivariados.
#ESTIMACIÓN DEL MODELO LOGÍSTICO
#Ajuste del modelo
modelo_logit <- glm(
rotacion_bin ~ horas_extra + viaje_de_negocios + estado_civil +
satisfacion_laboral + ingreso_mensual + antiguedad_cargo,
data = df_limpio,
family = binomial(link = "logit")
)
summary(modelo_logit)
Call:
glm(formula = rotacion_bin ~ horas_extra + viaje_de_negocios +
estado_civil + satisfacion_laboral + ingreso_mensual + antiguedad_cargo,
family = binomial(link = "logit"), data = df_limpio)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -5.952e-02 2.994e-01 -0.199 0.842409
horas_extraSi 1.480e+00 1.597e-01 9.269 < 2e-16 ***
viaje_de_negociosNo_Viaja -1.311e+00 3.561e-01 -3.681 0.000232 ***
viaje_de_negociosRaramente -6.932e-01 1.819e-01 -3.811 0.000139 ***
estado_civilDivorciado -3.472e-01 2.322e-01 -1.495 0.134789
estado_civilSoltero 8.615e-01 1.716e-01 5.021 5.13e-07 ***
satisfacion_laboral -3.342e-01 7.015e-02 -4.765 1.89e-06 ***
ingreso_mensual -1.004e-04 2.316e-05 -4.335 1.46e-05 ***
antiguedad_cargo -1.081e-01 2.730e-02 -3.959 7.52e-05 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 1298.6 on 1469 degrees of freedom
Residual deviance: 1067.9 on 1461 degrees of freedom
AIC: 1085.9
Number of Fisher Scoring iterations: 5
En esta sección se hace un resumen más interpretable del modelo logístico: Se calculan los odds ratio (probabilidades relativas) de cada variable, junto con sus intervalos de confianza. Esto permite entender cuánto aumenta o disminuye la probabilidad de rotación por cada unidad de cambio en la variable, manteniendo constantes las demás.
Se revisa la multicolinealidad con el VIF (Variance Inflation Factor) para detectar si alguna variable está demasiado correlacionada con otra. Valores altos de VIF podrían indicar que una variable no aporta información nueva y podría dificultar la interpretación de los coeficientes.
#TABLA CON ODDS RATIO E INTERVALOS DE CONFIANZA
tabla_or <- broom::tidy(modelo_logit, conf.int = TRUE) %>%
mutate(
odds_ratio = exp(estimate),
or_inf = exp(conf.low),
or_sup = exp(conf.high)
)
tabla_or %>%
kable(caption = "Coeficientes del modelo logístico y odds ratio") %>%
kable_styling(full_width = FALSE)| term | estimate | std.error | statistic | p.value | conf.low | conf.high | odds_ratio | or_inf | or_sup |
|---|---|---|---|---|---|---|---|---|---|
| (Intercept) | -0.0595223 | 0.2993890 | -0.1988127 | 0.8424093 | -0.6487736 | 0.5262167 | 0.9422145 | 0.5226864 | 1.6925169 |
| horas_extraSi | 1.4799685 | 0.1596644 | 9.2692444 | 0.0000000 | 1.1687880 | 1.7952787 | 4.3928075 | 3.2180901 | 6.0211523 |
| viaje_de_negociosNo_Viaja | -1.3110579 | 0.3561480 | -3.6812166 | 0.0002321 | -2.0506181 | -0.6448662 | 0.2695348 | 0.1286554 | 0.5247327 |
| viaje_de_negociosRaramente | -0.6932427 | 0.1819272 | -3.8105492 | 0.0001387 | -1.0478430 | -0.3337424 | 0.4999523 | 0.3506934 | 0.7162383 |
| estado_civilDivorciado | -0.3472411 | 0.2321937 | -1.4954805 | 0.1347891 | -0.8134210 | 0.0991286 | 0.7066350 | 0.4433388 | 1.1042083 |
| estado_civilSoltero | 0.8614850 | 0.1715634 | 5.0213786 | 0.0000005 | 0.5270361 | 1.2003035 | 2.3666726 | 1.6939044 | 3.3211246 |
| satisfacion_laboral | -0.3342400 | 0.0701452 | -4.7649720 | 0.0000019 | -0.4726067 | -0.1973650 | 0.7158819 | 0.6233752 | 0.8208909 |
| ingreso_mensual | -0.0001004 | 0.0000232 | -4.3352176 | 0.0000146 | -0.0001477 | -0.0000567 | 0.9998996 | 0.9998523 | 0.9999433 |
| antiguedad_cargo | -0.1080763 | 0.0272985 | -3.9590583 | 0.0000752 | -0.1627137 | -0.0555668 | 0.8975591 | 0.8498345 | 0.9459489 |
GVIF Df GVIF^(1/(2*Df))
horas_extra 1.036616 1 1.018143
viaje_de_negocios 1.013535 2 1.003367
estado_civil 1.026403 2 1.006536
satisfacion_laboral 1.024792 1 1.012320
ingreso_mensual 1.100009 1 1.048813
antiguedad_cargo 1.095149 1 1.046494
En esta etapa se divide el conjunto de datos en entrenamiento y prueba para evaluar la capacidad predictiva del modelo:
En esta sección revisamos cómo se distribuyen los empleados que cambiaron de puesto frente a los que no lo hicieron. Esto es importante porque si la mayoría de los datos pertenece a una sola categoría, los modelos podrían aprender más sobre esa categoría y menos sobre la otra, afectando su capacidad de predicción.
Se construyó una tabla con la frecuencia y el porcentaje de empleados en cada grupo (“No” rotación y “Sí” rotación) y un gráfico de barras para visualizar fácilmente la distribución. Esto permite ver si ambas categorías están equilibradas o si hay un desbalance que debemos considerar al entrenar modelos predictivos.
## **REVISIÓN DEL BALANCE DE LA VARIABLE RESPUESTA**
tabla_rotacion <- as.data.frame(table(df_limpio$rotacion))
colnames(tabla_rotacion) <- c("categoria", "frecuencia")
tabla_rotacion$porcentaje <- round(100 * tabla_rotacion$frecuencia / sum(tabla_rotacion$frecuencia), 2)
tabla_rotacion %>%
kable(caption = "Distribución de la variable rotación") %>%
kable_styling(full_width = FALSE)| categoria | frecuencia | porcentaje |
|---|---|---|
| No | 1233 | 83.88 |
| Si | 237 | 16.12 |
ggplot(tabla_rotacion, aes(x = categoria, y = frecuencia, fill = categoria)) +
geom_col() +
labs(title = "Balance de la variable rotación",
x = "Rotación", y = "Frecuencia") +
theme_minimal()En este paso se ajusta el modelo logístico usando solo los datos de entrenamiento (train).
Aquí usamos el modelo entrenado (modelo_train) para predecir la probabilidad de que cada empleado tenga rotación en el conjunto de prueba (test).
1 2 3 4 5 6
0.61473898 0.52949950 0.04620339 0.56473888 0.03381974 0.32315143
En esta sección se evalúa el desempeño del modelo utilizando una matriz de confusión, que permite comparar lo que el modelo predice frente a lo que realmente ocurrió.
Primero, se define un punto de corte de 0.50, lo que significa que:
Luego, se construye la matriz de confusión, donde se observan cuatro casos posibles:
Además, se calculan métricas importantes como la precisión, la sensibilidad y la exactitud, que ayudan a entender qué tan bien está funcionando el modelo.
## **MATRIZ DE CONFUSIÓN DEL MODELO BASE**
# Clasificación usando punto de corte 0.50
pred_test <- ifelse(prob_test >= 0.50, "Si", "No")
pred_test <- factor(pred_test, levels = c("No", "Si"))
# Matriz de confusión
matriz_confusion_base <- table(Real = test$rotacion, Predicho = pred_test)
matriz_confusion_base Predicho
Real No Si
No 357 9
Si 59 16
# Métricas de evaluación
conf_base <- confusionMatrix(pred_test, test$rotacion, positive = "Si")
conf_baseConfusion Matrix and Statistics
Reference
Prediction No Si
No 357 59
Si 9 16
Accuracy : 0.8458
95% CI : (0.8087, 0.8782)
No Information Rate : 0.8299
P-Value [Acc > NIR] : 0.2063
Kappa : 0.2568
Mcnemar's Test P-Value : 2.814e-09
Sensitivity : 0.21333
Specificity : 0.97541
Pos Pred Value : 0.64000
Neg Pred Value : 0.85817
Prevalence : 0.17007
Detection Rate : 0.03628
Detection Prevalence : 0.05669
Balanced Accuracy : 0.59437
'Positive' Class : Si
En esta sección evaluamos el desempeño del modelo usando Curva ROC y AUC:
El gráfico resultante permite ver visualmente qué tan bien nuestro modelo predice la rotación de empleados.
Area under the curve: 0.7515
plot(roc_modelo, main = paste("Curva ROC - AUC =", round(auc_modelo, 4)))
abline(a = 0, b = 1, lty = 2, col = "gray")Además de la curva ROC y el AUC, se construyó una matriz de confusión para evaluar el desempeño del modelo en términos de clasificaciones correctas e incorrectas. Esta matriz permite identificar cuántos empleados fueron correctamente clasificados como casos de rotación o no rotación, y calcular métricas como exactitud, sensibilidad, especificidad y precisión.
## **BALANCEO DE CATEGORÍAS EN LA MUESTRA DE ENTRENAMIENTO**
set.seed(123)
train_balanceado <- ovun.sample(
rotacion ~ horas_extra + viaje_de_negocios + estado_civil +
satisfacion_laboral + ingreso_mensual + antiguedad_cargo,
data = train,
method = "over"
)$data
# Ver distribución después del balanceo
tabla_balanceo <- as.data.frame(table(train_balanceado$rotacion))
colnames(tabla_balanceo) <- c("categoria", "frecuencia")
tabla_balanceo$porcentaje <- round(100 * tabla_balanceo$frecuencia / sum(tabla_balanceo$frecuencia), 2)
tabla_balanceo %>%
kable(caption = "Distribución de la variable rotación después del balanceo") %>%
kable_styling(full_width = FALSE)| categoria | frecuencia | porcentaje |
|---|---|---|
| No | 867 | 50.38 |
| Si | 854 | 49.62 |
Debido a que la variable respuesta presentaba desbalance entre sus categorías, se aplicó una técnica de sobremuestreo sobre la muestra de entrenamiento. Este procedimiento busca que el modelo aprenda de forma más equilibrada los patrones asociados tanto a la rotación como a la no rotación, mejorando así su capacidad predictiva sobre la clase menos frecuente.
## **ENTRENAMIENTO DEL MODELO LOGÍSTICO BALANCEADO**
modelo_balanceado <- glm(
rotacion ~ horas_extra + viaje_de_negocios + estado_civil +
satisfacion_laboral + ingreso_mensual + antiguedad_cargo,
data = train_balanceado,
family = binomial(link = "logit")
)
summary(modelo_balanceado)
Call:
glm(formula = rotacion ~ horas_extra + viaje_de_negocios + estado_civil +
satisfacion_laboral + ingreso_mensual + antiguedad_cargo,
family = binomial(link = "logit"), data = train_balanceado)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) 1.510e+00 2.193e-01 6.887 5.68e-12 ***
horas_extraSi 1.412e+00 1.167e-01 12.098 < 2e-16 ***
viaje_de_negociosNo_Viaja -1.335e+00 2.395e-01 -5.574 2.50e-08 ***
viaje_de_negociosRaramente -7.186e-01 1.343e-01 -5.350 8.78e-08 ***
estado_civilDivorciado -5.179e-01 1.588e-01 -3.260 0.00111 **
estado_civilSoltero 7.624e-01 1.246e-01 6.119 9.41e-10 ***
satisfacion_laboral -3.038e-01 5.062e-02 -6.001 1.96e-09 ***
ingreso_mensual -1.032e-04 1.583e-05 -6.516 7.20e-11 ***
antiguedad_cargo -7.846e-02 1.865e-02 -4.207 2.59e-05 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 2385.7 on 1720 degrees of freedom
Residual deviance: 1906.2 on 1712 degrees of freedom
AIC: 1924.2
Number of Fisher Scoring iterations: 4
En esta sección se evalúa el desempeño del modelo ajustado con datos balanceados, con el objetivo de verificar si mejora la capacidad de predicción, especialmente para los empleados que sí rotan. Primero, el modelo calcula la probabilidad de rotación para cada empleado del conjunto de prueba. Luego, se utiliza un punto de corte de 0.50 para clasificar:
Con estas predicciones se construye una matriz de confusión, que permite comparar lo que el modelo predice frente a la realidad. Esto ayuda a identificar aciertos y errores del modelo. Además, se calculan métricas como la exactitud, la sensibilidad (qué tan bien detecta a quienes sí rotan) y la especificidad (qué tan bien identifica a quienes no rotan).
## **PREDICCIÓN Y MATRIZ DE CONFUSIÓN DEL MODELO BALANCEADO**
prob_test_balanceado <- predict(modelo_balanceado, newdata = test, type = "response")
pred_test_balanceado <- ifelse(prob_test_balanceado >= 0.50, "Si", "No")
pred_test_balanceado <- factor(pred_test_balanceado, levels = c("No", "Si"))
matriz_confusion_balanceado <- table(Real = test$rotacion, Predicho = pred_test_balanceado)
matriz_confusion_balanceado Predicho
Real No Si
No 254 112
Si 23 52
conf_balanceado <- confusionMatrix(pred_test_balanceado, test$rotacion, positive = "Si")
conf_balanceadoConfusion Matrix and Statistics
Reference
Prediction No Si
No 254 23
Si 112 52
Accuracy : 0.6939
95% CI : (0.6485, 0.7366)
No Information Rate : 0.8299
P-Value [Acc > NIR] : 1
Kappa : 0.2632
Mcnemar's Test P-Value : 3.624e-14
Sensitivity : 0.6933
Specificity : 0.6940
Pos Pred Value : 0.3171
Neg Pred Value : 0.9170
Prevalence : 0.1701
Detection Rate : 0.1179
Detection Prevalence : 0.3719
Balanced Accuracy : 0.6937
'Positive' Class : Si
En esta sección se analiza qué tan bien funciona el modelo balanceado para distinguir entre empleados que rotan y los que no.
Además, se calcula el AUC (Área Bajo la Curva), que resume el desempeño del modelo en un solo número:
## **CURVA ROC Y AUC DEL MODELO BALANCEADO**
roc_balanceado <- roc(test$rotacion, prob_test_balanceado, levels = c("No", "Si"), direction = "<")
auc_balanceado <- auc(roc_balanceado)
auc_balanceadoArea under the curve: 0.7611
plot(roc_balanceado, main = paste("Curva ROC modelo balanceado - AUC =", round(auc_balanceado, 4)))
abline(a = 0, b = 1, lty = 2, col = "gray")En esta sección se comparan los resultados del modelo base y el modelo balanceado para identificar cuál tiene mejor desempeño al predecir la rotación de empleados.
Se analizan varias métricas importantes:
Esta comparación permite entender si el modelo balanceado realmente mejora el desempeño, especialmente en la detección de empleados con mayor riesgo de rotación. En muchos casos, aunque la exactitud general no cambie mucho, el modelo balanceado puede ser mejor identificando los casos más importantes, lo que lo hace más útil para la toma de decisiones.
## **COMPARACIÓN ENTRE MODELO BASE Y MODELO BALANCEADO**
comparacion_modelos <- data.frame(
Modelo = c("Base", "Balanceado"),
AUC = c(as.numeric(auc_modelo), as.numeric(auc_balanceado)),
Accuracy = c(conf_base$overall["Accuracy"], conf_balanceado$overall["Accuracy"]),
Sensibilidad = c(conf_base$byClass["Sensitivity"], conf_balanceado$byClass["Sensitivity"]),
Especificidad = c(conf_base$byClass["Specificity"], conf_balanceado$byClass["Specificity"]),
Precision = c(conf_base$byClass["Pos Pred Value"], conf_balanceado$byClass["Pos Pred Value"])
)
comparacion_modelos %>%
kable(caption = "Comparación entre modelo base y modelo balanceado") %>%
kable_styling(full_width = FALSE)| Modelo | AUC | Accuracy | Sensibilidad | Especificidad | Precision |
|---|---|---|---|---|---|
| Base | 0.7515118 | 0.8458050 | 0.2133333 | 0.9754098 | 0.6400000 |
| Balanceado | 0.7610929 | 0.6938776 | 0.6933333 | 0.6939891 | 0.3170732 |
Cómo interpretar esta comparación - Si el modelo balanceado mejora la sensibilidad, puede ser más útil si lo importante es detectar empleados en riesgo. - Si el modelo base conserva mejor el AUC y además tiene buen equilibrio, se podría dejar ese. - En rotación, muchas veces interesa bastante la sensibilidad, porque la empresa quiere identificar empleados que sí podrían cambiar de cargo.
En esta sección se realiza una predicción de la probabilidad de rotación para un empleado con características específicas:
#Crear un empleado hipotético
nuevo_empleado <- data.frame(
horas_extra = factor("Si", levels = levels(df_limpio$horas_extra)),
viaje_de_negocios = factor("Frecuentemente", levels = levels(df_limpio$viaje_de_negocios)),
estado_civil = factor("Soltero", levels = levels(df_limpio$estado_civil)),
satisfacion_laboral = 2,
ingreso_mensual = 3000,
antiguedad_cargo = 1
)
prob_rotacion <- predict(modelo_train, newdata = nuevo_empleado, type = "response")
prob_rotacion 1
0.7921705
En esta etapa se aplica un umbral de decisión para interpretar la probabilidad de rotación calculada por el modelo:
#Definir un punto de corte
corte <- 0.30
decision <- ifelse(prob_rotacion >= corte,
"Empleado con alto riesgo de cambiar de cargo",
"No requiere intervención inmediata el empleado")
decision 1
"Empleado con alto riesgo de cambiar de cargo"
Del análisis realizado, se puede ver que la rotación de empleados en la empresa no es al azar, sino que depende de varias características de los trabajadores y de su trabajo. Por ejemplo, encontramos que:
En otras palabras, la rotación se relaciona principalmente con tres cosas:
El modelo que construimos para predecir la rotación tiene una capacidad aceptable para diferenciar quiénes tienen más riesgo de cambiar de puesto. Esto significa que puede servir como una herramienta para ayudar a la empresa a tomar decisiones sobre su personal. Con base en los resultados, se recomiendan acciones como:
En resumen, la empresa puede reducir la rotación si cuida el equilibrio del trabajo, mejora la satisfacción y el salario, y acompaña a quienes están empezando. Esto ayuda a mantener un ambiente laboral más estable y agradable para todos.