language functions funcion family data r purrr

functions - ¿Por qué usar purrr:: map en lugar de lapply?



r apply lapply sapply mapply (3)

La comparación de lapply y lapply reduce a conveniencia y velocidad .

1. purrr::map es sintácticamente más conveniente que lapply

extraer el segundo elemento de la lista

map(list, 2)

que como @F. Privé señaló, es lo mismo que:

map(list, function(x) x[[2]])

con lapply

lapply(list, 2) # doesn''t work

necesitamos pasar una función anónima ...

lapply(list, function(x) x[[2]]) # now it works

... o como señaló @RichScriven, pasamos [[ como argumento a lapply

lapply(list, `[[`, 2) # a bit more simple syntantically

Entonces, si se encuentra aplicando funciones a muchas listas usando lapply , y lapply de definir una función personalizada o escribir una función anónima, la conveniencia es una razón para moverse a favor de purrr .

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() : mi favorito, devuelve un marco de datos.

Cada una de estas funciones de mapa específicas de tipo devuelve una lista atómica (vector), en lugar de las listas devueltas por map() y lapply() . Si se trata de listas anidadas de vectores atómicos dentro, puede utilizar 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 base R se parecería a as.numeric(sapply(...)) , as.character(sapply(...)) , etc. Esto le da a purrr otro punto de conveniencia y funcionalidad.

3. Conveniencia aparte, lapply es [ligeramente] más rápido que el map

Usando las funciones de conveniencia de purrr , como @F. Privé señaló que ralentiza un poco el procesamiento. Vamos a competir con cada uno de los 4 casos que presenté anteriormente.

# devtools::install_github("jennybc/repurrrsive") library(repurrrsive) library(purrr) library(microbenchmark) library(ggplot2) mbm <- microbenchmark( lapply = lapply(got_chars[1:4], function(x) x[[2]]), lapply_2 = lapply(got_chars[1:4], `[[`, 2), map_shortcut = map(got_chars[1:4], 2), map = map(got_chars[1:4], function(x) x[[2]]), times = 100 ) autoplot(mbm)

Y el ganador es....

lapply(list, `[[`, 2)

En resumen, si lo que buscas es velocidad bruta: base::lapply (aunque no es mucho más rápido)

Para sintaxis y purrr::map simples: purrr::map

Este excelente tutorial de purrr destaca la conveniencia de no tener que escribir explícitamente funciones anónimas cuando se usa purrr , y los beneficios de las funciones de map específicas del tipo.

¿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>)

la salida debería ser la misma y los puntos de referencia que hice parecen mostrar que el lapply es un poco más rápido (debería ser como el map necesita evaluar toda la entrada de evaluación no estándar).

Entonces, ¿hay alguna razón por la cual para casos tan simples debería considerar cambiar a purrr::map ? No estoy preguntando aquí sobre los gustos o disgustos de uno sobre la sintaxis, otras funcionalidades proporcionadas por purrr, etc., sino estrictamente sobre la comparación de purrr::map con lapply suponiendo que use la evaluación estándar, es decir, map(<list-like-object>, function(x) <do stuff>) . ¿Hay alguna ventaja que tenga 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?


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 de map() son los ayudantes que le permiten escribir código compacto para casos especiales comunes:

  • ~ . + 1 ~ . + 1 es equivalente a la function(x) x + 1

  • list("x", 1) es equivalente a la function(x) x[["x"]][[1]] . Estos ayudantes son un poco más generales que [[ - see ?pluck para más detalles. Para el rectángulo de datos , el argumento .default 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 purrr es una consistencia mucho mayor entre las funciones. Por ejemplo:

  • El primer argumento para lapply() son los datos; El primer argumento para mapply() es la función. El primer argumento para todas las funciones del mapa son siempre los datos.

  • Con vapply() , sapply() y mapply() puede elegir suprimir nombres en la salida con USE.NAMES = FALSE ; pero lapply() 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 ... pero mapply() usa MoreArgs (que esperaría que se llamara MORE.ARGS ), y Map() , Filter() y Reduce() 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() o mapply() . Sí, hay vapply() ; pero no hay equivalente para mapply() .

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 argumentos 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() conserva el tipo de datos usando [[<- para modificar "en el lugar". En combinación con la variante _if , esto permite código (IMO hermoso) como modify_if(df, is.factor, as.character)

  • map2() permite mapear simultáneamente sobre x e y . Esto facilita la expresión de ideas como map2(models, datasets, predict)

  • imap() permite mapear simultáneamente sobre x sus índices (ya sea nombres o posiciones). Esto facilita (por ejemplo) cargar todos los archivos csv en un directorio, agregando una columna de filename a cada uno.

    dir("//.csv$") %>% set_names() %>% map(read.csv) %>% imap(~ transform(.x, filename = .y))

  • 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() y partial() .

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 lo ayude.

Microbenchmarks

Sí, map() es un poco más lento que lapply() . Pero el costo de usar map() o lapply() de lo que esté mapeando, no de la sobrecarga de realizar el ciclo. El microbenchmark a continuación sugiere que el costo de map() comparación con lapply() es de alrededor de 40 ns por elemento, lo que parece poco probable que afecte materialmente a la mayoría del código R.

library(purrr) n <- 1e4 x <- 1:n f <- function(x) NULL mb <- microbenchmark::microbenchmark( lapply = lapply(x, f), map = map(x, f) ) summary(mb, unit = "ns")$median / n #> [1] 490.343 546.880


Si no consideramos los 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 ninguna razón especial para usar el map lugar de lapply u otras variantes de la familia de aplicación, como la más estricta vapply

PD: Para aquellas personas que votan negativamente, solo recuerden que el OP escribió:

No estoy preguntando aquí sobre los gustos o disgustos de uno sobre la sintaxis, otras funcionalidades proporcionadas por purrr, etc., sino estrictamente sobre la comparación de purrr :: map con lapply suponiendo que use la evaluación estándar

Si no considera la sintaxis ni otras funcionalidades de purrr , no hay ninguna razón especial para usar map . Yo uso purrr 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.