Semana 7 — Joins, Tidy Data y Relaciones entre Tablas

🎯 Objetivo: aprender a combinar, filtrar y auditar tablas mediante diferentes tipos de joins (controlando duplicados y registros huérfanos), así como a reestructurar datos aplicando los principios de Tidy Data.

Author

Adrian Rafael Guigni Guillen

- Librerías

Code
library(tidyverse)  
library(kableExtra)

- Dataset de practica:

Code
# 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)
)
Clientes
id_cliente nombre ciudad tipo
C001 Ana Torres Santo Domingo Premium
C002 Pedro Méndez Santiago Regular
C003 María López Santo Domingo Premium
C004 Luis García La Vega Regular
C005 Carmen Díaz San Pedro Regular
C006 Jorge Reyes Santo Domingo Premium
Facturas
id_factura id_cliente mes monto
F001 C001 Ene 15000
F002 C001 Feb 22000
F003 C002 Ene 8500
F004 C003 Feb 31000
F005 C005 Ene 4500
F006 C007 Feb 12000
F007 C002 Mar 9800
F008 C001 Ene 15000
Pagos
id_factura monto_pagado
F001 15000
F002 22000
F004 31000
F006 12000
F007 9800

Parte 1 — Diagnóstico Relacional

1.1 - Usa anti_join() para identificar qué facturas tienen un id_cliente que no existe en la tabla clientes. Documenta el hallazgo en un comentario.

Code
facturas_sin_cliente <- anti_join(facturas, clientes, by = "id_cliente")
Facturas sin cliente
id_factura id_cliente mes monto
F006 C007 Feb 12000

La factura F006 pertenece al cliente C007, el cual no existe en la tabla clientes.

1.2 - Usa anti_join() para identificar qué clientes no tienen ninguna factura registrada. ¿Cuántos son?

Code
sin_factura_registrada <- clientes |> 
  anti_join(facturas, clientes, by = "id_cliente")
Clientes sin factura registrada
id_cliente nombre ciudad tipo
C004 Luis García La Vega Regular
C006 Jorge Reyes Santo Domingo Premium

Hay 2 clientes sin facturas

1.3 - Usa anti_join() para identificar qué facturas no tienen pago registrado. Lista sus IDs.

Code
sin_pago_registrado <- anti_join(facturas, pagos, by = "id_factura" )
Facturas sin pago registrado
id_factura id_cliente mes monto
F003 C002 Ene 8500
F005 C005 Ene 4500
F008 C001 Ene 15000

IDs: F003, F005, F008

1.4 - Verifica si hay duplicados en la tabla facturas usando count() + filter(n > 1). ¿Cuál es el id_cliente que tiene una factura duplicada? Documenta en un comentario qué decisión tomarías.

Code
duplicados <- facturas |>
  count(id_cliente) |>
  filter(n > 1)
Duplicados (facturas)
id_cliente n
C001 3
C002 2

El cliente C001 tiene 3 facturas. El cliente C002 tiene 2 facturas.

Antes de eliminar alguna factura, primero me fijaria si son distintas, ya que un cliente puede tener varias facturas. Es entonces que tomaria una decision

Parte 2 — Aplicando los 4 Joins

2.1 — left_join: Une clientes con facturas por id_cliente. Llama al resultado clientes_con_facturas. ¿Cuántas filas tiene el resultado y por qué tiene más filas que la tabla clientes?

Code
nrow(clientes)
[1] 6
Code
clientes_con_facturas <- clientes |> left_join(facturas, by = "id_cliente")
nrow(clientes_con_facturas)
[1] 9
Resultado (left_join)
id_cliente nombre ciudad tipo id_factura mes monto
C001 Ana Torres Santo Domingo Premium F001 Ene 15000
C001 Ana Torres Santo Domingo Premium F002 Feb 22000
C001 Ana Torres Santo Domingo Premium F008 Ene 15000
C002 Pedro Méndez Santiago Regular F003 Ene 8500
C002 Pedro Méndez Santiago Regular F007 Mar 9800
C003 María López Santo Domingo Premium F004 Feb 31000
C004 Luis García La Vega Regular NA NA NA
C005 Carmen Díaz San Pedro Regular F005 Ene 4500
C006 Jorge Reyes Santo Domingo Premium NA NA NA

Hay más filas porque algunos clientes tienen más de una factura, entonces se agregan al unir ambas filas.

2.2 — inner_join: Une clientes con facturas usando inner_join. ¿Qué clientes desaparecen del resultado comparado con el left_join? Documenta en un comentario.

Code
clientes_con_facturas_inner_join <- clientes |> inner_join(facturas, by = "id_cliente")
Resultado (inner_join)
id_cliente nombre ciudad tipo id_factura mes monto
C001 Ana Torres Santo Domingo Premium F001 Ene 15000
C001 Ana Torres Santo Domingo Premium F002 Feb 22000
C001 Ana Torres Santo Domingo Premium F008 Ene 15000
C002 Pedro Méndez Santiago Regular F003 Ene 8500
C002 Pedro Méndez Santiago Regular F007 Mar 9800
C003 María López Santo Domingo Premium F004 Feb 31000
C005 Carmen Díaz San Pedro Regular F005 Ene 4500

Desaparecen: Luis García (C004) y Jorge Reyes (C006). Porque no tienen facturas registradas.

2.3 — full_join:

Une clientes con facturas usando full_join. ¿Qué fila extra aparece que no estaba en ninguna de las dos tablas originales? Explica en un comentario por qué aparece esa fila.

Code
clientes_con_facturas_full_join <- clientes |> full_join(facturas, by = "id_cliente")
Resultado (full_join)
id_cliente nombre ciudad tipo id_factura mes monto
C001 Ana Torres Santo Domingo Premium F001 Ene 15000
C001 Ana Torres Santo Domingo Premium F002 Feb 22000
C001 Ana Torres Santo Domingo Premium F008 Ene 15000
C002 Pedro Méndez Santiago Regular F003 Ene 8500
C002 Pedro Méndez Santiago Regular F007 Mar 9800
C003 María López Santo Domingo Premium F004 Feb 31000
C004 Luis García La Vega Regular NA NA NA
C005 Carmen Díaz San Pedro Regular F005 Ene 4500
C006 Jorge Reyes Santo Domingo Premium NA NA NA
C007 NA NA NA F006 Feb 12000

Aparece C007 porque está en facturas pero no en clientes. Lo que hace full_join() es que completa los datos faltantes con NA.

2.4 — Joins encadenados: Construye una tabla maestra uniendo las tres tablas en un solo pipeline:

Code
tabla_maestra <- clientes |>
  left_join(facturas, by = "id_cliente") |>
  left_join(pagos, by = "id_factura")
Tabla maestra
id_cliente nombre ciudad tipo id_factura mes monto monto_pagado
C001 Ana Torres Santo Domingo Premium F001 Ene 15000 15000
C001 Ana Torres Santo Domingo Premium F002 Feb 22000 22000
C001 Ana Torres Santo Domingo Premium F008 Ene 15000 NA
C002 Pedro Méndez Santiago Regular F003 Ene 8500 NA
C002 Pedro Méndez Santiago Regular F007 Mar 9800 9800
C003 María López Santo Domingo Premium F004 Feb 31000 31000
C004 Luis García La Vega Regular NA NA NA NA
C005 Carmen Díaz San Pedro Regular F005 Ene 4500 NA
C006 Jorge Reyes Santo Domingo Premium NA NA NA NA

¿Cuántas filas tiene? 9

¿Por qué algunos monto_pagado son NA? Sencillamente porque no hay un pago de forma registrada.

Parte 3 — Joins de Filtro: Auditoría

3.1 - Usa semi_join() para obtener solo los clientes que sí tienen al menos una factura. ¿Cuántos son?

Code
clientes_con_facturas_semi_join <- clientes |> semi_join(facturas, by = "id_cliente")
Resultado (semi_join)
id_cliente nombre ciudad tipo
C001 Ana Torres Santo Domingo Premium
C002 Pedro Méndez Santiago Regular
C003 María López Santo Domingo Premium
C005 Carmen Díaz San Pedro Regular

Son: 4

3.2 - Usa anti_join() para obtener los clientes que nunca han facturado. Muestra su nombre y ciudad.

Code
clientes_con_facturas_anti_join <- clientes |> anti_join(facturas, by = "id_cliente") |> select(id_cliente, nombre, ciudad)
Resultado (anti_join)
id_cliente nombre ciudad
C004 Luis García La Vega
C006 Jorge Reyes Santo Domingo

3.3 - Usa anti_join() al revés: ¿qué facturas pertenecen a clientes que no están registrados? Muestra el id_factura, id_cliente y monto.

Code
clientes_no_registrados <- facturas |> anti_join(clientes, by = "id_cliente") |> select(id_factura, id_cliente, monto)
Resultado (anti_join)
id_factura id_cliente monto
F006 C007 12000

Parte 4 — Manejo de Duplicados

4.1 — Eliminar duplicados exactos:

Code
#facturas_sin_dup <- facturas |> filter(!duplicated(.))  <----- no funciona, tuve que usar: 
facturas_sin_dup <- facturas |> distinct()
Facturas sin duplicados
id_factura id_cliente mes monto
F001 C001 Ene 15000
F002 C001 Feb 22000
F003 C002 Ene 8500
F004 C003 Feb 31000
F005 C005 Ene 4500
F006 C007 Feb 12000
F007 C002 Mar 9800
F008 C001 Ene 15000

¿Cuántas filas quedan? 8

4.2 — Quedarte con la factura de mayor monto por cliente:

Code
facturas_max <- facturas |>
  group_by(id_cliente) |>
  slice_max(order_by = monto, n = 1) |>
  ungroup()
Facturas de mayor monto por cliente
id_factura id_cliente mes monto
F002 C001 Feb 22000
F007 C002 Mar 9800
F004 C003 Feb 31000
F005 C005 Ene 4500
F006 C007 Feb 12000

4.3 - Vuelve a construir la tabla_maestra de la Parte 2.4 pero esta vez usando facturas_sin_dup en lugar de facturas. ¿Cambió el número de filas?

Code
tabla_maestra2 <- clientes |>
  left_join(facturas_sin_dup, by = "id_cliente") |>
  left_join(pagos, by = "id_factura")
Tabla maestra 2
id_cliente nombre ciudad tipo id_factura mes monto monto_pagado
C001 Ana Torres Santo Domingo Premium F001 Ene 15000 15000
C001 Ana Torres Santo Domingo Premium F002 Feb 22000 22000
C001 Ana Torres Santo Domingo Premium F008 Ene 15000 NA
C002 Pedro Méndez Santiago Regular F003 Ene 8500 NA
C002 Pedro Méndez Santiago Regular F007 Mar 9800 9800
C003 María López Santo Domingo Premium F004 Feb 31000 31000
C004 Luis García La Vega Regular NA NA NA NA
C005 Carmen Díaz San Pedro Regular F005 Ene 4500 NA
C006 Jorge Reyes Santo Domingo Premium NA NA NA NA

El numero de filas no cambio, sigue siendo 9. no había duplicados exactos para eliminar

Parte 5 — Tidy Data con pivot

5.1 — Formato ancho: A partir de tabla_maestra (usando facturas_sin_dup), construye una tabla que muestre cuánto facturó cada cliente por mes. Usa group_by() + summarise() primero, luego pivot_wider():

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     # Rellenar con 0 donde no hay ventas
  )
Ventas por mes
nombre Ene Feb Mar
Ana Torres 30000 22000 0
Carmen Díaz 4500 0 0
María López 0 31000 0
Pedro Méndez 8500 0 9800

5.2 — Volver a formato largo: Toma ventas_por_mes y conviértelo de vuelta a formato largo con pivot_longer():

Code
ventas_largo <- ventas_por_mes |>
  pivot_longer(
    cols      = -nombre,      # Todo excepto la columna nombre
    names_to  = "mes",
    values_to = "total"
  )
Formato largo
nombre mes total
Ana Torres Ene 30000
Ana Torres Feb 22000
Ana Torres Mar 0
Carmen Díaz Ene 4500
Carmen Díaz Feb 0
Carmen Díaz Mar 0
María López Ene 0
María López Feb 31000
María López Mar 0
Pedro Méndez Ene 8500
Pedro Méndez Feb 0
Pedro Méndez Mar 9800

5.3 - Con ventas_largo, usa group_by() + summarise() para calcular el total vendido por mes y ordena de mayor a menor.

Code
total_vendido_mes <- ventas_largo |> 
  group_by(mes) |> 
  summarise(
    total_vendido = sum(total), .groups = "drop"
  ) |>
  arrange(desc(total_vendido))
Formato largo
mes total_vendido
Feb 53000
Ene 43000
Mar 9800