Joins, Tidy Data y Relaciones entre Tablas

Author

Scarlin López

Published

June 13, 2026

Dataset de práctica.

Code
library(tidyverse)

# Tabla 1: Clientes
clientes <- tibble(
  id_cliente = c("C001", "C002", "C003", "C004", "C005", "C006"),
  nombre     = c("Ana Torres", "Pedro Méndez", "María López", "Luis García", "Carmen Díaz", "Jorge Reyes"),
  ciudad     = c("Santo Domingo", "Santiago", "Santo Domingo", "La Vega", "San Pedro", "Santo Domingo"),
  tipo       = c("Premium", "Regular", "Premium", "Regular", "Regular", "Premium")
)

# Tabla 2: Facturas
facturas <- tibble(
  id_factura = c("F001", "F002", "F003", "F004", "F005", "F006", "F007", "F008"),
  id_cliente = c("C001", "C001", "C002", "C003", "C005", "C007", "C002", "C001"),
  mes        = c("Ene", "Feb", "Ene", "Feb", "Ene", "Feb", "Mar", "Ene"),
  monto      = c(15000, 22000, 8500, 31000, 4500, 12000, 9800, 15000)
)

# Tabla 3: Pagos
pagos <- tibble(
  id_factura   = c("F001", "F002", "F004", "F006", "F007"),
  monto_pagado = c(15000, 22000, 31000, 12000, 9800)
)

1 Diagnóstico Relacional

1.1

Code
facturas |> anti_join(clientes, by = "id_cliente")
# A tibble: 1 × 4
  id_factura id_cliente mes   monto
  <chr>      <chr>      <chr> <dbl>
1 F006       C007       Feb   12000

1.2

Code
clientes |> anti_join(facturas, by = "id_cliente")
# A tibble: 2 × 4
  id_cliente nombre      ciudad        tipo   
  <chr>      <chr>       <chr>         <chr>  
1 C004       Luis García La Vega       Regular
2 C006       Jorge Reyes Santo Domingo Premium

1.3

Code
facturas |> anti_join(pagos, by = "id_factura")
# A tibble: 3 × 4
  id_factura id_cliente mes   monto
  <chr>      <chr>      <chr> <dbl>
1 F003       C002       Ene    8500
2 F005       C005       Ene    4500
3 F008       C001       Ene   15000

1.4

Code
facturas |> count(id_cliente) |> filter(n > 1)
# A tibble: 2 × 2
  id_cliente     n
  <chr>      <int>
1 C001           3
2 C002           2

2 Aplicando los 4 Joins.

2.1

Code
nrow(clientes)
[1] 6
Code
clientes_con_facturas <- clientes |>
  left_join(facturas, by = "id_cliente")

nrow(clientes_con_facturas)
[1] 9
Note

¿Cuántas filas tiene el resultado y por qué tiene más filas que la tabla clientes?

El resultado tiene más filas que la tabla clientes porque la relación entre clientes y facturas es de uno a muchos, lo que significa que un mismo cliente puede tener varias facturas asociadas; por eso, al hacer el left_join(), cada factura genera una nueva fila y los clientes se repiten según la cantidad de facturas que tengan.

2.2

Code
clientes |>
  inner_join(facturas, by = "id_cliente")
# A tibble: 7 × 7
  id_cliente nombre       ciudad        tipo    id_factura mes   monto
  <chr>      <chr>        <chr>         <chr>   <chr>      <chr> <dbl>
1 C001       Ana Torres   Santo Domingo Premium F001       Ene   15000
2 C001       Ana Torres   Santo Domingo Premium F002       Feb   22000
3 C001       Ana Torres   Santo Domingo Premium F008       Ene   15000
4 C002       Pedro Méndez Santiago      Regular F003       Ene    8500
5 C002       Pedro Méndez Santiago      Regular F007       Mar    9800
6 C003       María López  Santo Domingo Premium F004       Feb   31000
7 C005       Carmen Díaz  San Pedro     Regular F005       Ene    4500
Note

¿Qué clientes desaparecen del resultado comparado con el left_join?

En el inner_join() desaparecen los clientes que no tienen facturas registradas, en este caso C004 y C006, porque este tipo de unión solo mantiene las filas que tienen coincidencia en ambas tablas.

2.3

Code
clientes |>
  full_join(facturas, by = "id_cliente")
# A tibble: 10 × 7
   id_cliente nombre       ciudad        tipo    id_factura mes   monto
   <chr>      <chr>        <chr>         <chr>   <chr>      <chr> <dbl>
 1 C001       Ana Torres   Santo Domingo Premium F001       Ene   15000
 2 C001       Ana Torres   Santo Domingo Premium F002       Feb   22000
 3 C001       Ana Torres   Santo Domingo Premium F008       Ene   15000
 4 C002       Pedro Méndez Santiago      Regular F003       Ene    8500
 5 C002       Pedro Méndez Santiago      Regular F007       Mar    9800
 6 C003       María López  Santo Domingo Premium F004       Feb   31000
 7 C004       Luis García  La Vega       Regular <NA>       <NA>     NA
 8 C005       Carmen Díaz  San Pedro     Regular F005       Ene    4500
 9 C006       Jorge Reyes  Santo Domingo Premium <NA>       <NA>     NA
10 C007       <NA>         <NA>          <NA>    F006       Feb   12000
Note

¿Qué fila extra aparece que no estaba en ninguna de las dos tablas originales?

La fila extra que aparece es la del cliente C007, porque está en la tabla de facturas pero no existe en la tabla de clientes; por eso el full_join() la incluye al combinar todas las filas de ambas tablas, completando con NA los datos que no tienen coincidencia.

2.4

Code
tabla_maestra <- clientes |>
  left_join(facturas, by = "id_cliente") |>
  left_join(pagos, by = "id_factura")

print(tabla_maestra)
# A tibble: 9 × 8
  id_cliente nombre       ciudad       tipo  id_factura mes   monto monto_pagado
  <chr>      <chr>        <chr>        <chr> <chr>      <chr> <dbl>        <dbl>
1 C001       Ana Torres   Santo Domin… Prem… F001       Ene   15000        15000
2 C001       Ana Torres   Santo Domin… Prem… F002       Feb   22000        22000
3 C001       Ana Torres   Santo Domin… Prem… F008       Ene   15000           NA
4 C002       Pedro Méndez Santiago     Regu… F003       Ene    8500           NA
5 C002       Pedro Méndez Santiago     Regu… F007       Mar    9800         9800
6 C003       María López  Santo Domin… Prem… F004       Feb   31000        31000
7 C004       Luis García  La Vega      Regu… <NA>       <NA>     NA           NA
8 C005       Carmen Díaz  San Pedro    Regu… F005       Ene    4500           NA
9 C006       Jorge Reyes  Santo Domin… Prem… <NA>       <NA>     NA           NA
Note

¿Cuántas filas tiene?, ¿Por qué algunos monto_pagado son NA?

Tiene 8 filas, porque al hacer el left_join entre clientes y facturas, los clientes se repiten según la cantidad de facturas que tienen. Algunos monto_pagado son NA porque no todas las facturas tienen un registro en la tabla de pagos, por lo que al unirlas esos valores quedan vacíos cuando no hay coincidencia.

3 Joins de Filtro: Auditoría

3.1

Code
clientes |> semi_join(facturas, by = "id_cliente")
# A tibble: 4 × 4
  id_cliente nombre       ciudad        tipo   
  <chr>      <chr>        <chr>         <chr>  
1 C001       Ana Torres   Santo Domingo Premium
2 C002       Pedro Méndez Santiago      Regular
3 C003       María López  Santo Domingo Premium
4 C005       Carmen Díaz  San Pedro     Regular
Note

Los clientes que sí tienen al menos una factura son C001, C002, C003 y C005, en total 4 clientes, porque semi_join() solo devuelve los registros de clientes que tienen coincidencia en la tabla de facturas.

Code
clientes |> anti_join(facturas, by = "id_cliente")
# A tibble: 2 × 4
  id_cliente nombre      ciudad        tipo   
  <chr>      <chr>       <chr>         <chr>  
1 C004       Luis García La Vega       Regular
2 C006       Jorge Reyes Santo Domingo Premium
Note

Los clientes que nunca han facturado son:

C004 — Luis García — La Vega C006 — Jorge Reyes — Santo Domingo

Code
facturas |> anti_join(clientes, by = "id_cliente")
# A tibble: 1 × 4
  id_factura id_cliente mes   monto
  <chr>      <chr>      <chr> <dbl>
1 F006       C007       Feb   12000
Note

Las facturas de clientes no registrados son:

F006 — C007 — 12000

Esto ocurre porque el id_cliente C007 no existe en la tabla de clientes, por lo que anti_join() identifica esa factura como un registro huérfano sin correspondencia en la tabla principal.

4 Manejo de Duplicados.

4.1

Code
facturas_sin_dup <- facturas |> distinct()

nrow(facturas_sin_dup)
[1] 8
Note

8 filas.

4.2

Code
facturas_max <- facturas |>
  group_by(id_cliente) |>
  slice_max(order_by = monto, n = 1) |>
  ungroup()

facturas_max
# A tibble: 5 × 4
  id_factura id_cliente mes   monto
  <chr>      <chr>      <chr> <dbl>
1 F002       C001       Feb   22000
2 F007       C002       Mar    9800
3 F004       C003       Feb   31000
4 F005       C005       Ene    4500
5 F006       C007       Feb   12000

4.3

Code
tabla_maestra_nueva <- clientes |>
  left_join(facturas_sin_dup, by = "id_cliente") |>
  left_join(pagos, by = "id_factura")

tabla_maestra_nueva
# A tibble: 9 × 8
  id_cliente nombre       ciudad       tipo  id_factura mes   monto monto_pagado
  <chr>      <chr>        <chr>        <chr> <chr>      <chr> <dbl>        <dbl>
1 C001       Ana Torres   Santo Domin… Prem… F001       Ene   15000        15000
2 C001       Ana Torres   Santo Domin… Prem… F002       Feb   22000        22000
3 C001       Ana Torres   Santo Domin… Prem… F008       Ene   15000           NA
4 C002       Pedro Méndez Santiago     Regu… F003       Ene    8500           NA
5 C002       Pedro Méndez Santiago     Regu… F007       Mar    9800         9800
6 C003       María López  Santo Domin… Prem… F004       Feb   31000        31000
7 C004       Luis García  La Vega      Regu… <NA>       <NA>     NA           NA
8 C005       Carmen Díaz  San Pedro    Regu… F005       Ene    4500           NA
9 C006       Jorge Reyes  Santo Domin… Prem… <NA>       <NA>     NA           NA
Code
nrow(tabla_maestra_nueva)
[1] 9
Note

El número de filas cambió porque al usar facturas_sin_dup en el join,la estructura de coincidencias entre clientes y facturas se modifica,lo que puede afectar la expansión de filas en la tabla final.

5 Tidy Data con pivot.

5.1

Code
ventas_por_mes <- tabla_maestra |>
  filter(!is.na(mes)) |>
  group_by(nombre, mes) |>
  summarise(total = sum(monto, na.rm = TRUE), .groups = "drop") |>
  pivot_wider(
    names_from = mes,
    values_from = total,
    values_fill = 0
  )

ventas_por_mes
# A tibble: 4 × 4
  nombre         Ene   Feb   Mar
  <chr>        <dbl> <dbl> <dbl>
1 Ana Torres   30000 22000     0
2 Carmen Díaz   4500     0     0
3 María López      0 31000     0
4 Pedro Méndez  8500     0  9800

5.2

Code
ventas_largo <- ventas_por_mes |>
  pivot_longer(
    cols = -nombre,
    names_to = "mes",
    values_to = "total"
  )

ventas_largo
# A tibble: 12 × 3
   nombre       mes   total
   <chr>        <chr> <dbl>
 1 Ana Torres   Ene   30000
 2 Ana Torres   Feb   22000
 3 Ana Torres   Mar       0
 4 Carmen Díaz  Ene    4500
 5 Carmen Díaz  Feb       0
 6 Carmen Díaz  Mar       0
 7 María López  Ene       0
 8 María López  Feb   31000
 9 María López  Mar       0
10 Pedro Méndez Ene    8500
11 Pedro Méndez Feb       0
12 Pedro Méndez Mar    9800

5.3

Code
ventas_largo |>
  group_by(mes) |>
  summarise(total_vendido = sum(total, na.rm = TRUE)) |>
  arrange(desc(total_vendido))
# A tibble: 3 × 2
  mes   total_vendido
  <chr>         <dbl>
1 Feb           53000
2 Ene           43000
3 Mar            9800