# Mostrar las tablas base
cat("=== EMPLEADOS ===\n")
#> === EMPLEADOS ===
print(empleados[1:5, .(emp_id, nombre, departamento_id, salario)])
#> emp_id nombre departamento_id salario
#> <int> <char> <int> <num>
#> 1: 1 Empleado_A 2 39500
#> 2: 2 Empleado_B 5 62200
#> 3: 3 Empleado_C 5 69000
#> 4: 4 Empleado_D 4 66900
#> 5: 5 Empleado_E 1 46700
cat("\n=== DEPARTAMENTOS ===\n")
#>
#> === DEPARTAMENTOS ===
print(departamentos[, .(dept_id, nombre_dept, ubicacion)])
#> dept_id nombre_dept ubicacion
#> <int> <char> <char>
#> 1: 1 Ingeniería Madrid
#> 2: 2 Ventas Barcelona
#> 3: 3 Marketing Sevilla
#> 4: 4 RRHH Valencia
#> 5: 5 Finanzas Bilbao
cat("\n=== EVALUACIONES (parcial) ===\n")
#>
#> === EVALUACIONES (parcial) ===
print(evaluaciones[1:5, .(empleado_id, puntuacion, fecha_evaluacion)])
#> empleado_id puntuacion fecha_evaluacion
#> <int> <num> <Date>
#> 1: 2 3.8 2023-12-22
#> 2: 15 4.3 2023-12-01
#> 3: 14 4.1 2023-12-22
#> 4: 7 4.8 2024-02-09
#> 5: 4 4.1 2023-12-155 Uniones de Datos (Joins)
5.1 Fundamentos de Joins en data.table
data.table ofrece múltiples métodos para realizar joins, cada uno optimizado para diferentes casos de uso. La elección del método correcto puede marcar la diferencia entre segundos y minutos en datasets grandes.
5.1.1 1. Tipos de Joins: Conceptos Básicos
5.2 Joins con merge(): El Método Tradicional
La función merge() es familiar para usuarios de R base y ofrece una sintaxis clara para joins simples.
5.2.1 1. Inner Join Básico
# Inner join: solo registros que existen en ambas tablas
empleados_con_dept <- merge(empleados, departamentos,
by.x = "departamento_id", by.y = "dept_id")
cat("Empleados con información de departamento (Inner Join):\n")
#> Empleados con información de departamento (Inner Join):
print(empleados_con_dept[1:6, .(nombre, nombre_dept, salario, ubicacion)])
#> nombre nombre_dept salario ubicacion
#> <char> <char> <num> <char>
#> 1: Empleado_E Ingeniería 46700 Madrid
#> 2: Empleado_G Ingeniería 67600 Madrid
#> 3: Empleado_A Ventas 39500 Barcelona
#> 4: Empleado_H Ventas 35000 Barcelona
#> 5: Empleado_I Ventas 35800 Barcelona
#> 6: Empleado_N Ventas 62000 Barcelona5.2.2 2. Todos los Tipos de Joins con merge()
# Comparar diferentes tipos de joins
inner_join <- merge(empleados, evaluaciones,
by.x = "emp_id", by.y = "empleado_id")
cat("Inner join (empleados con evaluación):", nrow(inner_join), "filas\n")
#> Inner join (empleados con evaluación): 12 filas
left_join <- merge(empleados, evaluaciones,
by.x = "emp_id", by.y = "empleado_id", all.x = TRUE)
cat("Left join (todos los empleados):", nrow(left_join), "filas\n")
#> Left join (todos los empleados): 19 filas
right_join <- merge(empleados, evaluaciones,
by.x = "emp_id", by.y = "empleado_id", all.y = TRUE)
cat("Right join (todas las evaluaciones):", nrow(right_join), "filas\n")
#> Right join (todas las evaluaciones): 12 filas
full_join <- merge(empleados, evaluaciones,
by.x = "emp_id", by.y = "empleado_id", all = TRUE)
cat("Full join (empleados + evaluaciones):", nrow(full_join), "filas\n")
#> Full join (empleados + evaluaciones): 19 filas
# Mostrar ejemplo de left join
print(left_join[1:8, .(nombre, nivel, puntuacion, fecha_evaluacion)])
#> nombre nivel puntuacion fecha_evaluacion
#> <char> <char> <num> <Date>
#> 1: Empleado_A Senior NA <NA>
#> 2: Empleado_B Junior 3.8 2023-12-22
#> 3: Empleado_B Junior 4.6 2023-12-08
#> 4: Empleado_C Senior 3.4 2023-12-15
#> 5: Empleado_D Junior 4.1 2023-12-15
#> 6: Empleado_E Lead NA <NA>
#> 7: Empleado_F Lead NA <NA>
#> 8: Empleado_G Lead 4.8 2024-02-095.2.3 3. Joins por Múltiples Columnas
# Crear tabla de ejemplo con múltiples keys
historico_salarios <- data.table(
empleado_id = rep(c(1, 2, 3), each = 2),
departamento_id = rep(c(1, 2, 1), each = 2),
año = rep(c(2023, 2024), times = 3),
salario_historico = c(40000, 42000, 45000, 47000, 38000, 40000)
)
# Join por múltiples columnas
join_multiple <- merge(empleados[1:3, .(emp_id, nombre, departamento_id)],
historico_salarios,
by.x = c("emp_id", "departamento_id"),
by.y = c("empleado_id", "departamento_id"))
print(join_multiple)
#> Empty data.table (0 rows and 5 cols): emp_id,departamento_id,nombre,año,salario_historico5.3 Joins Optimizados con setkey()
Para datasets grandes y joins repetitivos, establecer keys proporciona un rendimiento excepcional.
5.3.1 1. Estableciendo Keys y Sintaxis X[Y]
# Hacer copias para no modificar originales
emp_key <- copy(empleados)
dept_key <- copy(departamentos)
# Establecer keys (ordena físicamente las tablas)
setkey(emp_key, departamento_id)
setkey(dept_key, dept_id)
# Verificar que las keys están establecidas
cat("Key de empleados:", key(emp_key), "\n")
#> Key de empleados: departamento_id
cat("Key de departamentos:", key(dept_key), "\n")
#> Key de departamentos: dept_id
# Join ultra-rápido con sintaxis X[Y]
resultado_key_join <- dept_key[emp_key]
print(resultado_key_join[1:6, .(nombre, nombre_dept, salario, presupuesto)])
#> nombre nombre_dept salario presupuesto
#> <char> <char> <num> <num>
#> 1: Empleado_E Ingeniería 46700 8e+05
#> 2: Empleado_G Ingeniería 67600 8e+05
#> 3: Empleado_A Ventas 39500 6e+05
#> 4: Empleado_H Ventas 35000 6e+05
#> 5: Empleado_I Ventas 35800 6e+05
#> 6: Empleado_N Ventas 62000 6e+055.3.2 2. Ventajas de las Keys
# Las tablas con key están ordenadas físicamente
cat("Empleados ordenados por departamento_id:\n")
#> Empleados ordenados por departamento_id:
print(emp_key[, .(emp_id, nombre, departamento_id)])
#> Key: <departamento_id>
#> emp_id nombre departamento_id
#> <int> <char> <int>
#> 1: 5 Empleado_E 1
#> 2: 7 Empleado_G 1
#> 3: 1 Empleado_A 2
#> 4: 8 Empleado_H 2
#> 5: 9 Empleado_I 2
#> ---
#> 11: 2 Empleado_B 5
#> 12: 3 Empleado_C 5
#> 13: 6 Empleado_F 5
#> 14: 10 Empleado_J 5
#> 15: 12 Empleado_L 5
cat("\nDepartamentos ordenados por dept_id:\n")
#>
#> Departamentos ordenados por dept_id:
print(dept_key[, .(dept_id, nombre_dept)])
#> Key: <dept_id>
#> dept_id nombre_dept
#> <int> <char>
#> 1: 1 Ingeniería
#> 2: 2 Ventas
#> 3: 3 Marketing
#> 4: 4 RRHH
#> 5: 5 Finanzas
# Acceso ultra-rápido por key
departamento_2 <- dept_key[2] # Busca dept_id == 2
empleados_dept_2 <- emp_key[2] # Busca departamento_id == 2
cat("\nEmpleados del departamento 2:\n")
#>
#> Empleados del departamento 2:
print(empleados_dept_2[, .(nombre, nivel, salario)])
#> nombre nivel salario
#> <char> <char> <num>
#> 1: Empleado_G Lead 676005.3.3 3. Múltiples Keys
# Establecer múltiples columnas como key
setkey(asignaciones, empleado_id, proyecto_id)
# Buscar por key compuesta
asignacion_especifica <- asignaciones[.(1, 2)] # empleado 1, proyecto 2
print(asignacion_especifica)
#> Key: <empleado_id, proyecto_id>
#> asignacion_id empleado_id proyecto_id horas_asignadas fecha_asignacion
#> <int> <int> <int> <int> <Date>
#> 1: NA 1 2 NA <NA>
#> rol_proyecto
#> <char>
#> 1: <NA>
# Búsquedas parciales
asignaciones_emp_1 <- asignaciones[.(1)] # Solo empleado 1
cat("Asignaciones del empleado 1:", nrow(asignaciones_emp_1), "proyectos\n")
#> Asignaciones del empleado 1: 3 proyectos5.4 Sintaxis on: Joins Flexibles sin Keys
La sintaxis on permite joins puntuales sin necesidad de establecer keys.
5.4.1 1. Join Básico con on
# Join sin modificar las tablas originales
resultado_on <- empleados[departamentos, on = .(departamento_id = dept_id)]
print(resultado_on[1:6, .(nombre, nombre_dept, salario, ubicacion)])
#> nombre nombre_dept salario ubicacion
#> <char> <char> <num> <char>
#> 1: Empleado_E Ingeniería 46700 Madrid
#> 2: Empleado_G Ingeniería 67600 Madrid
#> 3: Empleado_A Ventas 39500 Barcelona
#> 4: Empleado_H Ventas 35000 Barcelona
#> 5: Empleado_I Ventas 35800 Barcelona
#> 6: Empleado_N Ventas 62000 Barcelona5.4.2 2. Join con Renombrado de Columnas
# Join con selección y renombrado
empleados_completo <- empleados[
departamentos,
on = .(departamento_id = dept_id)
][
, .(
empleado = nombre,
departamento = nombre_dept,
salario,
presupuesto_dept = presupuesto,
ubicacion,
ratio_salario_presupuesto = round(salario / presupuesto * 100, 3)
)
]
print(empleados_completo[order(-ratio_salario_presupuesto)][1:6])
#> empleado departamento salario presupuesto_dept ubicacion
#> <char> <char> <num> <num> <char>
#> 1: Empleado_M RRHH 73900 3e+05 Valencia
#> 2: Empleado_D RRHH 66900 3e+05 Valencia
#> 3: Empleado_C Finanzas 69000 5e+05 Bilbao
#> 4: Empleado_B Finanzas 62200 5e+05 Bilbao
#> 5: Empleado_F Finanzas 59600 5e+05 Bilbao
#> 6: Empleado_K Marketing 46600 4e+05 Sevilla
#> ratio_salario_presupuesto
#> <num>
#> 1: 24.633
#> 2: 22.300
#> 3: 13.800
#> 4: 12.440
#> 5: 11.920
#> 6: 11.6505.4.3 3. Joins con Condiciones Adicionales
# Join con filtro simultáneo
empleados_evaluados_reciente <- empleados[
evaluaciones[fecha_evaluacion >= "2024-01-01"],
on = .(emp_id = empleado_id)
][
!is.na(puntuacion) # Solo empleados con evaluación
][
, .(nombre, nivel, puntuacion, fecha_evaluacion)
][
order(-puntuacion)
]
print(empleados_evaluados_reciente)
#> nombre nivel puntuacion fecha_evaluacion
#> <char> <char> <num> <Date>
#> 1: Empleado_G Lead 4.8 2024-02-09
#> 2: Empleado_M Senior 4.8 2024-02-09
#> 3: Empleado_O Lead 4.6 2024-01-19
#> 4: Empleado_O Lead 3.7 2024-02-165.5 Update Joins: La Característica Estrella
Los update joins permiten modificar una tabla basándose en valores de otra tabla de forma eficiente.
5.5.1 1. Update Join Básico
# Crear tabla de bonos
bonos_dept <- data.table(
dept_id = 1:5,
bono_porcentaje = c(0.15, 0.20, 0.12, 0.18, 0.16),
bono_fijo = c(5000, 7000, 4000, 6000, 5500)
)
# Hacer copia para update join
emp_con_bonus <- copy(empleados)
# Update join: agregar columnas basadas en departamento
emp_con_bonus[bonos_dept, on = .(departamento_id = dept_id),
`:=`(
bono_porcentaje = i.bono_porcentaje,
bono_calculado = salario * i.bono_porcentaje + i.bono_fijo
)]
print(emp_con_bonus[1:6, .(nombre, departamento_id, salario, bono_porcentaje, bono_calculado)])
#> nombre departamento_id salario bono_porcentaje bono_calculado
#> <char> <int> <num> <num> <num>
#> 1: Empleado_A 2 39500 0.20 14900
#> 2: Empleado_B 5 62200 0.16 15452
#> 3: Empleado_C 5 69000 0.16 16540
#> 4: Empleado_D 4 66900 0.18 18042
#> 5: Empleado_E 1 46700 0.15 12005
#> 6: Empleado_F 5 59600 0.16 150365.5.2 2. Update Join Condicional
# Update join solo para ciertos empleados
emp_con_bonus[bonos_dept, on = .(departamento_id = dept_id),
bono_extra := ifelse(i.bono_porcentaje > 0.15 & salario > 50000,
2000, 0)]
print(emp_con_bonus[bono_extra > 0, .(nombre, departamento_id, salario, bono_extra)])
#> nombre departamento_id salario bono_extra
#> <char> <int> <num> <num>
#> 1: Empleado_B 5 62200 2000
#> 2: Empleado_C 5 69000 2000
#> 3: Empleado_D 4 66900 2000
#> 4: Empleado_F 5 59600 2000
#> 5: Empleado_J 5 52600 2000
#> 6: Empleado_M 4 73900 2000
#> 7: Empleado_N 2 62000 2000
#> 8: Empleado_O 2 62200 20005.5.3 3. Update Join con Agregaciones
# Calcular estadísticas por departamento
stats_dept <- empleados[, .(
empleados_count = .N,
salario_promedio_dept = round(mean(salario), 0),
salario_max_dept = max(salario)
), by = departamento_id]
# Update join con estadísticas
emp_con_bonus[stats_dept, on = .(departamento_id),
`:=`(
empleados_en_dept = i.empleados_count,
percentil_en_dept = round(salario / i.salario_max_dept * 100, 1),
vs_promedio_dept = salario - i.salario_promedio_dept
)]
print(emp_con_bonus[1:8, .(nombre, departamento_id, salario, empleados_en_dept,
percentil_en_dept, vs_promedio_dept)])
#> nombre departamento_id salario empleados_en_dept percentil_en_dept
#> <char> <int> <num> <int> <num>
#> 1: Empleado_A 2 39500 5 63.5
#> 2: Empleado_B 5 62200 5 90.1
#> 3: Empleado_C 5 69000 5 100.0
#> 4: Empleado_D 4 66900 2 90.5
#> 5: Empleado_E 1 46700 2 69.1
#> 6: Empleado_F 5 59600 5 86.4
#> 7: Empleado_G 1 67600 2 100.0
#> 8: Empleado_H 2 35000 5 56.3
#> vs_promedio_dept
#> <num>
#> 1: -7400
#> 2: 5120
#> 3: 11920
#> 4: -3500
#> 5: -10450
#> 6: 2520
#> 7: 10450
#> 8: -119005.6 Joins Múltiples y Encadenamiento
Combinando múltiples tablas en operaciones complejas.
5.6.1 1. Joins Encadenados
# Pipeline complejo: empleados -> departamentos -> proyectos -> evaluaciones
analisis_completo <- empleados[
departamentos, on = .(departamento_id = dept_id) # Join 1: empleados + departamentos
][
asignaciones, on = .(emp_id = empleado_id), allow.cartesian = TRUE # Join 2: + asignaciones
][
proyectos, on = .(proyecto_id), allow.cartesian = TRUE # Join 3: + proyectos
][
evaluaciones, on = .(emp_id = empleado_id) # Join 4: + evaluaciones
][
!is.na(puntuacion) # Solo empleados evaluados
][
, .(
empleado = nombre,
departamento = nombre_dept,
proyecto = nombre_proyecto,
horas_asignadas,
rol_proyecto,
puntuacion,
salario,
presupuesto_proyecto
)
][
order(empleado, proyecto)
]
print(head(analisis_completo, 10))
#> empleado departamento proyecto horas_asignadas rol_proyecto puntuacion
#> <char> <char> <char> <int> <char> <num>
#> 1: Empleado_B Finanzas Proyecto_G 39 Líder 3.8
#> 2: Empleado_B Finanzas Proyecto_G 39 Líder 4.6
#> 3: Empleado_B Finanzas Proyecto_H 37 Tester 3.8
#> 4: Empleado_B Finanzas Proyecto_H 37 Tester 4.6
#> 5: Empleado_B Finanzas Proyecto_J 14 Tester 3.8
#> 6: Empleado_B Finanzas Proyecto_J 14 Tester 4.6
#> 7: Empleado_B Finanzas Proyecto_L 21 Analista 3.8
#> 8: Empleado_B Finanzas Proyecto_L 21 Analista 4.6
#> 9: Empleado_C Finanzas Proyecto_L 16 Líder 3.4
#> 10: Empleado_D RRHH Proyecto_A 36 Analista 4.1
#> salario presupuesto_proyecto
#> <num> <num>
#> 1: 62200 276000
#> 2: 62200 276000
#> 3: 62200 260000
#> 4: 62200 260000
#> 5: 62200 172000
#> 6: 62200 172000
#> 7: 62200 286000
#> 8: 62200 286000
#> 9: 69000 286000
#> 10: 66900 2510005.6.2 2. Agregaciones Complejas con Múltiples Joins
# Análisis de productividad por departamento
productividad_dept <- empleados[
asignaciones, on = .(emp_id = empleado_id), allow.cartesian = TRUE
][
proyectos, on = .(proyecto_id)
][
departamentos, on = .(departamento_id = dept_id)
][
, .(
empleados_únicos = uniqueN(emp_id),
proyectos_únicos = uniqueN(proyecto_id),
horas_totales = sum(horas_asignadas),
presupuesto_total = sum(presupuesto_proyecto),
proyectos_completados = sum(estado == "Completado")
), by = .(departamento_id, nombre_dept)
][
, `:=`(
horas_por_empleado = round(horas_totales / empleados_únicos, 1),
proyectos_por_empleado = round(proyectos_únicos / empleados_únicos, 2),
tasa_completación = round(proyectos_completados / proyectos_únicos * 100, 1)
)
][
order(-tasa_completación)
]
print(productividad_dept)
#> departamento_id nombre_dept empleados_únicos proyectos_únicos horas_totales
#> <int> <char> <int> <int> <int>
#> 1: 2 Ventas 5 6 257
#> 2: 5 Finanzas 5 6 186
#> 3: 1 Ingeniería 2 4 82
#> 4: 4 RRHH 2 2 71
#> 5: 3 Marketing 1 1 NA
#> presupuesto_total proyectos_completados horas_por_empleado
#> <num> <int> <num>
#> 1: 1748000 4 51.4
#> 2: 1902000 2 37.2
#> 3: 894000 1 41.0
#> 4: 323000 0 35.5
#> 5: NA NA NA
#> proyectos_por_empleado tasa_completación
#> <num> <num>
#> 1: 1.2 66.7
#> 2: 1.2 33.3
#> 3: 2.0 25.0
#> 4: 1.0 0.0
#> 5: 1.0 NA5.7 Casos Especiales: Joins con Diferentes Estructuras
5.7.1 1. Self-Joins: Jerarquías
# Self-join para obtener información de managers
empleados_con_manager <- empleados[
empleados[, .(manager_emp_id = emp_id, manager_nombre = nombre, manager_nivel = nivel)],
on = .(manager_id = manager_emp_id)
][
, .(empleado = nombre, nivel, salario,
manager = manager_nombre, manager_nivel)
][
!is.na(manager) # Solo empleados con manager
][
order(manager, empleado)
]
print(empleados_con_manager)
#> empleado nivel salario manager manager_nivel
#> <char> <char> <num> <char> <char>
#> 1: Empleado_B Junior 62200 Empleado_A Senior
#> 2: Empleado_C Senior 69000 Empleado_A Senior
#> 3: <NA> <NA> NA Empleado_B Junior
#> 4: <NA> <NA> NA Empleado_C Senior
#> 5: Empleado_E Lead 46700 Empleado_D Junior
#> ---
#> 14: Empleado_L Senior 42000 Empleado_K Junior
#> 15: <NA> <NA> NA Empleado_L Senior
#> 16: Empleado_N Junior 62000 Empleado_M Senior
#> 17: Empleado_O Lead 62200 Empleado_N Junior
#> 18: <NA> <NA> NA Empleado_O Lead5.7.2 2. Many-to-Many Joins
# Análisis de empleados en múltiples proyectos
empleados_multiproyecto <- asignaciones[, .N, by = empleado_id][N > 1]
detalle_multiproyecto <- empleados[empleados_multiproyecto, on = .(emp_id = empleado_id)][
asignaciones, on = .(emp_id = empleado_id), allow.cartesian = TRUE
][
proyectos, on = .(proyecto_id)
][
, .(
empleado = nombre,
nivel,
proyecto = nombre_proyecto,
horas_asignadas,
rol_proyecto,
estado_proyecto = estado
)
][
order(empleado, proyecto)
]
print(head(detalle_multiproyecto, 12))
#> empleado nivel proyecto horas_asignadas rol_proyecto estado_proyecto
#> <char> <char> <char> <int> <char> <char>
#> 1: Empleado_A Senior Proyecto_C 34 Tester Completado
#> 2: Empleado_A Senior Proyecto_E 34 Tester Pausado
#> 3: Empleado_A Senior Proyecto_J 19 Analista Planificación
#> 4: Empleado_B Junior Proyecto_G 39 Líder Completado
#> 5: Empleado_B Junior Proyecto_H 37 Tester En Curso
#> ---
#> 8: Empleado_E Lead Proyecto_G 17 Líder Completado
#> 9: Empleado_E Lead Proyecto_H 38 Líder En Curso
#> 10: Empleado_E Lead Proyecto_L 10 Analista En Curso
#> 11: Empleado_I Junior Proyecto_C 10 Desarrollador Completado
#> 12: Empleado_I Junior Proyecto_D 40 Tester En Curso
# Resumen por empleado
resumen_multiproyecto <- detalle_multiproyecto[, .(
proyectos_total = .N,
horas_totales = sum(horas_asignadas),
roles_únicos = uniqueN(rol_proyecto),
proyectos_completados = sum(estado_proyecto == "Completado")
), by = .(empleado, nivel)][
order(-horas_totales)
]
print(resumen_multiproyecto)
#> empleado nivel proyectos_total horas_totales roles_únicos
#> <char> <char> <int> <int> <int>
#> 1: Empleado_B Junior 4 111 3
#> 2: Empleado_A Senior 3 87 2
#> 3: Empleado_I Junior 3 72 2
#> 4: Empleado_E Lead 3 65 2
#> 5: <NA> <NA> 12 NA 5
#> proyectos_completados
#> <int>
#> 1: 1
#> 2: 1
#> 3: 1
#> 4: 1
#> 5: 35.8 Comparación de Performance entre Métodos
# Preparar datasets para benchmark
clientes_sample <- clientes_info[sample(.N, 500)]
trans_sample <- transacciones_grandes[sample(.N, 25000)]
# Copias para diferentes métodos
clientes_key <- copy(clientes_sample)
trans_key <- copy(trans_sample)
setkey(clientes_key, cliente_id)
setkey(trans_key, cliente_id)
# Benchmark de diferentes métodos
benchmark_joins <- microbenchmark(
"merge()" = merge(trans_sample, clientes_sample, by = "cliente_id"),
"X[Y, on=]" = trans_sample[clientes_sample, on = .(cliente_id)],
"setkey + X[Y]" = clientes_key[trans_key],
times = 10
)
print(benchmark_joins)
#> Unit: milliseconds
#> expr min lq mean median uq max neval
#> merge() 3.315414 3.615093 3.624520 3.660953 3.685535 3.741128 10
#> X[Y, on=] 2.275124 2.290552 2.324806 2.314798 2.345124 2.423731 10
#> setkey + X[Y] 1.318099 1.620793 1.620885 1.653484 1.679794 1.695613 10
# Mostrar eficiencia relativa
benchmark_summary <- as.data.table(summary(benchmark_joins))
print(benchmark_summary[, .(expr, median, relative_speed = round(median / min(median), 2))])
#> expr median relative_speed
#> <fctr> <num> <num>
#> 1: merge() 3.660953 2.21
#> 2: X[Y, on=] 2.314798 1.40
#> 3: setkey + X[Y] 1.653484 1.005.9 Ejercicios Prácticos
💡 Solución del Ejercicio 8
# Pipeline completo de joins y análisis
ranking_departamentos <- empleados[
evaluaciones[puntuacion > 4.0], on = .(emp_id = empleado_id) # 1. Join + filtro
][
departamentos, on = .(departamento_id = dept_id) # 2. Agregar info departamental
][
asignaciones, on = .(emp_id = empleado_id), allow.cartesian = TRUE # 3. Proyectos asignados
][
proyectos, on = .(proyecto_id) # 4. Info de proyectos
][
, .( # 5. Métricas por departamento
empleados_alto_performance = uniqueN(emp_id),
puntuacion_promedio = round(mean(puntuacion), 2),
salario_promedio = round(mean(salario), 0),
horas_totales_asignadas = sum(horas_asignadas),
proyectos_gestionados = uniqueN(proyecto_id),
presupuesto_total_proyectos = sum(presupuesto_proyecto),
proyectos_completados = sum(estado == "Completado"),
presupuesto_departamental = first(presupuesto)
), by = .(departamento_id, nombre_dept, ubicacion)
][
, `:=`( # 6. Cálculos de productividad
horas_por_empleado = round(horas_totales_asignadas / empleados_alto_performance, 1),
proyectos_por_empleado = round(proyectos_gestionados / empleados_alto_performance, 2),
tasa_completación = round(proyectos_completados / proyectos_gestionados * 100, 1),
eficiencia_presupuestal = round((proyectos_completados * presupuesto_total_proyectos) / presupuesto_departamental, 2)
)
][
, score_performance := round( # 7. Score compuesto
(puntuacion_promedio * 20) +
(tasa_completación * 0.5) +
(eficiencia_presupuestal * 10), 1
)
][
order(-score_performance) # 8. Ranking final
][
, ranking := 1:.N
]
print(ranking_departamentos[, .(
ranking, nombre_dept, ubicacion, empleados_alto_performance,
puntuacion_promedio, tasa_completación, eficiencia_presupuestal, score_performance
)])
#> ranking nombre_dept ubicacion empleados_alto_performance puntuacion_promedio
#> <int> <char> <char> <int> <num>
#> 1: 1 Finanzas Bilbao 2 4.50
#> 2: 2 Ventas Barcelona 2 4.33
#> 3: 3 Ingeniería Madrid 1 4.80
#> 4: 4 RRHH Valencia 2 4.45
#> 5: 5 <NA> <NA> 8 NA
#> tasa_completación eficiencia_presupuestal score_performance
#> <num> <num> <num>
#> 1: 75.0 9.28 220.3
#> 2: 50.0 1.21 123.7
#> 3: 0.0 0.00 96.0
#> 4: 0.0 0.00 89.0
#> 5: 36.4 NA NA
# Crear visualización del ranking
cat("\n🏆 RANKING DE DEPARTAMENTOS POR PERFORMANCE:\n")
#>
#> 🏆 RANKING DE DEPARTAMENTOS POR PERFORMANCE:
for(i in 1:nrow(ranking_departamentos)) {
dept <- ranking_departamentos[i]
medal <- ifelse(i == 1, "🥇", ifelse(i == 2, "🥈", ifelse(i == 3, "🥉", paste0("#", i))))
cat(sprintf("%s %s (%s) - Score: %.1f\n", medal, dept$nombre_dept, dept$ubicacion, dept$score_performance))
cat(sprintf(" • %d empleados alto performance, %.1f%% completación\n",
dept$empleados_alto_performance, dept$tasa_completación))
}
#> 🥇 Finanzas (Bilbao) - Score: 220.3
#> • 2 empleados alto performance, 75.0% completación
#> 🥈 Ventas (Barcelona) - Score: 123.7
#> • 2 empleados alto performance, 50.0% completación
#> 🥉 Ingeniería (Madrid) - Score: 96.0
#> • 1 empleados alto performance, 0.0% completación
#> #4 RRHH (Valencia) - Score: 89.0
#> • 2 empleados alto performance, 0.0% completación
#> #5 NA (NA) - Score: NA
#> • 8 empleados alto performance, 36.4% completación
💡 Solución del Ejercicio 9
# 1. Crear tabla de ajustes salariales
ajustes_salariales <- data.table(
dept_id = 1:5,
nombre_dept = c("Ingeniería", "Ventas", "Marketing", "RRHH", "Finanzas"),
ajuste_porcentaje = c(0.08, 0.12, 0.06, 0.10, 0.07), # 8%, 12%, 6%, 10%, 7%
bono_retencion = c(3000, 5000, 2000, 2500, 4000),
criterio_nivel = c(65000, 55000, 45000, 50000, 60000) # Umbral para nivel Senior+
)
print("Ajustes salariales por departamento:")
#> [1] "Ajustes salariales por departamento:"
print(ajustes_salariales)
#> dept_id nombre_dept ajuste_porcentaje bono_retencion criterio_nivel
#> <int> <char> <num> <num> <num>
#> 1: 1 Ingeniería 0.08 3000 65000
#> 2: 2 Ventas 0.12 5000 55000
#> 3: 3 Marketing 0.06 2000 45000
#> 4: 4 RRHH 0.10 2500 50000
#> 5: 5 Finanzas 0.07 4000 60000
# 2. Aplicar ajustes usando update joins
empleados_ajuste <- copy(empleados)
# Update join principal
empleados_ajuste[ajustes_salariales, on = .(departamento_id = dept_id),
`:=`(
salario_anterior = salario,
ajuste_pct = i.ajuste_porcentaje,
salario_ajustado = salario * (1 + i.ajuste_porcentaje),
bono_retencion = i.bono_retencion,
umbral_senior = i.criterio_nivel
)]
# Update join condicional para bonos extra
empleados_ajuste[nivel %in% c("Lead", "Manager") & salario_ajustado > 60000,
bono_extra := salario_ajustado * 0.05]
empleados_ajuste[is.na(bono_extra), bono_extra := 0]
# 3. Calcular impacto presupuestal
impacto_presupuestal <- empleados_ajuste[
departamentos, on = .(departamento_id = dept_id)
][
, .(
empleados = .N,
salario_total_anterior = sum(salario_anterior),
salario_total_nuevo = sum(salario_ajustado),
bonos_retencion = sum(bono_retencion),
bonos_extra = sum(bono_extra),
presupuesto_dept = first(presupuesto)
), by = .(departamento_id, nombre_dept)
][
, `:=`(
incremento_salarial = salario_total_nuevo - salario_total_anterior,
costo_total_ajuste = (salario_total_nuevo - salario_total_anterior) + bonos_retencion + bonos_extra,
impacto_presupuestal_pct = round(((salario_total_nuevo - salario_total_anterior) + bonos_retencion + bonos_extra) / presupuesto_dept * 100, 2)
)
][
order(-costo_total_ajuste)
]
print("\n3. Impacto presupuestal por departamento:")
#> [1] "\n3. Impacto presupuestal por departamento:"
print(impacto_presupuestal[, .(nombre_dept, empleados, incremento_salarial,
costo_total_ajuste, impacto_presupuestal_pct)])
#> nombre_dept empleados incremento_salarial costo_total_ajuste
#> <char> <int> <num> <num>
#> 1: Ventas 5 28140 56623.2
#> 2: Finanzas 5 19978 43166.6
#> 3: RRHH 2 14080 19080.0
#> 4: Ingeniería 2 9144 18794.4
#> 5: Marketing 1 2796 4796.0
#> impacto_presupuestal_pct
#> <num>
#> 1: 9.44
#> 2: 8.63
#> 3: 6.36
#> 4: 2.35
#> 5: 1.20
# 4. Identificar empleados para reclasificación
reclasificacion <- empleados_ajuste[, .(
nombre,
departamento_id,
nivel_actual = nivel,
salario_ajustado,
umbral_senior,
necesita_reclasificacion = salario_ajustado >= umbral_senior & nivel == "Junior"
)][necesita_reclasificacion == TRUE]
if(nrow(reclasificacion) > 0) {
cat("\n4. Empleados que necesitan reclasificación:\n")
print(reclasificacion[, .(nombre, nivel_actual, salario_ajustado, umbral_senior)])
} else {
cat("\n4. No hay empleados que requieran reclasificación inmediata.\n")
}
#>
#> 4. Empleados que necesitan reclasificación:
#> nombre nivel_actual salario_ajustado umbral_senior
#> <char> <char> <num> <num>
#> 1: Empleado_B Junior 66554 60000
#> 2: Empleado_D Junior 73590 50000
#> 3: Empleado_K Junior 49396 45000
#> 4: Empleado_N Junior 69440 55000
# Resumen ejecutivo
cat("\n📊 RESUMEN EJECUTIVO DEL AJUSTE SALARIAL:\n")
#>
#> 📊 RESUMEN EJECUTIVO DEL AJUSTE SALARIAL:
cat(sprintf("• Total empleados afectados: %d\n", nrow(empleados_ajuste)))
#> • Total empleados afectados: 15
cat(sprintf("• Incremento salarial total: $%s\n",
format(sum(impacto_presupuestal$incremento_salarial), big.mark = ",")))
#> • Incremento salarial total: $74,138
cat(sprintf("• Costo total del ajuste: $%s\n",
format(sum(impacto_presupuestal$costo_total_ajuste), big.mark = ",")))
#> • Costo total del ajuste: $142,460.2
cat(sprintf("• Departamento más impactado: %s (%.2f%% del presupuesto)\n",
impacto_presupuestal[1, nombre_dept], impacto_presupuestal[1, impacto_presupuestal_pct]))
#> • Departamento más impactado: Ventas (9.44% del presupuesto)5.10 Mejores Prácticas para Joins
5.10.1 1. Elección del Método Adecuado
# ✅ Para joins únicos: usar "on ="
resultado <- tabla1[tabla2, on = .(key)]
# ✅ Para joins repetitivos en datos grandes: usar setkey
setkey(tabla1, key)
setkey(tabla2, key)
resultado <- tabla2[tabla1]
# ✅ Para análisis exploratorio: usar merge()
resultado <- merge(tabla1, tabla2, by = "key")
# ✅ Para modificar tabla existente: usar update join
tabla1[tabla2, on = .(key), nueva_col := i.columna]5.10.2 2. Gestión de Memoria en Joins
# ✅ HACER: Filtrar antes de join
tabla_pequeña <- tabla_grande[filtro_importante]
resultado <- tabla_pequeña[otra_tabla, on = .(key)]
# ❌ EVITAR: Join primero, filtrar después
resultado <- tabla_grande[otra_tabla, on = .(key)][filtro_importante]
# ✅ HACER: Usar .SDcols para limitar columnas en joins complejos
resultado <- tabla1[tabla2, on = .(key), .SDcols = columnas_necesarias]5.10.3 3. Troubleshooting de Joins
# Diagnosticar problemas de joins
cat("Claves duplicadas en tabla1:", anyDuplicated(tabla1, by = "key"), "\n")
cat("Claves faltantes:", sum(is.na(tabla1$key)), "\n")
cat("Rango de keys:", range(tabla1$key, na.rm = TRUE), "\n")
# Verificar resultado de join
cat("Filas antes del join:", nrow(tabla1), "\n")
cat("Filas después del join:", nrow(resultado), "\n")
cat("Columnas agregadas:", ncol(resultado) - ncol(tabla1), "\n")
🎯 Puntos Clave de Este Capítulo
merge()es intuitivo para joins simples,setkey()es óptimo para datos grandes- Sintaxis
onofrece flexibilidad sin modificar las tablas originales - Update joins con
:=permiten modificar tablas de forma eficiente - La elección del método depende del tamaño de datos y frecuencia de uso
- Filtrar antes de join mejora significativamente el rendimiento
- Always verify el resultado de joins complejos para evitar cartesian products