1 La pregunta que guía todo el caso

¿El rendimiento realmente ayuda a decidir cuánto aumentarle el salario a alguien, o solo nos permite adivinar una regla que la empresa ya utiliza?

Imaginemos dos empleados. Uno lleva más tiempo en la empresa y suele hacer horas extra. El otro acaba de recibir una calificación de rendimiento más alta. Si tuviéramos que estimar qué aumento recibió cada uno, ¿qué dato nos ayudaría más? Y si el modelo acertara, ¿sería una buena idea dejar que decidiera futuros aumentos?

Esa diferencia entre predecir lo que pasó y decidir lo que debería pasar es el centro del trabajo. No buscamos mostrar cinco ecuaciones desconectadas: queremos averiguar qué regla parece describir la información, poner a prueba si agregar variables realmente ayuda y decidir con argumentos si el resultado merece confianza.

1.1 La hipótesis y la forma de comprobarla

Nuestra sospecha inicial es que la evaluación de rendimiento separa mucho más los aumentos que la antigüedad o las horas extra. Pero también sospechamos que esa relación puede estar incorporada en la propia manera en que se asignan o registran los aumentos. Si existe una separación casi exacta por calificaciones, un R² alto no necesariamente implica un descubrimiento útil.

Para no quedarnos con una impresión, seguiremos cuatro preguntas concretas:

  1. ¿Qué muestra la base? Revisaremos datos, rangos de aumentos y relación con tres variables.
  2. ¿Qué tan bien acierta? Compararemos tres modelos simples con la mediana como referencia.
  3. ¿Más información mejora algo? Probamos dos modelos múltiples, Stepwise, supuestos y transformación logarítmica, reservando datos de prueba.
  4. ¿Qué decisión sería responsable? Usaremos dos perfiles hipotéticos, el tamaño del error y un contraste descriptivo sobre rotación para valorar utilidad y límites.

1.2 Continuidad con el avance ya publicado

El Punto 1 se entregó el 6 de noviembre con los 1.470 empleados y tres modelos simples. Allí encontramos una correlación de 0,7735 entre rendimiento y aumento; el modelo correspondiente alcanzó R² = 59,84 % y MAE de validación cruzada = 1,8843 puntos porcentuales. La antigüedad y las horas extra prácticamente no predijeron el incremento. Esos son los resultados del avance, no cifras nuevas.

En el informe final repetimos el análisis con una partición adicional de entrenamiento y prueba, reservando empleados para una comprobación independiente. Por eso algunos coeficientes y R² de entrenamiento pueden diferir ligeramente de los publicados en el avance. No se cambió la variable dependiente: siempre es el porcentaje del último aumento salarial. La rotación solo se utilizará al final como contraste de contexto, no como otra regresión con una nueva respuesta.

Nuestro criterio para decidir: un modelo que reproduce una regla existente puede servir para auditar consistencia, pero no es automáticamente apropiado para asignar aumentos. Para recomendar su uso exigiremos precisión fuera de muestra, supuestos discutidos y una explicación de qué información falta.

2 Punto 1. Primero: ¿qué factores parecen influir en el aumento?

2.1 1. Las tres explicaciones que vamos a poner a prueba

Comparamos tres ideas que una empresa podría utilizar al reconocer a sus trabajadores: el mérito evaluado, el tiempo que llevan allí y el trabajo adicional que realizan. Elegimos estas variables porque cada una representa una lógica empresarial distinta, no porque sepamos de antemano cuál dará un resultado significativo.

Papel Variable Qué representa Qué esperamos contrastar
Lo que queremos explicar (Y) Porcentaje_aumento_salarial Porcentaje del último aumento Cuánto cambia de un empleado a otro
Primera explicación (X1) Rendimiento_Laboral Calificación observada, 3 o 4 Si una mejor evaluación está asociada con un mayor aumento
Segunda explicación (X2) Antiguedad Años en la empresa Si quedarse más tiempo se relaciona con mejores aumentos
Tercera explicación (X3) Horas_Extra Si reporta horas extra: No o Sí Si ese esfuerzo adicional se refleja en el porcentaje recibido

Nuestra hipótesis principal es que el rendimiento tendrá la relación más clara con el aumento. Para cada regresión pondremos a prueba si el coeficiente de la variable es distinto de cero, pero una relación estadística no será tomada automáticamente como una causa.

Criterio para no engañarnos con los resultados: no nos bastará con mirar una correlación o un R². Comprobaremos supuestos, mediremos el error de predicción y preguntaremos si esa precisión sería útil para una decisión real.

2.2 2. Importación reproducible y calidad de los datos

Para que el archivo no falle por rutas de Windows, el Excel, el CSV de particiones y el R Markdown se guardan en la misma carpeta del proyecto .Rproj. Las particiones están fijadas de antemano y permiten reproducir la comparación.

if(!file.exists("base3_ingreso.xlsx")) stop("No se encontró base3_ingreso.xlsx en la carpeta del proyecto")
if(!file.exists("Particiones_Caso2.csv")) stop("No se encontró Particiones_Caso2.csv en la carpeta del proyecto")
original <- readxl::read_excel("base3_ingreso.xlsx",sheet="Base")
particiones <- read.csv("Particiones_Caso2.csv",stringsAsFactors=FALSE)
datos <- data.frame(
  ID=seq_len(nrow(original)),
  Aumento=as.numeric(original[["Porcentaje_aumento_salarial"]]),
  Rendimiento=as.numeric(original[["Rendimiento_Laboral"]]),
  Antiguedad=as.numeric(original[["Antiguedad"]]),
  Horas_Extra=factor(trimws(as.character(original[["Horas_Extra"]])),levels=c("No","Si")),
  Promocion=as.numeric(original[["Años_ultima_promoción"]]),
  Capacitaciones=as.numeric(original[["Capacitaciones"]]),
  Genero=factor(as.character(original[["Genero"]]),levels=c("F","M")),
  Departamento=factor(as.character(original[["Departamento"]]),levels=c("IyD","RH","Ventas")),
  Ingreso_Mensual=as.numeric(original[["Ingreso_Mensual"]]),
  Rotacion=factor(as.character(original[["Rotación"]]),levels=c("No","Si")),
  Experiencia=as.numeric(original[["Años_Experiencia"]]))
datos$Rendimiento4 <- as.integer(datos$Rendimiento==4)
stopifnot(nrow(datos)==nrow(particiones),all(particiones$ID==datos$ID),
          all(particiones$Rendimiento==datos$Rendimiento),
          all(complete.cases(datos)),all(datos$Ingreso_Mensual>0),
          all(datos$Rendimiento %in% c(3,4)))
datos$Grupo <- particiones$Grupo
datos$Fold <- particiones$Fold
entrenamiento <- datos[datos$Grupo=="training",,drop=FALSE]
prueba <- datos[datos$Grupo=="test",,drop=FALSE]
calidad <- data.frame(
 Comprobacion=c("Registros originales","Variables originales","Faltantes originales",
 "Duplicados exactos","Antigüedad mayor que experiencia", "Registros entrenamiento",
 "Registros prueba", "Calificaciones presentes"),
 Resultado=c(nrow(original),ncol(original),sum(is.na(original)),sum(duplicated(original)),
 sum(datos$Antiguedad>datos$Experiencia),nrow(entrenamiento),nrow(prueba),
 paste(sort(unique(datos$Rendimiento)),collapse=" y ")))
knitr::kable(calidad,caption="Auditoría de calidad y división reproducible 80/20")
Auditoría de calidad y división reproducible 80/20
Comprobacion Resultado
Registros originales 1470
Variables originales 24
Faltantes originales 0
Duplicados exactos 0
Antigüedad mayor que experiencia 0
Registros entrenamiento 1176
Registros prueba 294
Calificaciones presentes 3 y 4

Hay 1470 observaciones y 24 variables de origen. Detectamos 0 faltantes, 0 duplicados exactos y 0 casos con antigüedad superior a la experiencia total. No eliminamos registros sin una razón empírica. El tamaño de la muestra por sí solo no demuestra representatividad de otras empresas.

Para evaluar predicciones sin usar el mismo registro tanto para ajustar como para validar, reservamos 294 empleados como prueba y 1176 como entrenamiento; dentro de entrenamiento comparamos modelos con cinco particiones estratificadas por rendimiento. Las particiones se hicieron por registro, no por tiempo, porque la base no contiene una secuencia temporal de aumentos. No interpretamos esto como una validación prospectiva.

reglas <- rbind(Aumento=regla_iqr(datos$Aumento), Antiguedad=regla_iqr(datos$Antiguedad),
               Ingreso_mensual=regla_iqr(datos$Ingreso_Mensual))
knitr::kable(round(reglas,2),caption="Regla de 1,5 veces el rango intercuartílico")
Regla de 1,5 veces el rango intercuartílico
limite_inf.25% limite_sup.75% n_fuera
Aumento 3 27 0
Antiguedad -6 18 104
Ingreso_mensual -5291000 16581000 114

Los atípicos potenciales en ingresos o antigüedad se conservan: pueden representar puestos directivos o trayectorias legítimas y no son errores automáticos. Tampoco usamos la regla del rango intercuartílico para identificar supuestos «atípicos» en variables binarias, como rendimiento 3/4 u horas extra.

2.3 3. Descripción: de qué tipo de empleados hablamos

resumen_num <- function(x)c(N=length(x),Minimo=min(x),Q1=unname(quantile(x,.25)),
  Mediana=median(x),Media=mean(x),Q3=unname(quantile(x,.75)),
  Maximo=max(x),DE=sd(x))
knitr::kable(round(t(sapply(datos[c("Aumento","Antiguedad","Promocion","Capacitaciones","Ingreso_Mensual")],resumen_num)),2),
             caption="Medidas descriptivas de variables de interés")
Medidas descriptivas de variables de interés
N Minimo Q1 Mediana Media Q3 Maximo DE
Aumento 1470 11 12 14 15.21 18 25 3.66
Antiguedad 1470 0 3 5 7.01 9 40 6.13
Promocion 1470 0 0 1 2.19 3 15 3.22
Capacitaciones 1470 0 2 3 2.80 3 6 1.29
Ingreso_Mensual 1470 1009000 2911000 4919000 6502931.29 8379000 19999000 4707956.78
knitr::kable(as.data.frame(table(Rendimiento=datos$Rendimiento)),caption="Calificaciones observadas")
Calificaciones observadas
Rendimiento Freq
3 1244
4 226
knitr::kable(as.data.frame(table(Horas_Extra=datos$Horas_Extra)),caption="Horas extra registradas")
Horas extra registradas
Horas_Extra Freq
No 1054
Si 416

El aumento medio fue 15,21 % y su mediana 14,00 %. Hay 1244 empleados con rendimiento 3 y 226 con rendimiento 4. Aunque el diccionario del archivo tiene una escala mayor, en esta muestra solo se observan 3 y 4, por lo que los resultados no deben extrapolarse a calificaciones no presentes.

barplot(table(datos$Aumento),col="#187C87",border=NA,
        main="¿Cuáles aumentos aparecen en la base?",
        xlab="Porcentaje de aumento",ylab="Número de empleados",las=1)
La distribución de incrementos no es continua: solo aparecen porcentajes enteros entre 11 % y 25 %.

La distribución de incrementos no es continua: solo aparecen porcentajes enteros entre 11 % y 25 %.

La respuesta está cuantizada en 15 valores enteros. Esta característica importa para el diagnóstico: un modelo lineal puede utilizarse como aproximación del promedio, pero no necesariamente describirá residuos normales o una distribución continua de aumentos.

2.4 4. Correlaciones y tres visualizaciones

x_horas <- as.integer(datos$Horas_Extra=="Si")
correlaciones <- data.frame(
  Relacion=c("Aumento y rendimiento 4 (0/1)","Aumento y antigüedad", "Aumento y horas extra (0/1)"),
  Pearson=c(cor(datos$Aumento,datos$Rendimiento4),cor(datos$Aumento,datos$Antiguedad),
            cor(datos$Aumento,x_horas)))
knitr::kable(transform(correlaciones,Pearson=round(Pearson,4)),caption="Correlación exploratoria, 1.470 empleados")
Correlación exploratoria, 1.470 empleados
Relacion Pearson
Aumento y rendimiento 4 (0/1) 0.7735
Aumento y antigüedad -0.0360
Aumento y horas extra (0/1) -0.0054

La relación con la calificación es alta y positiva (0,774), mientras que antigüedad (-0,036) y horas extra (-0,005) se acercan a cero. Las correlaciones con variables binarias son equivalentes a correlaciones punto-biseriales; no deben interpretarse como cambios causales.

par(mfrow=c(1,3),mar=c(4.2,4.1,3.2,1.1),cex=.86)
boxplot(Aumento~factor(Rendimiento),data=datos,col=c("#B6D7D9","#187C87"),
        xlab="Rendimiento",ylab="Aumento (%)",main="Rendimiento")
plot(datos$Antiguedad,datos$Aumento,pch=16,cex=.42,
     col=adjustcolor("#187C87",alpha.f=.2),
     xlab="Años",ylab="Aumento (%)",main="Antigüedad")
abline(lm(Aumento~Antiguedad,data=datos),col="#C47B37",lwd=2)
boxplot(Aumento~Horas_Extra,data=datos,col=c("#B6D7D9","#187C87"),
        xlab="Horas extra",ylab="Aumento (%)",main="Esfuerzo reportado")
Tres relaciones con el aumento: comparación por calificación, dispersión con antigüedad y comparación por horas extra.

Tres relaciones con el aumento: comparación por calificación, dispersión con antigüedad y comparación por horas extra.

par(mfrow=c(1,1))
bandas <- aggregate(datos$Aumento,list(Rendimiento=datos$Rendimiento),
                    function(z)c(N=length(z),Media=mean(z),Min=min(z),Max=max(z)))
bandas_tab <- data.frame(Rendimiento=bandas$Rendimiento,N=bandas$x[,"N"],
                         Media=round(bandas$x[,"Media"],2),Min=bandas$x[,"Min"],Max=bandas$x[,"Max"])
knitr::kable(bandas_tab,caption="Rangos de aumento por evaluación")
Rangos de aumento por evaluación
Rendimiento N Media Min Max
3 1244 14.00 11 19
4 226 21.85 20 25

Hallazgo que exige cautela: quienes tienen calificación 3 reciben aumentos de 11 a 19 %; quienes tienen 4, de 20 a 25 %. No existe una sola observación superpuesta. Esto es compatible con una regla de asignación por bandas o con una variable de rendimiento construida a partir del aumento. No conocemos la secuencia ni las reglas de medición; por tanto, sería incorrecto afirmar que el rendimiento causa el aumento o que el modelo descubre un mecanismo nuevo. Esa posible circularidad debe ser auditada antes de utilizarlo en decisiones reales.

2.5 5. Los tres modelos simples

Usamos una dummy Rendimiento4 = 1 si la calificación es 4 y 0 si es 3. Así el intercepto tiene una interpretación real: es el aumento promedio para los calificados con 3. En el modelo de horas extra, No es la categoría de referencia.

m1 <- lm(Aumento~Rendimiento4,data=entrenamiento)
m2 <- lm(Aumento~Antiguedad,data=entrenamiento)
m3 <- lm(Aumento~Horas_Extra,data=entrenamiento)
modelos_simples <- rbind(resumenmod(m1,"M1. Rendimiento"),
                        resumenmod(m2,"M2. Antigüedad"),
                        resumenmod(m3,"M3. Horas extra"))
knitr::kable(transform(modelos_simples,R2=round(R2,4),R2_ajustado=round(R2_ajustado,4),
                      AIC=round(AIC,1),MAE_ajuste=round(MAE_ajuste,3)),
             caption="Modelos simples ajustados SOLO en entrenamiento")
Modelos simples ajustados SOLO en entrenamiento
Modelo R2 R2_ajustado AIC N MAE_ajuste
M1. Rendimiento 0.6030 0.6026 5309.9 1176 1.883
M2. Antigüedad 0.0016 0.0007 6394.4 1176 3.043
M3. Horas extra 0.0002 -0.0006 6396.0 1176 3.049
knitr::kable(coeficientes(m1),caption="M1: coeficientes y significancia robusta HC3")
M1: coeficientes y significancia robusta HC3
Termino Coeficiente Error_HC3 P_HC3
(Intercept) 13.982 0.0765 < 0,0001
Rendimiento4 7.880 0.1429 < 0,0001
knitr::kable(coeficientes(m2),caption="M2: coeficientes y significancia robusta HC3")
M2: coeficientes y significancia robusta HC3
Termino Coeficiente Error_HC3 P_HC3
(Intercept) 15.3586 0.1578 < 0,0001
Antiguedad -0.0233 0.0178 0,1920
knitr::kable(coeficientes(m3),caption="M3: coeficientes y significancia robusta HC3")
M3: coeficientes y significancia robusta HC3
Termino Coeficiente Error_HC3 P_HC3
(Intercept) 15.2311 0.1249 < 0,0001
Horas_ExtraSi -0.1256 0.2410 0,6024

M1. Mérito evaluado. El intercepto 13,98 pp es el aumento medio estimado cuando rendimiento = 3. Pasar al grupo 4 se asocia con 7,88 puntos porcentuales adicionales, con p robusto < 0,0001. El R² de 60,30 % resume ajuste dentro de entrenamiento; no demuestra por sí mismo capacidad predictiva fuera de muestra ni causalidad.

M2. Permanencia. Por cada año adicional en la empresa, la pendiente estimada es -0,0233 pp, con p robusto 0,1920 y R² 0,157 %. El signo negativo observado es muy pequeño y no debe presentarse como una penalización comprobada por antigüedad.

M3. Horas extra. Frente a No, el grupo Si presenta una diferencia estimada de -0,1256 pp (p robusto 0,6024; R² 0,024 %). Este indicador binario no mide cuántas horas se trabajaron ni la productividad lograda: la ausencia de asociación salarial no equivale a ausencia de esfuerzo.

2.6 6. Supuestos y primer intento de transformación

Analizamos residuos, normalidad y homocedasticidad. Una p menor que 0,05 en Shapiro-Wilk o Breusch-Pagan aporta evidencia contra la normalidad o la constancia de varianza, respectivamente. La independencia entre registros no puede verificarse plenamente a partir del orden arbitrario de una tabla transversal; no aplicamos un Durbin-Watson temporal como si fueran meses sucesivos.

diagfun <- function(m,nombre) data.frame(Modelo=nombre,
 Shapiro_p=pval(shapiro.test(resid(m))$p.value),Breusch_Pagan_p=pval(bp(m)),
 Cook_superior_4n=sum(cooks.distance(m)>4/nobs(m)))
diag_simple <- rbind(diagfun(m1,"M1"),diagfun(m2,"M2"),diagfun(m3,"M3"))
knitr::kable(diag_simple,caption="Diagnóstico de supuestos, modelos simples")
Diagnóstico de supuestos, modelos simples
Modelo Shapiro_p Breusch_Pagan_p Cook_superior_4n
M1 < 0,0001 < 0,0001 16
M2 < 0,0001 0,0283 59
M3 < 0,0001 0,2260 63
par(mfrow=c(1,2),mar=c(4.1,4.1,3.1,1))
plot(jitter(fitted(m1),amount=.11),resid(m1),pch=16,cex=.5,
  col=adjustcolor("#187C87",alpha.f=.25),xlab="Aumento ajustado (%)",
  ylab="Residuo (pp)",main="Residuos frente a ajustes")
abline(h=0,col="#C47B37",lwd=2)
qqnorm(resid(m1),pch=16,cex=.5,col=adjustcolor("#187C87",alpha.f=.3),main="Q-Q")
qqline(resid(m1),col="#C47B37",lwd=2)
M1: residuos y normalidad aproximada

M1: residuos y normalidad aproximada

par(mfrow=c(1,1))

Para M1, Shapiro-Wilk tiene p < 0,0001 y Breusch-Pagan p < 0,0001. Los supuestos clásicos no se satisfacen plenamente: no escondemos este resultado por un R² atractivo. Usamos errores estándar HC3 como comprobación de inferencia frente a heterocedasticidad y, para la utilidad práctica, damos prioridad al MAE fuera de muestra. No eliminamos puntos para fabricar normalidad.

3 Punto 2. ¿Y si el aumento dependiera de varias cosas a la vez?

Primera respuesta: en el avance, el rendimiento fue claramente la variable más informativa. Además, las calificaciones 3 y 4 tuvieron rangos de aumento que no se cruzan. Ese resultado nos llevó a una duda más importante que el R²: ¿hemos encontrado un predictor independiente, o una manera de reconstruir un criterio administrativo conocido? El Punto 2 comprueba si otras características aportan algo distinto.

Los modelos simples nos permiten comparar una explicación a la vez. Pero los trabajadores no se definen por una sola característica: pueden tener distintos años de antigüedad, formación, departamentos o trayectorias. Ahora veremos si combinar información mejora de verdad las predicciones o si solo hace más complicada una respuesta que ya era bastante clara.

3.1 7. Dos modelos que responden a preguntas distintas

M4 — Modelo de gestión: ¿la antigüedad, las horas extra, el tiempo desde la última promoción y las capacitaciones agregan información al rendimiento? M5 — Modelo de auditoría: ¿persiste el patrón al considerar ingreso mensual y diferencias de grupo (género y departamento)? El género se utiliza para auditar asociaciones, jamás para recomendar un porcentaje distinto de aumento por sexo. El cargo se deja fuera para evitar introducir una combinación grande de categorías estrechamente ligada al departamento y al nivel salarial.

m4 <- lm(Aumento~Rendimiento4+Antiguedad+Horas_Extra+Promocion+Capacitaciones,
         data=entrenamiento)
m5 <- lm(Aumento~Rendimiento4+Antiguedad+Horas_Extra+Promocion+Capacitaciones+
           Genero+Departamento+log(Ingreso_Mensual),data=entrenamiento)
comparacion_ajuste <- rbind(resumenmod(m1,"Simple mérito"),
                            resumenmod(m4,"M4. Gestión"),
                            resumenmod(m5,"M5. Auditoría"))
knitr::kable(transform(comparacion_ajuste,R2=round(R2,4),R2_ajustado=round(R2_ajustado,4),
                      AIC=round(AIC,1),MAE_ajuste=round(MAE_ajuste,3)),
             caption="Ajuste: más variables no garantizan mejor predicción")
Ajuste: más variables no garantizan mejor predicción
Modelo R2 R2_ajustado AIC N MAE_ajuste
Simple mérito 0.6030 0.6026 5309.9 1176 1.883
M4. Gestión 0.6057 0.6040 5309.8 1176 1.888
M5. Auditoría 0.6065 0.6034 5315.6 1176 1.887
knitr::kable(coeficientes(m4),caption="M4: coeficientes y p robustas HC3")
M4: coeficientes y p robustas HC3
Termino Coeficiente Error_HC3 P_HC3
(Intercept) 14.1680 0.1906 < 0,0001
Rendimiento4 7.8881 0.1464 < 0,0001
Antiguedad -0.0192 0.0138 0,1626
Horas_ExtraSi -0.1507 0.1508 0,3179
Promocion -0.0241 0.0263 0,3613
Capacitaciones 0.0158 0.0532 0,7665
knitr::kable(coeficientes(m5),caption="M5: coeficientes y p robustas HC3")
M5: coeficientes y p robustas HC3
Termino Coeficiente Error_HC3 P_HC3
(Intercept) 12.1429 1.7881 < 0,0001
Rendimiento4 7.8867 0.1466 < 0,0001
Antiguedad -0.0253 0.0150 0,0916
Horas_ExtraSi -0.1479 0.1510 0,3276
Promocion -0.0256 0.0265 0,3345
Capacitaciones 0.0194 0.0533 0,7152
GeneroM 0.1029 0.1391 0,4597
DepartamentoRH -0.2476 0.3648 0,4974
DepartamentoVentas -0.0542 0.1535 0,7241
log(Ingreso_Mensual) 0.1308 0.1176 0,2663

M4. Manteniendo constantes los demás predictores, rendimiento 4 se asocia con 7,89 pp adicionales (p robusto < 0,0001). La antigüedad equivale a -0,019 pp por año; horas extra Si frente a No, a -0,151 pp; cada año desde última promoción, a -0,024 pp; cada capacitación adicional, a 0,016 pp, condicionales a las demás variables. Coeficientes muy pequeños o no significativos no equivalen a pruebas de que estas dimensiones carezcan de importancia humana o laboral.

M5. La categoría de referencia de Horas_Extra es No, de Genero es F y de Departamento es IyD. La comparación M frente a F es una diferencia ajustada de 0,103 pp (p robusto 0,4597). Para Ventas versus IyD, la diferencia ajustada es -0,054 pp; para RH versus IyD, -0,248 pp. No encontrar diferencias estadísticamente claras no demuestra ausencia de desigualdad salarial: la respuesta aquí es un porcentaje de aumento, no el salario total, y faltan responsabilidades, criterios de evaluación y fechas.

El coeficiente de log(Ingreso_Mensual) en M5 es 0,1308: por una variación pequeña del 1 % en ingreso, el cambio promedio aproximado en aumento sería 0,0013 puntos porcentuales, manteniendo lo demás constante. No significa un aumento del 1 % en el salario por efecto causal del ingreso. Aunque el modelo puede incluir esta variable para auditoría, el ingreso registrado y el aumento pueden estar relacionados temporalmente de maneras que la base no documenta.

El R² ajustado de M5 (60,34 %) debe compararse con el de M4 (60,40 %) y M1 (60,26 %). Si el ajuste mejora muy poco, la complejidad adicional difícilmente se justifica por capacidad predictiva; esa decisión se contrasta después con MAE de prueba.

3.2 8. Validez de M4 y M5: supuestos, colinealidad e influencia

knitr::kable(rbind(diagfun(m4,"M4. Gestión"),diagfun(m5,"M5. Auditoría")),
             caption="Normalidad, homocedasticidad y observaciones potencialmente influyentes")
Normalidad, homocedasticidad y observaciones potencialmente influyentes
Modelo Shapiro_p Breusch_Pagan_p Cook_superior_4n
M4. Gestión < 0,0001 < 0,0001 37
M5. Auditoría < 0,0001 < 0,0001 52
vif_manual <- function(m) {
 X <- model.matrix(m)[,-1,drop=FALSE]
 resultado <- vapply(seq_len(ncol(X)), function(j) {
   otras <- X[, -j, drop=FALSE]
   r2 <- summary(lm(X[, j] ~ otras))$r.squared
   if (is.na(r2) || r2 >= 1) Inf else 1/(1-r2)
 }, numeric(1))
 names(resultado) <- colnames(X)
 resultado
}
vif5 <- vif_manual(m5)
stopifnot(length(vif5) == ncol(model.matrix(m5)) - 1L)
tabla_vif <- data.frame(Columna=colnames(model.matrix(m5))[-1],
                        VIF=round(unname(as.numeric(vif5)),2))
knitr::kable(tabla_vif,
             caption="VIF por columna de la matriz de diseño (en factores, interpretar conjuntamente)")
VIF por columna de la matriz de diseño (en factores, interpretar conjuntamente)
Columna VIF
Rendimiento4 1.00
Antiguedad 1.97
Horas_ExtraSi 1.01
Promocion 1.67
Capacitaciones 1.01
GeneroM 1.01
DepartamentoRH 1.03
DepartamentoVentas 1.04
log(Ingreso_Mensual) 1.37

En M5, Shapiro-Wilk arroja p < 0,0001 y Breusch-Pagan p < 0,0001. El diagnóstico apunta a limitaciones de normalidad y varianza constante, por lo que no basta con leer las p clásicas. Reportamos p robustas HC3 y evaluamos errores en registros separados. Las distancias de Cook son alertas para revisar, no una orden para borrar empleados. La independencia y el mecanismo de muestreo siguen siendo supuestos no plenamente verificables aquí.

par(mfrow=c(1,2),mar=c(4.1,4.1,3,1))
for(mm in list(m4,m5)) {
  plot(fitted(mm),resid(mm),pch=16,cex=.45,col=adjustcolor("#187C87",alpha.f=.24),
       xlab="Predicción (%)",ylab="Residuo (pp)",
       main=if(identical(mm,m4)) "M4: gestión" else "M5: auditoría")
  abline(h=0,col="#C47B37",lwd=2)
}
M4 y M5: relación entre valores ajustados y residuos

M4 y M5: relación entre valores ajustados y residuos

par(mfrow=c(1,1))

3.2.1 Comprobación explícita de linealidad

El supuesto de linealidad es especialmente relevante para antigüedad y años desde la última promoción. Como prueba complementaria, agregamos términos cuadráticos de estas dos variables y contrastamos el modelo ampliado con M5 mediante un F parcial. Esto no reemplaza inspeccionar los residuos ni prueba linealidad perfecta; solo busca evidencia de curvatura que el modelo lineal no esté captando.

m5_curva <- update(m5, . ~ . + I(Antiguedad^2) + I(Promocion^2))
prueba_curva <- anova(m5,m5_curva)
print(knitr::kable(prueba_curva,digits=4,
             caption="Prueba de curvatura: M5 vs. M5 con dos términos cuadráticos"))
Prueba de curvatura: M5 vs. M5 con dos términos cuadráticos
Res.Df RSS Df Sum of Sq F Pr(>F)
1166 6206.4 NA NA NA NA
1164 6196.4 2 9.9887 0.9382 0.3916
p_curva <- as.numeric(prueba_curva[["Pr(>F)"]][2])
interpretacion_curva <- if (is.finite(p_curva) && p_curva < 0.05) {
  "Se observa evidencia de curvatura en al menos uno de estos predictores; conviene revisar una especificación flexible."
} else {
  "No encontramos evidencia suficiente de que esas dos curvas mejoren conjuntamente el modelo; esto no valida todas las posibles relaciones lineales."
}
cat("\n\n**Interpretación del F parcial:** p = ", pval(p_curva),
    ". ", interpretacion_curva, " El objetivo sigue siendo comparar predicciones sobre empleados no utilizados para ajustar los parámetros.\n\n", sep="")

Interpretación del F parcial: p = 0,3916. No encontramos evidencia suficiente de que esas dos curvas mejoren conjuntamente el modelo; esto no valida todas las posibles relaciones lineales. El objetivo sigue siendo comparar predicciones sobre empleados no utilizados para ajustar los parámetros.

qqnorm(resid(m5),pch=16,cex=.55,col=adjustcolor("#187C87",alpha.f=.25),
       main="M5: normalidad aproximada de los residuos")
qqline(resid(m5),col="#C47B37",lwd=2)
Q-Q de residuos del modelo múltiple integral: inspección visual complementaria a Shapiro-Wilk.

Q-Q de residuos del modelo múltiple integral: inspección visual complementaria a Shapiro-Wilk.

3.3 9. ¿La transformación logarítmica ayuda realmente?

La respuesta es positiva (11 a 25 %), así que podemos estimar log(Aumento). Sin embargo, transformar no es obligatorio. Para comparar precisión, retransformamos mediante la corrección de smearing de Duan: multiplicamos exp(predicción) por el promedio de exp(residuo) del entrenamiento. El MAE se mide en puntos porcentuales originales, no en unidades logarítmicas. No comparamos R² ni AIC de escalas de Y distintas como si fueran equivalentes.

mlog <- lm(log(Aumento)~Rendimiento4+Antiguedad+Horas_Extra+Promocion+Capacitaciones,
           data=entrenamiento)
knitr::kable(data.frame(Especificacion=c("M4: porcentaje original","Log(Aumento): gestión"),
  R2_escala_propia=round(c(summary(m4)$r.squared,summary(mlog)$r.squared),4),
  Shapiro_p=c(pval(shapiro.test(resid(m4))$p.value),pval(shapiro.test(resid(mlog))$p.value)),
  BP_p=c(pval(bp(m4)),pval(bp(mlog)))),
  caption="Diagnósticos en sus respectivas escalas; R² NO es directamente comparable")
Diagnósticos en sus respectivas escalas; R² NO es directamente comparable
Especificacion R2_escala_propia Shapiro_p BP_p
M4: porcentaje original 0.6057 < 0,0001 < 0,0001
Log(Aumento): gestión 0.5312 < 0,0001 < 0,0001

El logaritmo no garantiza normalidad ni homocedasticidad. La evaluación decisiva será la comparación del MAE retransformado frente al del modelo original. Si la transformación no mejora error y diagnóstico conjuntamente, se recomienda mantener porcentajes originales por interpretabilidad.

3.4 10. Selección Stepwise mediante AIC

La selección se ejecuta solo sobre entrenamiento, con step() de R y criterio AIC, partiendo del modelo integral M5. El rendimiento se exige como variable mínima por ser la hipótesis central. Es una selección predictiva, no una prueba causal ni una búsqueda de p inferiores a 0,05.

modelo_step <- step(m5,
                   scope=list(lower=~Rendimiento4,upper=formula(m5)),
                   direction="both",trace=0)
knitr::kable(data.frame(Termino=attr(terms(modelo_step),"term.labels")),
             caption="Términos conservados por Stepwise AIC")
Términos conservados por Stepwise AIC
Termino
Rendimiento4
Antiguedad
knitr::kable(rbind(resumenmod(m1,"M1"),resumenmod(m4,"M4"),
                    resumenmod(m5,"M5"),resumenmod(modelo_step,"Stepwise"))[,c("Modelo","R2_ajustado","AIC")],
             digits=3,caption="Comparación de AIC: todos con igual respuesta y datos de entrenamiento")
Comparación de AIC: todos con igual respuesta y datos de entrenamiento
Modelo R2_ajustado AIC
M1 0.603 5309.9
M4 0.604 5309.8
M5 0.603 5315.6
Stepwise 0.604 5305.8
knitr::kable(coeficientes(modelo_step),caption="Coeficientes del modelo seleccionado")
Coeficientes del modelo seleccionado
Termino Coeficiente Error_HC3 P_HC3
(Intercept) 14.1691 0.1086 < 0,0001
Rendimiento4 7.8834 0.1453 < 0,0001
Antiguedad -0.0267 0.0106 0,0121

El procedimiento mantuvo Rendimiento4, Antiguedad. El modelo seleccionado puede lograr menor AIC dentro del conjunto de entrenamiento y aun así no mejorar su MAE de prueba. Además, las p posteriores a Stepwise pueden ser optimistas por la selección de variables: no deben interpretarse como evidencia definitiva.

3.5 11. Validación cruzada, conjunto de prueba y MAE

El criterio de selección es explícito: preferir el menor MAE en validación cruzada dentro de entrenamiento, manteniendo interpretabilidad, y utilizar el conjunto de prueba reservado solamente para confirmar el resultado. Así evitamos escoger especificaciones a partir de sus errores de prueba. El MAE expresa, en promedio, cuántos puntos porcentuales se equivoca el incremento estimado. Comparamos contra un modelo trivial: la mediana de aumentos del entrenamiento. El mismo diseño de particiones se utiliza para todos.

especificaciones <- list("M1. Rendimiento"=formula(m1),
  "M2. Antigüedad"=formula(m2),"M3. Horas extra"=formula(m3),
  "M4. Gestión"=formula(m4),"M5. Auditoría"=formula(m5),
  "Stepwise (post-selección)"=formula(modelo_step),"Log-M4 retransformado"=formula(mlog))
lista_ajustes <- list(m1,m2,m3,m4,m5,modelo_step,mlog)
predicciones_test <- list()
metricas <- data.frame(Modelo=names(especificaciones),CV_MAE=numeric(length(especificaciones)),
                       Prueba_MAE=numeric(length(especificaciones)),
                       Prueba_RMSE=numeric(length(especificaciones)),
                       Prueba_R2=numeric(length(especificaciones)))
for(i in seq_along(especificaciones)) {
  f <- especificaciones[[i]]
  eslog <- names(especificaciones)[i]=="Log-M4 retransformado"
  p_cv <- cv_pred(f,entrenamiento,entrenamiento$Fold,logar=eslog)
  p_test <- predict(lista_ajustes[[i]],newdata=prueba)
  if(eslog) p_test <- exp(p_test)*mean(exp(resid(mlog)))
  predicciones_test[[names(especificaciones)[i]]] <- p_test
  metricas$CV_MAE[i] <- mae(entrenamiento$Aumento,p_cv)
  metricas$Prueba_MAE[i] <- mae(prueba$Aumento,p_test)
  metricas$Prueba_RMSE[i] <- rmse(prueba$Aumento,p_test)
  metricas$Prueba_R2[i] <- 1-sum((prueba$Aumento-p_test)^2)/sum((prueba$Aumento-mean(prueba$Aumento))^2)
}
# Modelo de referencia: la mediana se calcula de nuevo en cada fold, nunca usando el fold de validación
med_cv <- rep(NA_real_,nrow(entrenamiento))
for(k in sort(unique(entrenamiento$Fold))) {
 med_cv[entrenamiento$Fold==k] <- median(entrenamiento$Aumento[entrenamiento$Fold!=k])
}
metricas <- rbind(metricas,data.frame(Modelo="Referencia: mediana",
 CV_MAE=mae(entrenamiento$Aumento,med_cv),
 Prueba_MAE=mae(prueba$Aumento,rep(median(entrenamiento$Aumento),nrow(prueba))),
 Prueba_RMSE=rmse(prueba$Aumento,rep(median(entrenamiento$Aumento),nrow(prueba))),
 Prueba_R2=1-sum((prueba$Aumento-median(entrenamiento$Aumento))^2)/sum((prueba$Aumento-mean(prueba$Aumento))^2)))
metricas_vista <- metricas
for(v in c("CV_MAE","Prueba_MAE","Prueba_RMSE","Prueba_R2")) {
 metricas_vista[[v]] <- round(metricas_vista[[v]],3)
}
knitr::kable(metricas_vista,caption="Validación cruzada en entrenamiento y evaluación en 294 casos de prueba")
Validación cruzada en entrenamiento y evaluación en 294 casos de prueba
Modelo CV_MAE Prueba_MAE Prueba_RMSE Prueba_R2
M1. Rendimiento 1.887 1.853 2.363 0.580
M2. Antigüedad 3.046 3.057 3.645 0.000
M3. Horas extra 3.051 3.061 3.648 -0.002
M4. Gestión 1.897 1.876 2.370 0.577
M5. Auditoría 1.901 1.875 2.369 0.577
Stepwise (post-selección) 1.894 1.869 2.366 0.579
Log-M4 retransformado 1.907 1.892 2.378 0.574
Referencia: mediana 2.885 2.908 3.859 -0.121

Resultado clave. El MAE de prueba de rendimiento solo es 1,853 pp, frente a 1,876 pp del modelo de gestión, 1,875 pp del modelo de auditoría y 1,869 pp de Stepwise. La mejora de la regresión simple respecto a predecir la mediana equivale a 36,3 % de reducción de MAE en prueba. Además, el M1 también obtiene el menor MAE de validación cruzada (1,887 pp), por lo que la evaluación de los 294 casos reservados confirma una elección tomada sin utilizarlos para ajustar ni seleccionar los parámetros. El hecho de que los modelos más complejos no mejoren estos errores constituye un resultado sustantivo, no un fracaso del trabajo.

Transformación logarítmica. El MAE en prueba del Log-M4 retransformado es 1,892 pp, frente a 1,876 pp de M4 original. Si el logaritmo no reduce error y además conserva problemas de supuestos, su complejidad no se justifica. Advertencia de rigor: la fórmula Stepwise se seleccionó usando todo el entrenamiento; por ello su cifra de validación cruzada es posterior a la selección y puede ser optimista. El conjunto de prueba quedó aparte para la comparación principal.

comparables <- metricas[metricas$Modelo %in% c("M1. Rendimiento","M4. Gestión","M5. Auditoría",
                                                   "Stepwise (post-selección)","Referencia: mediana"),]
bar_x <- barplot(comparables$Prueba_MAE,names.arg=c("Rendimiento","Gestión","Auditoría","Stepwise","Mediana"),
        col=c("#187C87","#739FA7","#739FA7","#739FA7","#D2A15D"),border=NA,
        ylim=c(0,max(comparables$Prueba_MAE)*1.18),las=2,ylab="MAE en puntos porcentuales",
        main="¿Más variables significan mejores predicciones?")
text(bar_x,comparables$Prueba_MAE+.08,
     labels=fmt(comparables$Prueba_MAE,2),cex=.8)
MAE en conjunto de prueba: cuanto menor, más preciso.

MAE en conjunto de prueba: cuanto menor, más preciso.

3.6 12. Dos predicciones hipotéticas con predict()

Planteamos dos perfiles idénticos en el resto de variables, con antigüedad 7 años, horas extra Si, dos años desde última promoción, dos capacitaciones, género F, departamento IyD e ingreso mensual registrado de $5.000.000. Solo cambiamos rendimiento de 3 a 4. Son perfiles plausibles, no ofertas salariales reales. Usamos M5 para ilustrar el efecto ajustado; seleccionamos M1 como referencia predictiva principal por su menor MAE de prueba.

escenarios <- data.frame(Rendimiento4=c(0L,1L),Antiguedad=c(7,7),
 Horas_Extra=factor(c("Si","Si"),levels=levels(datos$Horas_Extra)),
 Promocion=c(2,2),Capacitaciones=c(2,2),
 Genero=factor(c("F","F"),levels=levels(datos$Genero)),
 Departamento=factor(c("IyD","IyD"),levels=levels(datos$Departamento)),
 Ingreso_Mensual=c(5000000,5000000))
pi_m5 <- predict(m5,newdata=escenarios,interval="prediction",level=.95)
pi_m1 <- predict(m1,newdata=escenarios,interval="prediction",level=.95)
perfiles <- data.frame(Perfil=c("A: rendimiento 3","B: rendimiento 4"),
 M5_prediccion=round(pi_m5[,"fit"],2),PI95_inf=round(pi_m5[,"lwr"],2),
 PI95_sup=round(pi_m5[,"upr"],2),M1_prediccion=round(pi_m1[,"fit"],2),
 Hipotetico_pesos_M5=round(5000000*pi_m5[,"fit"]/100))
knitr::kable(perfiles,caption="Dos escenarios con predict() e intervalos de predicción paramétricos del 95 %")
Dos escenarios con predict() e intervalos de predicción paramétricos del 95 %
Perfil M5_prediccion PI95_inf PI95_sup M1_prediccion Hipotetico_pesos_M5
A: rendimiento 3 13.82 9.29 18.36 13.98 691172
B: rendimiento 4 21.71 17.16 26.26 21.86 1085509

Interpretación: M5 estima 13,82 % para A y 21,71 % para B. La diferencia de 7,89 puntos porcentuales no implica que una calificación más alta produzca causalmente ese incremento: también podría reproducir bandas administrativas. Los intervalos 95 % son condicionales a los supuestos del modelo, que mostraron dificultades, y pueden exceder los rangos observados; no son garantías ni bandas normativas.

Si se aplicaran hipotéticamente estos porcentajes al salario mensual registrado de $5 millones, el costo del aumento mensual sería aproximadamente $691.172 para A y $1.085.509 para B. No afirmamos que esto fue lo pagado: no tenemos la nómina antes de la última subida ni la fecha de aplicación.

4 Punto 3. Lo que las regresiones no alcanzan a contar

Llegamos a la parte más importante para tomar decisiones. Aunque un modelo pueda aproximar los aumentos salariales, eso no significa que conozca todas las necesidades de los empleados. Vamos a contrastar el resultado con la rotación registrada y, al final, decidir qué sí se puede recomendar responsablemente.

4.1 13. Una prueba de coherencia: aumento, horas extra y rotación

Hasta aquí, las horas extra casi no agregaron poder predictivo sobre Aumento. Sin cambiar la variable dependiente de nuestras regresiones, realizamos una comparación exploratoria adicional con Rotacion: ¿son diferentes las proporciones de registros con Rotacion = Si entre quienes reportan horas extra y quienes no? Esta comparación sirve para evaluar la pertinencia empresarial de la información disponible; no es una cuarta regresión ni un modelo causal.

tab_rot <- table(datos$Horas_Extra,datos$Rotacion)
knitr::kable(addmargins(tab_rot),caption="Tabla de horas extra y rotación observada")
Tabla de horas extra y rotación observada
No Si Sum
No 944 110 1054
Si 289 127 416
Sum 1233 237 1470
tasas <- prop.table(tab_rot,margin=1)[,"Si"]
rr <- unname(tasas["Si"]/tasas["No"])
a <- tab_rot["Si","Si"]; n1 <- sum(tab_rot["Si",])
b <- tab_rot["No","Si"]; n0 <- sum(tab_rot["No",])
se_logrr <- sqrt(1/a-1/n1+1/b-1/n0)
ci_rr <- exp(log(rr)+c(-1,1)*1.96*se_logrr)
chi_rot <- chisq.test(tab_rot,correct=FALSE)
knitr::kable(data.frame(Horas_Extra=names(tasas),
                        Porcentaje_Rotacion=round(100*as.numeric(tasas),2),
                        Aumento_Promedio=round(as.numeric(tapply(datos$Aumento,datos$Horas_Extra,mean)),2)),
             caption="Riesgo registrado y aumento medio por horas extra")
Riesgo registrado y aumento medio por horas extra
Horas_Extra Porcentaje_Rotacion Aumento_Promedio
No 10.44 15.22
Si 30.53 15.18

La paradoja: con horas extra la rotación observada es 30,53 %; sin ellas, 10,44 %. La razón de proporciones es 2,93 veces (IC 95 % aproximado: 2,33 a 3,68; chi-cuadrado p < 0,0001). Sin embargo, el aumento medio de quienes reportan horas extra es 15,18 %, casi igual al de quienes no (15,22 %).

barplot(rbind(c(100*tasas["No"],mean(datos$Aumento[datos$Horas_Extra=="No"])),
              c(100*tasas["Si"],mean(datos$Aumento[datos$Horas_Extra=="Si"]))) ,
        beside=TRUE,col=c("#B6D7D9","#187C87"),border=NA,
        names.arg=c("Rotación (%)","Aumento medio (%)"),
        ylab="Porcentaje",ylim=c(0,36),
        main="La señal que un modelo salarial puede pasar por alto")
legend("topright",legend=c("Sin horas extra","Con horas extra"),
       fill=c("#B6D7D9","#187C87"),bty="n",cex=.85)
Las horas extra casi no cambian el aumento medio, pero sí aparecen asociadas con mayor rotación registrada.

Las horas extra casi no cambian el aumento medio, pero sí aparecen asociadas con mayor rotación registrada.

4.1.1 ¿Es solo una cuestión de diferencias en el rendimiento?

cruce <- aggregate(datos$Aumento,
  list(Rendimiento=datos$Rendimiento,Horas_extra=datos$Horas_Extra),
  function(z)c(N=length(z),Aumento=mean(z)))
rot_cruce <- aggregate(as.numeric(datos$Rotacion=="Si"),
  list(Rendimiento=datos$Rendimiento,Horas_extra=datos$Horas_Extra),mean)
tabla_cruce <- data.frame(Rendimiento=cruce$Rendimiento,Horas_extra=cruce$Horas_extra,
  N=cruce$x[,"N"],Aumento_medio=round(cruce$x[,"Aumento"],2),
  Rotacion_pct=round(100*rot_cruce$x,2))
knitr::kable(tabla_cruce,caption="Cruce descriptivo dentro de las dos categorías de rendimiento")
Cruce descriptivo dentro de las dos categorías de rendimiento
Rendimiento Horas_extra N Aumento_medio Rotacion_pct
3 No 893 14.04 10.75
4 No 161 21.79 8.70
3 Si 351 13.91 29.63
4 Si 65 22.00 35.38

Dentro de ambas categorías de rendimiento también observamos tasas de rotación mayores para los registros con horas extra. Esto reduce la posibilidad de que el patrón agregado se explique únicamente porque el grupo de horas extra tiene más personas con rendimiento 3 o 4, pero no controla todos los factores de confusión ni permite afirmar que el sobretiempo causó la rotación. No conocemos si los movimientos laborales ocurrieron antes o después del aumento ni la duración real de las horas extra.

4.2 14. La diferencia entre porcentaje y pesos: una decisión presupuestal

Una empresa puede otorgar a dos empleados el mismo aumento porcentual y aun así destinar montos muy distintos en pesos. El modelo de aumentos no es por sí solo un modelo de presupuesto salarial: faltan salarios base antes de los incrementos y fecha de aplicación.

ejemplo_costos <- data.frame(Salario_mensual_hipotetico=c(3000000,12000000),
                            Aumento_hipotetico_pct=c(15,15))
ejemplo_costos$Costo_mensual_hipotetico <- ejemplo_costos$Salario_mensual_hipotetico*ejemplo_costos$Aumento_hipotetico_pct/100
knitr::kable(ejemplo_costos,caption="Mismo porcentaje, distinto costo monetario (simulación, NO histórico)")
Mismo porcentaje, distinto costo monetario (simulación, NO histórico)
Salario_mensual_hipotetico Aumento_hipotetico_pct Costo_mensual_hipotetico
3000000 15 450000
12000000 15 1800000

Un 15 % sobre $3 millones equivale a $450.000 mensuales, mientras que sobre $12 millones equivale a $1.800.000. Antes de automatizar decisiones con un modelo porcentual, el área de gestión humana y finanzas debería integrar presupuesto, estructura de cargos, equidad y criterios de reconocimiento. El análisis de género en M5 es una alerta de auditoría, no una variable que deba usarse para asignar incrementos diferenciados.

4.3 15. ¿Usaríamos el modelo? Condiciones y limitaciones

Usuarios potenciales: Gestión Humana podría usar M1 para describir el patrón de incrementos y detectar desviaciones de una posible regla; Finanzas, únicamente como entrada exploratoria, no como proyección de presupuesto. El MAE de 1,85 pp significa que un aumento individual estimado puede diferir sustancialmente del observado. Por ejemplo, estimar 22 % cuando correspondía 19 % equivale a un error de 3 puntos porcentuales, que podría tener un efecto presupuestal considerable. Las consecuencias de sobreestimar incluyen asignaciones superiores a las previstas y presión sobre la nómina; las de subestimar, decisiones posiblemente injustas o difíciles de justificar.

Limitaciones críticas. (1) Los datos no identifican una política salarial real ni la dirección de construcción de rendimiento y aumento; la separación perfecta entre bandas puede indicar circularidad de medición. (2) Solo hay calificaciones 3/4; no existe soporte empírico para otras notas. (3) Faltan periodos y validación en otra empresa o año, así que la capacidad predictiva observada no equivale a una validación futura. (4) Los residuos no satisfacen todos los supuestos clásicos. (5) La rotación, las horas extra y los aumentos están observados conjuntamente, por lo que no puede deducirse que cambiar un aumento reduzca el retiro. (6) Sexo, área y salario se estudiaron para auditar diferencias, no para automatizar pagos por características personales.

Información adicional antes de implementarlo: manual de evaluación de desempeño, reglas internas de aumentos, fechas de evaluación, sueldo antes y después de cada aumento, horas extra efectivas, fecha y motivo de rotación, roles comparables y datos de nuevos periodos. Una prueba adecuada sería comparar resultados de la política vigente con decisiones supervisadas y auditadas antes de automatizar cualquier proceso.

4.4 La decisión que realmente le presentaríamos a Gestión Humana

No recomendaríamos empezar por un algoritmo que diga: «evaluación 4, entonces aumento de 22 %». Propondríamos una auditoría de la política existente con tres preguntas para la gerencia:

Pregunta de gestión Qué aportan los datos Qué debemos comprobar antes de actuar
¿La evaluación determina una banda de aumento? Los rangos observados de calificación 3 y 4 no se superponen Manual de evaluación y política escrita; orden cronológico de evaluación y aumento
¿Conviene sumar muchas variables al pronóstico? El MAE de modelos múltiples y Stepwise no mejora claramente el simple Repetir con aumentos de otro periodo y un conjunto externo de empleados
¿Estamos ignorando señales del contexto laboral? Las horas extra se asocian con mayor rotación observada, sin mayor aumento medio Carga real de trabajo, fechas, motivos de salida y diferencias entre cargos

Una decisión concreta y acotada: permitir que el modelo detecte casos que se apartan de las bandas históricas y enviarlos a revisión humana. No permitirle recomendar un aumento individual sin saber si el sistema de rendimiento es independiente de las bandas salariales, sin reglas de equidad y sin los salarios anteriores al incremento.

La pregunta no es «¿qué porcentaje predice el algoritmo?», sino «¿qué haría la empresa si esa predicción fuera equivocada?». Un error medio cercano a dos puntos porcentuales puede parecer bajo en una tabla; aplicado a una nómina, necesita controles presupuestales y de justicia interna. Este es el criterio con el que respondemos la pregunta central de la guía.

4.5 16. Entonces, ¿qué demostramos y qué decisión tomaríamos?

Volvamos a la pregunta inicial: ¿a qué responde el aumento salarial y alcanza esa información para tomar una buena decisión sobre las personas? Los datos permiten sostener tres hallazgos distintos, con alcances también distintos.

Primero: los aumentos parecen seguir la calificación de rendimiento. La asociación entre rendimiento y aumento es dominante, con rangos que no se superponen. Este hallazgo puede indicar una política de bandas; también es una advertencia de circularidad. En consecuencia, el buen ajuste no prueba que el sistema mida de manera independiente el mérito individual.

Segundo: un modelo con más variables no necesariamente ayuda más. El menor MAE de prueba corresponde al modelo M1. Rendimiento, con 1,853 puntos porcentuales, frente a la mediana de 2,908 pp. Añadir variables, aplicar Stepwise o transformar logarítmicamente no ofrece una mejora convincente para este objetivo. No seleccionamos el modelo que parece más sofisticado, sino el que cumple mejor la decisión que pretende apoyar.

Tercero: reconocer el rendimiento no equivale a entender toda la experiencia laboral. Los aumentos no cambian sustancialmente entre quienes reportan horas extra y quienes no, pero las proporciones de rotación observadas sí son muy distintas. Esto no identifica un efecto causal de las horas extra ni de los aumentos sobre la rotación; señala que un modelo de aumento puede omitir dimensiones prioritarias para entender el contexto laboral.

Nuestra decisión, con base en la evidencia: no usar esta base para automatizar los aumentos. Utilizar el modelo de rendimiento como herramienta descriptiva para revisar posibles reglas internas y abrir una investigación organizacional separada sobre horas extra, condiciones de trabajo y rotación. Si la empresa quiere decidir aumentos o prevenir retiros, debe recopilar datos temporales, validar fuera de muestra y establecer controles humanos y de equidad. Esa es una recomendación más útil que declarar «modelo exitoso» por tener un R² elevado.


Fuente única de los microdatos: base3_ingreso.xlsx, Base 3 suministrada en la asignatura Análisis predictivo y visualización. Marco de evaluación: Caso 2.pdf del curso. Las cifras y conclusiones aquí son cálculos y juicios de los autores sobre la base del curso; la procedencia real y el mecanismo de generación de los registros no se encuentran documentados en los archivos entregados.