Tl;dr
Je ne parle pas des goûts ou des préférences en matière de syntaxe ou d'autres fonctionnalités offertes par purrr.
Choisissez l'outil qui correspond à votre cas d'utilisation, et maximisez votre productivité. Pour un code de production qui privilégie la vitesse, utilisez *apply
, pour un code qui nécessite une empreinte mémoire réduite, utilisez map
. Basé sur l'ergonomie, map
est probablement préférable pour la plupart des utilisateurs et la plupart des tâches ponctuelles.
Convenience
mise à jour octobre 2021 Puisque la réponse acceptée et le 2ème post le plus voté mentionnent la convenance de la syntaxe :
Les versions R 4.1.1 et supérieures supportent désormais la fonction anonyme abrégée \(x)
et la syntaxe de pipeline |>
. Pour vérifier votre version R, utilisez version[['version.string']]
.
library(purrr)
library(repurrrsive)
lapply(got_chars[1:2], `[[`, 2) |>
lapply(\(.) . + 1)
#> [[1]]
#> [1] 1023
#>
#> [[2]]
#> [1] 1053
map(got_chars[1:2], 2) %>%
map(~ . + 1)
#> [[1]]
#> [1] 1023
#>
#> [[2]]
#> [1] 1053
La syntaxe de l'approche purrr
est généralement plus courte à taper si votre tâche implique plus de 2 manipulations d'objets de type liste.
nchar(
"lapply(x, fun, y) |>
lapply(\\(.) . + 1)")
#> [1] 45
nchar(
"library(purrr)
map(x, fun) %>%
map(~ . + 1)")
#> [1] 45
En considérant qu'une personne pourrait écrire des dizaines ou des centaines de milliers de ces appels dans sa carrière, cette différence de longueur de syntaxe peut équivaloir à l'écriture de 1 ou 2 romans (roman moyen 80 000 lettres), étant donné que le code est tapé. Pensez également à votre vitesse de saisie du code (~65 mots par minute ?), votre précision de saisie (vous arrive-t-il souvent de mal taper certaines syntaxes (\"<
?), votre souvenir des arguments de fonctions, alors vous pouvez faire une comparaison équitable de votre productivité en utilisant un style ou une combinaison des deux.
Une autre considération pourrait être votre public cible. Personnellement, j'ai trouvé plus difficile d'expliquer comment purrr::map
fonctionne par rapport à lapply
précisément en raison de sa syntaxe concise.
1 |>
lapply(\(.z) .z + 1)
#> [[1]]
#> [1] 2
1 %>%
map(~ .z+ 1)
#> Error in .f(.x[[i]], ...) : object '.z' not found
mais,
1 %>%
map(~ .+ 1)
#> [[1]]
#> [1] 2
Speed
Souvent, lorsqu'on travaille avec des objets de type liste, de multiples opérations sont effectuées. Une nuance à la discussion est que le surcoût de purrr
est négligeable dans la plupart des cas - traitant de grandes listes et des cas d'utilisation.
got_large <- rep(got_chars, 1e4) # 300 000 éléments, 1.3 Go en mémoire
bench::mark(
base = {
lapply(got_large, `[[`, 2) |>
lapply(\(.) . * 1e5) |>
lapply(\(.) . / 1e5) |>
lapply(\(.) as.character(.))
},
purrr = {
map(got_large, 2) %>%
map(~ . * 1e5) %>%
map(~ . / 1e5) %>%
map(~ as.character(.))
}, iterations = 100,
)[c(1, 3, 4, 5, 7, 8, 9)]
# A tibble: 2 x 7
expression median `itr/sec` mem_alloc n_itr n_gc total_time
1 base 1.19s 0.807 9.17MB 100 301 2.06m
2 purrr 2.67s 0.363 9.15MB 100 919 4.59m
Cela diverge davantage lorsque plusieurs actions sont effectuées. Si vous écrivez du code qui est utilisé régulièrement par des utilisateurs ou sur lequel dépendent des packages, la vitesse pourrait être un facteur significatif à prendre en compte dans votre choix entre R de base et purr. Remarquez que purrr
a une empreinte mémoire légèrement plus faible.
Cependant, il y a un contre-argument : Si vous voulez de la vitesse, passez à un langage de plus bas niveau.
15 votes
Pour les cas d'utilisation simples en effet, il vaut mieux rester avec R de base et éviter les dépendances. Si vous avez déjà chargé le
tidyverse
, vous pouvez néanmoins bénéficier de l'opérateur pipe%>%
et de la syntaxe des fonctions anonymes~ .x + 1
0 votes
De plus,
purrr::map
fournit une gamme de fonctions, telles quemap_int
,map_dbl
,map_lgl
etmap2
, etc., qui étendent la fonctionnalité au-delà delapply
tout en conservant une syntaxe cohérente.1 votes
Je suis d'accord avec vous deux, toutes les fonctionnalités mentionnées par vous sont super et sont la raison pour laquelle j'utilise purrr, mais je suis intéressé par le cas simple et je me demande s'il y a un avantage (peut-être par exemple une meilleure gestion des exceptions?).
0 votes
Malheureusement, je ne sais pas lire le code C. Peut-être que la réponse réside dans la comparaison entre github.com/tidyverse/purrr/blob/… et github.com/wch/r-source/blob/…
66 votes
C'est principalement une question de style. Vous devriez savoir ce que font les fonctions de base de R, car tout ce bazar tidyverse n'est qu'une coquille par-dessus. À un moment donné, cette coquille se cassera.
1 votes
J'ai trouvé un seul test dans
purrr/tests/
comparant les sorties demap()
etlapply()
:test_that("map force les arguments de la même manière que les R de base", {f_map <- map(1:2, function(i) function(x) x + i) ; f_base <- lapply(1:2, function(i) function(x) x + i) ; expect_equal(f_map[[1]](0), f_base[[1]](0)) ; expect_equal(f_map[[2]](0), f_base[[2]](0)) })
et curieusement, il échoue lorsque je le copie-colle-et-exécute. Est-ce lié aux règles d'évaluation? github.com/tidyverse/purrr/blob/master/tests/testthat/…12 votes
~{}
lambda raccourci (avec ou sans les{}
scelle l'accord pour moi avecpurrr::map()
simple. Le type de reconnaissance depurrr::map_…()
est utile et moins obscur quevapply()
.purrr::map_df()
est une fonction très coûteuse mais elle simplifie également le code. Il n'y a absolument rien de mal à rester avec les fonctions de base R[lsv]apply()
.3 votes
@JanLauGe veuillez rechercher
vapply
,mapply
et leurs amis. Ce n'est pas parce que tu ne sais pas le faire que cela n'existe pas dans R de base. Rien contrepurrr::map
, mais c'est JAF : Just Another Function.1 votes
@Aurèle Vous avez besoin de la dernière version de purrr. Semble être une correction de bug.
0 votes
@F.Privé J'ai mis à jour à
0.2.2.9000
, maintenant le test passe. Merci0 votes
Il est vrai que j'ai écrit ma réponse avant de lire votre publication très attentivement. Ma réponse met en avant des éléments que vous connaissez probablement déjà, mais en termes de performance pure, lapply est un peu plus rapide. Je pense que cela dépend simplement de ce avec quoi vous êtes le plus à l'aise...
7 votes
Merci pour la question - genre de truc que j'ai aussi examiné. J'utilise R depuis plus de 10 ans et je ne vais définitivement pas utiliser
purrr
. Mon point est le suivant :tidyverse
est fabuleux pour les analyses/interactives/rapports, pas pour la programmation. Si vous devez utiliserlapply
oumap
, alors vous programmez et vous pourriez finir par créer un package un jour. Moins il y a de dépendances, mieux c'est. De plus : je vois parfois des gens utilisermap
avec une syntaxe assez obscure. Et maintenant que je vois les tests de performances : si vous êtes habitué à la familleapply
: continuez avec ça.7 votes
Tim, tu as écrit: "Je ne demande pas ici sur les préférences de chacun concernant 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 tu as acceptée est celle qui passe exactement sur ce que tu as dit ne pas vouloir que les gens abordent.
1 votes
@CarlosCinelli vrai, mais cette réponse, tout comme les autres réponses, affirme qu'il n'y a pas de différence et offre l'examen le plus complet du sujet.
3 votes
À qui de droit : Cette question a été mise en attente car elle est principalement basée sur des opinions et a déjà été réouverte quatre fois d'affilée. Ce n'est pas que cela m'ait dérangé, mais veuillez remarquer que ce type de vote ne semble pas mener à grand-chose...
0 votes
@Tim tu pourrais reformuler la question comme "Puis-je remplacer en toute sécurité tout appel de
lapply
par un appel demap
et m'attendre à ce que mon code ne se casse pas?". Cela supprime le problème de certaines personnes interprétant votre question comme étant basée sur l'opinion et vous obtiendrez toujours les bonnes réponses (si j'ai bien compris votre question)...1 votes
@jena merci mais il semble que les problèmes se soient réglés, bien que le titre plus court soit plus facile à lire, donc je vais le garder.