mutate(): crear y transformar columnas
Por qué mutate() reemplaza a df$nueva <- ...
mutate() es el verbo de dplyr para añadir o modificar columnas. En R base, esto se hace con asignación:
ventas$ingresos_eur <- ventas$ingresos_usd * 0.92Funciona, pero:
- No compone con el pipe (te sale del flujo).
- Para varias columnas necesitas escribir el nombre del data frame cada vez.
- No queda en el código de qué pipeline forma parte la transformación.
mutate() resuelve los tres:
ventas |>
mutate(ingresos_eur = ingresos_usd * 0.92)Forma básica: crear una columna
ventas |>
mutate(
ingresos_eur = ingresos_usd * 0.92,
año = year(fecha),
es_premium = precio > 1000
)Las columnas se crean secuencialmente: puedes usar una recién creada en la siguiente línea del mismo mutate().
ventas |>
mutate(
ingresos_eur = ingresos_usd * 0.92,
margen_eur = ingresos_eur - coste_eur # usa la columna recién creada
)Esto es distinto a, por ejemplo, SQL puro, donde el orden no garantiza dependencia. En mutate() sí lo hace.
Múltiples columnas en un mismo mutate()
Lo idiomático es agrupar transformaciones relacionadas en un único mutate():
ventas |>
mutate(
año = year(fecha),
mes = month(fecha),
dia_sem = wday(fecha, label = TRUE)
)Frente a tres mutates encadenados. Es más legible y la lectura agrupa lo que pertenece al mismo paso conceptual.
mutate() vs transmute()
# mutate(): conserva todas las columnas y añade
ventas |> mutate(ingresos_eur = ingresos_usd * 0.92)
# transmute(): solo deja las columnas mencionadas
ventas |> transmute(
region,
ingresos_eur = ingresos_usd * 0.92
)transmute() es mutate() + select() en una sola operación. Útil cuando vas a quedarte solo con un subconjunto reducido.
Desde dplyr 1.0, transmute() está soft-deprecated en favor de mutate(.keep = "none"), pero la sintaxis explícita transmute() sigue siendo más legible y se entiende sin pensar.
case_when(): el ifelse adulto
Para recodificaciones con múltiples categorías, evita anidar ifelse():
# Lo que NO se debe escribir:
ventas |> mutate(
segmento = ifelse(ingresos > 10000, "alto",
ifelse(ingresos > 1000, "medio",
ifelse(ingresos > 100, "bajo", "marginal")))
)
# Lo correcto: case_when()
ventas |> mutate(
segmento = case_when(
ingresos > 10000 ~ "alto",
ingresos > 1000 ~ "medio",
ingresos > 100 ~ "bajo",
TRUE ~ "marginal" # default
)
)case_when() evalúa de arriba a abajo y devuelve el primer match. La línea TRUE ~ ... es el default.
Una versión moderna (dplyr 1.1+) usa .default:
ventas |> mutate(
segmento = case_when(
ingresos > 10000 ~ "alto",
ingresos > 1000 ~ "medio",
ingresos > 100 ~ "bajo",
.default = "marginal"
)
)Más legible. Adóptala si tu versión de dplyr lo permite.
.before y .after: posición de las nuevas columnas
Por defecto, las nuevas columnas van al final. Si quieres ponerlas en una posición concreta:
ventas |>
mutate(
ratio_margen = margen / ingresos,
.after = ingresos
).before = 1 las pone al principio. Útil cuando trabajas con tablas anchas y quieres que las columnas calculadas vayan al lado de las originales relacionadas.
across(): aplicar a varias columnas a la vez
Si quieres aplicar la misma transformación a múltiples columnas:
ventas |>
mutate(across(starts_with("ingresos_"), \(x) x * 0.92))
ventas |>
mutate(across(where(is.numeric), ~ round(.x, 2)))across() reemplaza al antiguo mutate_at(), mutate_if(), etc. Es el patrón unificado. Recibe dos cosas:
- Un selector de columnas (
starts_with(...),where(...), nombres concretos). - Una función a aplicar (con la sintaxis lambda moderna
\(x) ...o la forma vieja~ .x ...).
across() es probablemente el helper de dplyr que más eleva tu código cuando lo dominas: convierte 10 líneas repetitivas en una.
Trampas habituales
mutate()no es destructivo. Si quieres MODIFICAR una columna existente, asigna con el mismo nombre:mutate(precio = precio * 1.05). La columna se sobrescribe.case_when()requiere que todos los lados derechos sean del mismo tipo. Mezclar"alto"conNApuro falla porqueNApor defecto es lógico. UsaNA_character_cuando trabajas con strings.- No uses
mutate()para crear columnas que dependen de OTRAS filas. Para eso necesitasmutate()después degroup_by()(siguiente tutorial) o funciones de ventana comolag(),lead(),cumsum(). - El orden importa cuando una columna nueva depende de otra existente que también modificas. Si en el mismo
mutate()hacesB = A * 2y luegoA = A + 1, B usará el A original, no el actualizado.
En la siguiente entrega
Con filter(), select() y mutate(), ya puedes manipular un data frame fila a fila y columna a columna. Pero lo más útil del análisis es agregar: contar por categoría, calcular medias por grupo, encontrar el máximo por región. Eso es group_by() + summarise(), el patrón split-apply-combine. Es lo siguiente.