— Proyecto 02 · HR People Analytics

Análisis de Rotación
RetailCorp LATAM

Una empresa retail con 1,200 empleados en Panamá y Costa Rica presentaba una tasa de rotación del 24% anual, el doble del promedio de la industria. RR.HH. no tenía claridad sobre qué departamentos o factores causaban las salidas. Se construyó un análisis completo con SQL y Python para identificar patrones, calcular un score de riesgo por empleado y proponer acciones concretas de retención.

SQL · PostgreSQL Python · Pandas Seaborn · Matplotlib Análisis de Correlación Scoring de Riesgo
// El problema
01Tasa de rotación del 24% anual, el doble del promedio del sector retail en LATAM (12%).
02Sin datos sobre qué factores —salario, horas extra, satisfacción— predicen mejor las salidas.
03Costo estimado por reemplazo: $3,500 USD entre reclutamiento y capacitación.
04RR.HH. reaccionaba después de las renuncias, sin capacidad de anticipar el riesgo.
paso 01 · extracción con sql
1
Exploración y extracción del histórico de empleados
Se conectó a la base de datos PostgreSQL y se exploraron 4 tablas: empleados, salarios, desempeño y asistencia. Se construyó un JOIN entre todas para generar un dataset único con variables demográficas, laborales y de desempeño. Se etiquetó cada empleado con la variable binaria "salio" (1/0) para usarla como variable objetivo en el análisis.
2
Cálculo de tasas de rotación por segmento
Usando CTEs y funciones de ventana se calcularon las tasas de rotación por departamento y por rango de salario. Esto permitió identificar antes del análisis en Python que Ventas concentraba el mayor problema, con 38.2% de rotación frente al 8.9% de TI.
01_extraccion_empleados.sql
-- Dataset completo: activos + salientes
-- JOIN de 4 tablas con variable objetivo

SELECT
    e.id_empleado,
    e.edad,
    e.genero,
    e.departamento,
    e.cargo,
    DATEDIFF(
      COALESCE(e.fecha_salida,
        CURDATE()),
      e.fecha_ingreso
    ) / 365.0 AS anos_antiguedad,
    s.salario_mensual,
    s.ultimo_aumento_pct,
    d.nivel_satisfaccion,
    d.calificacion_desempeno,
    d.proyectos_completados,
    a.horas_extra_mes_prom,
    CASE
      WHEN e.fecha_salida
             IS NOT NULL
      THEN 1 ELSE 0
    END AS salio
FROM empleados e
LEFT JOIN salarios s
    ON e.id_empleado = s.id_empleado
LEFT JOIN desempeno d
    ON e.id_empleado = d.id_empleado
LEFT JOIN asistencia a
    ON e.id_empleado = a.id_empleado
WHERE
    e.fecha_ingreso >= '2021-01-01';
02_rotacion_por_dpto.sql
-- Tasa de rotación por departamento
-- CTE + RANK() para ordenar por riesgo

WITH totales AS (
  SELECT
    departamento,
    COUNT(*)   AS total,
    SUM(salio)  AS salidas
  FROM empleados_full
  GROUP BY departamento
),
rotacion AS (
  SELECT
    departamento,
    total,
    salidas,
    ROUND(
      salidas * 100.0
        / total, 2
    ) AS tasa_pct
  FROM totales
)
SELECT
  *,
  RANK() OVER(
    ORDER BY tasa_pct DESC
  ) AS ranking_riesgo
FROM rotacion
ORDER BY ranking_riesgo;
paso 02 · limpieza, eda y scoring con python
3
Limpieza y análisis exploratorio (EDA)
El CSV exportado tenía nulos en salario y horas extra, que se imputaron con la mediana del departamento respectivo. Se detectaron outliers en salario con el método IQR. Luego se calculó una matriz de correlaciones entre todas las variables numéricas y la variable objetivo "salio", revelando que salario (-0.42) y satisfacción (-0.38) son los factores con mayor poder predictivo.
4
Modelo de scoring de riesgo por empleado
Se construyó un score ponderado usando los 5 factores con mayor correlación con las salidas. Cada condición de riesgo suma puntos: salario bajo (3 pts), horas extra excesivas (2 pts), poca antigüedad (2 pts), baja satisfacción (2 pts) y bajo desempeño (1 pt). El score final clasifica a cada empleado como Bajo, Medio o Alto riesgo. El resultado se exportó como CSV para que RR.HH. pueda actuar directamente.
🐍 analisis_rotacion.py
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# ── 1. CARGA ───────────────────────────────────────────────────────
df = pd.read_csv('empleados_full.csv')

# Imputar nulos con mediana por departamento
df['salario_mensual'] = df.groupby('departamento')['salario_mensual'].\
    transform(lambda x: x.fillna(x.median()))
df['horas_extra_mes_prom'] = df['horas_extra_mes_prom'].fillna(0)

# Variable objetivo
df['salio'] = df['fecha_salida'].notna().astype(int)

# ── 2. CORRELACIONES ───────────────────────────────────────────────
vars_num = [
    'anos_antiguedad', 'salario_mensual',
    'nivel_satisfaccion', 'horas_extra_mes_prom',
    'calificacion_desempeno', 'proyectos_completados'
]
corr = df[vars_num + ['salio']].corr()['salio'].\
    drop('salio').sort_values()
print("Top factores de riesgo:\n", corr)

# ── 3. SCORE DE RIESGO ─────────────────────────────────────────────
p25 = df['salario_mensual'].quantile(0.25)

df['score_riesgo'] = (
    (df['salario_mensual']       < p25)  .astype(int) * 3 +
    (df['horas_extra_mes_prom']  > 60)  .astype(int) * 2 +
    (df['anos_antiguedad']       < 1)   .astype(int) * 2 +
    (df['nivel_satisfaccion']    < 3)   .astype(int) * 2 +
    (df['calificacion_desempeno'] < 3)   .astype(int) * 1
)
df['nivel_riesgo'] = pd.cut(
    df['score_riesgo'],
    bins=[-1, 2, 5, 10],
    labels=['Bajo', 'Medio', 'Alto']
)

# ── 4. EXPORTAR ────────────────────────────────────────────────────
df_riesgo = df[df['salio'] == 0].sort_values(
    'score_riesgo', ascending=False
)
df_riesgo.to_csv('empleados_riesgo.csv', index=False)

alto = (df['nivel_riesgo'] == 'Alto').sum()
print(f"Empleados riesgo alto : {alto}")
print(f"Costo potencial       : ${alto * 3500:,}")
paso 03 · resultados del análisis
5
Hallazgos y entregable final para RR.HH.
El análisis reveló que el 78% de las salidas ocurrió en empleados con menos de 2 años de antigüedad y salario por debajo del promedio del departamento. Se identificaron 89 empleados activos en riesgo alto, lo que representa un costo potencial de $311,500 si no se toman acciones. El CSV de riesgo fue entregado a RR.HH. para intervención directa en las próximas 4 semanas.
Tasa de Rotación
24.1%
↑ 2x el promedio del sector
Empleados Riesgo Alto
89
Costo potencial: $311K
Dpto. Mayor Rotación
Ventas
38.2% — 41% de salidas
Factor #1 de Riesgo
Salario
Correlación: −0.42
Rotación por Departamento (%)
SQL GROUP BY departamento → tasa calculada con CTE
Distribución de Riesgo de Rotación
Python · pd.cut() sobre score_riesgo ponderado
Salidas por Antigüedad
78% salió antes de cumplir 2 años
Correlaciones con Rotación
df.corr()['salio'].sort_values()
Motivos de Salida
Análisis del campo motivo_salida
tabla de riesgo — empleados activos priorizados
⚠️ empleados_riesgo.csv — entregado a RR.HH. para intervención
IDDepartamentoCargoAntigüedadSalarioHoras ExtraSatisfacciónScoreRiesgo
EMP-0412 VentasAsesor Comercial 0.8 años $620 72 hrs 2 / 5 9 Alto
EMP-0287 VentasSupervisor Zona 1.1 años $710 58 hrs 2 / 5 7 Alto
EMP-0534 LogísticaCoordinador 1.4 años $850 68 hrs 3 / 5 6 Alto
EMP-0619 At. al ClienteAgente Senior 2.1 años $780 55 hrs 3 / 5 4 Medio
EMP-0103 TIAnalista de Datos 3.5 años $1,200 38 hrs 4 / 5 1 Bajo
conclusiones e impacto
💰
El salario es el factor #1
Correlación de −0.42 entre salario y rotación. Empleados en el cuartil inferior tienen 3.2x más probabilidad de renunciar en el primer año. Un ajuste salarial focalizado en los 89 de riesgo alto costaría ~$42K vs $311K de reemplazarlos.
Las horas extra como señal de alerta
Empleados con más de 60 horas extra durante 3 meses consecutivos tienen 61% de tasa de salida en los siguientes 90 días. Se propuso configurar una alerta automática en el sistema de RR.HH. cuando se supere ese umbral.
🎯
ROI de retención: 7.4x
Intervenir a los 89 empleados de riesgo alto con ajustes salariales y redistribución de carga cuesta ~$42,000. El costo si todos salen es $311,500. El análisis de datos convirtió un problema de RR.HH. en una decisión financiera clara.