group_by() + summarise(): el patrón split-apply-combine
El patrón split-apply-combine
Casi todo análisis de datos hace lo mismo en algún momento: dividir los datos en grupos, aplicar una operación a cada grupo, combinar los resultados. Es el patrón group_by() + summarise() en dplyr, y es la operación que te permite ir de “10.000 ventas” a “ventas medias por región”.
En R base, esto se hace con aggregate() o con tapply(). Funcionan, pero la sintaxis es opaca y la composición con el pipe nula. En dplyr el patrón es:
ventas |>
group_by(region) |>
summarise(media_ingresos = mean(ingresos, na.rm = TRUE))Léelo: “agrupa por región, resume con la media de ingresos”. Es prosa.
group_by(): qué hace realmente
group_by() no transforma los datos. Lo que hace es marcar el tibble con metadatos: “a partir de aquí, considera estos valores de region como grupos separados”. Los verbos que vienen después actúan dentro de cada grupo.
Lo puedes ver imprimiendo el resultado:
ventas |> group_by(region)
#> # A tibble: 10,000 × 5
#> # Groups: region [4]
#> ...Ese Groups: region [4] te dice que la próxima operación de agregación operará 4 veces (una por región) y combinará los resultados.
summarise(): de N filas a 1 por grupo
ventas |>
group_by(region) |>
summarise(
n_ventas = n(),
media_ingr = mean(ingresos, na.rm = TRUE),
total_ingr = sum(ingresos, na.rm = TRUE),
max_ingr = max(ingresos, na.rm = TRUE)
)Cada columna de summarise() produce un valor por grupo. El resultado tiene tantas filas como grupos.
Funciones de agregación frecuentes:
n(): número de filas del grupo.n_distinct(x): número de valores únicos dexen el grupo.mean(),median(),sum(),min(),max(), los clásicos. Casi siempre conna.rm = TRUE.first(),last(),nth(x, 3), útiles tras unarrange()previo.quantile(x, 0.75): percentiles.
Agrupación por varias columnas
ventas |>
group_by(region, año) |>
summarise(media_ingr = mean(ingresos, na.rm = TRUE))Resultado: una fila por combinación region × año. Si hay 4 regiones y 3 años, salen 12 filas (asumiendo que todas las combinaciones existen en los datos).
.groups: el warning que la gente ignora
Cuando agrupas por varias columnas y haces summarise(), dplyr muestra este warning desde la versión 1.0:
`summarise()` has grouped output by 'region'. You can override using the `.groups` argument.
Significa: “el resultado sigue agrupado por la primera columna (region). Cualquier operación siguiente actuará dentro de cada región”. Esto sorprende a casi todo el mundo y es causa de bugs sutiles.
Las cuatro opciones explícitas:
summarise(..., .groups = "drop_last") # default: quita la última agrupación
summarise(..., .groups = "drop") # quita TODA la agrupación
summarise(..., .groups = "keep") # mantiene todas las agrupaciones
summarise(..., .groups = "rowwise") # cada fila pasa a ser su propio "grupo"Recomendación práctica: usa siempre .groups = "drop" cuando termines un pipeline, salvo que tengas razón explícita para mantener la agrupación. Así no te lleva sorpresas más adelante:
ventas |>
group_by(region, año) |>
summarise(media_ingr = mean(ingresos, na.rm = TRUE), .groups = "drop")group_by() + mutate() (sin summarise())
group_by() no es solo para summarise(). Combinado con mutate(), te permite calcular columnas que dependen de los demás valores del mismo grupo:
# Porcentaje del total dentro de cada región
ventas |>
group_by(region) |>
mutate(pct_region = ingresos / sum(ingresos))
# Rank de cada venta dentro de su año
ventas |>
group_by(año) |>
mutate(rank_anual = rank(-ingresos))Aquí la tabla mantiene su tamaño original (no agregamos), pero las columnas nuevas usan el contexto del grupo. Es una herramienta poderosísima cuando aprendes a verla.
ungroup(): cuándo y por qué
Si tu pipeline sigue después de un summarise() con .groups != "drop", o después de un mutate() agrupado, acuérdate de desagrupar:
ventas |>
group_by(region, año) |>
summarise(total = sum(ingresos), .groups = "drop_last") |>
# OJO: aquí sigue agrupado por region
mutate(pct = total / sum(total)) |> # el sum() es POR region, no global
ungroup()Si esperabas un porcentaje sobre el total global y obtienes porcentajes que suman 1 dentro de cada región (cada región = 100%), te falta un ungroup() o un .groups = "drop".
Adopta el hábito: .groups = "drop" por defecto en summarise(), ungroup() por defecto después de un mutate() agrupado. Pierdes muy poco y ganas robustez.
Trampas habituales
- El warning de
.groupsno es ruido, es información. Si lo ignoras, en algún momento sufres un bug donde un porcentaje “suma 100% en cada grupo” en vez de globalmente. n()cuenta filas.sum(!is.na(x))cuenta no-NAs. No es lo mismo si la columna tiene huecos. Sé explícito sobre qué quieres contar.- Mezclar
group_by()conarrange()en orden equivocado.arrange()ANTES degroup_by()ordena el conjunto entero.arrange()después ordena dentro de cada grupo (con.by_group = TRUE). - Olvidar
na.rm = TRUEen agregaciones. Si una columna tiene un solo NA,mean(x)devuelveNA. Todos losmean,sum, etc., aceptanna.rm = TRUE. Es una decisión consciente que conviene tomar explícitamente, no por reflejo.
En la siguiente entrega
Has completado el bloque 2, los cuatro verbos núcleo de dplyr que cualquier analista usa cada día. El siguiente bloque entra en operaciones que combinan varios data frames: los joins, que es donde la gente que viene de Excel realmente entiende por qué R y SQL piensan en términos relacionales. Es lo siguiente.