Pourquoi utiliser purrr :: map au lieu de lapply?


172

Y a-t-il une raison pour laquelle je devrais utiliser

map(<list-like-object>, function(x) <do stuff>)

au lieu de

lapply(<list-like-object>, function(x) <do stuff>)

le résultat devrait être le même et les points de repère que j'ai faits semblent montrer que lapplyc'est légèrement plus rapide (cela devrait être le cas mappour évaluer toutes les entrées d'évaluation non standard).

Alors, y a-t-il une raison pour laquelle, pour des cas aussi simples, je devrais envisager de passer purrr::map? Je ne demande pas ici ce que l'on aime ou n'aime pas sur la syntaxe, les autres fonctionnalités fournies par purrr etc., mais strictement sur la comparaison purrr::mapavec l' lapplyutilisation de l'évaluation standard, c'est-à-dire map(<list-like-object>, function(x) <do stuff>). Y a-t-il un avantage purrr::mapen termes de performances, de gestion des exceptions, etc.? Les commentaires ci-dessous suggèrent que ce n'est pas le cas, mais peut-être que quelqu'un pourrait élaborer un peu plus?


8
Pour les cas d'utilisation simples, mieux vaut s'en tenir à la base R et éviter les dépendances. Si vous chargez déjà le tidyversecependant, vous pouvez bénéficier de la syntaxe du tube %>%et des fonctions anonymes~ .x + 1
Aurèle

49
C'est à peu près une question de style. Vous devriez savoir ce que font les fonctions de base R, car tout ce truc tidyverse n'est qu'un shell au-dessus. À un moment donné, cette coquille se cassera.
Hong Ooi

9
~{}raccourci lambda (avec ou sans les {}sceaux, l'affaire pour moi pour le simple purrr::map(). L'application du type purrr::map_…()est pratique et moins obtuse que vapply(). purrr::map_df()est une fonction super chère mais elle simplifie aussi le code. Il n'y a absolument rien de mal à s'en tenir à la base R [lsv]apply(), bien que .
hrbrmstr

4
Merci pour la question - genre de choses que j'ai également regardées. J'utilise R depuis plus de 10 ans et je n'utilise et n'utiliserai définitivement rien purrr. Mon point est le suivant: tidyverseest fabuleux pour les analyses / interactifs / rapports, pas pour la programmation. Si vous devez utiliser lapplyou mapalors vous programmez et pouvez finir un jour par créer un package. Ensuite, les moins dépendances sont les meilleures. Plus: je vois parfois des gens utiliser mapavec une syntaxe assez obscure après. Et maintenant que je vois des tests de performances: si vous êtes habitué à la applyfamille: tenez-vous-y.
Eric Lecoutre

4
Tim vous avez écrit: "Je ne demande pas ici ce que l'on aime ou n'aime pas sur la syntaxe, les autres fonctionnalités fournies par purrr etc., mais strictement sur la comparaison de purrr :: map avec lapply en supposant l'utilisation de l'évaluation standard" et la réponse que vous avez acceptée est celui qui reprend exactement ce que vous avez dit que vous ne vouliez pas que les gens passent dessus.
Carlos Cinelli

Réponses:


232

Si la seule fonction que vous utilisez de purrr est map(), alors non, les avantages ne sont pas substantiels. Comme le souligne Rich Pauloo, le principal avantage de map()est les helpers qui vous permettent d'écrire du code compact pour les cas particuliers courants:

  • ~ . + 1 est équivalent à function(x) x + 1

  • list("x", 1)équivaut à function(x) x[["x"]][[1]]. Ces aides sont un peu plus générales que [[- voir ?pluckpour plus de détails. Pour les rectangles de données , l' .defaultargument est particulièrement utile.

Mais la plupart du temps, vous n'utilisez pas une seule fonction *apply()/ map(), vous en utilisez un certain nombre, et l'avantage de purrr est une plus grande cohérence entre les fonctions. Par exemple:

  • Le premier argument lapply()est les données; le premier argument de mapply()est la fonction. Le premier argument de toutes les fonctions cartographiques est toujours les données.

  • Avec vapply(), sapply()et mapply()vous pouvez choisir de noms Suppress sur la sortie avec USE.NAMES = FALSE; mais lapply()n'a pas cet argument.

  • Il n'existe aucun moyen cohérent de transmettre des arguments cohérents à la fonction mappeur. La plupart des fonctions utilisent ...mais mapply()utilise MoreArgs(ce que vous vous attendez à être appelé MORE.ARGS) et Map(), Filter()et Reduce()s'attendent à ce que vous créiez une nouvelle fonction anonyme. Dans les fonctions de carte, l'argument constant vient toujours après le nom de la fonction.

  • Presque toutes les fonctions purrr sont de type stable: vous pouvez prédire le type de sortie exclusivement à partir du nom de la fonction. Ce n'est pas vrai pour sapply()ou mapply(). Oui, il y en a vapply(); mais il n'y a pas d'équivalent pour mapply().

Vous pouvez penser que toutes ces distinctions mineures ne sont pas importantes (tout comme certaines personnes pensent qu'il n'y a aucun avantage à stringr par rapport aux expressions régulières de base R), mais d'après mon expérience, elles provoquent des frictions inutiles lors de la programmation (les différents ordres d'argumentation sont toujours utilisés pour trébucher me up), et ils rendent les techniques de programmation fonctionnelle plus difficiles à apprendre car en plus des grandes idées, vous devez également apprendre un tas de détails accessoires.

Purrr remplit également certaines variantes de carte pratiques qui sont absentes de la base R:

  • modify()préserve le type des données en utilisant [[<-pour modifier "en place". En conjonction avec la _ifvariante, cela permet un code (IMO beautiful) commemodify_if(df, is.factor, as.character)

  • map2()vous permet de mapper simultanément sur xet y. Cela facilite l'expression d'idées telles que map2(models, datasets, predict)

  • imap()vous permet de mapper simultanément sur xet ses indices (noms ou positions). Cela facilite (par exemple) le chargement de tous les csvfichiers dans un répertoire, en ajoutant une filenamecolonne à chacun.

    dir("\\.csv$") %>%
      set_names() %>%
      map(read.csv) %>%
      imap(~ transform(.x, filename = .y))
  • walk()renvoie son entrée de manière invisible; et est utile lorsque vous appelez une fonction pour ses effets secondaires (c'est-à-dire l'écriture de fichiers sur le disque).

Sans parler des autres aides comme safely()et partial().

Personnellement, je trouve que lorsque j'utilise purrr, je peux écrire du code fonctionnel avec moins de friction et une plus grande facilité; cela réduit l'écart entre la conception d'une idée et sa mise en œuvre. Mais votre kilométrage peut varier; il n'est pas nécessaire d'utiliser purrr à moins que cela ne vous aide réellement.

Microbenchmarks

Oui, map()est légèrement plus lent que lapply(). Mais le coût d'utilisation map()ou lapply()est déterminé par ce que vous mappez, et non par les frais généraux liés à l'exécution de la boucle. Le microbenchmark ci-dessous suggère que le coût de map()par rapport à lapply()est d'environ 40 ns par élément, ce qui semble peu susceptible d'avoir un impact significatif sur la plupart du code 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

2
Vouliez-vous utiliser transform () dans cet exemple? Comme dans la base R transform (), ou est-ce que je manque quelque chose? transform () vous donne le nom de fichier comme facteur, ce qui génère des avertissements lorsque vous souhaitez (naturellement) lier des lignes entre elles. mutate () me donne la colonne de caractères des noms de fichiers que je veux. Y a-t-il une raison de ne pas l'utiliser là-bas?
doctorG

2
Oui, mieux vaut utiliser mutate(), je voulais juste un exemple simple sans autres déps.
hadley

La spécificité de type ne devrait-elle pas apparaître quelque part dans cette réponse? map_*est ce qui m'a permis de charger purrrde nombreux scripts. Cela m'a aidé avec certains aspects de «flux de contrôle» de mon code ( stopifnot(is.data.frame(x))).
Fr.

2
ggplot et data.table sont excellents, mais avons-nous vraiment besoin d'un nouveau package pour chaque fonction de R?
adn bps

58

Comparer purrret lapplyse résume à la commodité et à la vitesse .


1. purrr::mapest syntaxiquement plus pratique que lapply

extraire le deuxième élément de la liste

map(list, 2)  

qui comme @F. Privé a souligné, c'est la même chose que:

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

avec lapply

lapply(list, 2) # doesn't work

nous devons passer une fonction anonyme ...

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

... ou comme l'a souligné @RichScriven, nous passons [[comme argument danslapply

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

Donc, si vous vous retrouvez à appliquer des fonctions à de nombreuses listes en utilisant lapply, et que vous vous lassez de définir une fonction personnalisée ou d'écrire une fonction anonyme, la commodité est une raison à privilégier purrr.

2. Les fonctions de carte spécifiques au type sont simplement de nombreuses lignes de code

  • map_chr()
  • map_lgl()
  • map_int()
  • map_dbl()
  • map_df()

Chacune de ces fonctions de mappage spécifiques au type renvoie un vecteur, plutôt que les listes renvoyées par map()et lapply(). Si vous avez affaire à des listes imbriquées de vecteurs, vous pouvez utiliser ces fonctions de carte spécifiques au type pour extraire les vecteurs directement et les contraindre directement en vecteurs int, dbl, chr. La version de base R ressemblerait à quelque chose comme as.numeric(sapply(...)), as.character(sapply(...)), etc.

Les map_<type>fonctions ont également la qualité utile que si elles ne peuvent pas renvoyer un vecteur atomique du type indiqué, elles échouent. Ceci est utile lors de la définition d'un flux de contrôle strict, où vous voulez qu'une fonction échoue si elle génère [d'une manière ou d'une autre] le mauvais type d'objet.

3. Mis à part la commodité, lapplyest [légèrement] plus rapide quemap

Utilisation purrrdes fonctions pratiques de, comme @F. Privé a souligné ralentit un peu le traitement. Faisons la course pour chacun des 4 cas que j'ai présentés ci-dessus.

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

entrez la description de l'image ici

Et le gagnant est....

lapply(list, `[[`, 2)

En résumé, si la vitesse brute est ce que vous recherchez: base::lapply(même si ce n'est pas beaucoup plus rapide)

Pour une syntaxe et une expressibilité simples: purrr::map


Cet excellent purrrtutoriel met en évidence la commodité de ne pas avoir à écrire explicitement des fonctions anonymes lors de l'utilisation purrr, et les avantages des mapfonctions spécifiques au type .


2
Notez que si vous utilisez function(x) x[[2]]au lieu de juste 2, ce serait moins lent. Tout ce temps supplémentaire est dû à des contrôles qui lapplyne fonctionnent pas.
F. Privé

17
Vous n'avez pas "besoin" de fonctions anonymes. [[est une fonction. Vous pouvez le faire lapply(list, "[[", 3).
Rich Scriven

@RichScriven qui a du sens. Cela simplifie la syntaxe d'utilisation de lapply sur purrr.
Rich Pauloo

37

Si nous ne considérons pas les aspects de goût (sinon cette question devrait être fermée) ou la cohérence de la syntaxe, le style, etc., la réponse est non, il n'y a pas de raison particulière d'utiliser map place lapplyou d'autres variantes de la famille apply, telles que la plus stricte vapply.

PS: À ces personnes qui votent gratuitement, rappelez-vous simplement que le PO a écrit:

Je ne demande pas ici ce que l'on aime ou n'aime pas sur la syntaxe, les autres fonctionnalités fournies par purrr etc., mais strictement sur la comparaison de purrr :: map avec lapply en supposant en utilisant l'évaluation standard

Si vous ne tenez pas compte de la syntaxe ou d'autres fonctionnalités de purrr, il n'y a aucune raison particulière à utiliser map. Je m'utilise purrrmoi-même et je suis d'accord avec la réponse de Hadley, mais ironiquement, cela revient sur les choses mêmes que l'OP a déclaré d'emblée qu'il ne demandait pas.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.