Analítica de Personas · Semestre otoño 2026 · Semana 2 · Prof. René Gempp
En people analytics, los datos casi nunca vienen en una sola tabla. El HRIS tiene una tabla de empleados, otra de evaluaciones de desempeño, otra de entrevistas de salida, otra de encuestas. Para responder preguntas de negocio, necesitas combinar estas tablas usando una columna en común (una clave).
En nuestro caso de InnovaCo:
Las funciones de unión (joins) de dplyr resuelven exactamente este problema. La idea es simple: "buscar coincidencias" entre dos tablas por una columna compartida.
left_join(): el join que más usarásleft_join() toma todas las filas de la tabla izquierda y les agrega columnas de la tabla derecha donde coincida la clave. Si no hay coincidencia, rellena con NA.
# Sintaxis básica
datos <- tabla_izquierda |>
left_join(tabla_derecha, by = "columna_clave")
# Ejemplo concreto de InnovaCo
datos <- innovaco |>
left_join(salidas, by = "id_empleado")
| id_empleado | edad | ... |
|---|---|---|
| INN-0001 | 30 | ... |
| INN-0002 | 35 | ... |
| INN-0003 | 42 | ... |
| INN-0004 | 25 | ... |
| id_empleado | tipo_salida |
|---|---|
| INN-0001 | Voluntaria |
| INN-0004 | Voluntaria |
| id_empleado | edad | ... | tipo_salida |
|---|---|---|---|
| INN-0001 | 30 | ... | Voluntaria |
| INN-0002 | 35 | ... | NA |
| INN-0003 | 42 | ... | NA |
| INN-0004 | 25 | ... | Voluntaria |
INN-0002 e INN-0003 no se fueron, así que no están en salidas. left_join() los mantiene y rellena con NA las columnas nuevas.
by# Cuando la clave se llama igual en ambas tablas
datos <- innovaco |>
left_join(salidas, by = "id_empleado")
# Si en una tabla la clave se llama "id" y en otra "id_empleado"
datos <- tabla_a |>
left_join(tabla_b, by = c("id_empleado" = "id"))
# Izquierda = derecha
# Cuando necesitas cruzar por dos columnas a la vez
# Ejemplo: encuesta por empleado Y por período
datos <- empleados |>
left_join(encuestas, by = c("id_empleado", "periodo"))
by?
R intentará unir por todas las columnas que tengan el mismo nombre en ambas tablas. Esto puede producir resultados inesperados si hay columnas con nombres coincidentes que no son la clave. Siempre especifica by explícitamente.
left_join() es el que usarás el 90% del tiempo, pero existen otros cuatro tipos. La diferencia es qué filas conservan cuando no hay coincidencia:
| Función | Conserva | Uso típico en RRHH |
|---|---|---|
left_join(A, B) |
Todas las filas de A. Si no hay match en B, rellena con NA. |
Enriquecer la tabla de empleados con datos de otra fuente |
right_join(A, B) |
Todas las filas de B. Si no hay match en A, rellena con NA. |
Rara vez. Es un left_join con las tablas al revés. |
inner_join(A, B) |
Solo filas que existen en ambas tablas. | Cuando solo quieres empleados que se fueron Y tienen entrevista de salida |
full_join(A, B) |
Todas las filas de ambas tablas. NA donde no hay match. |
Combinar dos fuentes donde ninguna es "la completa" |
anti_join(A, B) |
Filas de A que NO están en B. | ¿Quiénes se fueron y NO tuvieron entrevista de salida? |
# inner_join: solo empleados que se fueron (326 filas)
solo_salidas <- innovaco |>
inner_join(salidas, by = "id_empleado")
nrow(solo_salidas) # 326
# anti_join: empleados que NO se fueron (874 filas)
activos <- innovaco |>
anti_join(salidas, by = "id_empleado")
nrow(activos) # 874
left_join(). ¿Quieres solo los que tienen datos en ambas? → inner_join(). ¿Quieres saber quién NO está en la otra tabla? → anti_join().
Siempre verifica después de un join. Los tres chequeos esenciales:
# 1. ¿Cuántas filas tiene el resultado?
nrow(datos) # Debería ser 1200 con left_join
# 2. ¿Hay duplicados? (filas más que las esperadas)
datos |>
count(id_empleado) |>
filter(n > 1) # Si hay filas aquí, algo salió mal
# 3. ¿Los NA están donde esperamos?
datos |>
count(rotacion, tipo_salida)
# Los que NO se fueron deberían tener NA en tipo_salida
left_join() tienes más filas de las que empezaste, es porque la tabla derecha tiene duplicados en la clave. Por ejemplo, si un empleado tiene 3 registros de encuesta, el join creará 3 filas para ese empleado. Verifica siempre con nrow().
bind_rows(): apilar tablasA veces no necesitas cruzar tablas por una clave, sino simplemente apilar una debajo de otra (cuando tienen las mismas columnas). Esto es bind_rows():
# Ejemplo: juntar datos de dos oficinas
datos_stgo <- read_csv("oficina_santiago.csv")
datos_valpo <- read_csv("oficina_valparaiso.csv")
todos <- bind_rows(datos_stgo, datos_valpo)
# Si una tabla tiene una columna que la otra no tiene,
# bind_rows() rellena con NA. No da error.
left_join()Agrega columnas de otra tabla, cruzando por una clave.
A |> left_join(B, by = "id")
# A tiene 5 cols → resultado 8 cols
bind_rows()Agrega filas de otra tabla (misma estructura).
bind_rows(A, B)
# A tiene 100 filas, B tiene 50
# → resultado 150 filas
| Quiero... | Función | Ejemplo |
|---|---|---|
| Agregar columnas de otra tabla, conservando todos los empleados | left_join() | Empleados + datos de salida |
| Solo filas que existen en ambas tablas | inner_join() | Solo empleados que se fueron Y tienen encuesta |
| Filas de A que NO están en B | anti_join() | Empleados activos (sin salida registrada) |
| Apilar tablas con la misma estructura | bind_rows() | Datos de varias oficinas en una sola tabla |
| Error | Causa | Solución |
|---|---|---|
Error: `by` must be supplied |
No especificaste la columna de unión | Agrega by = "id_empleado" |
| El resultado tiene más filas de las esperadas | La tabla derecha tiene duplicados en la clave | Verifica con tabla_b |> count(clave) |> filter(n > 1) |
NA en columnas que no deberían tenerlos |
La clave no coincide (diferencias de mayúsculas, espacios, formato) | Compara las claves: setdiff(tabla_a$id, tabla_b$id) |
Columnas duplicadas con sufijo .x y .y |
Ambas tablas tienen una columna con el mismo nombre (que no es la clave) | Renombra antes del join, o usa suffix = c("_emp", "_sal") |