¿Hay alguna razón por la que debería usar
map(<list-like-object>, function(x) <do stuff>)
en vez de
lapply(<list-like-object>, function(x) <do stuff>)
el resultado debería ser el mismo y los puntos de referencia que realicé parecen mostrar que lapply
es un poco más rápido (debería ser como map
evaluar todas las entradas de evaluación no estándar).
Entonces, ¿hay alguna razón por la cual para casos tan simples debería considerar cambiarme purrr::map
? No estoy pidiendo aquí sobre gustos o disgustos sobre la sintaxis de uno, otras funcionalidades proporcionadas por purrr etc., pero estrictamente sobre la comparación de purrr::map
la lapply
suponiendo el uso de la evaluación estándar, es decir map(<list-like-object>, function(x) <do stuff>)
. ¿Hay alguna ventaja purrr::map
en términos de rendimiento, manejo de excepciones, etc.? Los comentarios a continuación sugieren que no es así, pero ¿tal vez alguien podría elaborar un poco más?
tidyverse
, puede beneficiarse de la sintaxis de canalización%>%
y funciones anónimas~ .x + 1
~{}
lambda acceso directo (con o sin los{}
sellos de la oferta para mí por llanopurrr::map()
. El tipo de aplicación de lapurrr::map_…()
son muy útiles y menos obtusos quevapply()
.purrr::map_df()
es una función caro súper pero también simplifica código. No hay absolutamente nada de malo en la pervivencia de la base R[lsv]apply()
, aunque .purrr
cosas. Mi punto es el siguiente:tidyverse
es fabuloso para análisis / interactivo / informes, no para programación. Si tiene que usarlapply
omap
está programando y puede terminar algún día creando un paquete. Entonces, cuanto menos dependencias, mejor. Además: a veces veo personas que usanmap
una sintaxis bastante oscura después. Y ahora que veo pruebas de rendimiento: si estás acostumbrado a laapply
familia: mantente firme.Respuestas:
Si la única función que está utilizando de purrr es
map()
, entonces no, las ventajas no son sustanciales. Como señala Rich Pauloo, la principal ventaja demap()
los ayudantes es que le permiten escribir código compacto para casos especiales comunes:~ . + 1
es equivalente afunction(x) x + 1
list("x", 1)
es equivalente afunction(x) x[["x"]][[1]]
. Estos ayudantes son un poco más generales que[[
- ver?pluck
para más detalles. Para el rectángulo de datos , el.default
argumento es particularmente útil.Pero la mayoría de las veces no estás usando una sola función
*apply()
/map()
, estás usando un montón de ellas, y la ventaja de ronronear es una consistencia mucho mayor entre las funciones. Por ejemplo:El primer argumento para
lapply()
es los datos; El primer argumentomapply()
es la función. El primer argumento para todas las funciones del mapa son siempre los datos.Con
vapply()
,,sapply()
ymapply()
puede optar por suprimir nombres en la salida conUSE.NAMES = FALSE
; perolapply()
no tiene ese argumento.No hay una forma consistente de pasar argumentos consistentes a la función del mapeador. La mayoría de las funciones usan
...
peromapply()
usanMoreArgs
(que esperaría que se llamaraMORE.ARGS
), yMap()
,Filter()
yReduce()
esperan que cree una nueva función anónima. En las funciones de mapa, el argumento constante siempre viene después del nombre de la función.Casi todas las funciones de purrr son de tipo estable: puede predecir el tipo de salida exclusivamente a partir del nombre de la función. Esto no es cierto para
sapply()
omapply()
. Sí lo hayvapply()
; pero no hay equivalente paramapply()
.Puede pensar que todas estas distinciones menores no son importantes (al igual que algunas personas piensan que no hay ninguna ventaja en encadenar sobre las expresiones regulares de base R), pero en mi experiencia causan fricciones innecesarias al programar (las diferentes órdenes de argumento siempre solían dispararse Me up), y hacen que las técnicas de programación funcional sean más difíciles de aprender porque además de las grandes ideas, también tienes que aprender un montón de detalles incidentales.
Purrr también completa algunas variantes de mapas útiles que están ausentes de la base R:
modify()
preserva el tipo de los datos utilizando[[<-
para modificar "en el lugar". En combinación con la_if
variante, esto permite un código (IMO hermoso) comomodify_if(df, is.factor, as.character)
map2()
le permite mapear simultáneamente sobrex
yy
. Esto hace que sea más fácil expresar ideas comomap2(models, datasets, predict)
imap()
le permite mapear simultáneamentex
y sus índices (ya sea nombres o posiciones). Esto facilita (por ejemplo) cargar todos loscsv
archivos en un directorio, agregando unafilename
columna a cada uno.walk()
devuelve su entrada de forma invisible; y es útil cuando se llama a una función por sus efectos secundarios (es decir, escribir archivos en el disco).Sin mencionar los otros ayudantes como
safely()
ypartial()
.Personalmente, encuentro que cuando uso purrr, puedo escribir código funcional con menos fricción y mayor facilidad; disminuye la brecha entre pensar una idea e implementarla. Pero tu kilometraje puede variar; no hay necesidad de usar ronroneo a menos que realmente te ayude.
Microbenchmarks
Sí,
map()
es un poco más lento quelapply()
. Sin embargo, el coste de utilizaciónmap()
olapply()
es impulsado por lo que se aplica la relación, no la carga de realizar el bucle. El microbench de abajo sugiere que el costo demap()
comparaciónlapply()
es de alrededor de 40 ns por elemento, lo que parece poco probable que afecte materialmente a la mayoría del código R.fuente
mutate()
, solo quería un ejemplo simple sin otros deps.map_*
es lo que me hizo cargarpurrr
en muchos scripts. Me ayudó con algunos aspectos de 'flujo de control' de mi código (stopifnot(is.data.frame(x))
).Comparando
purrr
y selapply
reduce a conveniencia y velocidad .1.
purrr::map
es sintácticamente más conveniente que lapplyextraer el segundo elemento de la lista
que como @F. Privé señaló, es lo mismo que:
con
lapply
necesitamos pasar una función anónima ...
... o como señaló @RichScriven, pasamos
[[
como argumento alapply
Entonces, si se encuentra aplicando funciones a muchas listas usando
lapply
, y se cansa de definir una función personalizada o escribir una función anónima, la conveniencia es una de las razones a favorpurrr
.2. El mapa específico del tipo funciona simplemente con muchas líneas de código
map_chr()
map_lgl()
map_int()
map_dbl()
map_df()
Cada una de estas funciones de mapa específicas de tipo devuelve un vector, en lugar de las listas devueltas por
map()
ylapply()
. Si se trata de listas anidadas de vectores, puede usar estas funciones de mapa específicas de tipo para extraer los vectores directamente y coaccionar los vectores directamente en vectores int, dbl, chr. La versión de la base R sería algo comoas.numeric(sapply(...))
,as.character(sapply(...))
, etc.Las
map_<type>
funciones también tienen la calidad útil de que si no pueden devolver un vector atómico del tipo indicado, fallarán. Esto es útil al definir un flujo de control estricto, donde desea que una función falle si [de alguna manera] genera el tipo de objeto incorrecto.3. Conveniencia aparte,
lapply
es [ligeramente] más rápido quemap
Utilizando
purrr
las funciones de conveniencia, como @F. Privé señaló que ralentiza un poco el procesamiento. Vamos a competir con cada uno de los 4 casos que presenté anteriormente.Y el ganador es....
En resumen, si la velocidad bruta es lo que buscas:
base::lapply
(aunque no es mucho más rápido)Para sintaxis simple y expresibilidad:
purrr::map
Este excelente
purrr
tutorial resalta la conveniencia de no tener que escribir explícitamente funciones anónimas cuando se usapurrr
, y los beneficios de lasmap
funciones específicas de tipo .fuente
function(x) x[[2]]
lugar de solo2
, sería menos lento. Todo este tiempo extra se debe a los controles quelapply
no funcionan.[[
Es una función. Puedes hacerlapply(list, "[[", 3)
.Si no consideramos aspectos del gusto (de lo contrario, esta pregunta debería cerrarse) o la consistencia de la sintaxis, el estilo, etc., la respuesta es no, no hay una razón especial para usar en
map
lugar delapply
otras variantes de la familia de aplicación, como la más estrictavapply
.PD: Para aquellas personas que votan negativamente, solo recuerden que el OP escribió:
Si no considera la sintaxis ni otras funcionalidades de
purrr
, no hay razón especial para usarmap
. Me usopurrr
y estoy de acuerdo con la respuesta de Hadley, pero irónicamente repasa las mismas cosas que el OP declaró por adelantado que no estaba preguntando.fuente