{"id":1324,"date":"2021-09-07T14:47:52","date_gmt":"2021-09-07T12:47:52","guid":{"rendered":"http:\/\/perso.ens-lyon.fr\/lise.vaudor\/?p=1324"},"modified":"2021-09-07T14:47:52","modified_gmt":"2021-09-07T12:47:52","slug":"practice-makes-purrr-fect","status":"publish","type":"post","link":"https:\/\/perso.ens-lyon.fr\/lise.vaudor\/practice-makes-purrr-fect\/","title":{"rendered":"Practice makes purrr-fect"},"content":{"rendered":"<p><img decoding=\"async\" src=\"..\/..\/lise.vaudor\/Rfigures\/Purrrfect\/Lise_Vaudor_headband-1.png\" alt=\"\" \/><\/p>\n<h1>purrr et dplyr sont dans un bateau: aucun ne tombe \u00e0 l\u2019eau<\/h1>\n<p>Voil\u00e0 d\u00e9j\u00e0 3 ans, je publiais sur ce blog un <a href=\"http:\/\/perso.ens-lyon.fr\/lise.vaudor\/iterer-des-fonctions-avec-purrr\/\">billet sur le package purrr<\/a>. Depuis, avec la pratique, j\u2019ai pu identifier quelques points techniques qui me mettaient en difficult\u00e9 assez fr\u00e9quemment et pour lesquels j\u2019aimerais vous pr\u00e9senter quelques explications.<\/p>\n<p>Ces difficult\u00e9s sont en fait n\u00e9es de l\u2019utilisation simultan\u00e9e de <code>dplyr<\/code> et de <code>purrr<\/code>, qui sont certes con\u00e7us pour fonctionner ensemble, mais dont l\u2019usage conjoint pouvait des fois causer quelques noeuds \u00e0 mon cerveau lors de l\u2019\u00e9criture de mes codes.<\/p>\n<p>Un petit rappel rapide d\u2019abord:<\/p>\n<p>Le principe de base de purrr, c\u2019est d\u2019<strong>it\u00e9rer n fois une fonction sur les n \u00e9l\u00e9ments d\u2019un vecteur ou d\u2019une liste<\/strong>.<\/p>\n<p>Si la fonction <code>.f()<\/code> prend en entr\u00e9e un argument <code>x<\/code> (et \u00e9ventuellement des arguments suppl\u00e9mentaires figur\u00e9s ici par <code>...<\/code>) et renvoie en sortie un r\u00e9sultat <code>y<\/code><\/p>\n<p><img decoding=\"async\" src=\"..\/..\/lise.vaudor\/Rfigures\/Purrr\/purrr1.png\" style=\"width:30.0%\" \/><\/p>\n<p>alors on peut gr\u00e2ce \u00e0 <code>purrr::map()<\/code> appliquer la fonction <code>.f()<\/code> \u00e0 tout un vecteur ou liste <code>.x=(x_1,x_2,x_3,...,x_n)<\/code> pour obtenir un vecteur ou liste <code>(y_1,y_2,y_3,...,y_n)<\/code>.|<\/p>\n<p><img decoding=\"async\" src=\"..\/..\/lise.vaudor\/Rfigures\/Purrr\/purrr2.png\" style=\"width:30.0%\" \/><\/p>\n<p>Alors, partant de ce principe, comment les fonctions de purrr peuvent-elles s\u2019articuler avec les id\u00e9es \u201ctidy\u201d et notamment l\u2019omnipr\u00e9sence de tableaux pour traiter les donn\u00e9es?<\/p>\n<h1>Fonctions, formules et pip\u00e9abilit\u00e9<\/h1>\n<p>Le premier petit souci que j\u2019ai pu avoir dans l\u2019usage conjoint de <code>dplyr<\/code> et <code>purrr<\/code> \u00e9tait li\u00e9 au fait que (faute d\u2019avoir compris toutes les possibilit\u00e9s syntaxiques des fonctions du package) je me retrouvais souvent oblig\u00e9e d\u2019\u00e9crire des petites fonctions \u201crustines\u201d destin\u00e9es \u00e0 ne servir qu\u2019une seule fois, et qui faisaient tache dans la <em>beaut\u00e9 ondoyante et serpentine<\/em> de mon code :-p (beaut\u00e9 ondoyante et serpentine conf\u00e9r\u00e9e par l\u2019usage de dplyr et des pipes bien s\u00fbr).<\/p>\n<p>C\u2019est-\u00e0-dire que, au lieu de faire<\/p>\n<pre><code>resultat &lt;- blabla %&gt;%\n  dplyr::truc() %&gt;%\n  dplyr::bidule() %&gt;%\n  dplyr::machin() %&gt;%\n  dplyr::mutate(chose=purrr::map(fonction_standard)) %&gt;% \n  dplyr::bidule() %&gt;%\n  dplyr::machin()\n<\/code><\/pre>\n<p>j\u2019\u00e9tais r\u00e9guli\u00e8rement de faire un truc du genre<\/p>\n<pre><code>resultat &lt;- blabla %&gt;%\n  dplyr::truc() %&gt;%\n  dplyr::bidule() %&gt;%\n  dplyr::machin()\n\nfonction_rustine=function(blabla){\n  blabla\n}\n\nresultat= resultat %&gt;%\n  dplyr::mutate(chose=purrr::map(fonction_rustine)) %&gt;% \n  dplyr::bidule() %&gt;%\n  dplyr::machin()\n<\/code><\/pre>\n<p>car je ne trouvais pas la <strong>fonction standard ad\u00e9quate<\/strong>.<\/p>\n<p>Ainsi donc, ma <strong>m\u00e9connaissance de l\u2019usage des formules<\/strong> dans les fonctions de <code>purrr<\/code> nuisaient \u00e0 la \u201cpip\u00e9abilit\u00e9\u201d de mon code. Cela vous semble peut-\u00eatre un d\u00e9tail, mais \u00e7a m\u2019ennuyait beaucoup (imaginez l\u2019exemple ci-dessus avec davantage de lignes et plusieurs \u201cpetites fonctions rustines\u201d par exemple: la relecture et compr\u00e9hension de la cha\u00eene de traitement s\u2019en trouve vite complexifi\u00e9e, m\u00eame pour des op\u00e9rations \u201ctoutes b\u00eates\u201d).<\/p>\n<p>Ainsi donc, premi\u00e8re prise de conscience de ma part, on peut \u00e9crire, au lieu de :<\/p>\n<pre><code>rustine=function(blabla){\n  lignes_de_commande_impliquant_blabla\n}\n\npurrr::map(.x=truc, .f=rustine)\n<\/code><\/pre>\n<p>quelque chose comme:<\/p>\n<pre><code>purrr::map(.x=truc,\n           ~lignes_de_commande_impliquant_.x)\n<\/code><\/pre>\n<p>L\u2019usage d\u2019une formule peut aussi permettre d\u2019<strong>utiliser une fonction standard qu\u2019on souhaite it\u00e9rer sur un autre argument que son premier argument<\/strong>. Par exemple:<\/p>\n<pre><code>purrr::map(.x=truc,\n           ~fonction_standard(a=33,b=.x))\n<\/code><\/pre>\n<p>Attention \u00e0 la <strong>position des arguments suppl\u00e9mentaires<\/strong> pour la fonction .f() dans l\u2019appel \u00e0 map!<\/p>\n<p>Dans le cas o\u00f9 on sp\u00e9cifie une <strong>fonction<\/strong>:<\/p>\n<pre><code>purrr::map(.x=truc,\n           .f=fonction_machin,\n           argument_suppl\u00e9mentaire=33) \n#argument sp\u00e9cifi\u00e9 dans l'appel \u00e0 map()\n<\/code><\/pre>\n<p>Dans le cas o\u00f9 on sp\u00e9cifie une <strong>formule<\/strong>:<\/p>\n<pre><code>purrr::map(.x=truc,\n           .f=~fonction_machin(blabla,\n                               argument_suppl\u00e9mentaire=33)) \n# argument sp\u00e9cifi\u00e9 dans l'appel \u00e0 fonction_machin()\n<\/code><\/pre>\n<p><small> Je n\u2019ai pas r\u00e9ussi pour le moment \u00e0 construire un \u201cvrai\u201d exemple permettant d\u2019illustrer ces principes tout en restant simple\u2026 Je vais donc me contenter pour le moment de ces \u2018fausses\u2019 lignes de code\u2026<\/small><\/p>\n<h1>Petit \u00e0 petit, les donn\u00e9es font leur nid<\/h1>\n<p>Passons maintenant \u00e0 une autre fonction qui me permet r\u00e9guli\u00e8rement d\u2019utiliser <code>purrr<\/code> pour mes jeux de donn\u00e9es.<\/p>\n<p>Il s\u2019agit de la fonction tidyr::nest().<\/p>\n<p>Chargeons le tidyverse:<\/p>\n<pre><code>library(tidyverse)\n<\/code><\/pre>\n<p>et examinons la situation suivante:<\/p>\n<pre><code>birds=tibble(id=paste0(\"ad_\",1:6),\n             species=c(\"orange\",\"yellow\",\"blue\",\n                       \"blue\",\"yellow\",\"orange\"),\n             sex=rep(c(\"M\",\"F\"),3))\n<\/code><\/pre>\n<p><img decoding=\"async\" src=\"..\/..\/lise.vaudor\/Rfigures\/Purrrfect\/nest_im1.jpg\" style=\"width:30.0%\" \/><\/p>\n<p>La fonction tidyr::nest() permet de regrouper des lignes et colonnes en sous-jeux de donn\u00e9es dans une colonne <code>data<\/code>. La colonne <code>data<\/code> correspond \u00e0 une <strong>colonne-liste<\/strong> (ou <code>list-column en anglais<\/code>). Autrement dit, la commande ci-dessous regroupe les donn\u00e9es (selon l\u2019argument sp\u00e9cifi\u00e9 pour <code>group_by()<\/code>) en <strong>nids<\/strong>.<\/p>\n<pre><code>nested_couples=birds %&gt;% \n  group_by(species) %&gt;% \n  nest()\n\nnested_couples\n\n## # A tibble: 3 \u00d7 2\n## # Groups:   species [3]\n##   species data            \n##   &lt;chr&gt;   &lt;list&gt;          \n## 1 orange  &lt;tibble [2 \u00d7 2]&gt;\n## 2 yellow  &lt;tibble [2 \u00d7 2]&gt;\n## 3 blue    &lt;tibble [2 \u00d7 2]&gt;\n<\/code><\/pre>\n<p><img decoding=\"async\" src=\"..\/..\/lise.vaudor\/Rfigures\/Purrrfect\/nest_im2.jpg\" style=\"width:30.0%\" \/><\/p>\n<p>Chaque couple-nid pond alors des oeufs selon des r\u00e8gles propres \u00e0 leur esp\u00e8ce. D\u00e9finissons la fonction <code>lay_eggs()<\/code> correspondant \u00e0 ce processus.<\/p>\n<pre><code>lay_eggs=function(species){\n  n_egg=case_when(species==\"orange\"~2,\n                  species==\"blue\"~1,\n                  species==\"yellow\"~3)\n  eggs=tibble(egg=paste0(\"egg_\",1:n_egg))\n  return(eggs)\n}\nlay_eggs(\"orange\")\n\n## # A tibble: 2 \u00d7 1\n##   egg  \n##   &lt;chr&gt;\n## 1 egg_1\n## 2 egg_2\n<\/code><\/pre>\n<p>Cette fonction prend en argument d\u2019entr\u00e9e l\u2019esp\u00e8ce consid\u00e9r\u00e9e, et renvoie en sortie une table comprenant autant de lignes que d\u2019oeufs pondus.<\/p>\n<p>On peut appliquer cette fonction \u00e0 l\u2019ensemble des couples-nids de la mani\u00e8re suivante:<\/p>\n<pre><code>after_lay_eggs=nested_couples %&gt;% \n  mutate(eggs=purrr::map(species,lay_eggs))\n<\/code><\/pre>\n<p><img decoding=\"async\" src=\"..\/..\/lise.vaudor\/Rfigures\/Purrrfect\/nest_im3.jpg\" style=\"width:30.0%\" \/><\/p>\n<p>Examinons de plus pr\u00e8s ce r\u00e9sultat, par exemple pour l\u2019esp\u00e8ce \u201cyellow\u201d:<\/p>\n<pre><code>after_lay_eggs %&gt;% filter(species==\"yellow\") %&gt;% pull(eggs)\n\n## [[1]]\n## # A tibble: 3 \u00d7 1\n##   egg  \n##   &lt;chr&gt;\n## 1 egg_1\n## 2 egg_2\n## 3 egg_3\n<\/code><\/pre>\n<p>Il va s\u2019agir maintenant de voir \u00e9clore les oeufs. Voici la fonction qui correspond \u00e0 ce processus. Elle prend deux arguments: <code>eggs<\/code>, \u00e9videmment, mais aussi <code>species<\/code>, dont d\u00e9pend le sex-ratio des juv\u00e9niles.<\/p>\n<pre><code>hatch_eggs=function(eggs,species){\n  sex_ratio=case_when(species==\"blue\"~0.54,\n                      species==\"yellow\"~0.6,\n                      species==\"orange\"~0.4)\n  youngs=eggs %&gt;% \n    mutate(young=str_replace(egg,\"egg\",\"young\")) %&gt;% \n    select(-egg) %&gt;% \n    mutate(sex=runif(nrow(eggs),0,1)) %&gt;% \n    mutate(sex=sex&lt;sex_ratio) %&gt;% \n    mutate(sex=case_when(sex==T~\"M\",\n                         sex!=T~\"F\"))\n}\n<\/code><\/pre>\n<p>On it\u00e8re sur les deux arguments de la fonction donc on utilise <code>purrr::map2()<\/code> :<\/p>\n<pre><code>set.seed(33)\nafter_hatch=after_lay_eggs %&gt;% \n  mutate(youngs=purrr::map2(eggs,species,hatch_eggs)) %&gt;% \n  select(-eggs)\n<\/code><\/pre>\n<p><small>Je retire la colonne <code>eggs<\/code> qui n\u2019a plus lieu d\u2019\u00eatre apr\u00e8s \u00e9closion\u2026<\/small><\/p>\n<p><img decoding=\"async\" src=\"..\/..\/lise.vaudor\/Rfigures\/Purrrfect\/nest_im4.jpg\" style=\"width:30.0%\" \/><\/p>\n<p>Examinons plus en d\u00e9tail par exemple ce qu\u2019on obtient pour l\u2019esp\u00e8ce \u201cyellow\u201d:<\/p>\n<pre><code>after_hatch %&gt;% filter(species==\"yellow\") %&gt;% pull(youngs)\n\n## [[1]]\n## # A tibble: 3 \u00d7 2\n##   young   sex  \n##   &lt;chr&gt;   &lt;chr&gt;\n## 1 young_1 F    \n## 2 young_2 F    \n## 3 young_3 M\n<\/code><\/pre>\n<p>Il est maintenant de laisser s\u2019envoler nos petits oiseaux! Nous allons les faire sortir du nid\u2026<\/p>\n<p><img decoding=\"async\" src=\"..\/..\/lise.vaudor\/Rfigures\/Purrrfect\/nest_im5.jpg\" style=\"width:30.0%\" \/><\/p>\n<pre><code>birds_youngs=after_hatch %&gt;% \n  unnest(cols=c(\"youngs\")) %&gt;% \n  ungroup()\nbirds_youngs\n\n## # A tibble: 6 \u00d7 4\n##   species data             young   sex  \n##   &lt;chr&gt;   &lt;list&gt;           &lt;chr&gt;   &lt;chr&gt;\n## 1 orange  &lt;tibble [2 \u00d7 2]&gt; young_1 M    \n## 2 orange  &lt;tibble [2 \u00d7 2]&gt; young_2 F    \n## 3 yellow  &lt;tibble [2 \u00d7 2]&gt; young_1 F    \n## 4 yellow  &lt;tibble [2 \u00d7 2]&gt; young_2 F    \n## 5 yellow  &lt;tibble [2 \u00d7 2]&gt; young_3 M    \n## 6 blue    &lt;tibble [2 \u00d7 2]&gt; young_1 M\n<\/code><\/pre>\n<p>En faisant appel \u00e0 <code>unnest()<\/code> sur la colonne <code>youngs<\/code>, on sort les juv\u00e9niles de leur nid et les attributs des \u201cnids\u201d (ici <code>species<\/code>) sont r\u00e9p\u00e9t\u00e9s autant de fois que n\u00e9cessaire pour qualifier d\u00e9sormais les individus juv\u00e9niles.<\/p>\n<p>Notez qu\u2019on ne pourrait pas faire la m\u00eame op\u00e9ration de mani\u00e8re concomittante sur <code>data<\/code> pour une question de dimensions (<code>data<\/code> comporte des \u00e9l\u00e9ments qui ont tous 2 lignes, <code>youngs<\/code> comporte des \u00e9l\u00e9ments dont le nombre de ligne varie entre 1 et 3).<\/p>\n<p>Ce petit exemple tr\u00e8s simple (et imag\u00e9) vous aidera j\u2019esp\u00e8re \u00e0 exploiter les possibilit\u00e9s offertes par le trio dplyr-tidyr::nest()-purrr!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>purrr et dplyr sont dans un bateau: aucun ne tombe \u00e0 l\u2019eau Voil\u00e0 d\u00e9j\u00e0 3 ans, je publiais sur ce blog un billet sur le package purrr. Depuis, avec la pratique, j\u2019ai pu identifier quelques points techniques qui me mettaient en difficult\u00e9 assez fr\u00e9quemment et pour lesquels j\u2019aimerais vous pr\u00e9senter quelques explications. Ces difficult\u00e9s sont en fait n\u00e9es de l\u2019utilisation simultan\u00e9e de dplyr et de purrr, qui sont certes con\u00e7us.. <a href=\"https:\/\/perso.ens-lyon.fr\/lise.vaudor\/practice-makes-purrr-fect\/\">Read More<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-1324","post","type-post","status-publish","format-standard","hentry","category-tous-les-posts"],"_links":{"self":[{"href":"https:\/\/perso.ens-lyon.fr\/lise.vaudor\/wp-json\/wp\/v2\/posts\/1324","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/perso.ens-lyon.fr\/lise.vaudor\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/perso.ens-lyon.fr\/lise.vaudor\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/perso.ens-lyon.fr\/lise.vaudor\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/perso.ens-lyon.fr\/lise.vaudor\/wp-json\/wp\/v2\/comments?post=1324"}],"version-history":[{"count":5,"href":"https:\/\/perso.ens-lyon.fr\/lise.vaudor\/wp-json\/wp\/v2\/posts\/1324\/revisions"}],"predecessor-version":[{"id":1377,"href":"https:\/\/perso.ens-lyon.fr\/lise.vaudor\/wp-json\/wp\/v2\/posts\/1324\/revisions\/1377"}],"wp:attachment":[{"href":"https:\/\/perso.ens-lyon.fr\/lise.vaudor\/wp-json\/wp\/v2\/media?parent=1324"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/perso.ens-lyon.fr\/lise.vaudor\/wp-json\/wp\/v2\/categories?post=1324"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/perso.ens-lyon.fr\/lise.vaudor\/wp-json\/wp\/v2\/tags?post=1324"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}