¿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.
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:
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.
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.
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")| 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")| 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.
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")| 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")| Rendimiento | Freq |
|---|---|
| 3 | 1244 |
| 4 | 226 |
| 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 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.
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")| 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.
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")| 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.
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")| 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 |
| Termino | Coeficiente | Error_HC3 | P_HC3 |
|---|---|---|---|
| (Intercept) | 13.982 | 0.0765 | < 0,0001 |
| Rendimiento4 | 7.880 | 0.1429 | < 0,0001 |
| Termino | Coeficiente | Error_HC3 | P_HC3 |
|---|---|---|---|
| (Intercept) | 15.3586 | 0.1578 | < 0,0001 |
| Antiguedad | -0.0233 | 0.0178 | 0,1920 |
| 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.
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")| 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
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.
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.
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")| 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 |
| 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 |
| 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.
knitr::kable(rbind(diagfun(m4,"M4. Gestión"),diagfun(m5,"M5. Auditoría")),
caption="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)")| 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
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"))| 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.
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")| 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.
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")| 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")| Modelo | R2_ajustado | AIC |
|---|---|---|
| M1 | 0.603 | 5309.9 |
| M4 | 0.604 | 5309.8 |
| M5 | 0.603 | 5315.6 |
| Stepwise | 0.604 | 5305.8 |
| 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.
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")| 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.
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 %")| 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.
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.
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")| 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")| 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.
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")| 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.
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)")| 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.
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.
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.
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.