La familia apply en una frase
Todos los miembros de la familia apply hacen el mismo trabajo: tomar una función y ejecutarla sobre cada elemento de alguna estructura, recogiendo los resultados. Lo que varía es la forma de la entrada y la forma de la salida. El mapa:
apply(m, MARGIN, FUN)- filas o columnas de una matriz.lapply(x, FUN)- cada elemento de una lista o un vector; siempre devuelve una lista.sapply(x, FUN)- igual quelapply, pero luego simplifica a un vector o una matriz cuando puede.vapply(x, FUN, FUN.VALUE)-sapplycon un tipo de retorno declarado y exigido.mapply(FUN, x, y, ...)- recorre varias entradas en paralelo, elemento a elemento.tapply(values, groups, FUN)- aplicaFUNdentro de cada grupo: un resultado por grupo.
Todas aceptan las funciones que escribas tú, incluidas las anónimas, que es de donde la familia saca su potencia.
apply(): filas y columnas de una matriz
apply toma una matriz, un MARGIN (1 para filas, 2 para columnas) y una función:
La matriz se rellena columna a columna, así que las filas son 1 3 5 y 2 4 6: las sumas por fila salen como 9 12 y las sumas por columna como 3 7 11. Para los dos casos más habituales, R base incluye ayudantes dedicados y más rápidos - rowSums, colSums, rowMeans, colMeans -, así que reserva apply para funciones que no tengan uno, como apply(m, 2, max).
Una trampa: apply sobre un data frame lo convierte antes en una matriz en silencio, lo que coacciona todas las columnas a un tipo común. Para los data frames, trata las columnas como una lista y usa lapply/sapply en su lugar.
lapply() siempre da una lista, sapply() simplifica
lapply mapea una función sobre cada elemento y devuelve una lista de la misma longitud, pase lo que pase:
Mismo cálculo, dos formas: lapply devuelve una lista que contiene 85 y 80; sapply se da cuenta de que todos los resultados tienen longitud 1 y simplifica a un vector numérico con nombres - mucho más agradable de leer y de alimentar a cálculos posteriores.
La comodidad tiene un filo, eso sí: el tipo de retorno de sapply depende de los datos. Si aunque sea un elemento produce una longitud distinta, la simplificación falla y vuelves a obtener una lista en silencio:
De forma interactiva eso es un encogimiento de hombros; dentro de un script significa que un código que funcionó todo el año se rompe el día en que los datos cambian de forma. Esa es toda la razón por la que existe vapply.
vapply(): el sapply con seguridad de tipos
vapply añade un tercer argumento, FUN.VALUE: una plantilla que declara cómo debe ser un resultado. integer(1) significa "cada llamada devuelve exactamente un entero":
Obtienes un vector de enteros con nombres - 5 6 6 - y, más importante, una garantía: si nchar llegara a devolver dos valores o un carácter, vapply se detendría con un error en el punto de llamada en lugar de dejar que un resultado mal formado flotara aguas abajo. La declaración hace además de documentación:
vapply(words, nchar, character(1))
# Error in vapply(words, nchar, character(1)) : values must be type 'character'
Regla general: sapply en la consola, vapply en funciones y scripts que deban ejecutarse sin supervisión.
mapply() y tapply(): entradas en paralelo y grupos
lapply recorre una estructura. Cuando cada llamada necesita un elemento de varias estructuras en posiciones coincidentes, usa mapply - fíjate en que la función va primero:
Cada elemento de la salida empareja los primeros valores, luego los segundos, y así sucesivamente: 10 200 3000 40000.
tapply es el miembro por grupos: divide values según groups y aplica la función dentro de cada grupo, devolviendo un resultado por grupo:
El grupo a promedia 30 y el grupo b promedia 40. Esta es la respuesta de R base a "la media por categoría" - el mismo tipo de pregunta que group_by + summarize responde para los data frames, así que si ya estás en territorio dplyr usa eso; tapply brilla cuando tienes dos vectores sueltos y quieres una sola línea.
Dos comodidades funcionan en toda la familia. Los argumentos extra tras la función se le reenvían en cada llamada:
Y las funciones anónimas encajan siempre que ninguna función ya hecha sirva - sapply(x, \(v) max(v) - min(v)) calcula un rango por elemento sin tener que nombrar antes una función auxiliar.
La familia apply frente a los bucles for
Oirás que los bucles for son lentos en R y que la familia apply es rápida. Eso es sobre todo un mito. Lo que es genuinamente lento es hacer crecer un resultado dentro de un bucle - result <- c(result, new_value) copia todo el vector en cada pasada. Un bucle que preasigna su salida rinde bien:
Así que elige por legibilidad, no por rendimiento. La versión con apply dice qué en una línea - "eleva al cuadrado cada elemento" - y se encarga de la reserva de memoria por ti, por lo que es el modismo para el trabajo elemento a elemento sencillo. Un bucle for se gana el sueldo cuando las iteraciones dependen de resultados previos, cuando necesitas salidas tempranas con break o cuando el cuerpo es lo bastante largo como para que una lambda estorbe más de lo que ayude. Ambos son R legítimo.
Lo que te llevas
- Toda la familia es una sola idea: ejecutar una función sobre cada elemento y recoger los resultados.
applyes para filas (MARGIN = 1) y columnas (MARGIN = 2) de matrices; prefiererowSums/colMeanscuando existan.lapplysiempre devuelve una lista;sapplysimplifica cuando puede - lo que significa que su tipo de retorno puede cambiar con los datos.vapplyfija el tipo de retorno; úsala en código que no debe sorprenderte.mapplyrecorre varias entradas a la vez;tapplyagrega valores dentro de grupos.- Los bucles for no son lentos por naturaleza - lo lento es hacer crecer vectores. Elige la grafía que mejor se lea.
Lo siguiente: los pipes - el operador que encadena estas llamadas en tuberías legibles, paso a paso.
Preguntas frecuentes
¿Cuál es la diferencia entre lapply y sapply en R?
lapply siempre devuelve una lista, sin excepciones. sapply hace el mismo cálculo y luego intenta simplificar el resultado - a un vector si todos los elementos tienen longitud 1, a una matriz si comparten longitud, y de vuelta a una lista si no puede. sapply es más agradable de forma interactiva; lapply (o vapply) es más seguro en los scripts porque su tipo de retorno nunca cambia.
¿Qué hace apply() en R?
apply(m, MARGIN, FUN) ejecuta FUN sobre una matriz: MARGIN = 1 la aplica a cada fila y MARGIN = 2 a cada columna. apply(m, 1, sum) da las sumas por fila y apply(m, 2, mean) las medias por columna. Es para matrices y arrays - para listas y vectores usa lapply/sapply.
¿Es la familia apply más rápida que un bucle for en R?
Normalmente no por mucho - eso es un mito. Un bucle for bien escrito con un vector de resultados preasignado rinde de forma comparable. Los bucles que le dieron mala fama al for hacen crecer su resultado elemento a elemento, copiándolo todo en cada pasada. Elige las funciones de la familia apply por concisión e intención, no por velocidad.
¿Para qué sirve vapply en R?
vapply es sapply con un contrato: declaras el tipo y la longitud de cada resultado, por ejemplo vapply(x, nchar, integer(1)). Si la función devuelve otra cosa, R lanza un error de inmediato en lugar de entregarte en silencio una estructura inesperada. Prefiérela en código que debe seguir funcionando sin supervisión.