Tema 03 - Datos: Tratamiento, Limpieza y Preparación

De datos en bruto a información útil para decidir con tidyverse

Pedro Albarrán

Dpto. de Fundamentos del Análisis Económico. Universidad de Alicante

Henry Redondo

Introducción. Datos Ordenados

¿Qué es una fila en cada tabla?

RetailCorp, cadena con 12 tiendas en España, nos manda las mismas ventas de enero y febrero de 2023 en tres formas distintas:

(A)

id_venta id_tienda mes total
222 1 1 252,6
83 2 1 757,3
54 2 2 232,1
406 2 2 325,6

(B)

id_tienda mes ingresos
1 1 14.674
1 2 7.668
2 1 10.502
2 2 10.355

(C)

id_tienda mes_1 mes_2
1 14.674 7.668
2 10.502 10.355

En parejas, 3 minutos:

  1. ¿Qué es una fila en (A)? ¿y en (B)? ¿y en (C)?: ¿una venta? ¿una tienda-mes? ¿una tienda?

  2. ¿Con cuál se responde “¿cuánto vendió la tienda 1 en febrero?” sin calcular nada?

  3. En (C), ¿mes_1 es una variable o es un valor de la variable mes?

¿Y esta columna es una variable?

Un gerente nos envía este Excel:

tienda ventas_ene ventas_feb
Madrid Centro 14.674 € 7.668 €
Barcelona Diagonal 10.502 € 10.355 €
Total 25.176 € 18.023 €

En parejas, 3 minutos: ¿qué falla en cada punto?

  1. La fila Total: ¿es una observación del mismo tipo que las otras dos?

  2. La columna ventas_ene: ¿es una variable o son dos cosas pegadas (mes y valor)?

  3. La celda 14.674 €: ¿es un número o un texto? ¿podemos sumarla?

Datos ordenados (“tidy”):

  1. cada columna, una variable

  2. cada fila, una observación (todas al mismo nivel)

  3. cada celda, un valor

load("data/retail_data.RData")
  • El 80% del tiempo del analista es trabajo “sucio” de limpieza y preparación; tidyverse está pensado para trabajar con datos ordenados

Transformación de datos (una tabla)

Funciones de transformación de datos

  • La mayoría de operaciones pueden realizarse combinando 5 “verbos”:

    1. select(): selecciona columnas (variables)

    2. filter(): filtra (extraer) filas

    3. mutate(): crea nuevas columnas

    4. arrange(): ordena filas

    5. summarize(): crea resúmenes de la tabla

    • Más la tubería %>% o |>

    • y group_by()

  • NOTA: las funciones auxiliares (elegir columnas por patrón, contar, recodificar, fechas, etc.) están en el Anexo: chuleta de funciones auxiliares; se consulta cuando hace falta, no se estudia.

  • Todos tienen como primer argumento un data frame, los siguientes describen qué hacer (con columnas o filas) y devuelven otro data frame

1. select()

  • Selecciona variables por nombres o posiciones de columnas, separados por comas
  • Ej., un analista solo necesita información básica de ventas
select(ventas, id_venta, fecha, id_tienda, id_producto, total)

select(ventas, 1:2, 5, 4, 12)

2. filter()

  • Conserva filas en las que una condición lógica (o varias separadas por comas) es verdadera
  • Caso de Uso: Gerente quiere analizar ventas específicas con determinadas características
ventas_top      <- filter(ventas, total > 1000)

ventas_ene_2024 <- filter(ventas, año == 2024, mes == 1)

ventas_mad_bcn  <- filter(ventas, id_tienda %in% c(1, 2))

ventas_premium  <- filter(ventas, 
                     total > 1000 & descuento_porcentaje == 0)

Encadenando operaciones con tuberías: %>% o |>

  • Las operaciones encadenadas crean objetos intermedios o no son legibles
ventas_top        <- filter(ventas, total > 1000)
ventas_top_markt  <- select(ventas_top, 
                       fecha, id_cliente, id_producto, total)

ventas_top_markt <- select(filter(ventas, total > 1000),
                      fecha, id_cliente, id_producto, total)
  • datos %>% filter(condicion) equivale a filter(datos, condicion)

  • El anidamiento con tuberías sigue el flujo natural de lectura

    • Toma una tabla y pásala a un comando que acepta y produce un data frame
    • Toma la nueva tabla resultante y pásala a otro comando
ventas_top_markt <- ventas |>                   
                     filter(total > 1000) |>     
                     select(fecha, id_cliente, id_producto, total)

3. mutate()

  • Crea o modifica variables mediante una fórmula a partir de otras columnas

ventas2 <- ventas |>
  mutate(
    precio_final_unitario = total / cantidad,
    es_inicio_mes         = day(fecha) <= 7
  )
  • Funciones para operar con fechas (usando lubridate)
ventas_tiempo <- ventas |>
  mutate(
    fecha_completa = as.Date(fecha),  # tipo de objeto "fecha"
    semana_año = week(fecha),
    nombre_mes = month(fecha, label = TRUE),
    dias_desde_venta = Sys.Date() - fecha
  )

4. arrange()

  • re-ordena las filas de un data frame (todas las columnas)

    • en orden ascendente (por defecto) o descendente con desc()

  • Caso de Uso: Top 5 ventas más altas
ventas |> 
  arrange(desc(total)) |> slice(5) |> 
  select(id_venta, fecha, total)  
  • Ordenamientos múltiples: ordena por la primera variable y luego, en caso de empate, por la siguiente, etc.
ventas |>
  arrange(id_tienda, desc(total)) |>
  select(id_tienda, id_venta, total)

5. summarize()

  • Crea un nuevo conjunto de datos de una sola fila, con variables nuevas de un solo valor que resumen los datos completos

  • Caso de Uso: KPIs para el dashboard ejecutivo
KPIs <- ventas |>
  summarize(
    total_ventas       = n(),           # Volumen (núm. de filas)
    ingresos_totales   = sum(total),    # Ingresos
    ingresos_promedio  = mean(total),
    ingresos_mediano   = median(total),
    descuento_promedio = mean(descuento_porcentaje), # Descuentos
    descuento_total    = sum(descuento_aplicado),
    unidades_vendidas  = sum(cantidad),             # Productos
    clientes_unicos    = n_distinct(id_cliente)     # Clientes 
                         # (núm. de filas distintas)
  )

group_by(): Análisis por Grupos

  • Cambia el alcance: cada función actúa dentro de grupos, no sobre toda la tabla

  • Ejemplo: encontrar la fecha con la mayor venta por tienda

ventas |> group_by(id_tienda) |>
  arrange(desc(total)) |>  
  slice(1) |>                     # Fila 1 (de cada grupo)
  select(id_tienda, fecha, total)
  • group_by() + summarize() = cambia el nivel de análisis, agregando, p.e., de transacciones a tiendas, productos, etc.

    • En Excel: Tablas dinámicas, AGRUPARPOR() (y SUMAR.SI/SUMIF)
ventas                             # tabla a nivel de transacción
ventas |>
  summarize(ingresos = sum(total)) # resumen global
ventas |>
  group_by(id_tienda) |>
  summarize(ingresos = sum(total)) # resumen por tienda
                                   # tabla a nivel de tienda

group_by() (cont.)

  • group_by() + summarize() reduce filas: nueva tabla agregada a nivel de los grupos
tabla_sum <- ventas |> group_by(id_tienda) |>
  summarize(ingresos = sum(total))
  • group_by() + mutate() mantiene filas: añade columnas calculadas por grupo a nivel de la tabla original
tabla_mut <- ventas |> group_by(id_tienda) |>
  mutate(ingresos = sum(total))
  • Ejemplo: Porcentaje de las ventas que representa cada transacción
ventas |> group_by(id_tienda) |>
  mutate(
    ventas_tienda = sum(total),
    pct_de_ventas = total / ventas_tienda * 100
  )

ungroup()

  • IMPORTANTE: No olvidar ungroup() o .groups = "drop" después de terminar operaciones agrupadas

  • Cálculo de Porcentajes Globales: sin desagrupar, el segundo sum(total) suma por tienda → siempre da 100%

ventas |>
  group_by(id_tienda) |>
  mutate(ventas_tienda = sum(total)) |>
  ungroup() |>
  mutate(porcentaje = ventas_tienda / sum(total) * 100)

# `.groups = "drop"` es de summarize(), NO de mutate()
ventas |>
  group_by(id_tienda) |>
  summarize(ventas_tienda = sum(total), .groups = "drop") |>
  mutate(porcentaje = ventas_tienda / sum(total) * 100)

PRÁCTICA en clase (1): porcentajes que siempre dan 100

  • Con la tabla ventas y solo con lo visto hasta aquí:

    1. Calcula los ingresos de 2023 de cada tienda y el porcentaje que representan sobre los ingresos totales de la cadena en 2023

    2. ¿Qué tienda tiene el porcentaje mayor? ¿Cuánto es?

    3. Un compañero propone este código para (a). Ejecútalo: ¿qué devuelve? ¿por qué?

ventas |>
  filter(año == 2023) |>
  group_by(id_tienda) |>
  mutate(pct = total / sum(total) * 100) |>
  summarize(pct_tienda = sum(pct))

Operaciones adicionales de limpieza

  • El informe de un gerente trae dos variables pegadas en una columna: separate() divide, unite() combina
informe <- tibble(tienda_region = c("Madrid Centro/Centro",
                                    "Alicante Playa/Levante"),
                  ventas        = c(14674, 9310))

informe |>
  separate(tienda_region, into = c("tienda", "region"), sep = "/")

informe |>
  separate(tienda_region, into = c("tienda", "region"), sep = "/") |>
  unite(etiqueta, region, tienda, sep = " - ")
  • Nombres de columna con espacios o símbolos: acento invertido (mejor, renombrarlos)
datos_problema |>
  rename(nombre_producto = `Nombre Producto`,
         precio_euros    = `Precio (€)`)

Transformación de Datos: Pivotar

¿Qué es una fila aquí? (otra vez)

Formato ANCHO (informe de los gestores)

tienda Q1 Q2 Q3 Q4
Madrid 145 158 151 169
Barcelona 152 164 156 175
Valencia 138 151 149 162
ventas_ancho

Formato LARGO (los mismos datos)

tienda trimestre ventas
Madrid Q1 145
Madrid Q2 158
Madrid Q3 151
Madrid Q4 169
Barcelona Q1 152
… … …

En parejas, 3 minutos:

  1. ¿Qué es una fila en la tabla ancha? ¿y en la larga? ¿Cuántas filas tiene cada una?

  2. En la ancha, Q1 ¿es una variable o un valor? ¿Dónde está el trimestre en cada tabla?

  3. ¿Con cuál se puede responder “¿qué trimestres superan 160 en ventas?” con un filter()?

pivot_longer(): girar de ancho a largo

  • Girar para analizar los datos
ventas_largo <- ventas_ancho |>
  pivot_longer(
    cols = Q1:Q4,              
    names_to = "trimestre",    
    values_to = "ventas"       
  )
  1. tabla a cambiar de forma

  2. nombres o índices de las columnas a girar (representan valores)

  3. nombre para la nueva variable que tendrá, como valores, las columnas a girar

  4. nombre para la nueva variable que tendrá como valores las antiguas celdas

pivot_wider(): girar de largo a ancho

  • Girar para crear tabla de presentación
ventas_largo |>             
  pivot_wider(
    names_from = trimestre, 
    values_from = ventas    
  )
  1. tabla a cambiar de forma

  2. nombre de la variable cuyos valores dan nombre a las nuevas columnas

  3. nombre de la variable de cuyas celdas toman los valores las nuevas columnas

PRÁCTICA en clase (2): girar para poder responder

  • Partimos de ventas_ancho (3 tiendas, columnas Q1 a Q4):

    1. ¿En qué trimestre vendió más la cadena entera? ¿Cuánto?

    2. Prepara la tabla de presentación para el informe: una fila por tienda, los trimestres en columnas y una última columna Total con la venta anual de cada tienda

    3. ¿Con qué formato has hecho (a)? ¿Y (b)? ¿Por qué no al revés?

Datos Relacionales

Por Qué Múltiples Tablas

  • Analizar datos suele implicar múltiples tablas

    • diferentes orígenes: ej., dptos. de empresa (personal, ventas, almacén)

    • almacenamiento más eficiente: elementos “similares” dentro de una tabla y diferentes entre ellas

  • Para poder combinar información los datos deben ser relacionales: cada par de tablas están relacionadas mediante identificadores comunes llamados claves

    • Primaria (o interna): identifican de forma única cada observación en una tabla. Puede ser una sola variable o múltiples

    • Secundaria (o externa): señala a la clave primaria de otra tabla

  • Una clave primaria y su externa asociada en otra tabla forman una relación: de uno-a-muchos, de uno-a-uno, de muchos-a-muchos, de muchos-a-uno

  • Antes de unir hay que saber qué es una fila en cada tabla: es lo que decide si la unión añade información o inventa filas

Tres tablas a tres niveles distintos

  • La dirección pregunta: ¿cuánto facturó cada tienda en 2023, a los precios vigentes en cada fecha, y cómo va frente a su objetivo mensual?
tabla una fila es… filas clave primaria
ventas una venta 5.000 id_venta
precios un producto en un periodo de vigencia 276 id_producto + desde
objetivos una tienda en un mes de 2023 144 id_tienda + año + mes
precios     # histórico de precios: productos solo guarda el precio de HOY
objetivos   # objetivo de facturación de cada tienda en cada mes de 2023
  • Ninguna de las dos se empareja con ventas una fila con una fila. Lo que dice si la unión está bien no es el nombre de la función: es el número de filas antes y después

Uniones de transformación

  • Añaden nuevas variables a una tabla desde filas coincidentes en otra.
  • Tenemos estos datos.

  • ¿Cómo conseguimos unirlos adecuadamente en R y en Excel?

  • Con cbind() o bind_columns() o copiando y pegando en Excel: nuevas columnas para filas en el mismo orden
tabla1 <- import("data/DosTablas.xlsx", sheet = 1)
tabla2 <- import("data/DosTablas.xlsx", sheet = 2)
union  <- inner_join(tabla1, tabla2, by = "Vendedor")
  • Aquí cada vendedor aparece una vez en cada tabla: una fila casa con una fila y salen las filas esperadas. Casi nunca es así

¿Qué unión necesito aquí?

  • La zona sombreada son las filas que quedan. En las uniones externas, las filas sin pareja se rellenan con NA

Quiero la región de cada venta y no perder ni una venta (hay tiendas sin ventas, me da igual)

# 5000 filas: las mismas que ventas
ventas |> left_join(tiendas, by = "id_tienda")

Quiero solo las ventas atendidas por un empleado (las ventas online no tienen id_empleado)

# 4850 filas: se van 150 ventas online
ventas |> inner_join(empleados, by = "id_empleado")

Quiero el margen medio de TODAS las categorías del catálogo, aunque alguna no tenga productos

productos |>
  mutate(margen = precio - costo) |>
  right_join(categorias, by = c("id_categoria" = "categoria"))

Quiero cuadrar el listado de RRHH con el de bonos sin perder a nadie de ninguna de las dos listas

empleados |>
  full_join(promociones_empleado,
            by = c("id_tienda"    = "cod_tienda",
                   "cod_empleado" = "cod_empleado"))
  • Antes de unir, decidir qué filas deben quedar y contarlas después: si el número cambia sin querer, la unión está mal

El precio no vive en la venta

  • ventas registra unidades; precio_unitario es el precio de hoy del catálogo. El del producto 7 subió el 1-sep-2023
id_producto precio desde hasta
7 174,58 2020-01-01 2021-12-31
7 188,55 2022-01-01 2023-08-31
7 211,18 2023-09-01 2024-12-31
  • Unimos por la clave, id_producto, y contamos las filas antes y después
ventas_2023 <- ventas |> filter(año == 2023)
nrow(ventas_2023)                                               # 2000

ventas_2023 |> left_join(precios, by = "id_producto") |> nrow()  # 3714
  • 1.714 filas que no existieron nunca: cada venta se ha copiado tantas veces como periodos de precio tiene su producto

El precio no vive en la venta (cont.)

  • Una venta del 19-ago-2023 casa con los tres periodos; solo uno estaba vigente
ventas_2023 |> filter(id_venta == 3662) |>
  select(id_venta, fecha, id_producto, cantidad) |>
  left_join(precios, by = "id_producto")   # 3 filas: 174,58 / 188,55 / 211,18
  • La condición que faltaba es la fecha: la clave es id_producto y el periodo
ventas_2023_precio <- ventas_2023 |>
  left_join(precios, by = "id_producto") |>
  filter(fecha >= desde, fecha <= hasta)

nrow(ventas_2023_precio)                # 2000: una fila por venta, otra vez
sum(is.na(ventas_2023_precio$precio))   # 0: ninguna venta sin precio vigente
  • Red de seguridad: relationship detiene la unión en vez de duplicar
ventas_2023 |> left_join(precios, by = "id_producto",
                         relationship = "many-to-one")
# Error: Each row in `x` must match at most 1 row in `y`

¿Cuánto facturó cada tienda en 2023?

facturacion_tienda <- ventas_2023_precio |>
  group_by(id_tienda) |>
  summarize(facturacion = sum(cantidad * precio), .groups = "drop") |>
  arrange(desc(facturacion))
id_tienda facturación (€)
4 126.255,61
2 114.491,31
9 113.042,98
… …
11 90.444,44
  • Total de la cadena: 1.251.667 €

  • Con el precio del catálogo habrían salido 1.268.551 €

  • La diferencia, 16.884 €, es un 1,3%: poco como porcentaje, bastante en la cuenta de resultados

Sobre el argumento by

  • Las claves, es decir, las variables que relacionan ambas tablas, se indican con

    • by = "varX", cuando la clave es una variable

    • by = c("varX", "varY"), cuando son varias variables: deben coincidir todas

  • Si los nombre son distintos en cada tabla, se empareja x1 en la primera con y1 en la segunda con by = c("x1" = "y1", "x2" = "y2")

  • objetivos no se identifica con una sola variable: hace falta tienda y mes

# 12 filas: no vale como clave
objetivos |> count(id_tienda) |> filter(n > 1)
# 0 filas: esta SI es la clave
objetivos |> count(id_tienda, año, mes) |> filter(n > 1)
  • Ej., calcular el bono medio de los Vendedores
empleados |> filter(puesto == "Vendedor") |>
  left_join(promociones_empleado, 
            by = c("id_tienda" = "cod_tienda", 
                   "cod_empleado") ) |> 
  pull(monto_bono) |> mean(na.rm = T)   # 501,79 euros

Dos columnas, una sola clave

  • promociones_empleado no guarda el identificador global del empleado: guarda su número dentro de su tienda, que es como numeran las cadenas de verdad. El 3 de Madrid y el 3 de Valencia son dos personas distintas
# 9 codigos repetidos: por si solo no identifica a nadie
promociones_empleado |> count(cod_empleado) |> filter(n > 1)
# 0 filas: la clave son LAS DOS columnas
promociones_empleado |> count(cod_tienda, cod_empleado) |> filter(n > 1)
  • Unir por una sola de las dos no da error: da filas de más
plantilla <- empleados |> filter(!is.na(id_tienda))
nrow(plantilla)                    # 85 empleados con tienda

# une con los bonos de otras tiendas
plantilla |>
  left_join(promociones_empleado, by = "cod_empleado") |>
  nrow()                           # 387

# la clave son las dos columnas
plantilla |>
  left_join(promociones_empleado,
            by = c("id_tienda" = "cod_tienda", "cod_empleado")) |>
  nrow()                           # 85

El objetivo tampoco vive en la venta

  • Tentación: unir objetivos a las ventas y sumar
ventas_2023_precio |>
  left_join(objetivos, by = c("id_tienda", "año", "mes")) |>
  group_by(id_tienda) |>
  summarize(objetivo_anual = sum(objetivo))   # tienda 1: 1.188.300 €
  • El objetivo anual de la tienda 1 son 92.900 €, no 1.188.300 €
objetivos |> group_by(id_tienda) |> summarize(objetivo_anual = sum(objetivo))
  • Y sin embargo la unión no ha duplicado ni una fila: 2.000 antes, 2.000 después

  • El objetivo de cada mes se ha copiado en todas las ventas de ese mes y sum() lo cuenta una vez por venta: la tienda 1 hizo 141 ventas en 2023, así que su objetivo sale casi 13 veces mayor

Agregar primero, unir después

  • Para comparar con el objetivo hay que llevar las ventas a su mismo nivel: tienda-mes
facturacion_mes <- ventas_2023_precio |>
  group_by(id_tienda, año, mes) |>
  summarize(facturacion = sum(cantidad * precio), .groups = "drop")

nrow(facturacion_mes)    # 144 = 12 tiendas x 12 meses, el nivel de objetivos

cumplimiento <- facturacion_mes |>
  left_join(objetivos, by = c("id_tienda", "año", "mes")) |>
  mutate(cumplimiento = round(facturacion / objetivo * 100, 1))
id_tienda mes facturación objetivo cumplimiento
1 1 15.336,91 15.600 98,3
1 2 7.806,36 8.500 91,8
1 3 6.366,60 5.700 111,7
  • En el año, 7 de las 12 tiendas cumplen: la 5 es la mejor (105,0%) y la 10 la peor (96,4%)

  • La regla: agregar hasta el nivel de la otra tabla y unir después. Al revés no da error, da un número sin sentido

Claves duplicadas

  • Si una coincidencia no es única, se generan todas las combinaciones posibles (producto cartesiano) de las observaciones coincidentes

  • id_producto no es clave primaria de precios: 97 de los 150 productos tienen más de un periodo de precio

precios |> count(id_producto) |> filter(n > 1)           # 97 filas

precios |> count(id_producto, desde) |> filter(n > 1)    # 0 filas: clave doble
  • Comprobar la clave de la tabla que se une cuesta una línea; explicar después por qué la facturación salía un 77% mayor (2.219.322 € en vez de 1.251.667 €) cuesta bastante más

  • NOTA: sólo unir cuando sea necesario y siempre usar solo filas y variables estrictamente necesarias

PRÁCTICA en clase (3): diciembre contra el objetivo

  • Con ventas, precios y objetivos:

    1. ¿Cuántas filas tiene precios y cuántos productos tienen más de un periodo de precio? ¿Cuál es su clave primaria? Compruébalo con count()

    2. Facturación real de diciembre de 2023 de cada tienda, a los precios vigentes, junto a su objetivo de diciembre y el porcentaje de cumplimiento, de mayor a menor. ¿Cuántas tiendas cumplieron?

    3. Un compañero hace (b) uniendo objetivos antes de agregar. Ejecútalo: ¿por qué la tienda 1 cumple el 7,0% de su objetivo?

# el código del compañero, para el apartado c)
ventas |> filter(año == 2023, mes == 12) |>
  left_join(precios, by = "id_producto") |>
  filter(fecha >= desde, fecha <= hasta) |>
  left_join(objetivos, by = c("id_tienda", "año", "mes")) |>
  group_by(id_tienda) |>
  summarize(facturacion = sum(cantidad * precio),
            objetivo    = sum(objetivo)) |>
  mutate(cumplimiento = round(facturacion / objetivo * 100, 1))

Uniones de Filtrado

  • Filtran las observaciones de la tabla de la izquierda basándose en si coinciden o no con una observación de la otra tabla

  • Se tiene un subconjunto de las filas de la tabla de la izquierda

  • semi_join(x, y) mantiene las observaciones en x que están en y

  • anti_join(x, y) elimina las observaciones en x que están en y

  • Nota: se puede conseguir lo mismo con inner_join(), full_join(), etc. pero es ineficiente

Uniones de Filtrado (cont.)

  • Útiles para diagnosticar desajustes de uniones (qué observaciones serán emparejadas). Importante antes de unir tablas MUY grandes
# empleados sin información de ventas
empleados |> anti_join(ventas, 
                       by = c("id_empleado", "id_tienda")) |> 
  count(id_empleado, id_tienda)  
  • Pueden ser una alternativa simple a usar filter() con filtrados complejos (que involucran varias variables), creando previamente tablas resumidas

    -Ej., venta promedio de los 10 productos con más cantidad vendida

  • Aplicación: ¿cuántos clientes tenemos y cuántos sí han comprado?
clientes |> distinct(id_cliente) |> count()
clientes |> semi_join(ventas) |> distinct(id_cliente) |> count()
  • Aplicación: ¿qué productos NO han sido devueltos?

PRÁCTICA en clase (4): unir sin perder (ni inventar) filas

  • Con ventas, tiendas y clientes:

    1. Ingresos de 2023 por región, con el número de ventas de cada una, ordenados de mayor a menor

    2. ¿Cuántos clientes de la tabla clientes no han comprado nunca? ¿Y cuántos sí?

    3. En (a) has unido ventas con tiendas. Si por un error de carga la tienda 1 apareciera dos veces en tiendas, ¿qué le pasaría a tu respuesta? Compruébalo

# bind_rows() apila las filas de dos tablas: aquí, tiendas con la 1 repetida
tiendas_mal <- bind_rows(tiendas, tiendas |> filter(id_tienda == 1))

Operaciones de conjunto

  • Trabajan con filas completas, comparando valores de cada variable.

  • Esperan que x e y tengan las mismas variables, y tratan las observaciones (filas) como elementos de un conjunto.

  • Útil cuando se quiere dividir un filtro complejo en piezas más simples.

df1 <- tibble(x = 1:2, y = c(1, 1))
df2 <- tibble(x = c(1,1), y = 1:2)

intersect(df1, df2)     # solo filas tanto en df1 como en df2
union(df1, df2)         # filas únicas en ambas tablas
union_all(df1, df2)     # todas las filas, con duplicados
setdiff(df1, df2)       # filas en df1, pero no en df2
setdiff(df2, df1)   

Conclusión. Buenas prácticas

Buenas Prácticas

  • Datos Ordenados

  • Código Legible y reproducible

    1. Scripts ordenados y comentados

    2. Encadenar operación con |> en un paso por línea

    3. Inspeccionar resultados intermedios

    4. Documentar decisiones de limpieza

    5. Nombres descriptivos de variables

    6. Guardar datos procesados

  • Errores comunes

    1. No verificar claves en uniones (¿qué variables? (¿hay claves dupolicadas?))

    2. Orden de operaciones incorrecto: ej., filtrar después de summarize() por una variable que ya no existe

Buenas Prácticas (cont.)

Eficiencia:

  1. Realizar transformaciones (seleccionar, filtrar, crear columnas) temprano, unir tarde
ventas_2023_prod <- ventas |>
  filter(año == 2023) |>    # reduce tamaño
  left_join(productos, by = "id_producto")

ventas_2023_prod_lento <- ventas |>
  left_join(productos, by = "id_producto") |>
  filter(año == 2023)
  1. Seleccionar solo columnas necesarias