<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>Pyranhia</title>
<link>https://www.pyranhia.eu/blog.html</link>
<atom:link href="https://www.pyranhia.eu/blog.xml" rel="self" type="application/rss+xml"/>
<description>Formations et accompagnement en analyse de données avec R</description>
<generator>quarto-1.10.18</generator>
<lastBuildDate>Mon, 14 Sep 2026 22:00:00 GMT</lastBuildDate>
<item>
  <title>Cartes du monde avec R : projections, graticules et palettes</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202609-carto-proj/carto-proj.html</link>
  <description><![CDATA[ 





<section id="cartes-du-monde-avec-r-projections-graticules-et-palettes" class="level1 section-full section-padded">
<h1>Cartes du monde avec R : projections, graticules et palettes</h1>
<p><span class="section-subtitle">Mercator, Equal Earth et les autres : comprendre les projections cartographiques et les manier avec ggplot2.</span></p>
</section>
<section id="pourquoi-la-projection-choisie-change-tout" class="level2 section-1">
<h2 data-anchor-id="pourquoi-la-projection-choisie-change-tout">Pourquoi la projection choisie change tout</h2>
<p>Vous avez peut-être vu passer l’info : le 4 septembre 2026, <a href="https://fr.euronews.com/2026/09/05/paris-abandonne-la-projection-de-mercator-la-france-paraitra-desormais-plus-petite-sur-les">l’Assemblée générale des Nations unies a adopté une résolution</a> portée par le Togo encourageant les États, écoles et organisations internationales à délaisser la projection de Mercator au profit de représentations plus fidèles aux superficies réelles, comme la projection Equal Earth. Ce n’est pas la première fois qu’un grand acteur prend ses distances avec Mercator : Google Maps l’a fait dès 2018 pour ses vues à l’échelle mondiale, en basculant sur un globe 3D plutôt qu’une carte plate. Le choix d’une projection cartographique <strong>change la façon dont on perçoit la taille réelle des pays</strong>.</p>
<p>Et pour cause, <strong>aplatir une sphère sans la déformer est mathématiquement impossible</strong>. Chaque projection fait donc un choix, elle préserve certaines propriétés au prix d’en déformer d’autres :</p>
<ol type="1">
<li><strong>les surfaces</strong> (un pays deux fois plus grand qu’un autre en réalité doit apparaître deux fois plus grand sur la carte) ;</li>
<li><strong>les angles et les formes</strong> (un contour de pays ne doit pas être distordu) ;</li>
<li><strong>les distances</strong> (la longueur d’un segment sur la carte doit refléter la distance réelle).</li>
</ol>
<p>Aucune projection ne préserve les trois à la fois. C’est là que Mercator et Equal Earth prennent des chemins différents.</p>
<section id="mercator-des-angles-parfaits-des-surfaces-qui-explosent" class="level3">
<h3 data-anchor-id="mercator-des-angles-parfaits-des-surfaces-qui-explosent">Mercator : des angles parfaits, des surfaces qui explosent</h3>
<p>La projection de Mercator, créée en 1569 pour la navigation maritime, <strong>préserve les angles</strong> : une ligne droite sur la carte correspond à un cap constant au compas, très utile en mer. Mais cette préservation des angles a un coût élevé sur les surfaces, qui <strong>grandissent démesurément à mesure qu’on s’éloigne de l’équateur</strong>.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-carte-mercator-s1" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center" alt="Carte du monde en projection Mercator, sans l'Antarctique. Le Groenland, tout au nord, apparaît presque aussi grand que l'Afrique alors qu'il est en réalité bien plus petit : les régions proches des pôles sont fortement dilatées par rapport aux régions équatoriales.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-carte-mercator-s1-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202609-carto-proj/carto-proj_files/figure-html/fig-carte-mercator-s1-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Carte du monde en projection Mercator, sans l'Antarctique. Le Groenland, tout au nord, apparaît presque aussi grand que l'Afrique alors qu'il est en réalité bien plus petit : les régions proches des pôles sont fortement dilatées par rapport aux régions équatoriales." width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-carte-mercator-s1-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Le monde en projection Mercator.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Avec cette projection, <strong>le Groenland paraît aussi grand que l’Afrique</strong>, alors qu’en réalité l’Afrique fait environ 14 fois sa taille. C’est le reproche le plus connu fait à cette projection, elle donne une importance visuelle disproportionnée aux régions proches des pôles, au détriment des régions équatoriales.</p>
<div class="note-box">
<p><strong>Pourquoi l’Antarctique est souvent absent sur une carte Mercator</strong></p>
<p>L’échelle de Mercator diverge à mesure qu’on approche des pôles. L’Antarctique, qui s’étend jusqu’à -90° de latitude, devient démesurément et inutilisablement grand sous cette projection. C’est pourquoi ce continent est traité à part avec cette projection. Le plus souvent, il est tout simplement exclu des données avant de générer la figure.</p>
</div>
</section>
<section id="equal-earth-des-surfaces-fidèles-des-formes-qui-bougent-un-peu" class="level3">
<h3 data-anchor-id="equal-earth-des-surfaces-fidèles-des-formes-qui-bougent-un-peu">Equal Earth : des surfaces fidèles, des formes qui bougent un peu</h3>
<p>La projection Equal Earth <span class="citation" data-cites="savric2019Equal">(Šavrič et al. 2019)</span> part du problème inverse : elle <strong>préserve les proportions de surface</strong> entre les pays. Le Groenland y retrouve une taille comparable à sa réalité, au prix de formes et d’angles légèrement déformés loin de l’équateur.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-carte-equalearth-s1" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center" alt="Le même monde que la carte précédente, mais en projection Equal Earth : le globe prend une forme elliptique sur fond bleu clair, l'Afrique retrouve une taille bien plus importante que le Groenland, à l'inverse de la carte en Mercator.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-carte-equalearth-s1-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202609-carto-proj/carto-proj_files/figure-html/fig-carte-equalearth-s1-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Le même monde que la carte précédente, mais en projection Equal Earth : le globe prend une forme elliptique sur fond bleu clair, l'Afrique retrouve une taille bien plus importante que le Groenland, à l'inverse de la carte en Mercator." width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-carte-equalearth-s1-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Le même monde en projection Equal Earth.
</figcaption>
</figure>
</div>
</div>
</div>
<p>L’Afrique retrouve ainsi sa vraie surface, tandis que le Groenland rétrécit en conséquence. Rien n’a changé dans les données, uniquement la façon de les projeter sur un plan.</p>
<div class="note-box">
<p><strong>Pourquoi Equal Earth et pas une autre projection équivalente ?</strong></p>
<p>Equal Earth n’est pas la première projection à préserver les surfaces : Mollweide (1805) ou Gall-Peters (1855) le font aussi. Elle a été conçue spécifiquement pour donner des formes de continents plus naturelles à l’œil que ses prédécesseurs, tout en gardant cette propriété. C’est ce compromis esthétique qui explique son adoption récente par plusieurs médias et organisations.</p>
</div>
<p>Mais comment on fait ça concrètement, avec R ?</p>
</section>
</section>
<section id="faire-des-cartes-du-monde-avec-r" class="level2 section-2">
<h2 data-anchor-id="faire-des-cartes-du-monde-avec-r">Faire des cartes du monde avec R</h2>
<p>Pour ce tutoriel, on utilise trois packages qui se combinent naturellement :</p>
<ul>
<li><a href="https://r-spatial.github.io/sf/"><code>sf</code></a> (<em>simple features</em>) pour manipuler des données géographiques (points, lignes, polygones) ;</li>
<li><a href="https://docs.ropensci.org/rnaturalearth/"><code>rnaturalearth</code></a> pour récupérer directement les contours des pays du monde ;</li>
<li><code>ggplot2</code>, qu’on ne présente plus, avec sa fonction <code>geom_sf()</code> dédiée aux objets spatiaux.</li>
</ul>
<section id="récupérer-les-contours-du-monde" class="level3">
<h3 data-anchor-id="récupérer-les-contours-du-monde">Récupérer les contours du monde</h3>
<p><code>rnaturalearth</code> télécharge et met en cache des données géographiques prêtes à l’emploi. Pas besoin de chercher un shapefile sur internet.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1">world <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ne_countries</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">scale =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"medium"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">returnclass =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sf"</span>)</span></code></pre></div></div>
</div>
<p>L’argument <code>scale</code> contrôle le niveau de détail des contours (<code>"small"</code>, <code>"medium"</code> ou <code>"large"</code>). Pour une carte du monde entière, <code>"medium"</code> suffit largement : <code>"large"</code> alourdit inutilement le fichier sans gain visuel à cette échelle.</p>
<p><code>world</code> est un objet <code>sf</code> : un tableau de données classique, avec une colonne spéciale <code>geometry</code> qui contient les polygones de chaque pays.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1">world <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> </span>
<span id="cb2-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">select</span>(name, continent, pop_est, geometry) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> </span>
<span id="cb2-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">head</span>()</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>Simple feature collection with 6 features and 3 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -73.36621 ymin: -22.40205 xmax: 109.4449 ymax: 41.9062
Geodetic CRS:  WGS 84
       name     continent  pop_est                       geometry
1  Zimbabwe        Africa 14645468 MULTIPOLYGON (((31.28789 -2...
2    Zambia        Africa 17861030 MULTIPOLYGON (((30.39609 -1...
3     Yemen          Asia 29161922 MULTIPOLYGON (((53.08564 16...
4   Vietnam          Asia 96462106 MULTIPOLYGON (((104.064 10....
5 Venezuela South America 28515829 MULTIPOLYGON (((-60.82119 9...
6   Vatican        Europe      825 MULTIPOLYGON (((12.43916 41...</code></pre>
</div>
</div>
</section>
<section id="une-première-carte" class="level3">
<h3 data-anchor-id="une-première-carte">Une première carte</h3>
<p>Avec un objet <code>sf</code>, on peut utiliser la fonction <code>geom_sf()</code> qui comprend nativement la colonne <code>geometry</code>.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(world) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb4-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#6BAED6"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"white"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">linewidth =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb4-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">coord_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">expand =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb4-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_void</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb4-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">panel.background =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">element_rect</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#E4EFF6"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NA</span>))</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-carte-base" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center" alt="Carte du monde en projection plate carrée : les pays sont affichés directement en longitude et latitude, dans un rectangle. Les continents sont étirés horizontalement près des pôles, notamment le Groenland et l'Antarctique.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-carte-base-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202609-carto-proj/carto-proj_files/figure-html/fig-carte-base-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Carte du monde en projection plate carrée : les pays sont affichés directement en longitude et latitude, dans un rectangle. Les continents sont étirés horizontalement près des pôles, notamment le Groenland et l'Antarctique." width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-carte-base-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Carte du monde en projection par défaut (WGS84, non projetée)
</figcaption>
</figure>
</div>
</div>
</div>
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>Ici on utilise <code>panel.background</code> dans <code>theme()</code> pour colorer les océans, cela fonctionne car cette carte est sans projection. Pour les autres cas, nous verrons plus bas comment faire.</p>
</div>
</div>
</div>
<p>Par défaut, <code>geom_sf()</code> affiche les coordonnées géographiques (longitude, latitude) telles quelles, sans appliquer de projection. C’est ce qu’on appelle la <strong>projection plate carrée</strong> : chaque degré de longitude et chaque degré de latitude occupent le même espace sur l’écran. Simple, mais ce n’est pas neutre pour autant : les distorsions de surface aux pôles y sont déjà bien visibles.</p>
</section>
<section id="changer-de-projection" class="level3">
<h3 data-anchor-id="changer-de-projection">Changer de projection</h3>
<p>C’est <code>coord_sf()</code> qui gère la projection, via son argument <code>crs</code> (<em>coordinate reference system</em>, le système de référence des coordonnées). Pour Equal Earth, il n’existe pas encore de code EPSG largement adopté : on passe par une chaîne <strong>PROJ</strong>, qui décrit directement les paramètres de la projection.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb5-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> globe, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#E4EFF6"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NA</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb5-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> world, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#6BAED6"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"white"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">linewidth =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb5-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">coord_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=eqearth"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">expand =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb5-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_void</span>()</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-carte-equalearth" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center" alt="Carte du monde en projection Equal Earth obtenue avec coord_sf(), forme elliptique sur fond bleu clair représentant les océans, proportions de surface entre pays fidèles à la réalité.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-carte-equalearth-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202609-carto-proj/carto-proj_files/figure-html/fig-carte-equalearth-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Carte du monde en projection Equal Earth obtenue avec coord_sf(), forme elliptique sur fond bleu clair représentant les océans, proportions de surface entre pays fidèles à la réalité." width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-carte-equalearth-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: Carte du monde en projection Equal Earth
</figcaption>
</figure>
</div>
</div>
</div>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>EPSG ou PROJ, comment choisir ?
</div>
</div>
<div class="callout-body-container callout-body">
<p>Utilisez un code <strong>EPSG</strong> dès qu’il existe pour votre projection, comme <code>"EPSG:3857"</code> pour Mercator : c’est plus lisible et moins sujet aux erreurs. Passez par une chaîne <strong>PROJ</strong> (comme <code>+proj=eqearth</code> ou <code>+proj=robin</code>) quand la projection n’a pas de code EPSG standard ou que vous avez besoin de paramètres spécifiques (méridien central personnalisé par exemple).</p>
</div>
</div>
<p>Autre exemple de chaîne PROJ courante : Robinson, une projection qui ne préserve ni les surfaces ni les angles à la perfection, mais cherche un compromis visuel entre les deux. Elle a longtemps servi de référence pour les cartes du monde généralistes : la <em>National Geographic Society</em> l’a utilisée de 1988 à 1998, avant de lui préférer la projection Winkel Tripel, qui réduit encore un peu plus la déformation près des pôles.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb6-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb6-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> globe, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#E4EFF6"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NA</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb6-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> world, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#6BAED6"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"white"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">linewidth =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb6-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">coord_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=robin"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">expand =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb6-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_void</span>()</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-carte-robinson" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center" alt="Carte du monde en projection Robinson, forme ovale légèrement aplatie aux pôles, sur fond bleu clair. Compromis visuel entre préservation des surfaces et des angles, sans en privilégier aucune à l'extrême.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-carte-robinson-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202609-carto-proj/carto-proj_files/figure-html/fig-carte-robinson-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Carte du monde en projection Robinson, forme ovale légèrement aplatie aux pôles, sur fond bleu clair. Compromis visuel entre préservation des surfaces et des angles, sans en privilégier aucune à l'extrême." width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-carte-robinson-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: Carte du monde en projection Robinson
</figcaption>
</figure>
</div>
</div>
</div>
</section>
</section>
<section id="les-touches-finales-qui-font-la-différence" class="level2 section-3">
<h2 data-anchor-id="les-touches-finales-qui-font-la-différence">Les touches finales qui font la différence</h2>
<section id="un-fond-qui-épouse-la-projection" class="level3">
<h3 data-anchor-id="un-fond-qui-épouse-la-projection">Un fond qui épouse la projection</h3>
<p>Sans fond du tout, une carte projetée devient vite perturbante à lire : impossible de savoir où s’arrête la Terre et où commence le vide.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(world) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb7-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#6BAED6"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"white"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">linewidth =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb7-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">coord_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=eqearth"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">expand =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb7-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_void</span>()</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-carte-sans-fond" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center" alt="Carte du monde en projection Equal Earth sans aucun fond : les pays flottent sur un arrière-plan blanc uni, sans repère visuel pour situer le contour elliptique de la projection ni distinguer la terre de l'espace vide autour.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-carte-sans-fond-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202609-carto-proj/carto-proj_files/figure-html/fig-carte-sans-fond-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Carte du monde en projection Equal Earth sans aucun fond : les pays flottent sur un arrière-plan blanc uni, sans repère visuel pour situer le contour elliptique de la projection ni distinguer la terre de l'espace vide autour." width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-carte-sans-fond-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;6: Sans fond, difficile de dire où s’arrête le globe.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Le contour elliptique de la projection Equal Earth se devine à peine. Sur toutes les autres cartes de cet article, l’arrière-plan bleu clair comble ce vide en suivant la forme réelle de la projection. <strong>On pourrait croire que <code>theme(panel.background = ...)</code> suffit, mais ce n’est pas le cas</strong> : ce réglage colorie toujours un rectangle, quelle que soit la projection, puisqu’il agit sur le panel ggplot2 et non sur la carte elle-même.</p>
<p>La solution consiste à tracer le fond comme une géométrie à part entière, projetée dans le même système que les pays : un rectangle couvrant tout le globe en longitude/latitude (-180° à 180°, -90° à 90°), mais construit comme un contour tracé point par point plutôt que 4 coins isolés, pour que la reprojection le courbe correctement plutôt que de garder des côtés droits.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb8-1">globe <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_sfc</span>(</span>
<span id="cb8-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_polygon</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cbind</span>(</span>
<span id="cb8-3">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rep</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">181</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rep</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">181</span>)),</span>
<span id="cb8-4">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rep</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">361</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rep</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">361</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb8-5">  ))),</span>
<span id="cb8-6">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># sytème longitude / latitude</span></span>
<span id="cb8-7">)</span></code></pre></div></div>
</div>
<p>Il suffit ensuite de tracer <code>globe</code> en premier, avant les pays.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb9-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb9-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> globe, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#E4EFF6"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NA</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb9-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> world, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#6BAED6"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"white"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">linewidth =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb9-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">coord_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=eqearth"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">expand =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb9-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_void</span>()</span></code></pre></div></div>
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.pyranhia.eu/posts/202609-carto-proj/carto-proj_files/figure-html/globe-fond-map-1.png" class="img-fluid figure-img" alt="La même carte en projection Equal Earth que précédemment, mais avec un fond bleu clair en forme d'ellipse qui épouse exactement le contour de la projection, dessiné derrière les pays plutôt qu'en rectangle." width="1344"></p>
<figcaption>Carte en projection Equal Earth, avec les océans colorés pour voir la limite du globe.</figcaption>
</figure>
</div>
</div>
</div>
<p><code>coord_sf()</code> projette <code>globe</code> exactement comme il projette <code>world</code> : le rectangle en lat/lon devient l’ellipse, le cercle ou les lobes propres à chaque projection, sans code supplémentaire. Le découpage en pas de 1° (plutôt que 4 coins) est ce qui permet cette courbure : <code>sf</code> ne courbe que les segments qu’on lui fournit, pas les lignes entre deux points éloignés.</p>
</section>
<section id="des-couleurs-sélectionnées-avec-soin" class="level3">
<h3 data-anchor-id="des-couleurs-sélectionnées-avec-soin">Des couleurs sélectionnées avec soin</h3>
<p>Une carte muette (juste des contours) est rarement le but final. On veut le plus souvent représenter une variable par pays : c’est la <strong>carte choroplèthe</strong>. Prenons un exemple qui n’a besoin d’aucune source externe : le nombre de voisins frontaliers de chaque pays, calculé directement à partir des géométries de <code>world</code>.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb10-1">voisins <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_touches</span>(world)</span>
<span id="cb10-2">world<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>n_voisins <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lengths</span>(voisins)</span>
<span id="cb10-3"></span>
<span id="cb10-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb10-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> globe, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#E4EFF6"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NA</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb10-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> world, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> n_voisins), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"darkgray"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">linewidth =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb10-7">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">coord_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=eqearth"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">expand =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb10-8">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_fill_viridis_c</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">name =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Voisins"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb10-9">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_void</span>()</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-carte-voisins" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center" alt="Carte choroplèthe du monde en projection Equal Earth, chaque pays coloré selon son nombre de voisins terrestres avec une palette viridis allant du jaune clair (peu ou pas de voisins, notamment les îles) au violet foncé (beaucoup de voisins, comme la Chine ou la Russie).">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-carte-voisins-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202609-carto-proj/carto-proj_files/figure-html/fig-carte-voisins-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Carte choroplèthe du monde en projection Equal Earth, chaque pays coloré selon son nombre de voisins terrestres avec une palette viridis allant du jaune clair (peu ou pas de voisins, notamment les îles) au violet foncé (beaucoup de voisins, comme la Chine ou la Russie)." width="1536">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-carte-voisins-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;7: Nombre de pays voisins, par pays (projection Equal Earth)
</figcaption>
</figure>
</div>
</div>
</div>
<p><code>st_touches()</code> renvoie, pour chaque pays de <code>world</code>, la liste des autres pays qui partagent au moins un point de frontière avec lui ; <code>lengths()</code> compte simplement la taille de chaque sous-liste. Pas de jointure ni de fichier externe : toute l’information est déjà dans la géométrie des pays.</p>
<div class="note-box">
<p><strong>Qui a le plus de voisins ?</strong></p>
<p>La Chine arrive en tête avec 17 voisins terrestres, suivie par la Russie avec 14. À l’autre extrême, les îles ressortent en clair : 0 voisin, ce que <code>st_touches()</code> calcule correctement sans traitement particulier. À prendre comme illustration plutôt que comme référence géopolitique : le tracé des frontières disputées varie selon les jeux de données, <code>rnaturalearth</code> fait ses propres choix à ce sujet.</p>
</div>
<p>Le choix de la palette n’est pas un détail cosmétique : mal choisie, elle peut inverser la lecture des données ou les rendre illisibles pour une partie des lecteurs, comme le détaille mon article <a href="../../posts/202605-dataviz-1/dataviz-1.html">une palette de couleurs, ça ne se choisit pas au hasard</a>. Ici, le nombre de voisins est une variable numérique avec un ordre naturel, ce qui appelle une palette <strong>séquentielle</strong> comme <code>viridis</code> car perceptuellement uniforme et lisible pour les personnes daltoniennes.</p>
</section>
<section id="un-graticule-qui-ne-se-cache-pas-derrière-le-fond" class="level3">
<h3 data-anchor-id="un-graticule-qui-ne-se-cache-pas-derrière-le-fond">Un graticule qui ne se cache pas derrière le fond</h3>
<p>Le <strong>graticule</strong>, c’est la grille de méridiens et de parallèles en filigrane sur la plupart des cartes. <code>coord_sf()</code> peut en calculer un automatiquement via le thème (<code>panel.grid</code>), mais <code>panel.grid</code> se dessine toujours <strong>derrière</strong> les couches <code>geom_sf()</code> : sur les cartes de cet article, il existe bien mais reste caché sous le fond opaque <code>globe</code>.</p>
<p>La solution consiste à ne pas dépendre du thème du tout, et à tracer le graticule comme une géométrie à part entière avec <a href="https://r-spatial.github.io/sf/reference/st_graticule.html"><code>st_graticule()</code></a>, au même titre que <code>world</code> ou <code>globe</code> : son ordre d’apparition dans le code contrôle alors réellement ce qui se superpose à quoi.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb11-1">graticule <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_graticule</span>(</span>
<span id="cb11-2">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lon =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>), </span>
<span id="cb11-3">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lat =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">80</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">80</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>),</span>
<span id="cb11-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># sytème longitude / latitude</span></span>
<span id="cb11-5">  )</span>
<span id="cb11-6"></span>
<span id="cb11-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb11-8">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> globe, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#E4EFF6"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NA</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb11-9">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> graticule, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"grey75"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">linewidth =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb11-10">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> world, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#6BAED6"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"white"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">linewidth =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb11-11">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">coord_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=eqearth"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">expand =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb11-12">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_void</span>()</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-carte-graticule" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center" alt="Carte du monde en projection Equal Earth avec une grille de méridiens et parallèles gris clair visible sur l'océan, tracée par-dessus le fond bleu clair mais sous les pays, sans aucune étiquette.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-carte-graticule-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202609-carto-proj/carto-proj_files/figure-html/fig-carte-graticule-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Carte du monde en projection Equal Earth avec une grille de méridiens et parallèles gris clair visible sur l'océan, tracée par-dessus le fond bleu clair mais sous les pays, sans aucune étiquette." width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-carte-graticule-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;8: Graticule tracé explicitement avec st_graticule(), entre le fond et les pays
</figcaption>
</figure>
</div>
</div>
</div>
<p><code>st_graticule()</code> calcule les lignes en <code>EPSG:4326</code> selon les intervalles fournis via <code>lon</code> et <code>lat</code>. Trois couches empilées dans l’ordre voulu : <code>globe</code>, <code>graticule</code>, <code>world</code>. Ainsi la grille reste visible sur l’océan sans passer devant les pays.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb12-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">head</span>(graticule)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>Simple feature collection with 6 features and 10 fields
Attribute-geometry relationships: constant (3), NAs (7)
Geometry type: LINESTRING
Dimension:     XY
Bounding box:  xmin: -180 ymin: -89.91 xmax: -80 ymax: 89.91
Geodetic CRS:  WGS 84 (CRS84)
  degree type   degree_label                       geometry x_start y_start
1   -180    E   "180"*degree LINESTRING (-180 -89.91, -1...    -180  -89.91
2   -160    E "160"*degree*W LINESTRING (-160 -89.91, -1...    -160  -89.91
3   -140    E "140"*degree*W LINESTRING (-140 -89.91, -1...    -140  -89.91
4   -120    E "120"*degree*W LINESTRING (-120 -89.91, -1...    -120  -89.91
5   -100    E "100"*degree*W LINESTRING (-100 -89.91, -1...    -100  -89.91
6    -80    E " 80"*degree*W LINESTRING (-80 -89.91, -80...     -80  -89.91
  x_end y_end angle_start angle_end plot12
1  -180 89.91          90        90   TRUE
2  -160 89.91          90        90   TRUE
3  -140 89.91          90        90   TRUE
4  -120 89.91          90        90   TRUE
5  -100 89.91          90        90   TRUE
6   -80 89.91          90        90   TRUE</code></pre>
</div>
</div>
<p>Chaque ligne du graticule porte, en plus de sa géométrie, une colonne <code>degree_label</code> et des colonnes <code>x_start</code>/<code>y_start</code> qui donnent un point d’ancrage sur la ligne. Ce sont ces colonnes qu’on va exploiter dans la section suivante pour annoter les axes.</p>
</section>
<section id="annoter-les-axes" class="level3">
<h3 data-anchor-id="annoter-les-axes">Annoter les axes</h3>
<p>Une fois le graticule visible, reste la question des labels. <code>st_graticule()</code> calcule directement des étiquettes prêtes à l’emploi : chaque ligne porte une colonne <code>degree_label</code> déjà formatée avec point cardinal (<code>"120"*degree*W</code>…) en syntaxe <code>plotmath</code>, plus des colonnes <code>x_start</code>/<code>y_start</code> qui donnent une position d’ancrage.</p>
<p>Le graticule repasse ici à un intervalle de 60° plutôt que 20° : à 20°, chaque ligne porterait une étiquette, soit 18 labels par axe, ce qui serait illisible à cette taille de carte.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb14-1">graticule <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_graticule</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lon =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lat =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">80</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">80</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>)</span>
<span id="cb14-2"></span>
<span id="cb14-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Labels de parallèles (type "N"), ancrés sur le bord gauche (méridien -180°) :</span></span>
<span id="cb14-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># chaque parallèle porte un label côté ouest, qu'on récupère via x_start/y_start</span></span>
<span id="cb14-5">labels_lat <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> graticule <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb14-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filter</span>(type <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"N"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb14-7">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_drop_geometry</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb14-8">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_as_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">coords =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x_start"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"y_start"</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>)</span>
<span id="cb14-9"></span>
<span id="cb14-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Labels de méridiens (type "E"), replacés sur le bord bas (parallèle -80°)</span></span>
<span id="cb14-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># plutôt qu'à leur point d'ancrage d'origine, pour aligner tous les labels</span></span>
<span id="cb14-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># de longitude sur une même ligne en bas de carte</span></span>
<span id="cb14-13">labels_lon <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> graticule <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb14-14">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filter</span>(type <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"E"</span>, degree <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb14-15">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_drop_geometry</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb14-16">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y_start =</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">80</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb14-17">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_as_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">coords =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x_start"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"y_start"</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>)</span>
<span id="cb14-18"></span>
<span id="cb14-19"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb14-20">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> globe, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#E4EFF6"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NA</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb14-21">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> graticule, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"grey75"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">linewidth =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb14-22">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> world, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#6BAED6"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"white"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">linewidth =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb14-23">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># nudge_x/nudge_y décalent les labels hors des pays plutôt que dessus</span></span>
<span id="cb14-24">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_sf_text</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> labels_lat, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">label =</span> degree_label), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">parse =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.5</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"grey40"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">hjust =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">nudge_x =</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">600000</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb14-25">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_sf_text</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> labels_lon, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">label =</span> degree_label), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">parse =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.5</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"grey40"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">vjust =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">nudge_y =</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">600000</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb14-26">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">coord_sf</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=eqearth"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">expand =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">clip =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"off"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb14-27">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_void</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb14-28">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">plot.margin =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">margin</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">t =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">r =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">b =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">l =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">35</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">unit =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pt"</span>))</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-carte-annotations" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center" alt="Même carte du monde en projection Equal Earth avec graticule, cette fois annotée : les degrés de latitude sont indiqués sur le bord gauche et les degrés de longitude, avec points cardinaux N/S/E/W, en bas de la carte, en petit texte gris.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-carte-annotations-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202609-carto-proj/carto-proj_files/figure-html/fig-carte-annotations-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Même carte du monde en projection Equal Earth avec graticule, cette fois annotée : les degrés de latitude sont indiqués sur le bord gauche et les degrés de longitude, avec points cardinaux N/S/E/W, en bas de la carte, en petit texte gris." width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-carte-annotations-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;9: Graticule annoté : parallèles à gauche, méridiens en bas
</figcaption>
</figure>
</div>
</div>
</div>
<p>Pour transformer les coordonnées <code>x_start</code>/<code>y_start</code> en un nouveau point à afficher, <code>st_drop_geometry()</code> retire d’abord la géométrie existante (la ligne), pour ne garder qu’un tableau classique ; <code>st_as_sf()</code> en recrée ensuite une, cette fois ponctuelle, à partir des colonnes <code>x_start</code>/<code>y_start</code> désignées via <code>coords</code>. Sans ce passage par un tableau intermédiaire, <code>sf</code> chercherait à combiner la géométrie de ligne déjà présente avec la nouvelle géométrie de point, ce qui échouerait.</p>
<p>Le point d’ancrage du méridien -180° est exclu de <code>labels_lon</code> : sans ce filtre, il tomberait au même endroit projeté que le premier label de <code>labels_lat</code> (80°S), dans le coin bas-gauche de la carte, et les deux labels se superposeraient. <code>parse = TRUE</code> dans <code>geom_sf_text()</code> interprète <code>degree_label</code> comme une expression <code>plotmath</code> pour afficher correctement <code>°</code> et les points cardinaux, et <code>coord_sf(clip = "off")</code> + <code>plot.margin</code> évitent que <code>hjust</code>/<code>vjust</code>/<code>nudge_x</code>/<code>nudge_y</code> ne poussent les labels hors du cadre de tracé.</p>
<div class="note-box">
<p><strong>Deux détails à ajuster pour le français</strong></p>
<p><code>degree_label</code> est généré en anglais (<code>"120"*degree*W</code> plutôt que <code>O</code>) : il faut substituer <code>W</code> par <code>O</code>, ou reconstruire le label soi-même depuis la colonne <code>degree</code> (<code>paste0(abs(degree), "°")</code>) pour une version sans point cardinal, plus compacte mais moins explicite sur le sens est/ouest.</p>
</div>
</section>
<section id="quelle-projection-pour-quel-usage" class="level3">
<h3 data-anchor-id="quelle-projection-pour-quel-usage">Quelle projection pour quel usage ?</h3>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Pas de projection universellement “meilleure”
</div>
</div>
<div class="callout-body-container callout-body">
<p>Mercator reste pertinente pour la navigation ou le web (elle préserve les angles à tout niveau de zoom), Equal Earth pour comparer des surfaces entre pays, Robinson pour un rendu généraliste sans enjeu de précision. Le vrai réflexe : identifier la propriété (surface, angle, distance) qui compte pour le message, puis choisir la projection en conséquence.</p>
</div>
</div>
<p>Envie d’aller plus loin en cartographie avec R ? Dans le prochain article, on sort des sentiers battus avec trois projections plus exotiques : vue satellite, vue polaire et écorce d’orange, avec tous les pièges qu’il a fallu déjouer pour les faire fonctionner.</p>
<div class="cta-group center">
<p><a href="../../formations.html" class="cta-button-outline">Découvrir les formations</a></p>
<p><a href="../../contact.html" class="cta-button">Me contacter</a></p>
</div>
</section>
</section>
<!--

::: {.cell layout-align="center"}

```{.r .cell-code}
world <- ne_countries(scale = "medium", returnclass = "sf") |>
  filter(name != "Antarctica")

world_eqearth <- world |> st_transform(crs = "+proj=eqearth")
globe_eqearth <- globe |> st_transform(crs = "+proj=eqearth")
world_merc <- world |> st_transform(crs = "EPSG:3857")

# Calibration de l'échelle sur l'équateur : c'est là que Mercator et
# Equal Earth coïncident presque parfaitement, contrairement aux bbox
# globales (faussées par la divergence de Mercator aux hautes latitudes)
ref_points <- st_sfc(
  st_point(c(-179, 0)), st_point(c(179, 0)),
  crs = "EPSG:4326"
)
ref_merc <- ref_points |> st_transform(crs = "EPSG:3857") |> st_coordinates()
ref_eq   <- ref_points |> st_transform(crs = "+proj=eqearth") |> st_coordinates()
scale_factor <- (ref_eq[2, "X"] - ref_eq[1, "X"]) / (ref_merc[2, "X"] - ref_merc[1, "X"])

center_merc <- st_point(c(0, 0)) |> st_sfc(crs = "EPSG:4326") |> st_transform(crs = "EPSG:3857")
center_eq   <- st_point(c(0, 0)) |> st_sfc(crs = "EPSG:4326") |> st_transform(crs = "+proj=eqearth")

world_merc_rescaled <- st_geometry(world_merc) |>
  (\(g) (g - center_merc) * scale_factor + center_eq)() |>
  st_set_crs(st_crs(world_eqearth))

# Recadrage entre 58°S et 80°N : coupe l'essentiel du vide au sud et le
# nord extrême (Groenland) là où l'alignement décroche trop
crop_bounds <- st_sfc(
  st_point(c(0, -58)), st_point(c(0, 80)),
  crs = "EPSG:4326"
) |> st_transform(crs = "+proj=eqearth") |> st_coordinates()

cover <- ggplot() +
  geom_sf(data = globe_eqearth, fill = "#E4EFF6", color = NA) +
  geom_sf(data = world_merc_rescaled, fill = "#F97316", color = NA, alpha = 0.55) +
  geom_sf(data = world_eqearth, fill = "#0B3D5C", color = NA, alpha = 0.75) +
  coord_sf(expand = FALSE, ylim = crop_bounds[, "Y"]) +
  theme_void() +
  theme(plot.background = element_rect(fill = "#E4EFF6", color = NA))

ggsave(
  "cartes-projections.png", cover,
  width = 10, height = 6, dpi = 150, bg = "#E4EFF6"
)
```
:::

-->




<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-bibliography"><h2 class="anchored quarto-appendix-heading">Les références</h2><div id="refs" class="references csl-bib-body hanging-indent">
<div id="ref-savric2019Equal" class="csl-entry">
Šavrič, Bojan, Tom Patterson, et Bernhard Jenny. 2019. <span>«&nbsp;The <span>Equal Earth</span> Map Projection&nbsp;»</span>. <em>International Journal of Geographical Information Science</em> 33 (3): 454‑65. <a href="https://doi.org/10.1080/13658816.2018.1504949">https://doi.org/10.1080/13658816.2018.1504949</a>.
</div>
</div></section></div> ]]></description>
  <category>Tutoriels R</category>
  <category>Data viz</category>
  <guid>https://www.pyranhia.eu/posts/202609-carto-proj/carto-proj.html</guid>
  <pubDate>Mon, 14 Sep 2026 22:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202609-carto-proj/cartes-projections.png" medium="image" type="image/png" height="86" width="144"/>
</item>
<item>
  <title>vegan ou FactoMineR : deux packages, deux visions du monde</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202608-vegan-factominer/vegan-factominer.html</link>
  <description><![CDATA[ 





<section id="vegan-ou-factominer-deux-packages-deux-visions-du-monde" class="level1 section-full section-padded">
<h1>vegan ou FactoMineR : deux packages, deux visions du monde</h1>
<p><span class="section-subtitle">Derrière le choix d’un package R se cache une histoire, une communauté, et une façon de penser les données.</span></p>
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>Cet article est une vulgarisation historique, pas une revue exhaustive des méthodes ou des packages. Il contient toutefois un exemple de code, pour illustrer concrètement une différence entre les deux packages. L’objectif reste de montrer comment des choix techniques s’enracinent dans des traditions intellectuelles, pas de comparer les performances ou les fonctionnalités.</p>
</div>
</div>
</div>
</section>
<section id="un-choix-qui-na-lair-de-rien" class="level2 section-1">
<h2 data-anchor-id="un-choix-qui-na-lair-de-rien">Un choix qui n’a l’air de rien</h2>
<p>Mes premières ACP, je les ai faites en TD de M2, sur des données génériques, avec le package <code>FactoMineR</code> : l’outil du cours, utilisé sans se poser de question.</p>
<p>Quelques mois plus tard, en stage de M2, j’ai analysé des communautés de plancton pour la première fois. Avec mon encadrant, on a décidé de se pencher sur la composition de ces communautés, pas seulement sur l’abondance totale. Or pour comparer des profils de composition plutôt que des quantités brutes, on transforme les données avec une distance de Hellinger avant l’ACP<sup>1</sup>, une transformation que propose <code>vegan</code>. En R, ça tenait en deux lignes :</p>
<pre><code>df_hel &lt;- decostand(df_plankton, "hellinger")  # la transformation
pca    &lt;- rda(df_hel)                          # l'ACP</code></pre>
<p>Notez que c’est la même fonction <code>rda()</code> qui sert aussi à faire une véritable RDA<sup>2</sup>.</p>
<p>Je suis passée de <code>FactoMineR</code> à <code>vegan</code> sans vraiment le décider : c’est la question posée sur les données qui a entraîné le choix de la méthode, qui a entraîné le choix du package. Je n’avais pas conscience, à ce moment-là, que ce changement traduisait quelque chose de plus profond qu’une préférence syntaxique.</p>
<p>Pendant longtemps, j’ai cru que la différence entre les deux packages était cosmétique, un choix qui dépendait surtout de ce qu’on avait sous la main. Ce n’est pas tout à fait faux. Mais c’est loin d’être tout à fait vrai : derrière ces deux packages se trouvent deux histoires différentes, deux façons de poser les questions, deux traditions qui ont grandi en parallèle, et qui se sont parfois croisées de façon inattendue.</p>
</section>
<section id="deux-traditions-un-pont" class="level2 section-2">
<h2 data-anchor-id="deux-traditions-un-pont">Deux traditions, un pont</h2>
<section id="la-tradition-française-la-géométrie-avant-tout" class="level3">
<h3 data-anchor-id="la-tradition-française-la-géométrie-avant-tout">La tradition française : la géométrie avant tout</h3>
<p>Dans les années 1960, Jean-Paul Benzécri enseigne à Rennes, avant de rejoindre l’Institut de statistique de l’université de Paris en 1965. Mathématicien de formation, il développe l’analyse factorielle des correspondances (AFC) : à partir d’un tableau de contingence, la méthode projette lignes et colonnes dans un même espace géométrique, sans modèle probabiliste imposé ni loi de distribution supposée. On regarde la forme des nuages de points, on interprète les proximités.</p>
<p>Benzécri résume sa philosophie ainsi : le modèle doit suivre les données, non l’inverse <span class="citation" data-cites="benzecri1973Analyse">(Benzécri 1973)</span>. L’analyse des données à la française part du particulier pour aller vers le général, refuse d’imposer une structure a priori, et fait confiance à la géométrie comme langage universel.</p>
<p>Cet héritage est codifié dans <code>FactoMineR</code>, publié en 2008 par Sébastien Lê, Julie Josse et François Husson <span class="citation" data-cites="le2008FactoMineR">(Lê et al. 2008)</span>, qui propose une implémentation unifiée de toute la famille des analyses factorielles (ACP, AFC, ACM et leurs extensions pour les données mixtes), dans la continuité explicite de l’école de Benzécri.</p>
</section>
<section id="la-tradition-écologique-la-question-de-lenvironnement" class="level3">
<h3 data-anchor-id="la-tradition-écologique-la-question-de-lenvironnement">La tradition écologique : la question de l’environnement</h3>
<p>De l’autre côté, une question différente émerge dans les années 1980 en écologie des communautés. Ce n’est plus « quelle est la forme de mes données ? » mais « comment les espèces répondent-elles à leur environnement ? ».</p>
<p>En 1986, Cajo ter Braak publie dans la revue <em>Ecology</em> un article décrivant l’analyse canonique des correspondances (CCA) <span class="citation" data-cites="terbraak1986Canonical">(<span class="nocase">ter Braak</span> 1986)</span>. La méthode relie directement la composition des communautés (tableau espèces × sites) aux variables environnementales (tableau environnement × sites) : c’est une analyse sous contrainte, où les axes factoriels sont des combinaisons linéaires des variables environnementales. On ne cherche pas à décrire un nuage de points pour lui-même, mais à expliquer pourquoi les espèces sont là où elles sont, en lien avec la température, les nutriments, la profondeur.</p>
<p>C’est cette tradition que <code>vegan</code> incarne. Développé par Jari Oksanen <span class="citation" data-cites="oksanen2025vegan">(Oksanen et al. 2025)</span>, le package est devenu la référence pour l’écologie des communautés en R : CCA, RDA (analyse de redondance, version linéaire de la CCA), indices de diversité, tests de permutation. Tout ce dont un écologue a besoin pour relier composition et environnement.</p>
</section>
<section id="le-pont-legendre-legendre" class="level3">
<h3 data-anchor-id="le-pont-legendre-legendre">Le pont : Legendre &amp; Legendre</h3>
<p>Entre ces deux traditions, il y a eu des passeurs. Le plus important est sans doute le livre <em>Écologie numérique</em> de Pierre et Louis Legendre, paru en français en 1979, puis en anglais sous le titre <em>Numerical Ecology</em> en 1983 <span class="citation" data-cites="legendre1979Ecologie legendre2012Numerical">(Legendre et Legendre 1979, 2012)</span>. Les deux frères, l’un à l’Université de Montréal, l’autre à l’Université Laval avant de diriger le Laboratoire d’Océanographie de Villefranche-sur-Mer de 2001 à 2009, y font dialoguer Benzécri et ter Braak, les ACP et les CCA, les distances euclidiennes et les distances de Bray-Curtis. Le livre n’appartient ni à la tradition française ni à la tradition écologique anglophone : c’est une synthèse, devenue référence mondiale et citée dans les deux communautés.</p>
<p>Pierre et Louis Legendre racontent eux-mêmes l’origine du livre : en mai 1975, à la Station marine de Villefranche-sur-Mer, ils participent à un atelier de trois jours sur l’analyse statistique des données écologiques. Le soir de clôture, en terrasse d’un restaurant, ils décident d’écrire un manuel accessible aux écologues non mathématiciens, et esquissent sur un set de table en papier la liste des sujets qui deviendra le sommaire de la première de <em>Numerical Ecology</em> <span class="citation" data-cites="legendre2021Pleasurea">(Legendre 2021)</span>. Ce genre de transmission, dans les marges, entre deux discussions, loin des formats officiels, dit quelque chose de réel sur la façon dont naissent parfois les idées scientifiques. Ce n’est pas un hasard si des chercheurs francophones ont occupé ce rôle de pont, la tradition française d’analyse des données et la pratique de l’écologie quantitative s’étant croisées naturellement dans certains labos et certains séminaires.</p>
</section>
<section id="un-même-jeu-de-données-deux-lectures" class="level3">
<h3 data-anchor-id="un-même-jeu-de-données-deux-lectures">Un même jeu de données, deux lectures</h3>
<p>Cette différence de philosophie se voit jusque dans les graphiques. Prenons un exemple concret, sur des données physico-chimiques de qualité du vin<sup>3</sup> : pour chaque échantillon, onze mesures chimiques (acidité, sucre résiduel, sulfates, alcool…) et une note de qualité. C’est un jeu de données multivarié typique : les variables sont corrélées entre elles, et aucune ne suffit seule à résumer ce qui distingue les échantillons. C’est exactement le terrain de jeu d’une ACP, qui cherche les combinaisons de variables qui capturent le plus de variabilité, pour résumer l’essentiel de l’information en deux ou trois axes plutôt que onze.</p>
<div class="cell" data-layout-align="center">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">data</span>(wine, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">package =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"datapyranhia"</span>)</span>
<span id="cb2-2"></span>
<span id="cb2-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># FactoMineR : centrage-réduction automatique (scale.unit = TRUE par défaut)</span></span>
<span id="cb2-4">pca_facto <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">PCA</span>(wine, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">graph =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>)</span>
<span id="cb2-5"></span>
<span id="cb2-6">p_ind <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(pca_facto, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">choix =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ind"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">label =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"none"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">title =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Plan des individus"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">cex =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>)</span>
<span id="cb2-7">p_var <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(pca_facto, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">choix =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"var"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">title =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Plan des variables"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">cex =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>)</span>
<span id="cb2-8"></span>
<span id="cb2-9">p_ind <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> p_var <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot_layout</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">widths =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span></code></pre></div></div>
</details>
<div class="cell-output-display">
<div id="fig-pca-facto" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-pca-facto-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="vegan-factominer_files/figure-html/fig-pca-facto-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: avec FactoMineR, deux plans séparés."><img src="https://www.pyranhia.eu/posts/202608-vegan-factominer/vegan-factominer_files/figure-html/fig-pca-facto-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-pca-facto-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: avec FactoMineR, deux plans séparés.
</figcaption>
</figure>
</div>
</div>
</div>
<div class="cell" data-layout-align="center">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># vegan : centrage-réduction demandé explicitement via l'argument scale</span></span>
<span id="cb3-2">pca_vegan <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rda</span>(wine, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">scale =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb3-3"></span>
<span id="cb3-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># % de variance expliquée par les deux premiers axes, pour les afficher sur le biplot</span></span>
<span id="cb3-5">eig_pct <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> pca_vegan<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>CA<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>eig <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>(pca_vegan<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>CA<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>eig)</span>
<span id="cb3-6"></span>
<span id="cb3-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">biplot</span>(</span>
<span id="cb3-8">  pca_vegan, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">scaling =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">main =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">type =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"text"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"points"</span>),</span>
<span id="cb3-9">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">xlab =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">paste0</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PC1 ("</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">round</span>(eig_pct[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"%)"</span>),</span>
<span id="cb3-10">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ylab =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">paste0</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PC2 ("</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">round</span>(eig_pct[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"%)"</span>),</span>
<span id="cb3-11">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">cex =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.02</span></span>
<span id="cb3-12">)</span></code></pre></div></div>
</details>
<div class="cell-output-display">
<div id="fig-pca-vegan" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-pca-vegan-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="vegan-factominer_files/figure-html/fig-pca-vegan-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure&nbsp;2: avec vegan, un biplot unique."><img src="https://www.pyranhia.eu/posts/202608-vegan-factominer/vegan-factominer_files/figure-html/fig-pca-vegan-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-pca-vegan-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: avec vegan, un biplot unique.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Les deux premiers axes résument 45% de la variabilité totale (26,5% pour l’axe 1, 18,6% pour l’axe 2), ce qui reste modeste : avec onze variables physico-chimiques, aucun résumé en deux dimensions ne peut être exhaustif. Mais les axes restent interprétables. Le premier axe oppose <code>fixed_acidity</code>, <code>citric_acid</code> et <code>density</code> (fortement positifs) à <code>p_h</code> (fortement négatif) : c’est un axe d’acidité globale, cohérent avec la chimie du vin puisque le pH mesure justement l’inverse de l’acidité. Le second axe est porté par <code>volatile_acidity</code> et <code>total_sulfur_dioxide</code> : une autre dimension, indépendante de la première, qui oppose des vins plus ou moins soumis à ces deux composés. Sur le biplot <code>vegan</code>, ces deux logiques se lisent d’un coup d’œil, dans les mêmes flèches que sur le plan des variables FactoMineR : le contenu de l’information est identique, seule la mise en forme diffère.</p>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>La normalisation : un geste qui en dit long
</div>
</div>
<div class="callout-body-container callout-body">
<p><code>FactoMineR::PCA()</code> centre-réduit les variables par défaut (<code>scale.unit = TRUE</code>). <code>vegan::rda()</code> ne le fait pas : sur une ACP non contrainte, il faut demander explicitement <code>scale = TRUE</code>, sans quoi les variables aux plus grandes échelles dominent les axes.</p>
<p>Ce n’est pas un détail d’implémentation, c’est une trace de la même divergence philosophique. <code>FactoMineR</code>, héritier de Benzécri, part du principe qu’on veut une structure géométrique propre : la standardisation est la norme, pas l’exception. <code>vegan</code> part du terrain écologique, où les unités et les échelles portent souvent un sens biologique qu’on ne veut pas effacer par défaut : c’est à l’utilisateur de décider s’il veut gommer ces différences d’échelle ou les garder.</p>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Astuce</span>Biplot ou pas : un choix mathématique, pas seulement esthétique
</div>
</div>
<div class="callout-body-container callout-body">
<p><code>FactoMineR</code> sait produire un biplot si on le demande explicitement, par exemple via le package compagnon <code>factoextra</code>. Ce n’est simplement pas son affichage par défaut, alors que <code>vegan</code> en fait le sien.</p>
<p>Le biplot pose un problème mathématique concret : individus et variables ne vivent pas naturellement dans le même espace, donc les superposer suppose de choisir une mise à l’échelle (le <code>scaling</code>). Avec <code>scaling = 1</code>, les distances entre individus sont fidèles, mais pas les angles entre variables. Avec <code>scaling = 2</code>, c’est l’inverse : les angles entre variables reflètent leurs corrélations, mais plus les distances entre individus. Il existe un troisième compromis, <code>scaling = 3</code> (symétrique), qui met les deux à l’échelle par la racine carrée des valeurs propres, sans toutefois préserver parfaitement ni les distances ni les angles.</p>
<p><code>FactoMineR</code>, fidèle à l’héritage de Benzécri, préfère séparer les deux lectures plutôt que d’imposer un compromis : deux figures propres, chacune fidèle à ce qu’elle représente. <code>vegan</code>, plus proche des besoins de terrain de l’écologue habitué à lire des relations espèces-environnement d’un coup d’œil, préfère un graphique unique, quitte à demander à l’utilisateur de choisir son scaling en connaissance de cause. Encore une fois : pas de bon ou de mauvais choix, juste deux réponses différentes à la question « qu’est-ce que je veux lire en premier sur ce graphique ? ».</p>
</div>
</div>
</section>
</section>
<section id="et-moi-dans-tout-ça" class="level2 section-3">
<h2 data-anchor-id="et-moi-dans-tout-ça">Et moi dans tout ça</h2>
<p>Mon profil est à cheval entre les deux mondes. Thèse en écologie marine quantitative, formation en machine learning, passage par des labos anglophones : j’ai appris à naviguer entre les deux traditions sans vraiment le savoir au départ. Ce stage de M2 sur le plancton a débouché sur un article <span class="citation" data-cites="panaiotis2023Three">(Panaïotis et al. 2023)</span> où <code>vegan</code> reste l’outil central, puisque ce travail se concentre sur la composition des communautés planctoniques via un tableau espèces × sites.</p>
<p>Mais depuis, ça dépend des données et de la question. Un coup c’est <code>vegan</code>, un coup c’est <code>FactoMineR</code>, que j’utilise dans les contextes où je reviens à une logique de structure géométrique pure : données mixtes, tableaux d’enquête, situations où je veux explorer la structure générale sans a priori sur ce qui explique quoi. La FAMD (<em>factorial analysis of mixed data</em>, analyse factorielle de données mixtes) de <code>FactoMineR</code> est particulièrement utile pour ça, comme je l’ai montré dans un <a href="https://pyranhia.eu/posts/202603-retour-questionnaire/retour-questionnaire.html">article récent</a> sur l’analyse d’un questionnaire marché.</p>
<p>Lors de ma soutenance de thèse, Louis Legendre était président du jury, dans une salle de la station marine de Villefranche-sur-Mer qu’il a dirigée pendant plusieurs années, le même lieu où lui et son frère avaient esquissé <em>Écologie numérique</em> quarante ans plus tôt. Cela m’a fait réaliser que les deux lignes de code qui ouvraient mon analyse de plancton avaient une histoire que je n’avais jamais pris le temps de retracer.</p>
<p>Comprendre d’où vient un outil, c’est comprendre ce qu’il présuppose, et donc quand ne pas l’utiliser. <code>vegan</code> part de la question « comment les espèces répondent-elles à l’environnement ? ». <code>FactoMineR</code> part de la question « quelle est la géométrie de mes données ? ». Ce ne sont pas les mêmes questions, et choisir le bon package, c’est d’abord clarifier laquelle on pose.</p>
<p>C’est exactement ce genre de réflexion que j’essaie de transmettre dans ma formation Machine Learning avec R : pas seulement comment appeler les fonctions, mais pourquoi ces fonctions existent, ce qu’elles supposent, et dans quels cas une autre approche serait plus juste.</p>
<div class="cta-group center">
<p><a href="../../formations/machine_learning_avec_r.html" class="cta-button-outline">Découvrir la formation ML avec R</a></p>
<p><a href="../../contact.html" class="cta-button">Me contacter</a></p>
</div>
</section>





<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-bibliography"><h2 class="anchored quarto-appendix-heading">Les références</h2><div id="refs" class="references csl-bib-body hanging-indent">
<div id="ref-benzecri1973Analyse" class="csl-entry">
Benzécri, Jean-Paul. 1973. <em>L’Analyse Des Données</em>. Dunod.
</div>
<div id="ref-le2008FactoMineR" class="csl-entry">
Lê, Sébastien, Julie Josse, et François Husson. 2008. <span>«&nbsp;<span>FactoMineR</span>: An <span>R</span> Package for Multivariate Analysis&nbsp;»</span>. <em>Journal of Statistical Software</em> 25 (1): 1‑18. <a href="https://doi.org/10.18637/jss.v025.i01">https://doi.org/10.18637/jss.v025.i01</a>.
</div>
<div id="ref-legendre2021Pleasurea" class="csl-entry">
Legendre, Louis. 2021. <span>«&nbsp;The Pleasure of Writing, Being Published, Appealing to Readers, and Contributing to the Advancement of Knowledge&nbsp;»</span>. <em>ICES Journal of Marine Science</em> 78 (6): 1943‑55. <a href="https://doi.org/10.1093/icesjms/fsab097">https://doi.org/10.1093/icesjms/fsab097</a>.
</div>
<div id="ref-legendre2001Ecologically" class="csl-entry">
Legendre, Pierre, et Eugène D. Gallagher. 2001. <span>«&nbsp;Ecologically Meaningful Transformations for Ordination of Species Data&nbsp;»</span>. <em>Oecologia</em> 129 (2): 271‑80. <a href="https://doi.org/10.1007/s004420100716">https://doi.org/10.1007/s004420100716</a>.
</div>
<div id="ref-legendre1979Ecologie" class="csl-entry">
Legendre, Pierre, et Louis Legendre. 1979. <em><span>É</span>cologie Numérique</em>. Masson et Presses de l’Universit<span>é</span> du Qu<span>é</span>bec.
</div>
<div id="ref-legendre2012Numerical" class="csl-entry">
Legendre, Pierre, et Louis Legendre. 2012. <em>Numerical Ecology</em>. Elsevier.
</div>
<div id="ref-oksanen2025vegan" class="csl-entry">
Oksanen, Jari, Gavin L. Simpson, F. Guillaume Blanchet, et al. 2025. <em>vegan: Community Ecology Package</em>. <a href="https://vegandevs.github.io/vegan/">https://vegandevs.github.io/vegan/</a>.
</div>
<div id="ref-panaiotis2023Three" class="csl-entry">
Panaïotis, Thelma, Marcel Babin, Tristan Biard, et al. 2023. <span>«&nbsp;Three Major Mesoplanktonic Communities Resolved by in Situ Imaging in the Upper 500 m of the Global Ocean&nbsp;»</span>. <em>Global Ecology and Biogeography</em> 32 (11): 1991‑2005. <a href="https://doi.org/10.1111/geb.13741">https://doi.org/10.1111/geb.13741</a>.
</div>
<div id="ref-terbraak1986Canonical" class="csl-entry">
<span class="nocase">ter Braak, Cajo J. F.</span> 1986. <span>«&nbsp;Canonical Correspondence Analysis: <span>A</span> New Eigenvector Technique for Multivariate Direct Gradient Analysis&nbsp;»</span>. <em>Ecology</em> 67 (5): 1167‑79. <a href="https://doi.org/10.2307/1938672">https://doi.org/10.2307/1938672</a>.
</div>
</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Notes de bas de page</h2>

<ol>
<li id="fn1"><p>La distance de Hellinger est une racine carrée des fréquences relatives, qui neutralise les écarts d’abondance totale entre échantillons pour se concentrer sur les différences de composition. Introduite en écologie par <span class="citation" data-cites="legendre2001Ecologically">Legendre et Gallagher (2001)</span>.↩︎</p></li>
<li id="fn2"><p>Détail amusant : <code>rda()</code> est la même fonction qui sert à faire une RDA (analyse de redondance, contrainte par des variables environnementales). Sans second tableau de données en argument, elle bascule simplement en ACP non contrainte. Un raccourci pratique une fois qu’on le sait, mais qui peut dérouter au premier abord : le nom de la fonction ne dit pas ce qu’elle fait par défaut.↩︎</p></li>
<li id="fn3"><p>Source : <a href="https://archive.ics.uci.edu/dataset/186/wine+quality">UCI Machine Learning Repository</a>↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Écologie quantitative</category>
  <category>Pédagogie</category>
  <guid>https://www.pyranhia.eu/posts/202608-vegan-factominer/vegan-factominer.html</guid>
  <pubDate>Sun, 09 Aug 2026 22:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202608-vegan-factominer/vegan-factominer-cover.png" medium="image" type="image/png" height="74" width="144"/>
</item>
<item>
  <title>Le diagramme de Taylor, enfin décrypté</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202607-taylor/taylor.html</link>
  <description><![CDATA[ 





<section id="le-diagramme-de-taylor-enfin-décrypté" class="level1 section-full section-padded">
<h1>Le diagramme de Taylor, enfin décrypté</h1>
<p><span class="section-subtitle">Trois métriques, un seul coup d’œil.</span></p>
<p>On croise le diagramme de Taylor <span class="citation" data-cites="taylor2001Summarizing">(Taylor 2001)</span> un peu partout dès qu’il s’agit de comparer plusieurs modèles à une observation de référence : articles de climatologie, rapports du GIEC, études hydrologiques ou océanographiques. Autant dire qu’il vaut mieux savoir le lire.</p>
<p>Et pourtant, c’est un graphique que presque personne n’apprend vraiment à lire. On le déchiffre à la louche, en repérant vaguement « le point le plus proche du bord », sans vraiment savoir ce que cette position signifie. Moi la première : je l’ai toujours interprété sur le tas, jamais appris une bonne fois pour toutes.</p>
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>Cet article explique comment <strong>lire</strong> un diagramme de Taylor déjà tracé, pas comment le construire de A à Z avec un package en particulier. On revient en fin d’article sur les implémentations disponibles.</p>
</div>
</div>
</div>
</section>
<section id="un-graphique-quon-croise-partout-mais-quon-apprend-rarement" class="level2 section-1">
<h2 data-anchor-id="un-graphique-quon-croise-partout-mais-quon-apprend-rarement">Un graphique qu’on croise partout, mais qu’on apprend rarement</h2>
<p>Prenons un cas simple : la température hebdomadaire sur un an, avec trois modèles qui tentent de la reproduire, plus ou moins bien (un modèle, ici, désigne n’importe quel outil statistique cherchant à reproduire les observations : modèle de <em>machine learning</em>, modèle mécanistique, etc.).</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-donnees-temp" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center" alt="Courbe de température hebdomadaire sur un an, cycle saisonnier en cloche. La courbe noire (observation) est comparée à trois courbes de modèles : le modèle 1 suit la même forme mais avec une amplitude atténuée, le modèle 2 a une amplitude correcte mais un léger décalage temporel, le modèle 3 est plat et bruité, nettement moins fidèle à l'observation.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-donnees-temp-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202607-taylor/taylor_files/figure-html/fig-donnees-temp-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Courbe de température hebdomadaire sur un an, cycle saisonnier en cloche. La courbe noire (observation) est comparée à trois courbes de modèles : le modèle 1 suit la même forme mais avec une amplitude atténuée, le modèle 2 a une amplitude correcte mais un léger décalage temporel, le modèle 3 est plat et bruité, nettement moins fidèle à l'observation." width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-donnees-temp-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Température observée et prédite par trois modèles sur une année.
</figcaption>
</figure>
</div>
</div>
</div>
<p>À l’œil nu, difficile de dire précisément lequel des trois modèles s’en sort le mieux, et sur quel plan les deux autres se trompent. C’est exactement le problème que le diagramme de Taylor permet de résoudre en un seul graphique.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-taylor-brut" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center" alt="Diagramme de Taylor en demi-cercle avec trois points colorés représentant les trois modèles, positionnés à différents angles et distances par rapport au point d'observation situé sur l'axe horizontal. Les positions relatives ne sont pas encore expliquées à ce stade de l'article.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-taylor-brut-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202607-taylor/taylor_files/figure-html/fig-taylor-brut-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Diagramme de Taylor en demi-cercle avec trois points colorés représentant les trois modèles, positionnés à différents angles et distances par rapport au point d'observation situé sur l'axe horizontal. Les positions relatives ne sont pas encore expliquées à ce stade de l'article." width="1152">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-taylor-brut-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Le même trio de modèles, sur un diagramme de Taylor. Beaucoup d’informations sur un seul graphique, pas évident à lire du premier coup.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Le problème que ce graphique résout est en réalité assez simple à formuler : quand on compare plusieurs modèles à une observation, on a envie de regarder au moins trois choses à la fois. Le modèle suit-il bien la forme du signal observé (<strong>corrélation</strong>) ? A-t-il une variabilité comparable à la réalité (<strong>écart-type</strong>) ? Et globalement, à quel point ses valeurs sont-elles proches de l’observation (<strong>erreur quadratique moyenne</strong>, ou RMSE) ?</p>
<p>Sans le diagramme de Taylor, il faudrait comparer ces trois métriques dans trois tableaux ou trois graphiques séparés, modèle par modèle. Le tour de force du diagramme de Taylor, c’est de rassembler ces trois informations sur un seul graphique, pour un nombre quelconque de modèles, grâce à une relation géométrique qui les relie entre elles.</p>
</section>
<section id="anatomie-du-graphique-trois-métriques-à-lire" class="level2 section-2">
<h2 data-anchor-id="anatomie-du-graphique-trois-métriques-à-lire">Anatomie du graphique : trois métriques à lire</h2>
<p>Le diagramme de Taylor repose à la fois sur une lecture cartésienne et une lecture polaire : un même axe horizontal gradué sert de référence pour l’écart-type, tandis que l’angle par rapport à cet axe donne la corrélation. Un point sert de repère fixe à tout le graphique : l’observation, toujours placée sur l’axe horizontal, à une distance de l’origine égale à son propre écart-type. Trois éléments se lisent simultanément sur le graphique.</p>
<section id="langle-la-corrélation" class="level3">
<h3 data-anchor-id="langle-la-corrélation">L’angle : la corrélation</h3>
<p>L’angle (<img src="https://latex.codecogs.com/png.latex?%5Ctheta">) par rapport à l’axe horizontal indique la <strong>corrélation</strong> entre le modèle et l’observation (plus précisément, <img src="https://latex.codecogs.com/png.latex?%5Ctext%7Bcorr%C3%A9lation%7D%20=%20%5Ccos(%5Ctheta)">). Plus un point est proche de l’axe horizontal (angle proche de 0°), plus la corrélation est forte et proche de 1. Un point à 90° correspond à une corrélation nulle.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-anatomie-angle" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center" alt="Trois points colorés reliés à l'origine par un trait plein, avec un petit arc entre chaque trait et l'axe horizontal pour matérialiser son angle. Seul cet arc est coloré ; le reste du repère (segments, distances) apparaît en gris clair pour ne pas détourner l'attention de l'angle.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-anatomie-angle-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202607-taylor/taylor_files/figure-html/fig-anatomie-angle-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Trois points colorés reliés à l'origine par un trait plein, avec un petit arc entre chaque trait et l'axe horizontal pour matérialiser son angle. Seul cet arc est coloré ; le reste du repère (segments, distances) apparaît en gris clair pour ne pas détourner l'attention de l'angle." width="1152">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-anatomie-angle-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: L’angle par rapport à l’axe horizontal indique la corrélation de chaque modèle avec l’observation.
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="la-distance-à-lorigine-lécart-type" class="level3">
<h3 data-anchor-id="la-distance-à-lorigine-lécart-type">La distance à l’origine : l’écart-type</h3>
<p>La distance entre un point et le centre du repère (l’origine) représente l’<strong>écart-type</strong> du modèle, c’est-à-dire sa variabilité propre. L’observation de référence est elle-même placée sur l’axe horizontal, à une distance de l’origine égale à son propre écart-type : c’est le point de comparaison pour tous les modèles.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-anatomie-ecart-type" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center" alt="Même repère que la figure précédente, mais ce sont maintenant les segments reliant chaque modèle à l'origine qui sont colorés et épaissis, tandis que les petits arcs d'angle passent en gris clair : l'attention se déplace de l'angle vers la longueur du segment.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-anatomie-ecart-type-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202607-taylor/taylor_files/figure-html/fig-anatomie-ecart-type-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Même repère que la figure précédente, mais ce sont maintenant les segments reliant chaque modèle à l'origine qui sont colorés et épaissis, tandis que les petits arcs d'angle passent en gris clair : l'attention se déplace de l'angle vers la longueur du segment." width="1152">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-anatomie-ecart-type-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: La distance de chaque modèle à l’origine indique son écart-type, c’est-à-dire sa variabilité propre.
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="la-distance-à-lobservation-la-rmse" class="level3">
<h3 data-anchor-id="la-distance-à-lobservation-la-rmse">La distance à l’observation : la RMSE</h3>
<p>C’est le point le plus souvent mal compris. La distance directe entre un modèle et le point représentant l’observation (pas l’origine) correspond à la <strong>racine de l’erreur quadratique moyenne</strong> (<em>Root Mean Square Error</em>, ou <strong>RMSE</strong>). Cette distance n’est généralement pas représentée par un axe gradué classique, mais par des arcs de cercle concentriques centrés sur le point d’observation.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-anatomie-rmse" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center" alt="Même repère à nouveau, mais les segments colorés relient désormais chaque modèle au point d'observation plutôt qu'à l'origine, et des arcs pointillés concentriques centrés sur ce point remplacent les arcs d'angle : c'est cette nouvelle distance, au point d'observation, qui est maintenant mise en avant.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-anatomie-rmse-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202607-taylor/taylor_files/figure-html/fig-anatomie-rmse-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Même repère à nouveau, mais les segments colorés relient désormais chaque modèle au point d'observation plutôt qu'à l'origine, et des arcs pointillés concentriques centrés sur ce point remplacent les arcs d'angle : c'est cette nouvelle distance, au point d'observation, qui est maintenant mise en avant." width="1152">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-anatomie-rmse-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: La distance de chaque modèle au point d’observation, matérialisée par des arcs concentriques, donne la RMSE.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Sur cet exemple, le <strong>modèle 1</strong> est le meilleur en termes de corrélation (angle <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> le plus petit) : sa courbe suit fidèlement la forme du signal. Il sous-estime cependant l’amplitude du signal, avec un écart-type (<img src="https://latex.codecogs.com/png.latex?%5Csigma">) plus faible que celui de l’observation (3.5 contre 5.7). Malgré ce défaut, c’est son point qui reste le plus proche de l’observation sur le graphique, donc celui qui a la RMSE la plus faible : la bonne corrélation compense l’écart-type imparfait. Le modèle 1 est donc, dans l’ensemble, celui qui représente le mieux les données. Le <strong>modèle 3</strong>, plus bruité et moins corrélé, s’écarte nettement plus de l’observation, donc sa RMSE est plus élevée.</p>
</section>
<section id="synthèse-de-lecture" class="level3">
<h3 data-anchor-id="synthèse-de-lecture">Synthèse de lecture</h3>
<p>Face à un diagramme de Taylor déjà tracé, dans un article ou un rapport, voici l’ordre de lecture le plus efficace.</p>
<ol type="1">
<li><strong>Repérer le point d’observation.</strong> C’est la référence : celle-ci est en général indiquée explicitement, sur l’axe horizontal.</li>
<li><strong>Regarder l’angle de chaque modèle</strong> pour juger de sa corrélation avec l’observation : plus l’angle est petit, meilleure est la corrélation.</li>
<li><strong>Regarder la distance à l’origine</strong> pour juger de l’écart-type de chaque modèle : plus elle est proche de celle de l’observation, plus la variabilité du modèle est réaliste.</li>
<li><strong>Regarder la distance au point d’observation</strong> pour juger de la RMSE globale : plus un modèle est proche du point d’observation, meilleur il est dans l’ensemble.</li>
</ol>
<p>Un point de vigilance mérite d’être gardé en tête : cette lecture suppose que l’observation et les modèles ont la même moyenne. Sans cette condition, un modèle avec un biais systématique peut sembler excellent sur le diagramme sans l’être réellement. La démonstration ci-après explique ce mécanisme, et montre que nos trois modèles n’y sont pas exposés.</p>
</section>
</section>
<section id="pourquoi-ça-marche-la-loi-des-cosinus" class="level2 section-3">
<h2 data-anchor-id="pourquoi-ça-marche-la-loi-des-cosinus">Pourquoi ça marche : la loi des cosinus</h2>
<p>Ces trois métriques ne sont pas indépendantes : elles sont liées par une relation trigonométrique. Voyons laquelle.</p>
<p>Pour un modèle donné, l’origine, le point d’observation et le point du modèle forment un triangle.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-triangle-simple" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center" alt="Diagramme de Taylor limité à un seul modèle, avec le triangle origine-observation-modèle mis en évidence par un fond coloré, sans annotation des longueurs à ce stade.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-triangle-simple-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202607-taylor/taylor_files/figure-html/fig-triangle-simple-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Diagramme de Taylor limité à un seul modèle, avec le triangle origine-observation-modèle mis en évidence par un fond coloré, sans annotation des longueurs à ce stade." width="1152">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-triangle-simple-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;6: Le triangle formé par l’origine, l’observation et un modèle.
</figcaption>
</figure>
</div>
</div>
</div>
<p>L’écart-type de l’observation (<img src="https://latex.codecogs.com/png.latex?%5Csigma_%7Bobs%7D">) et l’écart-type du modèle (<img src="https://latex.codecogs.com/png.latex?%5Csigma_%7Bmod%7D">) sont deux côtés de ce triangle ; l’angle entre eux est justement l’angle de corrélation <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> défini plus haut. Le troisième côté (celui qui relie le modèle à l’observation) est la RMSE, et c’est très exactement ce que donne la <a href="https://fr.wikipedia.org/wiki/Loi_des_cosinus"><strong>loi des cosinus</strong></a> appliquée à ce triangle : deux côtés et l’angle entre eux suffisent à déterminer le troisième.</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7BRMSE%7D%5E2%20=%20%5Csigma_%7Bobs%7D%5E2%20+%20%5Csigma_%7Bmod%7D%5E2%20-%202%20%5C,%20%5Csigma_%7Bobs%7D%20%5C,%20%5Csigma_%7Bmod%7D%20%5Ccos(%5Ctheta)"></p>
<p>Cette équation dit quelque chose de fort : une fois <img src="https://latex.codecogs.com/png.latex?%5Csigma_%7Bobs%7D">, <img src="https://latex.codecogs.com/png.latex?%5Csigma_%7Bmod%7D"> et <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> fixés, la RMSE n’a plus aucune liberté. Elle ne peut prendre qu’une seule valeur, celle donnée par l’équation ci-dessus. Ce n’est pas une troisième mesure indépendante qu’on pourrait faire varier à sa guise : c’est la conséquence géométrique forcée des deux autres. Autrement dit, sur le graphique, connaître l’écart-type et la corrélation d’un modèle revient à connaître sa RMSE.</p>
<p>D’où sort cette équation, concrètement ? Le widget ci-dessous le montre pas à pas, en traçant une hauteur dans le triangle pour faire apparaître un théorème de Pythagore caché.</p>
<div id="fig-widget-loi-cosinus" class="quarto-float quarto-figure quarto-figure-center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-widget-loi-cosinus-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<iframe src="cosinus_widget/index.html" title="Widget interactif : démonstration de la loi des cosinus par Pythagore" width="100%" height="1400px" frameborder="0" style="border-radius: 12px; overflow: hidden;"></iframe>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-widget-loi-cosinus-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;7: Démonstration interactive : en traçant la hauteur depuis le modèle jusqu’à la droite reliant l’origine à l’observation, le triangle se scinde en deux triangles rectangles. Le théorème de Pythagore, appliqué dans le petit triangle qui a la RMSE pour hypoténuse, puis développé algébriquement, donne exactement la loi des cosinus. Les curseurs permettent d’explorer librement l’écart-type du modèle et sa corrélation à l’observation.
</figcaption>
</figure>
</div>
<section id="ce-que-le-diagramme-ne-montre-pas-le-biais" class="level3">
<h3 data-anchor-id="ce-que-le-diagramme-ne-montre-pas-le-biais">Ce que le diagramme ne montre pas : le biais</h3>
<p>La loi des cosinus qu’on vient de démontrer est une identité géométrique, toujours vraie pour un triangle donné. Ce qui n’est pas toujours vrai, c’est que ce triangle représente exactement la RMSE.</p>
<div class="callout callout-style-default callout-warning callout-titled" title="Une hypothèse à respecter">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Avertissement</span>Une hypothèse à respecter
</div>
</div>
<div class="callout-body-container callout-body">
<p>Cette lecture suppose que l’observation et le modèle ont <strong>la même moyenne</strong>, sans quoi un biais systématique gonfle la RMSE réelle sans que rien ne le trahisse sur le graphique. Le détail ci-dessous explique pourquoi.</p>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled" title="La démonstration, en détail">
<div class="callout-header d-flex align-content-center collapsed" data-bs-toggle="collapse" data-bs-target=".callout-2-contents" aria-controls="callout-2" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>La démonstration, en détail
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-2" class="callout-2-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p>Par définition, la RMSE entre une observation et un modèle est :</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7BRMSE%7D%5E2%20=%20%5Cfrac%7B1%7D%7Bn%7D%5Csum_%7Bi=1%7D%5E%7Bn%7D%20(x_i%20-%20y_i)%5E2"></p>
<p>où <img src="https://latex.codecogs.com/png.latex?x_i"> est l’observation, <img src="https://latex.codecogs.com/png.latex?y_i"> le modèle, et <img src="https://latex.codecogs.com/png.latex?n"> le nombre de points. Pour faire apparaître les moyennes, on ajoute et on retranche <img src="https://latex.codecogs.com/png.latex?%5Cbar%7Bx%7D"> et <img src="https://latex.codecogs.com/png.latex?%5Cbar%7By%7D"> à chaque terme :</p>
<p><img src="https://latex.codecogs.com/png.latex?x_i%20-%20y_i%20=%20(x_i%20-%20%5Cbar%7Bx%7D)%20-%20(y_i%20-%20%5Cbar%7By%7D)%20+%20(%5Cbar%7Bx%7D%20-%20%5Cbar%7By%7D)"></p>
<p>En élevant au carré, en sommant sur les <img src="https://latex.codecogs.com/png.latex?n"> points et en divisant par <img src="https://latex.codecogs.com/png.latex?n">, les termes croisés impliquant <img src="https://latex.codecogs.com/png.latex?(%5Cbar%7Bx%7D%20-%20%5Cbar%7By%7D)"> s’annulent<sup>1</sup>, et il ne reste que :</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7BRMSE%7D%5E2%20=%20%5Cunderbrace%7B(%5Cbar%7Bx%7D%20-%20%5Cbar%7By%7D)%5E2%7D_%7B%5Ctext%7Bbiais%7D%5E2%7D%20+%20%5Cunderbrace%7B%5Cfrac%7B1%7D%7Bn%7D%5Csum%20(x_i%20-%20%5Cbar%7Bx%7D)%5E2%20+%20%5Cfrac%7B1%7D%7Bn%7D%5Csum%20(y_i%20-%20%5Cbar%7By%7D)%5E2%20-%20%5Cfrac%7B2%7D%7Bn%7D%5Csum%20(x_i%20-%20%5Cbar%7Bx%7D)(y_i%20-%20%5Cbar%7By%7D)%7D_%7B%5Csigma_x%5E2%20%5C,%20+%20%5C,%20%5Csigma_y%5E2%20%5C,%20-%20%5C,%202%5Csigma_x%5Csigma_y%20r_%7Bxy%7D%7D"></p>
<p>Le second groupe de termes est, par définition, l’écart-type de chaque série et leur corrélation <img src="https://latex.codecogs.com/png.latex?r_%7Bxy%7D">. C’est très exactement la loi des cosinus démontrée juste au-dessus, avec <img src="https://latex.codecogs.com/png.latex?%5Ccos(%5Ctheta)%20=%20r_%7Bxy%7D">. La RMSE se décompose donc en deux parts indépendantes :</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7BRMSE%7D%5E2%20=%20%5Ctext%7Bbiais%7D%5E2%20+%20%5Cbig(%5Csigma_%7Bobs%7D%5E2%20+%20%5Csigma_%7Bmod%7D%5E2%20-%202%5Csigma_%7Bobs%7D%5Csigma_%7Bmod%7D%5Ccos(%5Ctheta)%5Cbig)"></p>
<p>Le diagramme de Taylor n’affiche que le second terme, celui qui dépend de l’écart-type et de la corrélation. Le biais, lui, n’apparaît nulle part sur le graphique : deux modèles avec le même écart-type et la même corrélation occupent exactement le même point, que leur moyenne soit égale à celle de l’observation ou non.</p>
<p>Figure&nbsp;8 reprend cette décomposition sur le modèle 1. En haut, le scatter obs/modèle : le segment orange, de la moyenne du nuage à la diagonale, matérialise le biais ; les segments gris fins, la dispersion résiduelle des points (le « reste » de l’équation). En bas, la même décomposition en barres empilées, qui donne à voir directement le poids relatif des deux termes dans la RMSE². À gauche, le modèle original : le biais est quasi nul, la RMSE² est presque entièrement due au reste. À droite, le même modèle décalé de 3°C : la dispersion des points est identique, donc le reste ne change pas, mais le biais s’ajoute et fait grimper la RMSE² d’autant, alors que la position sur le diagramme de Taylor, elle, resterait rigoureusement la même.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-biais-rmse" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center" alt="Figure à deux niveaux. En haut, deux nuages de points côte à côte, température observée en abscisse et température prédite en ordonnée, avec une diagonale y = x en pointillés, un point noir marquant la moyenne de chaque nuage relié par un segment épais orange à son projeté sur la diagonale (le biais), et des segments gris fins reliant chaque point à la diagonale. En bas, deux barres empilées correspondant aux mêmes deux modèles : la partie basse en orange représente le biais au carré, la partie haute en bleu le reste de la RMSE² (dispersion et corrélation). Pour le modèle non biaisé, la barre est presque entièrement bleue ; pour le modèle biaisé, la part orange occupe une portion importante de la barre.">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-biais-rmse-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202607-taylor/taylor_files/figure-html/fig-biais-rmse-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Figure à deux niveaux. En haut, deux nuages de points côte à côte, température observée en abscisse et température prédite en ordonnée, avec une diagonale y = x en pointillés, un point noir marquant la moyenne de chaque nuage relié par un segment épais orange à son projeté sur la diagonale (le biais), et des segments gris fins reliant chaque point à la diagonale. En bas, deux barres empilées correspondant aux mêmes deux modèles : la partie basse en orange représente le biais au carré, la partie haute en bleu le reste de la RMSE² (dispersion et corrélation). Pour le modèle non biaisé, la barre est presque entièrement bleue ; pour le modèle biaisé, la part orange occupe une portion importante de la barre." width="1536">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-biais-rmse-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;8: Décomposition de la RMSE² en biais² et reste (la part expliquée par la loi des cosinus). En haut, le scatter obs/modèle : le segment orange épais matérialise le biais, les segments gris fins la dispersion résiduelle des points. En bas, la même décomposition sous forme de barres empilées : à gauche le biais est quasi nul, à droite il domine largement la RMSE².
</figcaption>
</figure>
</div>
</div>
</div>
</div>
</div>
</div>
<p>C’est la limite à garder en tête : un modèle peut occuper une excellente position sur le diagramme tout en ayant un biais important. Le diagramme de Taylor résume la forme de l’erreur (dispersion, corrélation), pas sa position (biais). En pratique, pour garantir l’égalité des moyennes sans avoir à y penser, le diagramme de Taylor est généralement construit sur des anomalies (des écarts à une moyenne de référence) plutôt que sur des valeurs brutes. Nos trois modèles, eux, respectent déjà cette condition tels quels, puisqu’ils oscillent tous autour de la même température moyenne que l’observation.</p>
<p>Dès lors que cette condition est respectée, le diagramme de Taylor est donc une représentation fidèle : les trois métriques qu’il affiche ne sont jamais en tension entre elles. Un coup d’œil suffit ainsi pour comparer plusieurs dizaines de modèles, là où des tableaux de chiffres demanderaient bien plus de temps.</p>
<div class="callout callout-style-default callout-tip callout-titled" title="Construire un diagramme de Taylor en R">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Astuce</span>Construire un diagramme de Taylor en R
</div>
</div>
<div class="callout-body-container callout-body">
<p>Deux implémentations permettent de construire un diagramme de Taylor en R :</p>
<ul>
<li><a href="https://search.r-project.org/CRAN/refmans/plotrix/html/taylor.diagram.html"><strong><code>plotrix::taylor.diagram()</code></strong></a> : utilisée pour les graphiques de cet article, elle trace un diagramme complet en une ligne.</li>
<li><a href="https://search.r-project.org/CRAN/refmans/openair/html/TaylorDiagram.html"><strong><code>openair::TaylorDiagram()</code></strong></a> : plus récente, plus flexible pour comparer des groupes de modèles.</li>
</ul>
</div>
</div>
<hr>
</section>
<section id="références" class="level3">
<h3 data-anchor-id="références">Références</h3>
<div id="refs" class="references csl-bib-body hanging-indent">
<div id="ref-taylor2001Summarizing" class="csl-entry">
Taylor, Karl E. 2001. <span>«&nbsp;Summarizing Multiple Aspects of Model Performance in a Single Diagram&nbsp;»</span>. <em>Journal of Geophysical Research: Atmospheres</em> 106 (D7): 7183‑92. <a href="https://doi.org/10.1029/2000JD900719">https://doi.org/10.1029/2000JD900719</a>.
</div>
</div>
<p>Savoir lire un diagramme de Taylor, c’est une chose. Savoir construire et valider ses propres modèles en est une autre. Envie d’aller plus loin ?</p>
<div class="cta-group center">
<p><a href="../../formations/machine_learning_avec_r.html" class="cta-button-outline">Découvrir la formation ML avec R</a></p>
<p><a href="mailto:contact@pyranhia.eu" class="cta-button">Me contacter</a></p>
</div>
</section>
</section>
<!-- 

::: {.cell layout-align="center" execute='false'}
::: {.cell-output-display}
![](taylor_files/figure-html/unnamed-chunk-1-1.png){fig-align='center' width=1344}
:::
:::


 -->




<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Notes de bas de page</h2>

<ol>
<li id="fn1"><p>Ils s’annulent parce que <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B1%7D%7Bn%7D%5Csum%20(x_i%20-%20%5Cbar%7Bx%7D)%20=%200"> et <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B1%7D%7Bn%7D%5Csum%20(y_i%20-%20%5Cbar%7By%7D)%20=%200"> par définition de la moyenne.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Data viz</category>
  <category>Tutoriels R</category>
  <guid>https://www.pyranhia.eu/posts/202607-taylor/taylor.html</guid>
  <pubDate>Sun, 02 Aug 2026 22:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202607-taylor/taylor.png" medium="image" type="image/png" height="90" width="144"/>
</item>
<item>
  <title>Les regex, enfin comprises</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202607-regex/regex.html</link>
  <description><![CDATA[ 





<section id="les-regex-enfin-comprises" class="level1 section-full section-padded">
<h1>Les regex, enfin comprises</h1>
<p><span class="section-subtitle">Ce charabia de symboles cache une logique très simple. Promis.</span></p>
</section>
<section id="pourquoi-les-regex-font-peur-et-pourquoi-elles-ne-devraient-pas" class="level2 section-1">
<h2 data-anchor-id="pourquoi-les-regex-font-peur-et-pourquoi-elles-ne-devraient-pas">Pourquoi les regex font peur (et pourquoi elles ne devraient pas)</h2>
<p>Il y a un rituel bien connu chez qui écrit du code : on cherche « comment extraire un numéro de téléphone d’une chaîne de caractère », on tombe sur une réponse qui mentionne du regex et un motif du genre <code>^\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$</code> (pour un format US à dix chiffres), on copie-colle, ça marche, et on passe à autre chose sans avoir rien compris. Enfin, ça c’était à l’époque de Google et StackOverflow : maintenant ChatGPT, Claude et compagnie génèrent le regex encore plus vite. Mais le problème reste le même&nbsp;: on copie-colle un charabia de symboles qui fonctionne, sans savoir pourquoi. Moi aussi je suis passée par là.</p>
<p>Une <strong>regex</strong>, de l’anglais <strong><em>regular expression</em></strong> (expression régulière pour nous francophones), c’est en réalité juste un langage pour décrire un motif dans du texte. Rien de plus. Le mot « chat » est déjà une regex&nbsp;: elle décrit le motif « la lettre c, suivie de h, suivie de a, suivie de t ». Ce qui fait peur, ce sont les symboles qu’on ajoute pour décrire des motifs plus flexibles&nbsp;: « un chiffre », « n’importe quel caractère », « zéro ou plusieurs répétitions »… Une fois qu’on connaît la dizaine de symboles qui reviennent tout le temps, la plupart des regex se lisent presque comme une phrase.</p>
<p>Petit exemple pour se faire une idée. Est-ce qu’une chaîne contient un chiffre ?</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(stringr)</span>
<span id="cb1-2"></span>
<span id="cb1-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_detect</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"adresse123"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"[0-9]"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] TRUE</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_detect</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"adresse"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"[0-9]"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] FALSE</code></pre>
</div>
</div>
<p><code>[0-9]</code> se lit « n’importe quel chiffre entre 0 et 9 ». <code>str_detect()</code> renvoie <code>TRUE</code> si le motif est trouvé quelque part dans la chaîne, <code>FALSE</code> sinon. C’est tout. Pas de magie.</p>
<p>Deuxième exemple, un cran au-dessus&nbsp;: on ne cherche plus juste un chiffre, mais un format précis. Une chaîne ressemble-t-elle à un code postal de France métropolitaine (5 chiffres) ?</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1">codes <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"83600"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"83"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AB123"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"830000"</span>)</span>
<span id="cb5-2"></span>
<span id="cb5-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_detect</span>(codes, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"^[0-9]{5}$"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1]  TRUE FALSE FALSE FALSE</code></pre>
</div>
</div>
<p>Trois symboles suffisent à exprimer « exactement 5 chiffres, ni plus ni moins »&nbsp;: <code>[0-9]</code> pour un chiffre, <code>{5}</code> pour répéter exactement 5 fois, et les symboles <code>^</code> / <code>$</code> pour dire « du tout début à toute fin de la chaîne » (sans eux, <code>"830000"</code> matcherait aussi, puisqu’il contient bien 5 chiffres consécutifs quelque part). Pas besoin de tout retenir dès maintenant&nbsp;: chacun de ces symboles est détaillé dans la section suivante. Ce qu’il faut surtout retenir ici c’est qu’<strong>une regex complexe n’est qu’un assemblage de briques simples</strong>, posées les unes après autres.</p>
<p>En R, deux mondes se côtoient pour manipuler du texte avec des regex. D’un côté, <a href="https://stringr.tidyverse.org/"><code>stringr</code></a>, le package du tidyverse&nbsp;: des fonctions cohérentes, toutes préfixées <code>str_</code>, qui prennent la chaîne en premier argument. De l’autre, les fonctions de base R comme <code>grepl()</code>, <code>gsub()</code> ou <code>regmatches()</code>&nbsp;: plus anciennes, syntaxe un peu différente, mais toujours là dans énormément de code existant. On va utiliser <code>stringr</code> comme fil rouge, avec les équivalents base R en note à chaque étape clé.</p>
</section>
<section id="les-briques-de-base-une-par-une" class="level2 section-2">
<h2 data-anchor-id="les-briques-de-base-une-par-une">Les briques de base, une par une</h2>
<p>Une regex se construit en assemblant des éléments de base, si bien qu’une fois connus, on peut lire et écrire la grande majorité des motifs qu’on croise en pratique. Voici les plus courants.</p>
<section id="les-caractères-littéraux" class="level3">
<h3 data-anchor-id="les-caractères-littéraux">Les caractères littéraux</h3>
<p>La brique la plus simple&nbsp;: chercher du texte tel quel.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_detect</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bonjour tout le monde"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"jour"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] TRUE</code></pre>
</div>
</div>
<p><code>"jour"</code> cherche exactement la séquence j-o-u-r, n’importe où dans la chaîne. C’est déjà une regex, juste sans symbole spécial.</p>
</section>
<section id="les-classes-de-caractères" class="level3">
<h3 data-anchor-id="les-classes-de-caractères">Les classes de caractères</h3>
<p>Une classe de caractères représente « un caractère parmi un ensemble ». C’est là que ça devient utile.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 75%">
</colgroup>
<thead>
<tr class="header">
<th>Symbole</th>
<th>Signification</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>[abc]</code></td>
<td>a, b ou c</td>
</tr>
<tr class="even">
<td><code>[a-z]</code></td>
<td>une lettre minuscule</td>
</tr>
<tr class="odd">
<td><code>[0-9]</code> ou <code>\\d</code></td>
<td>un chiffre</td>
</tr>
<tr class="even">
<td><code>\\w</code></td>
<td>un caractère alphanumérique (lettre, chiffre ou <code>_</code>)</td>
</tr>
<tr class="odd">
<td><code>\\s</code></td>
<td>un espace (espace, tabulation, retour à la ligne)</td>
</tr>
<tr class="even">
<td><code>.</code></td>
<td>n’importe quel caractère (sauf retour à la ligne)</td>
</tr>
</tbody>
</table>
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>En R, le backslash <code>\</code> doit être doublé dans une chaîne de caractères classique (<code>\\d</code>), car <code>\</code> est lui-même un caractère d’échappement. C’est une source fréquente d’erreurs pour qui débute.</p>
</div>
</div>
</div>
<p>Un exemple concret&nbsp;: est-ce qu’une chaîne contient au moins un chiffre ?</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb9-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_detect</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"code42"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sans chiffre"</span>), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">d"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1]  TRUE FALSE</code></pre>
</div>
</div>
</section>
<section id="les-quantificateurs" class="level3">
<h3 data-anchor-id="les-quantificateurs">Les quantificateurs</h3>
<p>Un quantificateur précise <strong>combien de fois</strong> répéter ce qui précède.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Symbole</th>
<th>Signification</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>*</code></td>
<td>zéro ou plusieurs fois</td>
</tr>
<tr class="even">
<td><code>+</code></td>
<td>une ou plusieurs fois</td>
</tr>
<tr class="odd">
<td><code>?</code></td>
<td>zéro ou une fois (optionnel)</td>
</tr>
<tr class="even">
<td><code>{n}</code></td>
<td>exactement n fois</td>
</tr>
<tr class="odd">
<td><code>{n,m}</code></td>
<td>entre n et m fois</td>
</tr>
</tbody>
</table>
<p>Par exemple, <code>\\d+</code> veut dire « un ou plusieurs chiffres à la suite », c’est-à-dire un nombre entier&nbsp;:</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb11-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_extract</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"le prix est 1250 euros et 12 centimes"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">d+"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] "1250"</code></pre>
</div>
</div>
<p><code>str_extract()</code> renvoie la première portion de texte qui correspond au motif, ici <code>"1250"</code>.</p>
<p>Le <code>?</code> sert souvent à rendre un caractère optionnel, pour couvrir deux façons d’écrire un même code. Un cas fréquent en pratique : un code de lot ou de zone, saisi parfois avec un tiret, parfois sans.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb13-1">codes_lot <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"FR-001"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"FR001"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"FR-002"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"US-001"</span>)</span>
<span id="cb13-2"></span>
<span id="cb13-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_detect</span>(codes_lot, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"FR-?001"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1]  TRUE  TRUE FALSE FALSE</code></pre>
</div>
</div>
<p><code>"FR-?001"</code> se lit « FR, suivi d’un tiret optionnel, suivi de 001 » : ça matche <code>"FR-001"</code> et <code>"FR001"</code>, qu’il y ait un tiret ou non, mais pas <code>"FR-002"</code> (mauvais numéro) ni <code>"US-001"</code> (mauvais indicatif). Ici, le <code>?</code> change vraiment le résultat : sans lui, le motif littéral <code>"FR-001"</code> ne matcherait que la version avec tiret, et raterait <code>"FR001"</code>.</p>
</section>
<section id="les-ancres" class="level3">
<h3 data-anchor-id="les-ancres">Les ancres</h3>
<p>Les ancres ne représentent pas un caractère&nbsp;: elles précisent une <strong>position</strong> dans la chaîne.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Symbole</th>
<th>Signification</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>^</code></td>
<td>début de la chaîne</td>
</tr>
<tr class="even">
<td><code>$</code></td>
<td>fin de la chaîne</td>
</tr>
</tbody>
</table>
<p>Elles peuvent s’utiliser seules ou ensemble. Prenons une liste de poissons&nbsp;:</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb15-1">poissons <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thon rouge"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"thon blanc"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"maquereau"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"requin marteau"</span>)</span>
<span id="cb15-2"></span>
<span id="cb15-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Commence par "thon"</span></span>
<span id="cb15-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_detect</span>(poissons, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"^thon"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1]  TRUE  TRUE FALSE FALSE</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb17-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Finit par "eau"</span></span>
<span id="cb17-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_detect</span>(poissons, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"eau$"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] FALSE FALSE  TRUE  TRUE</code></pre>
</div>
</div>
<p><code>"^thon"</code> matche les deux thons, mais pas les autres éléments, qui ne commence pas par ces lettres. <code>"eau$"</code> matche <code>"maquereau"</code> et <code>"requin marteau"</code>, qui se terminent tous les deux par ces lettres, mais pas les thons.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Astuce</span>Le symbole <code>^</code>&nbsp;: négation ou ancre selon le contexte
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><strong>À l’intérieur de crochets <code>[…]</code>, en première position</strong>&nbsp;: <code>^</code> veut dire « négation ». <code>[^a-z0-9]</code> se lit « tout caractère qui n’est PAS une lettre minuscule ni un chiffre ».</li>
<li><strong>En dehors de crochets, seul</strong>&nbsp;: <code>^</code> veut dire « ancre de début de chaîne », comme dans <code>"^thon"</code> ci-dessus.</li>
</ul>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb19-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Négation : un caractère qui n'est PAS une lettre</span></span>
<span id="cb19-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_detect</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"abc1"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"[^a-z]"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] TRUE</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb21-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Ancre : la chaîne commence par "abc"</span></span>
<span id="cb21-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_detect</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"abc1"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"^abc"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] TRUE</code></pre>
</div>
</div>
</div>
</div>
<p>Les deux ancres combinées permettent d’exiger que le motif couvre la chaîne <strong>entière</strong>, du début à la fin, pas juste une portion&nbsp;:</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb23" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb23-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Contient au moins un chiffre, n'importe où</span></span>
<span id="cb23-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_detect</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"daurade123"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">d"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] TRUE</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb25" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb25-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Est composé UNIQUEMENT de chiffres, du début à la fin</span></span>
<span id="cb25-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_detect</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"daurade123"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"^</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">d+$"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] FALSE</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb27" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb27-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_detect</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"123456"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"^</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">d+$"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] TRUE</code></pre>
</div>
</div>
<p>Sans les ancres, <code>"^\\d+$"</code> deviendrait simplement <code>"\\d+"</code>, qui matcherait aussi <code>"daurade123"</code> puisqu’il contient des chiffres quelque part. Les ancres forcent le motif à couvrir la chaîne entière.</p>
</section>
<section id="les-regroupements" class="level3">
<h3 data-anchor-id="les-regroupements">Les regroupements</h3>
<p>Un quantificateur ne s’applique qu’au caractère (ou à la classe) qui le précède immédiatement. Pour répéter un <strong>groupe entier</strong> de caractères, on l’entoure de parenthèses <code>(...)</code>.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb29" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb29-1">codes <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1212"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1222"</span>)</span>
<span id="cb29-2"></span>
<span id="cb29-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_detect</span>(codes, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"^(12)+$"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1]  TRUE FALSE</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb31" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb31-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_detect</span>(codes, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"^12+$"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] FALSE  TRUE</code></pre>
</div>
</div>
<p>Avec le regroupement <code>(12)+</code>, seul <code>"1212"</code> matche : c’est bien une répétition du bloc complet “12”. Sans les parenthèses, <code>12+</code> veut dire tout autre chose : « un 1, suivi de 2 répété une ou plusieurs fois », ce matche <code>"1222"</code> mais pas <code>"1212"</code> (le second “1” casse la série de 2). Le regroupement change donc entièrement ce que le quantificateur répète.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Astuce</span>Comment lire une regex composée
</div>
</div>
<div class="callout-body-container callout-body">
<p>Face à un motif comme <code>^(19|20)\\d{2}$</code>, on le décompose brique par brique plutôt que de le lire d’un bloc&nbsp;:</p>
<ul>
<li><code>^</code>&nbsp;: début de la chaîne</li>
<li><code>(19|20)</code>&nbsp;: le groupe “19” ou “20”</li>
<li><code>\\d{2}</code>&nbsp;: suivi de deux chiffres</li>
<li><code>$</code>&nbsp;: fin de la chaîne</li>
</ul>
<p>Soit&nbsp;: « la chaîne est composée de 4 chiffres, commençant par 19 ou 20 », un motif utile pour repérer une année entre 1900 et 2099. C’est exactement ce réflexe de décomposition qu’on va appliquer aux exemples plus corsés de la section suivante.</p>
</div>
</div>
</section>
</section>
<div class="section-3">
<section id="les-fonctions-stringr-à-connaître-et-le-raisonnement-pour-les-cas-corsés" class="level2">
<h2 data-anchor-id="les-fonctions-stringr-à-connaître-et-le-raisonnement-pour-les-cas-corsés">Les fonctions stringr à connaître, et le raisonnement pour les cas corsés</h2>
<section id="le-mapping-mental" class="level3">
<h3 data-anchor-id="le-mapping-mental">Le mapping mental</h3>
<p>Quatre familles de fonctions couvrent la grande majorité des besoins&nbsp;:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Besoin</th>
<th>Fonction stringr</th>
<th>Équivalent base R</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Détecter (vrai/faux)</td>
<td><code>str_detect()</code></td>
<td><code>grepl()</code></td>
</tr>
<tr class="even">
<td>Extraire</td>
<td><code>str_extract()</code>, <code>str_extract_all()</code></td>
<td><code>regmatches()</code></td>
</tr>
<tr class="odd">
<td>Remplacer</td>
<td><code>str_replace()</code>, <code>str_replace_all()</code></td>
<td><code>sub()</code>, <code>gsub()</code></td>
</tr>
<tr class="even">
<td>Découper</td>
<td><code>str_split()</code></td>
<td><code>strsplit()</code></td>
</tr>
</tbody>
</table>
<p>Petite nuance importante&nbsp;: <code>str_extract()</code> ne remplace que la <strong>première</strong> occurrence trouvée, <code>str_extract_all()</code> remplace <strong>toutes</strong> les occurrences. Le même principe s’applique à <code>sub()</code> (une fois) contre <code>gsub()</code> (toutes les fois) en base R.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb33" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb33-1">x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"poulpe crabe poulpe"</span></span>
<span id="cb33-2"></span>
<span id="cb33-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_replace</span>(x, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"poulpe"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"seiche"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] "seiche crabe poulpe"</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb35" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb35-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_replace_all</span>(x, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"poulpe"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"seiche"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] "seiche crabe seiche"</code></pre>
</div>
</div>
</section>
<section id="cas-pratique-1-nettoyer-des-noms-de-colonnes" class="level3">
<h3 data-anchor-id="cas-pratique-1-nettoyer-des-noms-de-colonnes">Cas pratique 1&nbsp;: nettoyer des noms de colonnes</h3>
<p>Un classique après un import&nbsp;: des noms de colonnes avec des espaces, des majuscules et des caractères bizarres.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb37" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb37-1">noms_colonnes <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Nom Espece"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Prix au kg (€)"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Zone de peche"</span>)</span></code></pre></div></div>
</div>
<p>Le raisonnement&nbsp;: on veut passer de <code>"Nom Espece"</code> à quelque chose comme <code>nom_espece</code>. Ça se décompose en plusieurs étapes distinctes, qu’on traite une par une plutôt que de chercher un seul motif magique qui ferait tout d’un coup.</p>
<ol type="1">
<li>Tout mettre en minuscules, avec <code>str_to_lower()</code> (pas de regex nécessaire).</li>
<li>Remplacer tout ce qui n’est pas une lettre ou un chiffre par un underscore.</li>
<li>Nettoyer les underscores en trop (doublons, ou en bord de chaîne).</li>
</ol>
<p>L’étape 2 utilise <code>[^a-z0-9]</code>, la classe négative vue en section précédente&nbsp;: « tout caractère qui n’est pas une lettre minuscule ni un chiffre ».</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb38" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb38-1">noms_propres <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> noms_colonnes <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> </span>
<span id="cb38-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_to_lower</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> </span>
<span id="cb38-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_replace_all</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"[^a-z0-9]+"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"_"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> </span>
<span id="cb38-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_replace_all</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"^_|_$"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>)</span>
<span id="cb38-5"></span>
<span id="cb38-6">noms_propres</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] "nom_espece"    "prix_au_kg"    "zone_de_peche"</code></pre>
</div>
</div>
<p><code>"[^a-z0-9]+"</code> remplace tout bloc de caractères indésirables par un seul underscore (le <code>+</code> évite d’avoir plusieurs <code>_</code> à la suite pour <code>"(€)"</code>). Et <code>"^_|_$"</code> utilise cette fois <code>^</code> et <code>$</code> comme ancres, combinées à <code>|</code> (« ou »), ce qui matche un underscore en tout début ou en toute fin de chaîne, pour le retirer.</p>
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>Ici, <code>noms_colonnes</code> ne contient volontairement aucun accent&nbsp;: avec <code>[^a-z0-9]</code>, un <code>è</code> serait traité comme indésirable et remplacé par un <code>_</code> (<code>"esp_ce"</code> plutôt que <code>"espece"</code>). En pratique, mieux vaut de toute façon passer par le package <a href="https://sfirke.github.io/janitor/"><code>janitor</code></a> et sa fonction <code>clean_names()</code>, qui gère ce nettoyage (accents compris) en une ligne. Mais comprendre le mécanisme sous-jacent reste utile pour ajuster le résultat quand <code>clean_names()</code> ne fait pas exactement ce qu’il faut.</p>
</div>
</div>
</div>
</section>
<section id="cas-pratique-2-extraire-un-nombre-décimal-dans-du-texte-libre" class="level3">
<h3 data-anchor-id="cas-pratique-2-extraire-un-nombre-décimal-dans-du-texte-libre">Cas pratique 2&nbsp;: extraire un nombre décimal dans du texte libre</h3>
<p>Extraire <code>"1250"</code> d’une phrase, on l’a déjà fait. Mais que se passe-t-il avec des nombres décimaux ou négatifs ?</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb40" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb40-1">textes <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(</span>
<span id="cb40-2">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Le solde est de -42.5 euros"</span>,</span>
<span id="cb40-3">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Température : 18,3°C"</span>,</span>
<span id="cb40-4">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Aucune valeur numérique ici"</span></span>
<span id="cb40-5">)</span></code></pre></div></div>
</div>
<p>Première tentative avec <code>\\d+</code>, qui ne gère que les chiffres&nbsp;:</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb41" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb41-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_extract</span>(textes, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">d+"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] "42" "18" NA  </code></pre>
</div>
</div>
<p>Ça coupe la partie décimale (<code>"42"</code> au lieu de <code>"-42.5"</code>) et perd le signe négatif. On raisonne étape par étape sur ce qu’on veut vraiment matcher&nbsp;:</p>
<ul>
<li>un signe moins optionnel&nbsp;: <code>-?</code></li>
<li>un ou plusieurs chiffres&nbsp;: <code>\\d+</code></li>
<li>un séparateur décimal optionnel (point ou virgule), suivi de chiffres&nbsp;: <code>[.,]\\d+</code> rendu optionnel avec <code>?</code></li>
</ul>
<p>Ce qui donne, en assemblant les briques&nbsp;:</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb43" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb43-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_extract</span>(textes, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-?</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">d+[.,]?</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">d*"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] "-42.5" "18,3"  NA     </code></pre>
</div>
</div>
<p>Chaque brique reste indépendante et peut se tester séparément si le résultat surprend. C’est la meilleure façon de déboguer une regex qui ne fait pas ce qu’on attend&nbsp;: la couper en morceaux, et vérifier chaque morceau isolément.</p>
</section>
<section id="cas-pratique-3-valider-un-format-dadresse-e-mail-simplifié" class="level3">
<h3 data-anchor-id="cas-pratique-3-valider-un-format-dadresse-e-mail-simplifié">Cas pratique 3&nbsp;: valider un format d’adresse e-mail (simplifié)</h3>
<p>Un motif souvent copié-collé sans être compris. Voici comment le construire soi-même, en partant de la structure d’une adresse&nbsp;: <code>quelquechose@domaine.extension</code>.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb45" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb45-1">emails <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"contact@pyranhia.eu"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pas-un-email"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"test@site"</span>)</span></code></pre></div></div>
</div>
<p>On découpe le problème en trois blocs, dans l’ordre où ils apparaissent&nbsp;:</p>
<ol type="1">
<li>avant le <code>@</code>&nbsp;: un ou plusieurs caractères alphanumériques (ou <code>.</code>, <code>_</code>, <code>-</code>) → <code>[\\w.-]+</code></li>
<li>le <code>@</code> littéral</li>
<li>après le <code>@</code>&nbsp;: un nom de domaine avec au moins un point, suivi d’une extension → <code>[\\w-]+\\.[a-z]+</code></li>
</ol>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb46" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb46-1">pattern_email <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"^[</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">w.-]+@[</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">w-]+</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">.[a-z]+$"</span></span>
<span id="cb46-2"></span>
<span id="cb46-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str_detect</span>(emails, pattern_email)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1]  TRUE FALSE FALSE</code></pre>
</div>
</div>
<p><code>"test@site"</code> est rejeté à juste titre&nbsp;: pas de point ni d’extension après le <code>@</code>. C’est un motif volontairement simplifié, la vraie spécification des adresses e-mail est bien plus permissive et complexe.</p>
</section>
</section>
<section id="à-vous-de-jouer" class="level2">
<h2 data-anchor-id="à-vous-de-jouer">À vous de jouer</h2>
<p>Rien ne vaut la pratique pour que ces briques deviennent des réflexes. Voici un petit défi en 8 niveaux, du plus simple au plus corsé, pour tester ce qu’on vient de voir : classes de caractères, quantificateurs, ancres, regroupements, négation, et leurs combinaisons.</p>
<div id="fig-regex-widget" class="hide-in-pdf quarto-float quarto-figure quarto-figure-center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-regex-widget-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<iframe src="regex-widget/index.html" title="Widget interactif : défi regex en 8 niveaux" width="100%" height="800" style="border:none;border-radius:12px;"></iframe>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-regex-widget-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Écrivez un motif regex qui capture tous les mots à trouver, sans capturer les pièges. Indices et solutions disponibles à chaque niveau en cas de blocage.
</figcaption>
</figure>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Astuce</span>Retour sur l’exemple du début
</div>
</div>
<div class="callout-body-container callout-body">
<p>Le motif qui ouvrait cet article, <code>^\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$</code>, se lit maintenant (presque) sans effort :</p>
<ul>
<li>ancré du début à la fin (<code>^...$</code>),</li>
<li>une parenthèse ouvrante optionnelle (<code>\(?</code>),</li>
<li>trois chiffres (<code>\d{3}</code>), une parenthèse fermante optionnelle (<code>\)?</code>),</li>
<li>un séparateur optionnel (<code>[-.\s]?</code>),</li>
<li>trois chiffres,</li>
<li>un séparateur optionnel,</li>
<li>quatre chiffres.</li>
</ul>
<p>De quoi valider un numéro américain écrit <code>(555) 123-4567</code>, <code>555-123-4567</code> ou <code>5551234567</code>.</p>
</div>
</div>
</section>
<section id="pour-aller-plus-loin" class="level2">
<h2 data-anchor-id="pour-aller-plus-loin">Pour aller plus loin</h2>
<p>Les regex ne se limitent pas à <code>stringr</code>&nbsp;: elles apparaissent partout, dans <code>grep</code> en ligne de commande, dans les éditeurs de texte, dans presque tous les langages de programmation. La syntaxe de base présentée ici (classes, quantificateurs, ancres) se retransporte quasiment telle quelle d’un contexte à l’autre.</p>
<p>Pour tester un motif sans relancer R à chaque essai, <a href="https://regex101.com">regex101.com</a> est une référence&nbsp;: coloration syntaxique en direct, explication de chaque brique du motif, et bibliothèque d’exemples. Pratique pour vérifier une regex avant de la coller dans son code (penser à choisir le mode “PCRE” ou équivalent pour rester proche de la syntaxe R).</p>
<div class="note-box">
<p><strong>Envie d’aller plus loin en R ?</strong></p>
<p>Manipulation de données, nettoyage, construction de modèles… J’aborde tout ça avec des cas concrets et réels dans mes formations.</p>
</div>
<div class="cta-group center">
<p><a href="../../formations/machine_learning_avec_r.html" class="cta-button-outline">Découvrir la formation ML avec R</a></p>
<p><a href="../../contact.html" class="cta-button">Me contacter</a></p>
</div>
</section>
</div>



 ]]></description>
  <category>Tutoriels R</category>
  <category>R bits</category>
  <guid>https://www.pyranhia.eu/posts/202607-regex/regex.html</guid>
  <pubDate>Thu, 30 Jul 2026 22:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202607-regex/regex.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Trop de couleurs tue la couleur</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202607-dataviz-2/dataviz-2.html</link>
  <description><![CDATA[ 





<section id="trop-de-couleurs-tue-la-couleur" class="level1 section-full section-padded">
<h1>Trop de couleurs tue la couleur</h1>
<p><span class="section-subtitle">Une palette qualitative avec trop de catégories, c’est un peu comme un camembert avec trop de parts : joli, mais illisible.</span></p>
<div class="note-box">
<p>Cet article fait partie d’une série sur les couleurs en dataviz :</p>
<ul>
<li><a href="posts/202605-dataviz-1/dataviz-1.html">Bien choisir sa palette de couleur</a></li>
<li><strong>Palettes qualitatives et nombre de couleurs</strong> ← vous êtes ici</li>
<li>Dataviz accessible</li>
</ul>
</div>
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>Les données utilisées dans cet article proviennent du dataset <em>many_penguins</em> (<a href="https://github.com/rfordatascience/tidytuesday">TidyTuesday</a>, 14 juillet 2026), lui-même dérivé de la base AVONET <span class="citation" data-cites="tobias2022AVONET">(Tobias et al. 2022)</span>.</p>
</div>
</div>
</div>
</section>
<section id="les-palettes-qualitatives" class="level2 section-1">
<h2 data-anchor-id="les-palettes-qualitatives">Les palettes qualitatives</h2>
<p>Prenons un jeu de données réel avec beaucoup de catégories : <a href="https://github.com/rfordatascience/tidytuesday/tree/main/data/2026/2026-07-14"><code>many_penguins</code></a>, publié cette semaine dans TidyTuesday. C’est un prolongement naturel du dataset <code>penguins</code> utilisé dans le premier article de cette série : là où <code>penguins</code> ne couvre que 3 espèces (qui au passage ne sont pas des pingouins mais des manchots !), <code>many_penguins</code> en couvre 18, réparties en 6 genres, issues du dataset AVONET qui recense les traits morphologiques de l’ensemble des oiseaux du monde.</p>
<p>93 individus, 18 espèces. C’est exactement le genre de situation où on a besoin d’une palette qualitative solide. Sans réfléchir, on laisse d’abord ggplot2 choisir sa palette par défaut.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1">many_penguins <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb1-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> beak.width, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> wing.length, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> species)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb1-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_point</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb1-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Largeur du bec (mm)"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Longueur des ailes (mm)"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Espèce"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb1-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">legend.text =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">element_text</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>))</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-penguins-defaut" class="quarto-float quarto-figure quarto-figure-center" alt="Nuage de points de la largeur du bec en fonction de la longueur des ailes chez 93 manchots, colorés par espèce avec la palette par défaut de ggplot2. Les couleurs se ressemblent beaucoup, rendant les 18 espèces difficiles à distinguer les unes des autres." data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-penguins-defaut-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202607-dataviz-2/dataviz-2_files/figure-html/fig-penguins-defaut-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Nuage de points de la largeur du bec en fonction de la longueur des ailes chez 93 manchots, colorés par espèce avec la palette par défaut de ggplot2. Les couleurs se ressemblent beaucoup, rendant les 18 espèces difficiles à distinguer les unes des autres." width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-penguins-defaut-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: 18 espèces de manchots, palette par défaut de ggplot2.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Le résultat est illisible. La palette par défaut de ggplot2 répartit les teintes de façon égale sur le cercle chromatique, sans se soucier de leur ressemblance perceptuelle : certaines couleurs voisines se confondent presque, d’autres sont beaucoup plus saillantes que le reste sans raison. Ce n’est pas conçu pour ça.</p>
<p>C’est là qu’interviennent les palettes de <a href="https://colorbrewer2.org/">RColorBrewer</a>, conçues à l’origine pour la cartographie. Il en existe huit, chacune avec sa propre logique :</p>
<ul>
<li><strong>Set1</strong> (9 couleurs max) : teintes très saturées, fort contraste, la plus polyvalente</li>
<li><strong>Set2</strong> (8 couleurs max) : teintes plus douces, adaptée aux fonds clairs</li>
<li><strong>Set3</strong> (12 couleurs max) : le plus grand nombre de teintes disponibles, mais des tons plus pastel donc moins contrastés</li>
<li><strong>Dark2</strong> (8 couleurs max) : tons foncés, bon contraste sur fond blanc</li>
<li><strong>Paired</strong> (12 couleurs max) : des paires clair/foncé pour chaque teinte, pensée pour des catégories qui vont naturellement deux par deux, pas pour des catégories complètement indépendantes</li>
<li><strong>Accent</strong> (8 couleurs max) : pensée pour faire ressortir une ou deux classes importantes au milieu des autres, pas pour distinguer un grand nombre de catégories à égalité</li>
<li><strong>Pastel1</strong> (9 couleurs max) : teintes pastel, faible contraste, à éviter dès qu’on a beaucoup de catégories</li>
<li><strong>Pastel2</strong> (8 couleurs max) : même logique que Pastel1, palette d’origine plus restreinte</li>
</ul>
<div class="note-box">
<p>Il existe d’autres familles de palettes qualitatives en dehors de ColorBrewer, notamment <a href="https://jfly.uni-koeln.de/color/">okabe-ito</a>, conçue spécifiquement pour rester lisible en cas de daltonisme. On la retrouve dans le widget de la section suivante, et plus en détail dans le troisième article de la série.</p>
</div>
<p>Même la plus généreuse de ces huit palettes, <code>Set3</code>, plafonne à 12 couleurs. Avec 18 espèces, on est structurellement au-delà de ce que n’importe quelle palette qualitative de ColorBrewer peut proposer.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1">many_penguins <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb2-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> beak.width, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> wing.length, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> species)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb2-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_point</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb2-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_colour_brewer</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">palette =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Set3"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb2-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Largeur du bec (mm)"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Longueur des ailes (mm)"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Espèce"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb2-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">legend.text =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">element_text</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>))</span></code></pre></div></div>
<div class="cell-output cell-output-stderr">
<pre><code>Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Set3 is 12
Returning the palette you asked for with that many colors</code></pre>
</div>
<div class="cell-output cell-output-stderr">
<pre><code>Warning: Removed 39 rows containing missing values or values outside the scale range
(`geom_point()`).</code></pre>
</div>
<div class="cell-output-display">
<div id="fig-penguins-set3" class="quarto-float quarto-figure quarto-figure-center" alt="Le même nuage de points, coloré cette fois avec la palette Set3. Les points de six espèces sur dix-huit apparaissent en gris uniforme, faute de couleur disponible dans la palette." data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-penguins-set3-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202607-dataviz-2/dataviz-2_files/figure-html/fig-penguins-set3-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Le même nuage de points, coloré cette fois avec la palette Set3. Les points de six espèces sur dix-huit apparaissent en gris uniforme, faute de couleur disponible dans la palette." width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-penguins-set3-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Le même graphique avec la palette Set3 (12 couleurs maximum). Les six espèces en trop n’apparaissent plus sur le graphique.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Cette fois, ce n’est plus une question de lisibilité difficile : R refuse purement et simplement d’attribuer une couleur aux espèces excédentaires. Elles disparaissent du graphique, sans point ni couleur, tout en restant listées dans la légende. Six espèces sur dix-huit s’évaporent ainsi silencieusement. Ce n’est plus une palette qui se dégrade, c’est un mur.</p>
<div class="note-box">
<p>Il existe un cas intermédiaire souvent oublié : les variables <strong>ordinales</strong>, c’est-à-dire des catégories qui ont un ordre naturel mais pas de valeur numérique continue. Par exemple des tranches d’âge, des niveaux de satisfaction, ou faible / moyen / fort.</p>
<p>Ni une palette qualitative (qui ignore l’ordre) ni une palette séquentielle continue (qui suppose des intervalles réguliers) ne conviennent vraiment. La bonne réponse est une palette séquentielle discrète, comme <code>scale_fill_brewer(palette = "Blues")</code> avec des niveaux ordonnés, ou <code>scale_fill_steps()</code> pour discrétiser une variable continue.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1">palmerpenguins<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>penguins <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb5-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">drop_na</span>(body_mass_g) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb5-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">masse_cat =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cut</span>(</span>
<span id="cb5-4">    body_mass_g,</span>
<span id="cb5-5">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">breaks =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>,</span>
<span id="cb5-6">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">labels =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Légère"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Moyenne"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Lourde"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Très lourde"</span>))</span>
<span id="cb5-7">  ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb5-8">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> flipper_length_mm, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> bill_length_mm, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> masse_cat)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb5-9">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_point</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb5-10">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_colour_brewer</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">palette =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"RdPu"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">direction =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb5-11">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(</span>
<span id="cb5-12">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Longueur des nageoires (mm)"</span>,</span>
<span id="cb5-13">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Longueur du bec (mm)"</span>,</span>
<span id="cb5-14">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Masse"</span></span>
<span id="cb5-15">  )</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-ordinal" class="quarto-float quarto-figure quarto-figure-center" alt="Nuage de points de la longueur du bec en fonction de la longueur des nageoires chez les manchots, colorés selon quatre tranches de masse ordonnées, d'un rose clair pour les plus légers à un rose foncé pour les plus lourds." data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-ordinal-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202607-dataviz-2/dataviz-2_files/figure-html/fig-ordinal-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Nuage de points de la longueur du bec en fonction de la longueur des nageoires chez les manchots, colorés selon quatre tranches de masse ordonnées, d'un rose clair pour les plus légers à un rose foncé pour les plus lourds." width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-ordinal-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Palette séquentielle discrète : la masse des manchots (dataset penguins, article 1) découpée en quatre tranches ordonnées.
</figcaption>
</figure>
</div>
</div>
</div>
</div>
<p>Ni assez de couleurs disponibles, ni un moyen de contourner le problème en changeant simplement de palette. C’est cette question qu’on creuse dans la section suivante.</p>
</section>
<section id="combien-de-couleurs-peut-on-vraiment-distinguer" class="level2 section-2">
<h2 data-anchor-id="combien-de-couleurs-peut-on-vraiment-distinguer">Combien de couleurs peut-on vraiment distinguer ?</h2>
<p>En section précédente, R a refusé net d’attribuer une couleur à six espèces sur dix-huit avec <code>Set3</code>. C’est un mur facile à repérer : soit la palette a assez de couleurs, soit elle n’en a pas. Mais la vraie question est plus subtile que ce simple seuil technique : même quand une palette <em>a</em> assez de couleurs, à partir de combien de catégories devient-elle difficile à lire ?</p>
<p>Il n’existe pas de nombre magique validé scientifiquement. Le fameux « 7 plus ou moins 2 » du psychologue George Miller, souvent cité en dataviz, porte en réalité sur la mémoire à court terme en général, pas spécifiquement sur la couleur. Deux facteurs jouent en même temps : le nombre de catégories, et la qualité de la palette elle-même.</p>
<p>Plutôt que de vous donner un chiffre unique, essayez par vous-même</p>
<div id="fig-widget-couleurs-2" class="quarto-float quarto-figure quarto-figure-center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-widget-couleurs-2-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<iframe src="couleurs-widget/index.html" title="Widget interactif : comparer des palettes de couleurs sur des espèces de manchots" width="100%" height="720" style="border:none;"></iframe>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-widget-couleurs-2-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: <em>18 espèces de manchots, deux palettes de votre choix, un curseur pour faire varier le nombre d’espèces affichées. Observez à quel rythme chaque palette se dégrade.</em>
</figcaption>
</figure>
</div>
<p>Avec des palettes qui assignent toujours une couleur, quel que soit le nombre de catégories, le mur disparaît : chaque espèce reste identifiable, même quand deux teintes se ressemblent beaucoup. C’est exactement le compromis que permet l’interactivité du widget : pas besoin de distinguer visuellement 18 teintes à l’œil nu, un survol suffit à lever le doute. La dégradation reste réelle, elle est seulement progressive plutôt que brutale : les teintes se rapprochent de plus en plus à mesure que le nombre de catégories augmente, jusqu’à devenir quasiment interchangeables au premier coup d’œil.</p>
<div class="note-box">
<p>Faire disparaître une catégorie n’est pas un bug, c’est une conséquence du fonctionnement de <code>RColorBrewer</code> : la palette a un nombre de couleurs fixe, et <code>scale_colour_brewer()</code> ne sait pas en inventer une de plus. R signale d’ailleurs le problème par un avertissement (<code>n too large, allowed maximum for palette...</code>) plutôt que de masquer la limite. C’est un signal utile : il oblige à changer de stratégie plutôt que de laisser croire que tout va bien.</p>
</div>
<p>Dans les deux cas, qu’il s’agisse d’un mur nettement visible ou d’une dérive progressive, le message est le même : au-delà d’un certain nombre de catégories, changer de palette ne suffit plus. C’est ce qu’on regarde dans la dernière section.</p>
</section>
<section id="que-faire-quand-on-a-trop-de-catégories" class="level2 section-3">
<h2 data-anchor-id="que-faire-quand-on-a-trop-de-catégories">Que faire quand on a trop de catégories ?</h2>
<p>Colorer chacune des dix-huit espèces individuellement ne fonctionne pas, on l’a vu. Voici trois façons différentes de contourner le problème, parmi bien d’autres possibles.</p>
<section id="regrouper-par-une-hiérarchie-qui-a-du-sens" class="level3">
<h3 data-anchor-id="regrouper-par-une-hiérarchie-qui-a-du-sens">Regrouper par une hiérarchie qui a du sens</h3>
<p>Les 18 espèces se répartissent en seulement 6 genres. Plutôt que de regrouper les catégories rares dans un « Autres » artificiel, on peut s’appuyer sur une hiérarchie biologique qui existe déjà dans les données.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb6-1">many_penguins <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb6-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> beak.width, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> wing.length, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> genus)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb6-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_point</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb6-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_colour_brewer</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">palette =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Dark2"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb6-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Largeur du bec (mm)"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Longueur des ailes (mm)"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Genre"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb6-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">legend.text =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">element_text</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>))</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-genus-color" class="quarto-float quarto-figure quarto-figure-center" alt="Le même nuage de points de largeur du bec et longueur des ailes, coloré cette fois par genre plutôt que par espèce : six couleurs nettement distinctes au lieu de dix-huit." data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-genus-color-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202607-dataviz-2/dataviz-2_files/figure-html/fig-genus-color-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Le même nuage de points de largeur du bec et longueur des ailes, coloré cette fois par genre plutôt que par espèce : six couleurs nettement distinctes au lieu de dix-huit." width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-genus-color-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: Les mêmes données, colorées par genre plutôt que par espèce : 6 catégories au lieu de 18.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Six catégories, n’importe quelle palette qualitative standard s’en sort bien. Le prix à payer est net : on perd complètement la distinction entre espèces d’un même genre.</p>
<p>Il existe un moyen simple de récupérer ce détail sans toucher à la couleur : rendre le même graphique interactif. Le package <a href="https://davidgohel.github.io/ggiraph/">ggiraph</a> transforme un graphique ggplot2 en objet interactif, où survoler un point affiche l’espèce correspondante.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(ggiraph)</span>
<span id="cb7-2"></span>
<span id="cb7-3">p <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> many_penguins <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb7-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">drop_na</span>(beak.width, wing.length) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb7-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(</span>
<span id="cb7-6">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> beak.width, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> wing.length, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> genus,</span>
<span id="cb7-7">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">tooltip =</span> species, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data_id =</span> species</span>
<span id="cb7-8">  )) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb7-9">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_point_interactive</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb7-10">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_colour_brewer</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">palette =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Dark2"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb7-11">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Largeur du bec (mm)"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Longueur des ailes (mm)"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Genre"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb7-12">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">legend.text =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">element_text</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>))</span>
<span id="cb7-13"></span>
<span id="cb7-14"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">girafe</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ggobj =</span> p, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">width_svg =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">height_svg =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)</span></code></pre></div></div>
<div id="fig-interactive" class="cell-output-display quarto-float quarto-figure quarto-figure-center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-interactive-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="girafe html-widget html-fill-item" id="htmlwidget-0510818f6e267199fa0e" style="width:100%;height:464px;"></div>
<script type="application/json" data-for="htmlwidget-0510818f6e267199fa0e">{"x":{"html":"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' class='ggiraph-svg' role='graphics-document' id='svg_02c026c57f8f519b' viewBox='0 0 504 360'>\n <defs id='svg_02c026c57f8f519b_defs'>\n  <clipPath id='svg_02c026c57f8f519b_c1'>\n   <rect x='0' y='0' width='504' height='360'/>\n  <\/clipPath>\n  <clipPath id='svg_02c026c57f8f519b_c2'>\n   <rect x='48.41' y='6.97' width='340.84' height='313.89'/>\n  <\/clipPath>\n <\/defs>\n <g id='svg_02c026c57f8f519b_rootg' class='ggiraph-svg-rootg'>\n  <g clip-path='url(#svg_02c026c57f8f519b_c1)'>\n   <rect x='0' y='0' width='504' height='360' fill='#FFFFFF' fill-opacity='1' stroke='#FFFFFF' stroke-opacity='1' stroke-width='0.75' stroke-linejoin='round' stroke-linecap='round' class='ggiraph-svg-bg'/>\n   <rect x='0' y='0' width='504' height='360' fill='#FFFFFF' fill-opacity='1' stroke='none'/>\n  <\/g>\n  <g clip-path='url(#svg_02c026c57f8f519b_c2)'>\n   <rect x='48.41' y='6.97' width='340.84' height='313.89' fill='#FFFFFF' fill-opacity='1' stroke='none'/>\n   <polyline points='48.41,285.01 389.25,285.01' fill='none' stroke='#AAAAAA' stroke-opacity='1' stroke-width='0.11' stroke-linejoin='round' stroke-linecap='butt'/>\n   <polyline points='48.41,165.12 389.25,165.12' fill='none' stroke='#AAAAAA' stroke-opacity='1' stroke-width='0.11' stroke-linejoin='round' stroke-linecap='butt'/>\n   <polyline points='48.41,45.22 389.25,45.22' fill='none' stroke='#AAAAAA' stroke-opacity='1' stroke-width='0.11' stroke-linejoin='round' stroke-linecap='butt'/>\n   <polyline points='88.96,320.86 88.96,6.97' fill='none' stroke='#AAAAAA' stroke-opacity='1' stroke-width='0.11' stroke-linejoin='round' stroke-linecap='butt'/>\n   <polyline points='202.88,320.86 202.88,6.97' fill='none' stroke='#AAAAAA' stroke-opacity='1' stroke-width='0.11' stroke-linejoin='round' stroke-linecap='butt'/>\n   <polyline points='316.80,320.86 316.80,6.97' fill='none' stroke='#AAAAAA' stroke-opacity='1' stroke-width='0.11' stroke-linejoin='round' stroke-linecap='butt'/>\n   <polyline points='48.41,225.07 389.25,225.07' fill='none' stroke='#888888' stroke-opacity='1' stroke-width='0.21' stroke-linejoin='round' stroke-linecap='butt'/>\n   <polyline points='48.41,105.17 389.25,105.17' fill='none' stroke='#888888' stroke-opacity='1' stroke-width='0.21' stroke-linejoin='round' stroke-linecap='butt'/>\n   <polyline points='145.92,320.86 145.92,6.97' fill='none' stroke='#888888' stroke-opacity='1' stroke-width='0.21' stroke-linejoin='round' stroke-linecap='butt'/>\n   <polyline points='259.84,320.86 259.84,6.97' fill='none' stroke='#888888' stroke-opacity='1' stroke-width='0.21' stroke-linejoin='round' stroke-linecap='butt'/>\n   <polyline points='373.76,320.86 373.76,6.97' fill='none' stroke='#888888' stroke-opacity='1' stroke-width='0.21' stroke-linejoin='round' stroke-linecap='butt'/>\n   <circle id='svg_02c026c57f8f519b_e1' cx='214.27' cy='57.21' r='1.54pt' fill='#1B9E77' fill-opacity='1' stroke='#1B9E77' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Aptenodytes forsteri' data-id='Aptenodytes forsteri'/>\n   <circle id='svg_02c026c57f8f519b_e2' cx='241.61' cy='21.24' r='1.54pt' fill='#1B9E77' fill-opacity='1' stroke='#1B9E77' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Aptenodytes forsteri' data-id='Aptenodytes forsteri'/>\n   <circle id='svg_02c026c57f8f519b_e3' cx='230.22' cy='33.23' r='1.54pt' fill='#1B9E77' fill-opacity='1' stroke='#1B9E77' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Aptenodytes forsteri' data-id='Aptenodytes forsteri'/>\n   <circle id='svg_02c026c57f8f519b_e4' cx='250.73' cy='28.44' r='1.54pt' fill='#1B9E77' fill-opacity='1' stroke='#1B9E77' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Aptenodytes forsteri' data-id='Aptenodytes forsteri'/>\n   <circle id='svg_02c026c57f8f519b_e5' cx='232.5' cy='33.23' r='1.54pt' fill='#1B9E77' fill-opacity='1' stroke='#1B9E77' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Aptenodytes forsteri' data-id='Aptenodytes forsteri'/>\n   <circle id='svg_02c026c57f8f519b_e6' cx='196.05' cy='66.8' r='1.54pt' fill='#1B9E77' fill-opacity='1' stroke='#1B9E77' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Aptenodytes patagonicus' data-id='Aptenodytes patagonicus'/>\n   <circle id='svg_02c026c57f8f519b_e7' cx='161.87' cy='71.6' r='1.54pt' fill='#1B9E77' fill-opacity='1' stroke='#1B9E77' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Aptenodytes patagonicus' data-id='Aptenodytes patagonicus'/>\n   <circle id='svg_02c026c57f8f519b_e8' cx='202.88' cy='69.2' r='1.54pt' fill='#1B9E77' fill-opacity='1' stroke='#1B9E77' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Aptenodytes patagonicus' data-id='Aptenodytes patagonicus'/>\n   <circle id='svg_02c026c57f8f519b_e9' cx='148.2' cy='47.62' r='1.54pt' fill='#1B9E77' fill-opacity='1' stroke='#1B9E77' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Aptenodytes patagonicus' data-id='Aptenodytes patagonicus'/>\n   <circle id='svg_02c026c57f8f519b_e10' cx='207.44' cy='229.86' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes chrysocome' data-id='Eudyptes chrysocome'/>\n   <circle id='svg_02c026c57f8f519b_e11' cx='259.84' cy='222.67' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes chrysocome' data-id='Eudyptes chrysocome'/>\n   <circle id='svg_02c026c57f8f519b_e12' cx='182.38' cy='205.88' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes chrysocome' data-id='Eudyptes chrysocome'/>\n   <circle id='svg_02c026c57f8f519b_e13' cx='232.5' cy='251.44' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes chrysocome' data-id='Eudyptes chrysocome'/>\n   <circle id='svg_02c026c57f8f519b_e14' cx='191.49' cy='232.26' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes chrysocome' data-id='Eudyptes chrysocome'/>\n   <circle id='svg_02c026c57f8f519b_e15' cx='239.33' cy='225.07' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes chrysocome' data-id='Eudyptes chrysocome'/>\n   <circle id='svg_02c026c57f8f519b_e16' cx='239.33' cy='234.66' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes chrysocome' data-id='Eudyptes chrysocome'/>\n   <circle id='svg_02c026c57f8f519b_e17' cx='248.45' cy='239.45' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes chrysocome' data-id='Eudyptes chrysocome'/>\n   <circle id='svg_02c026c57f8f519b_e18' cx='360.09' cy='172.31' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes chrysolophus' data-id='Eudyptes chrysolophus'/>\n   <circle id='svg_02c026c57f8f519b_e19' cx='259.84' cy='186.7' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes chrysolophus' data-id='Eudyptes chrysolophus'/>\n   <circle id='svg_02c026c57f8f519b_e20' cx='218.83' cy='191.49' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes chrysolophus' data-id='Eudyptes chrysolophus'/>\n   <circle id='svg_02c026c57f8f519b_e21' cx='227.94' cy='191.49' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes chrysolophus' data-id='Eudyptes chrysolophus'/>\n   <circle id='svg_02c026c57f8f519b_e22' cx='266.68' cy='225.07' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes moseleyi' data-id='Eudyptes moseleyi'/>\n   <circle id='svg_02c026c57f8f519b_e23' cx='257.56' cy='213.08' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes moseleyi' data-id='Eudyptes moseleyi'/>\n   <circle id='svg_02c026c57f8f519b_e24' cx='241.61' cy='215.47' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes moseleyi' data-id='Eudyptes moseleyi'/>\n   <circle id='svg_02c026c57f8f519b_e25' cx='214.27' cy='225.07' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes moseleyi' data-id='Eudyptes moseleyi'/>\n   <circle id='svg_02c026c57f8f519b_e26' cx='296.29' cy='245.93' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes pachyrhynchus' data-id='Eudyptes pachyrhynchus'/>\n   <circle id='svg_02c026c57f8f519b_e27' cx='325.91' cy='33.23' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes pachyrhynchus' data-id='Eudyptes pachyrhynchus'/>\n   <circle id='svg_02c026c57f8f519b_e28' cx='209.72' cy='57.21' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes pachyrhynchus' data-id='Eudyptes pachyrhynchus'/>\n   <circle id='svg_02c026c57f8f519b_e29' cx='330.47' cy='33.23' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes pachyrhynchus' data-id='Eudyptes pachyrhynchus'/>\n   <circle id='svg_02c026c57f8f519b_e30' cx='307.69' cy='239.45' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes robustus' data-id='Eudyptes robustus'/>\n   <circle id='svg_02c026c57f8f519b_e31' cx='337.3' cy='225.07' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes robustus' data-id='Eudyptes robustus'/>\n   <circle id='svg_02c026c57f8f519b_e32' cx='259.84' cy='213.08' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes robustus' data-id='Eudyptes robustus'/>\n   <circle id='svg_02c026c57f8f519b_e33' cx='223.39' cy='244.25' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes robustus' data-id='Eudyptes robustus'/>\n   <circle id='svg_02c026c57f8f519b_e34' cx='321.36' cy='172.31' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes schlegeli' data-id='Eudyptes schlegeli'/>\n   <circle id='svg_02c026c57f8f519b_e35' cx='305.41' cy='165.12' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes schlegeli' data-id='Eudyptes schlegeli'/>\n   <circle id='svg_02c026c57f8f519b_e36' cx='253' cy='184.3' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes schlegeli' data-id='Eudyptes schlegeli'/>\n   <circle id='svg_02c026c57f8f519b_e37' cx='284.9' cy='177.11' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes schlegeli' data-id='Eudyptes schlegeli'/>\n   <circle id='svg_02c026c57f8f519b_e38' cx='289.46' cy='207.8' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes sclateri' data-id='Eudyptes sclateri'/>\n   <circle id='svg_02c026c57f8f519b_e39' cx='350.97' cy='192.21' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes sclateri' data-id='Eudyptes sclateri'/>\n   <circle id='svg_02c026c57f8f519b_e40' cx='266.68' cy='192.93' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes sclateri' data-id='Eudyptes sclateri'/>\n   <circle id='svg_02c026c57f8f519b_e41' cx='373.76' cy='198.69' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptes sclateri' data-id='Eudyptes sclateri'/>\n   <circle id='svg_02c026c57f8f519b_e42' cx='68.46' cy='301.8' r='1.54pt' fill='#7570B3' fill-opacity='1' stroke='#7570B3' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptula minor' data-id='Eudyptula minor'/>\n   <circle id='svg_02c026c57f8f519b_e43' cx='68.46' cy='301.8' r='1.54pt' fill='#7570B3' fill-opacity='1' stroke='#7570B3' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptula minor' data-id='Eudyptula minor'/>\n   <circle id='svg_02c026c57f8f519b_e44' cx='107.19' cy='306.6' r='1.54pt' fill='#7570B3' fill-opacity='1' stroke='#7570B3' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptula minor' data-id='Eudyptula minor'/>\n   <circle id='svg_02c026c57f8f519b_e45' cx='63.9' cy='304.2' r='1.54pt' fill='#7570B3' fill-opacity='1' stroke='#7570B3' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Eudyptula minor' data-id='Eudyptula minor'/>\n   <circle id='svg_02c026c57f8f519b_e46' cx='145.92' cy='167.52' r='1.54pt' fill='#E7298A' fill-opacity='1' stroke='#E7298A' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Megadyptes antipodes' data-id='Megadyptes antipodes'/>\n   <circle id='svg_02c026c57f8f519b_e47' cx='209.72' cy='105.17' r='1.54pt' fill='#E7298A' fill-opacity='1' stroke='#E7298A' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Megadyptes antipodes' data-id='Megadyptes antipodes'/>\n   <circle id='svg_02c026c57f8f519b_e48' cx='157.31' cy='177.11' r='1.54pt' fill='#E7298A' fill-opacity='1' stroke='#E7298A' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Megadyptes antipodes' data-id='Megadyptes antipodes'/>\n   <circle id='svg_02c026c57f8f519b_e49' cx='143.64' cy='179.51' r='1.54pt' fill='#E7298A' fill-opacity='1' stroke='#E7298A' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Megadyptes antipodes' data-id='Megadyptes antipodes'/>\n   <circle id='svg_02c026c57f8f519b_e50' cx='145.92' cy='258.64' r='1.54pt' fill='#66A61E' fill-opacity='1' stroke='#66A61E' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Pygoscelis adeliae' data-id='Pygoscelis adeliae'/>\n   <circle id='svg_02c026c57f8f519b_e51' cx='241.61' cy='233.94' r='1.54pt' fill='#66A61E' fill-opacity='1' stroke='#66A61E' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Pygoscelis adeliae' data-id='Pygoscelis adeliae'/>\n   <circle id='svg_02c026c57f8f519b_e52' cx='223.39' cy='234.66' r='1.54pt' fill='#66A61E' fill-opacity='1' stroke='#66A61E' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Pygoscelis adeliae' data-id='Pygoscelis adeliae'/>\n   <circle id='svg_02c026c57f8f519b_e53' cx='205.16' cy='229.86' r='1.54pt' fill='#66A61E' fill-opacity='1' stroke='#66A61E' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Pygoscelis adeliae' data-id='Pygoscelis adeliae'/>\n   <circle id='svg_02c026c57f8f519b_e54' cx='248.45' cy='233.7' r='1.54pt' fill='#66A61E' fill-opacity='1' stroke='#66A61E' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Pygoscelis adeliae' data-id='Pygoscelis adeliae'/>\n   <circle id='svg_02c026c57f8f519b_e55' cx='259.84' cy='213.56' r='1.54pt' fill='#66A61E' fill-opacity='1' stroke='#66A61E' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Pygoscelis antarcticus' data-id='Pygoscelis antarcticus'/>\n   <circle id='svg_02c026c57f8f519b_e56' cx='257.56' cy='221.47' r='1.54pt' fill='#66A61E' fill-opacity='1' stroke='#66A61E' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Pygoscelis antarcticus' data-id='Pygoscelis antarcticus'/>\n   <circle id='svg_02c026c57f8f519b_e57' cx='216.55' cy='230.58' r='1.54pt' fill='#66A61E' fill-opacity='1' stroke='#66A61E' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Pygoscelis antarcticus' data-id='Pygoscelis antarcticus'/>\n   <circle id='svg_02c026c57f8f519b_e58' cx='305.41' cy='229.86' r='1.54pt' fill='#66A61E' fill-opacity='1' stroke='#66A61E' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Pygoscelis antarcticus' data-id='Pygoscelis antarcticus'/>\n   <circle id='svg_02c026c57f8f519b_e59' cx='196.05' cy='155.53' r='1.54pt' fill='#66A61E' fill-opacity='1' stroke='#66A61E' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Pygoscelis papua' data-id='Pygoscelis papua'/>\n   <circle id='svg_02c026c57f8f519b_e60' cx='211.99' cy='156.72' r='1.54pt' fill='#66A61E' fill-opacity='1' stroke='#66A61E' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Pygoscelis papua' data-id='Pygoscelis papua'/>\n   <circle id='svg_02c026c57f8f519b_e61' cx='211.99' cy='163.92' r='1.54pt' fill='#66A61E' fill-opacity='1' stroke='#66A61E' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Pygoscelis papua' data-id='Pygoscelis papua'/>\n   <circle id='svg_02c026c57f8f519b_e62' cx='184.65' cy='173.99' r='1.54pt' fill='#66A61E' fill-opacity='1' stroke='#66A61E' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Pygoscelis papua' data-id='Pygoscelis papua'/>\n   <circle id='svg_02c026c57f8f519b_e63' cx='173.26' cy='229.86' r='1.54pt' fill='#E6AB02' fill-opacity='1' stroke='#E6AB02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Spheniscus demersus' data-id='Spheniscus demersus'/>\n   <circle id='svg_02c026c57f8f519b_e64' cx='143.64' cy='237.06' r='1.54pt' fill='#E6AB02' fill-opacity='1' stroke='#E6AB02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Spheniscus demersus' data-id='Spheniscus demersus'/>\n   <circle id='svg_02c026c57f8f519b_e65' cx='139.09' cy='222.67' r='1.54pt' fill='#E6AB02' fill-opacity='1' stroke='#E6AB02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Spheniscus demersus' data-id='Spheniscus demersus'/>\n   <circle id='svg_02c026c57f8f519b_e66' cx='125.42' cy='225.07' r='1.54pt' fill='#E6AB02' fill-opacity='1' stroke='#E6AB02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Spheniscus demersus' data-id='Spheniscus demersus'/>\n   <circle id='svg_02c026c57f8f519b_e67' cx='170.98' cy='193.89' r='1.54pt' fill='#E6AB02' fill-opacity='1' stroke='#E6AB02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Spheniscus humboldti' data-id='Spheniscus humboldti'/>\n   <circle id='svg_02c026c57f8f519b_e68' cx='111.75' cy='229.86' r='1.54pt' fill='#E6AB02' fill-opacity='1' stroke='#E6AB02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Spheniscus humboldti' data-id='Spheniscus humboldti'/>\n   <circle id='svg_02c026c57f8f519b_e69' cx='95.8' cy='213.08' r='1.54pt' fill='#E6AB02' fill-opacity='1' stroke='#E6AB02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Spheniscus humboldti' data-id='Spheniscus humboldti'/>\n   <circle id='svg_02c026c57f8f519b_e70' cx='70.74' cy='213.08' r='1.54pt' fill='#E6AB02' fill-opacity='1' stroke='#E6AB02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Spheniscus humboldti' data-id='Spheniscus humboldti'/>\n   <circle id='svg_02c026c57f8f519b_e71' cx='150.48' cy='205.88' r='1.54pt' fill='#E6AB02' fill-opacity='1' stroke='#E6AB02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Spheniscus magellanicus' data-id='Spheniscus magellanicus'/>\n   <circle id='svg_02c026c57f8f519b_e72' cx='170.98' cy='213.08' r='1.54pt' fill='#E6AB02' fill-opacity='1' stroke='#E6AB02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Spheniscus magellanicus' data-id='Spheniscus magellanicus'/>\n   <circle id='svg_02c026c57f8f519b_e73' cx='148.2' cy='203.48' r='1.54pt' fill='#E6AB02' fill-opacity='1' stroke='#E6AB02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Spheniscus magellanicus' data-id='Spheniscus magellanicus'/>\n   <circle id='svg_02c026c57f8f519b_e74' cx='120.86' cy='198.69' r='1.54pt' fill='#E6AB02' fill-opacity='1' stroke='#E6AB02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Spheniscus magellanicus' data-id='Spheniscus magellanicus'/>\n   <circle id='svg_02c026c57f8f519b_e75' cx='159.59' cy='189.1' r='1.54pt' fill='#E6AB02' fill-opacity='1' stroke='#E6AB02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Spheniscus magellanicus' data-id='Spheniscus magellanicus'/>\n   <circle id='svg_02c026c57f8f519b_e76' cx='98.08' cy='273.02' r='1.54pt' fill='#E6AB02' fill-opacity='1' stroke='#E6AB02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Spheniscus mendiculus' data-id='Spheniscus mendiculus'/>\n   <circle id='svg_02c026c57f8f519b_e77' cx='84.41' cy='273.02' r='1.54pt' fill='#E6AB02' fill-opacity='1' stroke='#E6AB02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Spheniscus mendiculus' data-id='Spheniscus mendiculus'/>\n   <circle id='svg_02c026c57f8f519b_e78' cx='125.42' cy='265.83' r='1.54pt' fill='#E6AB02' fill-opacity='1' stroke='#E6AB02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Spheniscus mendiculus' data-id='Spheniscus mendiculus'/>\n   <circle id='svg_02c026c57f8f519b_e79' cx='88.96' cy='253.84' r='1.54pt' fill='#E6AB02' fill-opacity='1' stroke='#E6AB02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round' title='Spheniscus mendiculus' data-id='Spheniscus mendiculus'/>\n  <\/g>\n  <g clip-path='url(#svg_02c026c57f8f519b_c1)'>\n   <polyline points='48.41,320.86 48.41,6.97' fill='none' stroke='#000000' stroke-opacity='1' stroke-width='1.36' stroke-linejoin='round' stroke-linecap='square'/>\n   <text x='22.11' y='229.2' font-size='9pt' font-family='Liberation Sans' fill='#0A3D62' fill-opacity='1'>100<\/text>\n   <text x='22.11' y='109.3' font-size='9pt' font-family='Liberation Sans' fill='#0A3D62' fill-opacity='1'>150<\/text>\n   <polyline points='44.92,225.07 48.41,225.07' fill='none' stroke='#000000' stroke-opacity='1' stroke-width='1.36' stroke-linejoin='round' stroke-linecap='butt'/>\n   <polyline points='44.92,105.17 48.41,105.17' fill='none' stroke='#000000' stroke-opacity='1' stroke-width='1.36' stroke-linejoin='round' stroke-linecap='butt'/>\n   <polyline points='48.41,320.86 389.25,320.86' fill='none' stroke='#000000' stroke-opacity='1' stroke-width='1.36' stroke-linejoin='round' stroke-linecap='square'/>\n   <polyline points='145.92,324.35 145.92,320.86' fill='none' stroke='#000000' stroke-opacity='1' stroke-width='1.36' stroke-linejoin='round' stroke-linecap='butt'/>\n   <polyline points='259.84,324.35 259.84,320.86' fill='none' stroke='#000000' stroke-opacity='1' stroke-width='1.36' stroke-linejoin='round' stroke-linecap='butt'/>\n   <polyline points='373.76,324.35 373.76,320.86' fill='none' stroke='#000000' stroke-opacity='1' stroke-width='1.36' stroke-linejoin='round' stroke-linecap='butt'/>\n   <text x='139.25' y='335.4' font-size='9pt' font-family='Liberation Sans' fill='#0A3D62' fill-opacity='1'>10<\/text>\n   <text x='253.17' y='335.4' font-size='9pt' font-family='Liberation Sans' fill='#0A3D62' fill-opacity='1'>15<\/text>\n   <text x='367.08' y='335.4' font-size='9pt' font-family='Liberation Sans' fill='#0A3D62' fill-opacity='1'>20<\/text>\n   <text x='158.14' y='350.33' font-size='9.75pt' font-family='Liberation Sans' fill='#0A3D62' fill-opacity='1'>Largeur du bec (mm)<\/text>\n   <text transform='translate(15.92,235.81) rotate(-90.00)' font-size='9.75pt' font-family='Liberation Sans' fill='#0A3D62' fill-opacity='1'>Longueur des ailes (mm)<\/text>\n   <rect x='403.2' y='95.79' width='93.83' height='136.25' fill='#FFFFFF' fill-opacity='1' stroke='none'/>\n   <rect x='403.2' y='95.79' width='93.83' height='136.25' fill='#FFFFFF' fill-opacity='1' stroke='none'/>\n   <text x='410.17' y='113.07' font-size='9.75pt' font-weight='bold' font-family='Liberation Sans' fill='#0A3D62' fill-opacity='1'>Genre<\/text>\n   <rect x='410.17' y='121.39' width='17.28' height='17.28' fill='#FFFFFF' fill-opacity='1' stroke='none'/>\n   <circle cx='418.81' cy='130.03' r='1.54pt' fill='#1B9E77' fill-opacity='1' stroke='#1B9E77' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round'/>\n   <rect x='410.17' y='138.67' width='17.28' height='17.28' fill='#FFFFFF' fill-opacity='1' stroke='none'/>\n   <circle cx='418.81' cy='147.31' r='1.54pt' fill='#D95F02' fill-opacity='1' stroke='#D95F02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round'/>\n   <rect x='410.17' y='155.95' width='17.28' height='17.28' fill='#FFFFFF' fill-opacity='1' stroke='none'/>\n   <circle cx='418.81' cy='164.59' r='1.54pt' fill='#7570B3' fill-opacity='1' stroke='#7570B3' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round'/>\n   <rect x='410.17' y='173.23' width='17.28' height='17.28' fill='#FFFFFF' fill-opacity='1' stroke='none'/>\n   <circle cx='418.81' cy='181.87' r='1.54pt' fill='#E7298A' fill-opacity='1' stroke='#E7298A' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round'/>\n   <rect x='410.17' y='190.51' width='17.28' height='17.28' fill='#FFFFFF' fill-opacity='1' stroke='none'/>\n   <circle cx='418.81' cy='199.15' r='1.54pt' fill='#66A61E' fill-opacity='1' stroke='#66A61E' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round'/>\n   <rect x='410.17' y='207.79' width='17.28' height='17.28' fill='#FFFFFF' fill-opacity='1' stroke='none'/>\n   <circle cx='418.81' cy='216.43' r='1.54pt' fill='#E6AB02' fill-opacity='1' stroke='#E6AB02' stroke-opacity='1' stroke-width='0.9' stroke-linejoin='round' stroke-linecap='round'/>\n   <text x='434.43' y='133.47' font-size='7.5pt' font-family='Liberation Sans' fill='#0A3D62' fill-opacity='1'>Aptenodytes<\/text>\n   <text x='434.43' y='150.75' font-size='7.5pt' font-family='Liberation Sans' fill='#0A3D62' fill-opacity='1'>Eudyptes<\/text>\n   <text x='434.43' y='168.03' font-size='7.5pt' font-family='Liberation Sans' fill='#0A3D62' fill-opacity='1'>Eudyptula<\/text>\n   <text x='434.43' y='185.31' font-size='7.5pt' font-family='Liberation Sans' fill='#0A3D62' fill-opacity='1'>Megadyptes<\/text>\n   <text x='434.43' y='202.59' font-size='7.5pt' font-family='Liberation Sans' fill='#0A3D62' fill-opacity='1'>Pygoscelis<\/text>\n   <text x='434.43' y='219.87' font-size='7.5pt' font-family='Liberation Sans' fill='#0A3D62' fill-opacity='1'>Spheniscus<\/text>\n  <\/g>\n <\/g>\n<\/svg>","js":null,"uid":"svg_02c026c57f8f519b","ratio":1.4,"settings":{"tooltip":{"css":".tooltip_SVGID_ { padding:5px;background:black;color:white;border-radius:2px;text-align:left; ; position:absolute;pointer-events:none;z-index:9999;}","placement":"doc","opacity":0.9,"offx":10,"offy":10,"use_cursor_pos":true,"use_fill":false,"use_stroke":false,"delay_over":200,"delay_out":500},"hover":{"css":".hover_data_SVGID_ { fill:orange;stroke:black;cursor:pointer; }\ntext.hover_data_SVGID_ { stroke:none;fill:orange; }\ncircle.hover_data_SVGID_ { fill:orange;stroke:black; }\nline.hover_data_SVGID_, polyline.hover_data_SVGID_ { fill:none;stroke:orange; }\nrect.hover_data_SVGID_, polygon.hover_data_SVGID_, path.hover_data_SVGID_ { fill:orange;stroke:none; }\nimage.hover_data_SVGID_ { stroke:orange; }","reactive":true,"nearest_distance":null,"linked":false},"hover_inv":{"css":""},"hover_key":{"css":".hover_key_SVGID_ { fill:orange;stroke:black;cursor:pointer; }\ntext.hover_key_SVGID_ { stroke:none;fill:orange; }\ncircle.hover_key_SVGID_ { fill:orange;stroke:black; }\nline.hover_key_SVGID_, polyline.hover_key_SVGID_ { fill:none;stroke:orange; }\nrect.hover_key_SVGID_, polygon.hover_key_SVGID_, path.hover_key_SVGID_ { fill:orange;stroke:none; }\nimage.hover_key_SVGID_ { stroke:orange; }","reactive":true},"hover_theme":{"css":".hover_theme_SVGID_ { fill:orange;stroke:black;cursor:pointer; }\ntext.hover_theme_SVGID_ { stroke:none;fill:orange; }\ncircle.hover_theme_SVGID_ { fill:orange;stroke:black; }\nline.hover_theme_SVGID_, polyline.hover_theme_SVGID_ { fill:none;stroke:orange; }\nrect.hover_theme_SVGID_, polygon.hover_theme_SVGID_, path.hover_theme_SVGID_ { fill:orange;stroke:none; }\nimage.hover_theme_SVGID_ { stroke:orange; }","reactive":true},"select":{"css":".select_data_SVGID_ { fill:red;stroke:black;cursor:pointer; }\ntext.select_data_SVGID_ { stroke:none;fill:red; }\ncircle.select_data_SVGID_ { fill:red;stroke:black; }\nline.select_data_SVGID_, polyline.select_data_SVGID_ { fill:none;stroke:red; }\nrect.select_data_SVGID_, polygon.select_data_SVGID_, path.select_data_SVGID_ { fill:red;stroke:none; }\nimage.select_data_SVGID_ { stroke:red; }","type":"multiple","only_shiny":true,"selected":[],"linked":false},"select_inv":{"css":""},"select_key":{"css":".select_key_SVGID_ { fill:red;stroke:black;cursor:pointer; }\ntext.select_key_SVGID_ { stroke:none;fill:red; }\ncircle.select_key_SVGID_ { fill:red;stroke:black; }\nline.select_key_SVGID_, polyline.select_key_SVGID_ { fill:none;stroke:red; }\nrect.select_key_SVGID_, polygon.select_key_SVGID_, path.select_key_SVGID_ { fill:red;stroke:none; }\nimage.select_key_SVGID_ { stroke:red; }","type":"single","only_shiny":true,"selected":[]},"select_theme":{"css":".select_theme_SVGID_ { fill:red;stroke:black;cursor:pointer; }\ntext.select_theme_SVGID_ { stroke:none;fill:red; }\ncircle.select_theme_SVGID_ { fill:red;stroke:black; }\nline.select_theme_SVGID_, polyline.select_theme_SVGID_ { fill:none;stroke:red; }\nrect.select_theme_SVGID_, polygon.select_theme_SVGID_, path.select_theme_SVGID_ { fill:red;stroke:none; }\nimage.select_theme_SVGID_ { stroke:red; }","type":"single","only_shiny":true,"selected":[]},"zoom":{"min":1,"max":1,"duration":300,"default_on":false},"toolbar":{"position":"topright","pngname":"diagram","tooltips":null,"fixed":false,"hidden":[],"delay_over":200,"delay_out":500},"sizing":{"rescale":true,"width":1}}},"evals":[],"jsHooks":[]}</script>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-interactive-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;6: Le même graphique, rendu interactif. Survolez un point pour voir l’espèce correspondante.
</figcaption>
</figure>
</div>
</div>
<p>L’identité de chaque point reste disponible, à la demande plutôt qu’affichée en permanence, sans couleur supplémentaire. Le compromis à assumer : ça ne fonctionne qu’à l’écran, avec JavaScript actif. Imprimé ou lu hors ligne, l’interactivité disparaît.</p>
<p>Pour une solution qui reste utilisable même à l’impression, il faut sortir de la couleur autrement. On pourrait être tenté d’ajouter encore un canal visuel : une forme différente par espèce en plus de la couleur, une légende propre à chaque sous-graphique… Chaque tentative de ce genre revient buter sur le même mur qu’on a déjà rencontré. La couleur plafonne quelque part entre 8 et 12, <code>scale_shape()</code> plafonne à 6. Peu importe le canal choisi, tant qu’on essaie d’y faire tenir 18 identités distinctes, on retombe sur une limite. La vraie sortie ne consiste pas à trouver un meilleur canal, mais à faire porter l’identité par la <strong>position</strong> plutôt que par une esthétique de couleur ou de forme, qui elle n’a pas ce genre de plafond.</p>
</section>
<section id="facetter-directement-par-espèce" class="level3">
<h3 data-anchor-id="facetter-directement-par-espèce">Facetter directement par espèce</h3>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb8-1">many_penguins <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb8-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">drop_na</span>(beak.width, wing.length) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb8-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">species =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fct_reorder</span>(species, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.numeric</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">factor</span>(genus)))) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb8-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> beak.width, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> wing.length, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> genus)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_point</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_colour_brewer</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">palette =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Dark2"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-7">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_x_continuous</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">limits =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">21</span>)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-8">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_y_continuous</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">limits =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">190</span>)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-9">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">facet_wrap</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>species, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ncol =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-10">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Largeur du bec (mm)"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Longueur des ailes (mm)"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Genre"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-11">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span>(</span>
<span id="cb8-12">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">strip.text =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">element_text</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">face =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"italic"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>), </span>
<span id="cb8-13">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">legend.text =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">element_text</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>)</span>
<span id="cb8-14">  )</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-species-facets" class="quarto-float quarto-figure quarto-figure-center" alt="Grille de dix-huit petits graphiques, un par espèce de manchot, partageant les mêmes axes de largeur du bec et longueur des ailes. Les points sont colorés par genre pour regrouper visuellement les espèces apparentées." data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-species-facets-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202607-dataviz-2/dataviz-2_files/figure-html/fig-species-facets-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Grille de dix-huit petits graphiques, un par espèce de manchot, partageant les mêmes axes de largeur du bec et longueur des ailes. Les points sont colorés par genre pour regrouper visuellement les espèces apparentées." width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-species-facets-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;7: Un panneau par espèce (18 au total), couleur par genre pour le regroupement visuel, axes partagés.
</figcaption>
</figure>
</div>
</div>
</div>
<p>La couleur est toujours là, mais elle ne porte plus l’identité individuelle : elle confirme seulement que les espèces d’un même genre sont regroupées. C’est la position du panneau, pas la couleur du point, qui distingue les 18 espèces.</p>
</section>
<section id="changer-complètement-de-type-de-graphique" class="level3">
<h3 data-anchor-id="changer-complètement-de-type-de-graphique">Changer complètement de type de graphique</h3>
<p>Une dernière option, plus radicale : abandonner le nuage de points. Avec les espèces en position sur l’axe plutôt qu’en couleur, la question du nombre de couleurs disparaît complètement. Un boxplot, par exemple, se lit très bien avec 18 catégories en position, tant qu’on garde une seule couleur par genre pour le regroupement visuel.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb9-1">many_penguins <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb9-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fct_rev</span>(species), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">hand-wing.index</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> genus)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb9-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_boxplot</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">outlier.alpha =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">box.linewidth =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">alpha =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb9-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_fill_brewer</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">palette =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Dark2"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb9-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">coord_flip</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb9-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(</span>
<span id="cb9-7">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>,</span>
<span id="cb9-8">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Indice de forme des ailes (Hand-Wing Index)"</span>,</span>
<span id="cb9-9">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Genre"</span></span>
<span id="cb9-10">  ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb9-11">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">axis.text.y =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">element_text</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">legend.text =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">element_text</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>))</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-boxplot" class="quarto-float quarto-figure quarto-figure-center" alt="Diagramme en boîtes horizontal de l'indice de forme des ailes pour dix-huit espèces de manchots, colorées par genre. Les espèces du genre Aptenodytes affichent les indices les plus élevés, signe d'ailes plus effilées." data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-boxplot-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202607-dataviz-2/dataviz-2_files/figure-html/fig-boxplot-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Diagramme en boîtes horizontal de l'indice de forme des ailes pour dix-huit espèces de manchots, colorées par genre. Les espèces du genre Aptenodytes affichent les indices les plus élevés, signe d'ailes plus effilées." width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-boxplot-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;8: Indice de forme des ailes par espèce, position plutôt que couleur. Le remplissage par genre (6 couleurs) aide à repérer les regroupements sans surcharger le graphique.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Le <em>Hand-Wing Index</em> mesure la forme de l’aile : plus il est élevé, plus l’aile est effilée, adaptée au vol sur de longues distances. Ici encore, la couleur ne porte plus l’information principale, elle regroupe seulement. On retrouve la hiérarchie des encodages vue dans le <a href="posts/202605-dataviz-1/dataviz-1.html">premier article de cette série</a> : la position sur un axe reste l’encodage le plus précis, sans les limites perceptuelles ou techniques de la couleur.</p>
<div class="note-box">
<p><strong>Pour aller plus loin</strong></p>
<p>Pour explorer d’autres palettes que celles de ColorBrewer, deux ressources utiles : le <a href="https://github.com/EmilHvitfeldt/r-color-palettes">repo de palettes de couleurs pour R</a> d’Emil Hvitfeldt, qui recense des dizaines de collections, et son package <a href="https://github.com/EmilHvitfeldt/paletteer">paletteer</a>, qui centralise plus de 2500 palettes directement utilisables dans R. Pour tester visuellement une palette avant de l’adopter, <a href="https://r-graph-gallery.com/color-palette-finder.html">R Graph Gallery</a> propose un outil interactif de recherche par couleur.</p>
</div>
<p>Choisir une palette qualitative, c’est se heurter tôt ou tard à deux murs différents : celui, net et technique, du nombre de couleurs qu’une palette peut proposer avant de laisser des catégories sans couleur ; et celui, plus flou mais tout aussi réel, du nombre de couleurs qu’un œil humain peut suivre sans effort, même quand la palette en a suffisamment.</p>
<p>Dans le prochain article de la série, on s’intéresse à l’accessibilité : et si votre graphique était illisible pour 8 % de vos lecteurs ?</p>
<div class="center">
<p><strong>Envie de faire de la dataviz propre et efficace avec R ?</strong></p>
</div>
<div class="cta-group center">
<p><a href="../../formations.html" class="cta-button-outline">Découvrir les formations</a></p>
<p><a href="mailto:contact@pyranhia.eu" class="cta-button">Me contacter</a></p>
</div>
</section>
<section id="références" class="level3">
<h3 data-anchor-id="références">Références</h3>
<div id="refs" class="references csl-bib-body hanging-indent">
<div id="ref-tobias2022AVONET" class="csl-entry">
Tobias, Joseph A., Catherine Sheard, Alex L. Pigot, et al. 2022. <span>«&nbsp;<span>AVONET</span>: Morphological, Ecological and Geographical Data for All Birds&nbsp;»</span>. <em>Ecology Letters</em> 25 (3): 581‑97. <a href="https://doi.org/10.1111/ele.13898">https://doi.org/10.1111/ele.13898</a>.
</div>
</div>
</section>
</section>



 ]]></description>
  <category>Data viz</category>
  <category>Tutoriels R</category>
  <guid>https://www.pyranhia.eu/posts/202607-dataviz-2/dataviz-2.html</guid>
  <pubDate>Sun, 12 Jul 2026 22:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202607-dataviz-2/rainbow.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Air : un peu d’air frais dans votre code R</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202607-air/air_formatter.html</link>
  <description><![CDATA[ 





<section id="air-un-peu-dair-frais-dans-votre-code-r" class="level1 section-full section-padded">
<h1>Air : un peu d’air frais dans votre code R</h1>
<p><span class="section-subtitle">Fini les débats sur l’indentation et les espaces, laissez la machine s’en charger.</span></p>
<p>Vous avez déjà perdu du temps à débattre d’indentation ou d’espaces autour d’un opérateur en relisant du code écrit par quelqu’un d’autre ? Je vous présente Air, un formatteur pour R développé par l’équipe Posit.</p>
</section>
<section id="un-formatteur-à-quoi-ça-sert" class="level2 section-1">
<h2 data-anchor-id="un-formatteur-à-quoi-ça-sert">Un formatteur, à quoi ça sert ?</h2>
<p>Un formatteur, c’est un outil qui réécrit automatiquement votre code pour qu’il respecte des règles de mise en forme cohérentes : indentation, espaces autour des opérateurs, longueur des lignes, retours à la ligne… Le tout sans jamais toucher à ce que le code fait.</p>
<p>À titre personnel, je suis un peu maniaque de la typographie (espaces insécables, guillemets français, ce genre de détails), et quand je code c’est un peu pareil : j’aime que les choses soient bien faites et agréables à lire. Ceci dit, je ne suis pas infaillible. Là où ça se complique sérieusement, c’est lors de la collaboration sur des projets : je m’arrache les cheveux dès que le code n’est pas mis en forme correctement. C’est dommage, au lieu de se concentrer sur le fond, les discussions ne portent que sur la forme.</p>
<p>Voici un exemple concret : le code ci-dessous est parfaitement valide en R.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1">x<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)</span></code></pre></div></div>
</div>
<p>Mais pour moi, c’est illisible sans clarifier mentalement la précédence des opérateurs. Avec Air :</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1">x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)</span></code></pre></div></div>
</div>
<p>Le résultat est exactement le même, mais on comprend tout de suite l’ordre des opérations effectuées. C’est tout l’intérêt d’un formatteur : <strong>on peut se permettre d’écrire en se concentrant sur de fond, sans se soucier de la forme, l’outil s’en charge après coup.</strong></p>
</section>
<section id="air-en-action" class="level2 section-2">
<h2 data-anchor-id="air-en-action">Air en action</h2>
<p><a href="https://posit-dev.github.io/air/">Air</a> est un formatteur développé par l’équipe Posit. Codé en Rust, il est extrêmement rapide et capable de formater un projet entier en quelques millisecondes. Il se base principalement sur le <a href="https://style.tidyverse.org/"><strong><em>tidyverse style guide</em></strong></a> (que je vous recommande vivement !), avec très peu d’options de configuration : le type d’indentation, sa taille et la longueur maximale de ligne.</p>
<p>Air a aussi son mot à dire sur la façon d’assigner une valeur : par défaut, il convertit les <code>=</code> d’assignation en <code>&lt;-</code>.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Avant</span></span>
<span id="cb3-2">x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb3-3"></span>
<span id="cb3-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Après</span></span>
<span id="cb3-5">x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span></code></pre></div></div>
</div>
<p>En temps normal, c’est Air qui décide seul où placer les retours à la ligne : si un appel de fonction tient sur une seule ligne sans dépasser la longueur autorisée, il le remet à plat, peu importe la façon dont vous l’aviez écrit au départ.</p>
<p>Toutefois, il existe quelques endroits précis où Air considère qu’un retour à la ligne est une intention délibérée de votre part, et le conserve ! C’est ce qu’on appelle un <strong>retour à la ligne persistant</strong>. Dans l’exemple ci-dessous, un retour à la ligne est inséré juste après la parenthèse ouvrante d’un appel de fonction, avant le premier argument.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1">poissons <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(</span>
<span id="cb4-2">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">daurade =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.75</span>,</span>
<span id="cb4-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">bonite =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span></span>
<span id="cb4-4">)</span></code></pre></div></div>
</div>
<p>Même si cette liste tiendrait facilement sur une seule ligne, Air ne la recompactera pas tout seul : le retour à la ligne après <code>list(</code> est persistant. Pour revenir à une version compacte, il faut recoller la première ligne soi-même. L’idée : Air garde la main sur la mise en page dans l’immense majorité des cas, mais reconnaît qu’on sait parfois mieux que lui quand une présentation étalée améliore la lecture !</p>
<p>Autre fonctionnalité pratique, l’<strong><em>autobracing</em></strong> : Air ajoute automatiquement des accolades <code>{ }</code> autour du corps d’un <code>if</code>, d’une boucle ou d’une fonction quand elles manquent et que leur absence peut poser problème.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Avant</span></span>
<span id="cb5-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (condition) a <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (condition2) b <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> c</span>
<span id="cb5-3"></span>
<span id="cb5-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Après</span></span>
<span id="cb5-5"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (condition) {</span>
<span id="cb5-6">  a</span>
<span id="cb5-7">} <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (condition2) {</span>
<span id="cb5-8">  b</span>
<span id="cb5-9">} <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> {</span>
<span id="cb5-10">  c</span>
<span id="cb5-11">}</span></code></pre></div></div>
</div>
<p>Ce changement n’est pas juste esthétique : un <code>if</code> multiligne sans accolades peut casser si vous copiez-collez ce bout de code hors de son contexte d’origine, avec une erreur du type <code>unexpected 'else'</code>. Les accolades rendent le code portable.</p>
</section>
<section id="comment-lutiliser" class="level2 section-3">
<h2 data-anchor-id="comment-lutiliser">Comment l’utiliser</h2>
<p>Air s’intègre dans les principaux environnements de développement R :</p>
<ul>
<li>dans <strong>Positron</strong>, Air est inclus par défaut : rien à installer, il suffit d’activer le formatage à la sauvegarde ;</li>
<li>dans <strong>VS Code</strong>, installez l’extension Air depuis le panneau des extensions, puis activez le formatage à la sauvegarde ;</li>
<li>dans <strong>RStudio</strong>, Air peut être configuré comme formatteur externe, après avoir installé l’outil en ligne de commande ;</li>
<li>en <strong>ligne de commande</strong>, un script d’installation autonome permet d’utiliser <code>air</code> directement, y compris dans des pipelines d’intégration continue.</li>
</ul>
<p>Pour un projet partagé avec d’autres personnes, mieux vaut créer un fichier <code>air.toml</code> (voir exemple ci-dessous) à la racine, même avec les réglages par défaut. Il fixe les règles pour tout le monde et évite que Air ne se base sur les préférences propres à l’éditeur de chacun.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode toml code-with-copy"><code class="sourceCode toml"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">[format]</span></span>
<span id="cb6-2"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">line-width</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">80</span></span>
<span id="cb6-3"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">indent-width</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb6-4"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">indent-style</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"space"</span></span></code></pre></div></div>
<p>Une fois configuré, plus besoin d’y penser : le code se formate tout seul à chaque sauvegarde, les discussions peuvent se concentrer sur ce qui compte vraiment !</p>
<p><em>Aérez-vous l’esprit, passez à Air !</em></p>
<div class="cta-group center">
<p><a href="../../formations.html" class="cta-button-outline">Découvrir les formations</a></p>
<p><a href="mailto:contact@pyranhia.eu" class="cta-button">Me contacter</a></p>
</div>
</section>



 ]]></description>
  <category>R bits</category>
  <guid>https://www.pyranhia.eu/posts/202607-air/air_formatter.html</guid>
  <pubDate>Wed, 08 Jul 2026 22:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202607-air/air.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Visualiser une course avec Quarto Dashboard, plotly et leaflet</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202606-bikingman/bikingman_dashboard.html</link>
  <description><![CDATA[ 





<section id="visualiser-une-course-avec-quarto-dashboard-plotly-et-leaflet" class="level1 section-full section-padded">
<h1>Visualiser une course avec Quarto Dashboard, plotly et leaflet</h1>
<p><span class="section-subtitle">381 km, 7 760 m de D+, tout ça pour faire un dashboard !</span></p>
</section>
<section id="résumé-de-la-course" class="level2 section-all">
<h2 data-anchor-id="résumé-de-la-course">Résumé de la course</h2>
<p>Le BikingMan, c’est une course d’ultracyclisme non-stop et en autonomie complète. L’édition Alpes-Maritimes 2026 proposait plusieurs formats, dont le 555 que j’ai couru : un tracé de 480 km et 12 000 m de D+.</p>
<p>Arrivée au checkpoint de Tende, j’ai pris la décision de ne pas faire la boucle de la Haute Route du Sel, qui m’aurait mis hors délai. Un abandon, certes, mais en guise de consolation, j’ai repris la route en reprenant la suite du tracé officiel vers Valberg, pour un total de 381 km.</p>
<p>Le reste, le dashboard ci-dessous le raconte mieux que moi.</p>
<div style="max-width: 95%; margin: 2rem auto;">
<iframe src="https://019f1cf4-02eb-e8c5-61b4-d61f5ab06bc9.share.connect.posit.cloud/" title="Dashboard interactif du parcours BikingMan 555" width="100%" height="900" style="border:none;border-radius:12px;"></iframe>
</div>
<p><span style="color: #007B6F;"><em>Construit avec Quarto Dashboard, plotly et leaflet, publié sur Posit Connect Cloud.</em></span></p>
<div class="cta-group center">
<p><a href="../../blog.html" class="cta-button-outline">Lire d’autres articles</a></p>
<p><a href="../../contact.html" class="cta-button">Me contacter</a></p>
</div>
</section>



 ]]></description>
  <category>Parcours</category>
  <category>Data viz</category>
  <guid>https://www.pyranhia.eu/posts/202606-bikingman/bikingman_dashboard.html</guid>
  <pubDate>Tue, 30 Jun 2026 22:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202606-bikingman/biking.JPG" medium="image"/>
</item>
<item>
  <title>Les maths cachées du Dobble</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202606-dobble/dobble.html</link>
  <description><![CDATA[ 





<section id="les-maths-cachées-du-dobble" class="level1 section-full section-padded">
<h1>Les maths cachées du Dobble</h1>
<p><span class="section-subtitle">Il y a toujours exactement un symbole en commun et ce n’est pas un hasard !</span></p>
<div class="note-box">
<p>Les illustrations de cet article utilisent les packages R suivants :</p>
<ul>
<li><a href="https://github.com/nschiett/fishualize">fishualize</a> par Nina Schiettekatte : silhouettes et palettes de couleurs inspirées de poissons marins ;</li>
<li><a href="https://github.com/EmilHvitfeldt/paletteer">paletteer</a> par Emil Hvitfeldt : collection du plus grand nombre de palettes dans un package R.</li>
</ul>
</div>
</section>
<section id="le-problème-du-dobble" class="level2 section-1">
<h2 data-anchor-id="le-problème-du-dobble">Le problème du Dobble</h2>
<p>Le Dobble, c’est très simple : deux cartes sont retournées simultanément et il faut trouver le symbole en commun le plus vite possible. Il y en a exactement toujours un et un seul, quelle que soit la paire de cartes. 55 cartes dans la boîte, 8 symboles par carte, des centaines de paires possibles, et cette propriété tient à chaque fois.</p>
<p>Quand on y pense deux secondes, c’est étrange. Comment a-t-on conçu ces cartes ?</p>
<section id="lapproche-naïve" class="level3">
<h3 data-anchor-id="lapproche-naïve">L’approche naïve</h3>
<p>Essayons de construire un mini-Dobble à la main, avec 3 symboles par carte. On démarre avec un stock de 3 poissons et on génère une première carte.</p>
<p><strong>Carte 1</strong> : on place 3 poissons, au choix.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-carte1" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-carte1-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202606-dobble/dobble_files/figure-html/fig-carte1-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-carte1-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Une première carte avec 3 poissons.
</figcaption>
</figure>
</div>
</div>
</div>
<p><strong>Carte 2</strong> : elle doit partager exactement un poisson avec la carte 1. On est obligé d’introduire 2 nouveaux poissons : on est maintenant à 5 poissons au total.</p>
<div id="fig-cartes-1-2-bis" class="quarto-layout-panel">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-cartes-1-2-bis-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="quarto-layout-row">
<div class="cell-output-display quarto-layout-cell-subref quarto-layout-cell" data-ref-parent="fig-cartes-1-2-bis" style="flex-basis: 50.0%;justify-content: center;">
<div id="fig-cartes-1-2-bis-1" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-subfloat-fig figure">
<div aria-describedby="fig-cartes-1-2-bis-1-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202606-dobble/dobble_files/figure-html/fig-cartes-1-2-bis-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" data-ref-parent="fig-cartes-1-2-bis" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-subfloat-caption quarto-subfloat-fig" id="fig-cartes-1-2-bis-1-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
(a) Carte 1
</figcaption>
</figure>
</div>
</div>
<div class="cell-output-display quarto-layout-cell-subref quarto-layout-cell" data-ref-parent="fig-cartes-1-2-bis" style="flex-basis: 50.0%;justify-content: center;">
<div id="fig-cartes-1-2-bis-2" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-subfloat-fig figure">
<div aria-describedby="fig-cartes-1-2-bis-2-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202606-dobble/dobble_files/figure-html/fig-cartes-1-2-bis-2.png" class="img-fluid quarto-figure quarto-figure-center figure-img" data-ref-parent="fig-cartes-1-2-bis" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-subfloat-caption quarto-subfloat-fig" id="fig-cartes-1-2-bis-2-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
(b) Carte 2
</figcaption>
</figure>
</div>
</div>
</div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-cartes-1-2-bis-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: La carte 2 doit partager exactement un poisson avec la carte 1.
</figcaption>
</figure>
</div>
<p><strong>Carte 3</strong> : elle doit partager exactement un poisson avec la carte 1, et exactement un avec la carte 2. Ces deux poissons communs doivent être différents, sinon la carte 3 partagerait deux poissons avec l’une des deux. On introduit encore un nouveau poisson : on a maintenant 6 poissons.</p>
<div id="fig-cartes-1-2-3-bis" class="quarto-layout-panel">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-cartes-1-2-3-bis-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="quarto-layout-row">
<div class="cell-output-display quarto-layout-cell-subref quarto-layout-cell" data-ref-parent="fig-cartes-1-2-3-bis" style="flex-basis: 33.3%;justify-content: center;">
<div id="fig-cartes-1-2-3-bis-1" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-subfloat-fig figure">
<div aria-describedby="fig-cartes-1-2-3-bis-1-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202606-dobble/dobble_files/figure-html/fig-cartes-1-2-3-bis-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" data-ref-parent="fig-cartes-1-2-3-bis" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-subfloat-caption quarto-subfloat-fig" id="fig-cartes-1-2-3-bis-1-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
(a) Carte 1
</figcaption>
</figure>
</div>
</div>
<div class="cell-output-display quarto-layout-cell-subref quarto-layout-cell" data-ref-parent="fig-cartes-1-2-3-bis" style="flex-basis: 33.3%;justify-content: center;">
<div id="fig-cartes-1-2-3-bis-2" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-subfloat-fig figure">
<div aria-describedby="fig-cartes-1-2-3-bis-2-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202606-dobble/dobble_files/figure-html/fig-cartes-1-2-3-bis-2.png" class="img-fluid quarto-figure quarto-figure-center figure-img" data-ref-parent="fig-cartes-1-2-3-bis" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-subfloat-caption quarto-subfloat-fig" id="fig-cartes-1-2-3-bis-2-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
(b) Carte 2
</figcaption>
</figure>
</div>
</div>
<div class="cell-output-display quarto-layout-cell-subref quarto-layout-cell" data-ref-parent="fig-cartes-1-2-3-bis" style="flex-basis: 33.3%;justify-content: center;">
<div id="fig-cartes-1-2-3-bis-3" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-subfloat-fig figure">
<div aria-describedby="fig-cartes-1-2-3-bis-3-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202606-dobble/dobble_files/figure-html/fig-cartes-1-2-3-bis-3.png" class="img-fluid quarto-figure quarto-figure-center figure-img" data-ref-parent="fig-cartes-1-2-3-bis" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-subfloat-caption quarto-subfloat-fig" id="fig-cartes-1-2-3-bis-3-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
(c) Carte 3
</figcaption>
</figure>
</div>
</div>
</div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-cartes-1-2-3-bis-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: La carte 3 partage exactement un poisson avec chacune des deux premières.
</figcaption>
</figure>
</div>
<p><strong>Carte 4</strong> : même logique, elle doit partager exactement un poisson avec chacune des 3 cartes précédentes, sans en partager deux avec aucune d’elles. On est obligé d’introduire un poisson supplémentaire : on atteint 7 poissons au total.</p>
<div id="fig-cartes-1-2-3-4-bis" class="quarto-layout-panel">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-cartes-1-2-3-4-bis-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="quarto-layout-row">
<div class="cell-output-display quarto-layout-cell-subref quarto-layout-cell" data-ref-parent="fig-cartes-1-2-3-4-bis" style="flex-basis: 50.0%;justify-content: center;">
<div id="fig-cartes-1-2-3-4-bis-1" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-subfloat-fig figure">
<div aria-describedby="fig-cartes-1-2-3-4-bis-1-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202606-dobble/dobble_files/figure-html/fig-cartes-1-2-3-4-bis-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" data-ref-parent="fig-cartes-1-2-3-4-bis" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-subfloat-caption quarto-subfloat-fig" id="fig-cartes-1-2-3-4-bis-1-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
(a) Carte 1
</figcaption>
</figure>
</div>
</div>
<div class="cell-output-display quarto-layout-cell-subref quarto-layout-cell" data-ref-parent="fig-cartes-1-2-3-4-bis" style="flex-basis: 50.0%;justify-content: center;">
<div id="fig-cartes-1-2-3-4-bis-2" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-subfloat-fig figure">
<div aria-describedby="fig-cartes-1-2-3-4-bis-2-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202606-dobble/dobble_files/figure-html/fig-cartes-1-2-3-4-bis-2.png" class="img-fluid quarto-figure quarto-figure-center figure-img" data-ref-parent="fig-cartes-1-2-3-4-bis" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-subfloat-caption quarto-subfloat-fig" id="fig-cartes-1-2-3-4-bis-2-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
(b) Carte 2
</figcaption>
</figure>
</div>
</div>
</div>
<div class="quarto-layout-row">
<div class="cell-output-display quarto-layout-cell-subref quarto-layout-cell" data-ref-parent="fig-cartes-1-2-3-4-bis" style="flex-basis: 50.0%;justify-content: center;">
<div id="fig-cartes-1-2-3-4-bis-3" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-subfloat-fig figure">
<div aria-describedby="fig-cartes-1-2-3-4-bis-3-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202606-dobble/dobble_files/figure-html/fig-cartes-1-2-3-4-bis-3.png" class="img-fluid quarto-figure quarto-figure-center figure-img" data-ref-parent="fig-cartes-1-2-3-4-bis" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-subfloat-caption quarto-subfloat-fig" id="fig-cartes-1-2-3-4-bis-3-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
(c) Carte 3
</figcaption>
</figure>
</div>
</div>
<div class="cell-output-display quarto-layout-cell-subref quarto-layout-cell" data-ref-parent="fig-cartes-1-2-3-4-bis" style="flex-basis: 50.0%;justify-content: center;">
<div id="fig-cartes-1-2-3-4-bis-4" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-subfloat-fig figure">
<div aria-describedby="fig-cartes-1-2-3-4-bis-4-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202606-dobble/dobble_files/figure-html/fig-cartes-1-2-3-4-bis-4.png" class="img-fluid quarto-figure quarto-figure-center figure-img" data-ref-parent="fig-cartes-1-2-3-4-bis" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-subfloat-caption quarto-subfloat-fig" id="fig-cartes-1-2-3-4-bis-4-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
(d) Carte 4
</figcaption>
</figure>
</div>
</div>
</div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-cartes-1-2-3-4-bis-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: La carte 4 partage exactement un poisson avec chacune des trois premières.
</figcaption>
</figure>
</div>
<p><strong>Cartes 5 à 7</strong> : la bonne nouvelle, c’est qu’on n’a plus besoin d’introduire de nouveaux poissons. Les 7 déjà posés suffisent à construire 3 cartes supplémentaires qui respectent toutes les contraintes.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-7-cartes" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-7-cartes-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202606-dobble/dobble_files/figure-html/fig-7-cartes-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-7-cartes-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: Les 7 cartes du mini-Dobble. Chaque paire partage exactement un poisson, vérifiez !
</figcaption>
</figure>
</div>
</div>
</div>
<p>Et maintenant, essayons d’en ajouter une 8ème. Elle devrait partager exactement un poisson avec chacune des 7 cartes, mais on peut vérifier exhaustivement : aucune combinaison de 3 poissons parmi nos 7 ne satisfait cette contrainte simultanément. <strong>7 cartes, c’est le maximum.</strong></p>
<p>Ce n’est pas un hasard. En fixant 3 symboles par carte, on a aussi fixé sans le savoir le nombre total de symboles (7) et le nombre total de cartes (7). <strong>Tout est lié.</strong></p>
</section>
</section>
<section id="les-plans-projectifs-finis-ou-pourquoi-les-maths-sont-là" class="level2 section-2">
<h2 data-anchor-id="les-plans-projectifs-finis-ou-pourquoi-les-maths-sont-là">Les plans projectifs finis, ou pourquoi les maths sont là</h2>
<p>Pour comprendre comment le Dobble est construit, il faut faire un petit détour par la géométrie. Pas la géométrie euclidienne du lycée, une variante plus étrange, où les règles habituelles sont légèrement tordues.</p>
<section id="une-géométrie-sans-parallèles" class="level3">
<h3 data-anchor-id="une-géométrie-sans-parallèles">Une géométrie sans parallèles</h3>
<p>Dans la géométrie classique, deux droites parallèles ne se croisent jamais. Dans un <strong>plan projectif</strong>, cette règle n’existe pas : <strong>toutes les droites se croisent, toujours, en exactement un point</strong>.</p>
<p>C’est la propriété clé. Et si on remplace « droites » par « cartes » et « point d’intersection » par « poisson en commun », on obtient exactement la propriété du Dobble.</p>
<p>Chaque carte est une droite. Chaque poisson est un point. Deux droites se croisent en exactement un point : deux cartes partagent exactement un poisson.</p>
<p>Le diagramme ci-dessous illustre le cas le plus simple : le plan de Fano, plan projectif d’ordre 2, avec 7 points et 7 droites. Une subtilité : dans cette représentation euclidienne, une des droites ne peut pas être tracée comme un segment sans déformer la structure, elle apparaît donc sous forme de cercle.</p>
<div class="quarto-layout-panel" data-layout="[1,1]">
<div class="quarto-layout-row">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div id="fig-7-cartes-colorees" class="quarto-float quarto-figure quarto-figure-center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-7-cartes-colorees-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.pyranhia.eu/posts/202606-dobble/dobble_files/figure-html/unnamed-chunk-1-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344"></p>
</figure>
</div>
</div>
</div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-7-cartes-colorees-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;6: Les 7 cartes de notre Dobble à 3 poissons. Chaque couleur correspond à une carte : vous retrouverez ces mêmes couleurs dans le diagramme ci-contre.
</figcaption>
</figure>
</div>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div id="fig-fano" class="quarto-float quarto-figure quarto-figure-center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-fano-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<iframe src="fano-widget/index.html" title="Widget interactif : le plan de Fano" width="100%" height="550px" frameborder="0" style="border-radius: 12px; overflow: hidden;"></iframe>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-fano-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;7: Le plan de Fano : 7 symboles (poissons), 7 cartes (droites colorées). Cliquez sur une carte dans la légende pour voir ses 3 symboles.
</figcaption>
</figure>
</div>
</div>
</div>
</div>
</section>
<section id="la-version-finie" class="level3">
<h3 data-anchor-id="la-version-finie">La version finie</h3>
<p>Un plan projectif <em>fini</em> d’ordre <img src="https://latex.codecogs.com/png.latex?q">, c’est la même idée, mais avec un nombre fini de points et de droites. La structure est entièrement déterminée par <img src="https://latex.codecogs.com/png.latex?q"> :</p>
<ul>
<li>chaque droite contient <img src="https://latex.codecogs.com/png.latex?q%20+%201"> points (= <img src="https://latex.codecogs.com/png.latex?q%20+%201"> symboles par carte) ;</li>
<li>il y a <img src="https://latex.codecogs.com/png.latex?q%5E2%20+%20q%20+%201"> droites au total (= <img src="https://latex.codecogs.com/png.latex?q%5E2%20+%20q%20+%201"> cartes) ;</li>
<li>il y a <img src="https://latex.codecogs.com/png.latex?q%5E2%20+%20q%20+%201"> points au total (= <img src="https://latex.codecogs.com/png.latex?q%5E2%20+%20q%20+%201"> symboles).</li>
</ul>
<p>Notez que le nombre de cartes et le nombre de symboles sont toujours égaux, c’est une conséquence directe de la symétrie du plan projectif.</p>
<p>Notre mini-Dobble à 3 symboles par carte correspond à <img src="https://latex.codecogs.com/png.latex?q%20=%202"> :</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?2%20+%201%20=%203"> symboles par carte,</li>
<li><img src="https://latex.codecogs.com/png.latex?2%5E2%20+%202%20+%201%20=%207"> cartes,</li>
<li>7 symboles.</li>
</ul>
<p>Le vrai Dobble utilise <img src="https://latex.codecogs.com/png.latex?q%20=%207"> : <img src="https://latex.codecogs.com/png.latex?7%20+%201%20=%208"> symboles par carte, <img src="https://latex.codecogs.com/png.latex?7%5E2%20+%207%20+%201%20=%2057"> cartes et symboles. Les 55 cartes de la boîte ? Deux cartes ont été retirées pour des raisons commerciales, mais la structure sous-jacente est bien là.</p>
</section>
<section id="le-coup-de-théâtre" class="level3">
<h3 data-anchor-id="le-coup-de-théâtre">Le coup de théâtre</h3>
<p>Voilà où ça devient vraiment surprenant. Les plans projectifs finis d’ordre <img src="https://latex.codecogs.com/png.latex?q"> <strong>n’existent que si <img src="https://latex.codecogs.com/png.latex?q"> est une puissance d’un nombre premier</strong> : 2, 3, 4, 5, 7, 8, 9…</p>
<p><img src="https://latex.codecogs.com/png.latex?q%20=%206"> ? Aucun plan projectif fini d’ordre 6 n’existe. La démonstration passe par un objet mathématique surprenant, les carrés latins orthogonaux (Note&nbsp;1). Pour <img src="https://latex.codecogs.com/png.latex?q%20=%2010">, il a fallu attendre 1988 et plusieurs milliers d’heures de calcul sur ordinateur pour conclure <span class="citation" data-cites="lam1991search">(Lam 1991)</span>. On ne peut tout simplement pas construire un Dobble valide avec 7 ou 11 symboles par carte. Les maths l’interdisent, ce n’est pas une limitation technique, c’est une contrainte mathématique.</p>
<div id="nte-carre" class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note&nbsp;1: Carrés latins et plans projectifs
</div>
</div>
<div class="callout-body-container callout-body">
<p>Un carré latin d’ordre <img src="https://latex.codecogs.com/png.latex?n"> est un tableau <img src="https://latex.codecogs.com/png.latex?n%20%5Ctimes%20n"> où chaque symbole apparaît exactement une fois par ligne et par colonne (comme une grille de Sudoku). Deux carrés latins sont <strong>orthogonaux</strong> si, superposés, chaque paire de symboles apparaît exactement une fois.</p>
<p><span class="citation" data-cites="bose1938Application">Bose (1938)</span> a démontré qu’un plan projectif fini d’ordre <img src="https://latex.codecogs.com/png.latex?n"> existe si et seulement s’il existe <img src="https://latex.codecogs.com/png.latex?n%20-%201"> carrés latins mutuellement orthogonaux d’ordre <img src="https://latex.codecogs.com/png.latex?n">. Pour <img src="https://latex.codecogs.com/png.latex?n%20=%206">, il en faudrait 5. <span class="citation" data-cites="tarry1900probleme">Tarry (1900)</span> avait déjà vérifié (à la main, case par case !) qu’il n’en existe même pas une paire. Ce résultat, combiné avec celui de Bose, suffit à exclure définitivement le plan projectif d’ordre 6.</p>
</div>
</div>
</section>
</section>
<section id="notre-propre-dobble" class="level2 section-3">
<h2 data-anchor-id="notre-propre-dobble">Notre propre Dobble !</h2>
<p>Maintenant qu’on comprend la structure, on peut s’amuser. En implémentant la construction des plans projectifs finis en R, on peut générer n’importe quel Dobble valide à partir d’un ordre <img src="https://latex.codecogs.com/png.latex?q"> et de visualiser les cartes produites.</p>
<div id="fig-dobble-2" class="quarto-float quarto-figure quarto-figure-center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-dobble-2-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<iframe src="dobble-widget-2/index.html" title="Widget interactif : jeu du Dobble à 7 poissons" width="100%" height="450px" frameborder="0" style="border-radius: 12px; overflow: hidden;"></iframe>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-dobble-2-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;8: Choisissez un ordre et retournez deux cartes : il y a toujours exactement un poisson en commun. À vous de le trouver !
</figcaption>
</figure>
</div>
<p>Si vous aimez comprendre ce qui se cache derrière les choses et explorer des données en R avec la même curiosité, c’est exactement l’état d’esprit que j’essaie de transmettre dans mes formations.</p>
<hr>
<section id="références" class="level3">
<h3 data-anchor-id="références">Références</h3>
<div id="refs" class="references csl-bib-body hanging-indent">
<div id="ref-bose1938Application" class="csl-entry">
Bose, Raj Chandra. 1938. <span>«&nbsp;On the <span>Application</span> of the <span>Properties</span> of <span>Galois Fields</span> to the <span>Problem</span> of <span>Construction</span> of <span>Hyper-Græco-Latin Squares</span>&nbsp;»</span>. <em>Sankhyā: The Indian Journal of Statistics (1933-1960)</em> 3 (4): 323‑38.
</div>
<div id="ref-lam1991search" class="csl-entry">
Lam, Clement WH. 1991. <span>«&nbsp;The search for a finite projective plane of order 10&nbsp;»</span>. <em>The American mathematical monthly</em> 98 (4): 305‑18.
</div>
<div id="ref-tarry1900probleme" class="csl-entry">
Tarry, Gaston. 1900. <em>Le probl<span>è</span>me des 36 officiers</em>. Secr<span>é</span>tariat de l’Association fran<span>ç</span>aise pour l’avancement des sciences.
</div>
</div>
<div class="cta-group center">
<p><a href="../../formations.html" class="cta-button-outline">Découvrir les formations</a></p>
<p><a href="../../blog.html" class="cta-button-outline">Lire d’autres articles</a></p>
<p><a href="../../contact.html" class="cta-button">Me contacter</a></p>
</div>
</section>
</section>
<!-- 







::: {.cell layout-align="center"}

```{.r .cell-code}
couleurs_pyranhia_15 <- c(
  "#0A3D62", "#27B9B2", "#2E7BB5", "#4DD9D3", "#1A5A8A",
  "#3CBFB8", "#4A96CC", "#007B6F", "#47C2D8", "#2A8FA8",
  "#00A896", "#6BAED6", "#0D4F6E", "#38A8C0", "#1B6E8A"
)

set.seed(42)
c1 <- sample(cartes_q7[[1]])
c2 <- sample(cartes_q7[[5]])

# Symboles uniques dans les deux cartes -> 15 max
symboles_uniques <- unique(c(c1, c2))

# Vecteur de couleurs de longueur 57 (nb total de symboles q7)
# Par défaut gris, couleurs pyranhia pour les symboles présents
couleurs_carte <- rep("#CCCCCC", length(especes_q7))
couleurs_carte[symboles_uniques] <- couleurs_pyranhia_15[seq_along(symboles_uniques)]

p1 <- dessiner_carte_widget(c1, especes_q7, couleurs_carte, positions_fixes[["8"]], seed = 1)
p2 <- dessiner_carte_widget(c2, especes_q7, couleurs_carte, positions_fixes[["8"]], seed = 5)

p_cover <- wrap_plots(p1, p2, ncol = 2) &
  theme(plot.margin = margin(0, 0, 0, 0))

ggsave(
  "cover.png",
  plot = p_cover,
  width = 10,
  height = 6,
  dpi = 300,
  bg = "white"
)
```
:::

-->



 ]]></description>
  <category>Hors piste</category>
  <guid>https://www.pyranhia.eu/posts/202606-dobble/dobble.html</guid>
  <pubDate>Sun, 21 Jun 2026 22:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202606-dobble/cover.png" medium="image" type="image/png" height="86" width="144"/>
</item>
<item>
  <title>R pour le machine learning : pourquoi changer de langage quand on peut rester dans le sien ?</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202606-r-python/r-python.html</link>
  <description><![CDATA[ 





<section id="r-pour-le-machine-learning-pourquoi-changer-de-langage-quand-on-peut-rester-dans-le-sien" class="level1 section-full section-padded">
<h1>R pour le machine learning : pourquoi changer de langage quand on peut rester dans le sien ?</h1>
<p><span class="section-subtitle">Le plus grand obstacle au ML pour les utilisateurs de R, ce n’est pas R. C’est de croire qu’il faut passer à Python.</span></p>
<div class="note-box">
<p>Je suis formatrice en machine learning avec R, mon biais est assumé. Cet article reflète ma conviction pédagogique, pas une vérité universelle !</p>
</div>
</section>
<section id="r-vs-python-la-mauvaise-question" class="level2 section-1">
<h2 data-anchor-id="r-vs-python-la-mauvaise-question">R vs Python : la mauvaise question</h2>
<p>Soyons honnêtes : Python est le langage dominant pour le machine learning. Ce n’est pas un débat, c’est un fait. La majorité des librairies, des tutoriels, des offres d’emploi, tout tourne sous Python. Alors pourquoi faire du ML en R ?</p>
<p>Ce n’est pas pour contester la suprématie de Python. C’est parce que la vraie question n’est pas « <em>quel est le meilleur langage pour le ML ?</em> » mais « <em>comment se lancer dans le ML quand on est à l’aise en R et pas prêt à changer de langage ?</em> »</p>
<p>Ma propre expérience m’a aidée à comprendre ça. Pendant ma thèse, j’ai appris le ML directement en Python, c’était imposé par le contexte, et à ce moment là, c’était le bon choix. Mais ce que j’ai retenu de cette expérience, ce ne sont pas des lignes de code Python, ce sont des concepts : comment on sépare ses données, pourquoi on valide un modèle, ce que signifie vraiment une métrique de performance. Ces concepts, ils n’appartiennent à aucun langage.</p>
<p>C’est ça, l’argument central. <strong>Le ML s’apprend avec des idées, pas avec une syntaxe</strong>. Et si vous êtes à l’aise en R, vous pouvez acquérir ces idées dans R, sans la friction supplémentaire d’un nouveau langage.</p>
<p>Pour les utilisateurs de R qui veulent se lancer, la question n’est pas « R ou Python ? ». La question est : « <em>est-ce que je dois changer de langage pour apprendre le ML ?</em> ». Et la réponse est non.</p>
</section>
<section id="ce-que-r-permet-vraiment-de-faire-en-ml" class="level2 section-2">
<h2 data-anchor-id="ce-que-r-permet-vraiment-de-faire-en-ml">Ce que R permet vraiment de faire en ML</h2>
<p>L’écosystème <a href="https://www.tidymodels.org/"><code>tidymodels</code></a> est une interface sérieuse pour le machine learning. Il ne s’agit pas d’un package unique mais d’une collection de packages cohérents, chacun dédié à une étape du pipeline : <a href="https://rsample.tidymodels.org/"><code>rsample</code></a> pour séparer les données, <a href="https://recipes.tidymodels.org/"><code>recipes</code></a> pour les prétraiter, <a href="https://parsnip.tidymodels.org/"><code>parsnip</code></a> pour spécifier et entraîner les modèles, <a href="https://tune.tidymodels.org/"><code>tune</code></a> pour optimiser les hyperparamètres, <a href="https://yardstick.tidymodels.org/"><code>yardstick</code></a> pour évaluer les performances. On peut y faire de la régression, de la classification, de la validation croisée, de l’optimisation… : toutes les briques fondamentales du ML appliqué.</p>
<p>Ce n’est pas un jouet pédagogique. C’est un outil utilisé sérieusement, avec une communauté active et un écosystème en croissance constante (Figure&nbsp;1). Mais l’argument le plus fort n’est pas technique, il est cognitif.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-tidymodels-timeline" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-tidymodels-timeline-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202606-r-python/r-python_files/figure-html/fig-tidymodels-timeline-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-tidymodels-timeline-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Téléchargements mensuels depuis le CRAN entre 2019 et 2025. En haut : le méta-package tidymodels. En bas : les packages du cœur de l’écosystème (rsample, parsnip, recipes, workflows, tune, yardstick, broom, dials). Source : package cranlogs.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Apprendre le ML, c’est déjà beaucoup. Comprendre pourquoi on sépare ses données, ce que mesure vraiment une AUC, comment fonctionne la régularisation, pourquoi un modèle qui performe parfaitement à l’entraînement est suspect… : tout ça demande du temps et de l’attention. Si en parallèle vous devez apprendre une nouvelle syntaxe, de nouveaux packages, de nouveaux réflexes, vous divisez cette attention en deux.</p>
<p>Si vous connaissez déjà R, vous pouvez concentrer toute votre énergie sur les concepts. C’est là que se joue l’apprentissage.</p>
<p>Et une fois ces concepts solides, la transition vers Python devient beaucoup moins intimidante. Vous savez ce que vous cherchez à faire : il ne reste qu’à apprendre à le dire dans une autre syntaxe. Les LLMs rendent ça encore plus accessible : si vous comprenez ce que fait un pipeline <code>tidymodels</code>, vous pouvez demander à un assistant IA de vous montrer l’équivalent en <a href="https://scikit-learn.org/stable/"><code>scikit-learn</code></a>.</p>
<p>Mais, et c’est <strong>essentiel</strong>, cette transition ne fonctionne que si les concepts sont vraiment acquis ! Un LLM peut générer du code Python qui a l’air juste et qui ne l’est pas. Il peut oublier de standardiser les variables, évaluer un modèle sur les mauvaises données, choisir une métrique inadaptée. Si vous ne savez pas ce que vous cherchez à faire, vous n’avez aucun moyen de détecter ces erreurs. <strong>La compréhension n’est pas optionnelle : c’est elle qui vous permet de garder le contrôle, quel que soit le langage.</strong></p>
</section>
<section id="les-limites-honnêtement" class="level2 section-3">
<h2 data-anchor-id="les-limites-honnêtement">Les limites, honnêtement</h2>
<p>R pour le ML, c’est une porte d’entrée. Et comme toute porte d’entrée, elle ne mène pas partout.</p>
<p>Le deep learning avancé, c’est Python. Les grandes librairies (<a href="https://pytorch.org/"><code>PyTorch</code></a> et <a href="https://www.tensorflow.org/?hl=fr"><code>TensorFlow</code></a>) sont pensées pour Python, maintenues pour Python, documentées pour Python. Il existe des <a href="https://torch.mlverse.org/">interfaces R</a>, mais elles tournent sous le capot en Python, et elles accusent souvent un retard sur les versions originales. Si votre objectif est de fine-tuner des grands modèles de langage ou de construire des architectures de vision par ordinateur sur mesure, R ne sera pas votre outil final.</p>
<p>La mise en production est aussi plus compliquée. Déployer un modèle R dans un environnement de production reste moins standard que l’équivalent Python, et vous trouverez moins de ressources, moins de collègues qui l’ont fait avant vous.</p>
<p>L’écosystème est plus petit, et <code>tidymodels</code> fait des choix qui ont des conséquences concrètes. Par exemple, un <a href="https://parsnip.tidymodels.org/reference/details_rand_forest_ranger.html">Random Forest entraîné via <code>parsnip</code> avec le moteur <code>ranger</code></a> n’expose que 3 hyperparamètres tunables, là où <code>scikit-learn</code> en propose une dizaine. Pour quelqu’un qui apprend, c’est un avantage : moins de complexité, plus de clarté. Pour quelqu’un qui veut optimiser finement un modèle en production, ça peut devenir une contrainte.</p>
<p>Mais il faut mettre ces limites en perspective. <strong>Ce sont les limites d’une porte d’entrée, pas celles d’un mur</strong>. L’objectif n’est pas de faire tout votre ML en R pour toujours, c’est d’acquérir des bases solides dans un environnement que vous maîtrisez déjà, pour ensuite aller où vous voulez. Et si votre travail ne vous emmène jamais vers du deep learning ou de la mise en production à grande échelle, <code>tidymodels</code> couvre très largement ce dont vous avez besoin. R n’est pas Python. C’est précisément pour ça qu’il est utile ici.</p>
<p>Alors, envie de découvrir le machine learning avec R ?</p>
<div class="cta-group center">
<p><a href="../../formations/machine_learning_avec_r.html" class="cta-button-outline">Découvrir la formation ML avec R</a></p>
<p><a href="../../contact.html" class="cta-button">Me contacter</a></p>
</div>
</section>



 ]]></description>
  <category>Parcours</category>
  <category>Pédagogie</category>
  <guid>https://www.pyranhia.eu/posts/202606-r-python/r-python.html</guid>
  <pubDate>Tue, 09 Jun 2026 22:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202606-r-python/row.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Une palette de couleurs, ça ne se choisit pas au hasard</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202605-dataviz-1/dataviz-1.html</link>
  <description><![CDATA[ 





<section id="une-palette-de-couleurs-ça-ne-se-choisit-pas-au-hasard" class="level1 section-full section-padded">
<h1>Une palette de couleurs, ça ne se choisit pas au hasard</h1>
<p><span class="section-subtitle">Séquentiel, divergent, qualitatif : trois familles de palettes, trois logiques différentes.</span></p>
<div class="note-box">
<p>Cet article fait partie d’une série sur les couleurs en dataviz :</p>
<ul>
<li><strong>Partie 1 : Choisir sa palette</strong></li>
<li>Partie 2 : Les palettes qualitatives</li>
<li>Partie 3 : Dataviz accessible</li>
</ul>
</div>
</section>
<section id="trois-familles-trois-logiques" class="level2 section-1">
<h2 data-anchor-id="trois-familles-trois-logiques">Trois familles, trois logiques</h2>
<p>Quand on choisit une palette de couleurs pour un graphique, on pense souvent esthétique. Est-ce que ça rend bien ? Est-ce que les couleurs s’harmonisent ? C’est une question légitime, mais c’est la mauvaise question. Le bon point de départ, c’est : <strong>quelle est la nature de ma variable ?</strong></p>
<p>Il existe trois grandes familles de palettes, qui correspondent à trois types de données différents.</p>
<section id="séquentiel-encoder-une-grandeur" class="level3">
<h3 data-anchor-id="séquentiel-encoder-une-grandeur">Séquentiel : encoder une grandeur</h3>
<p>Une palette séquentielle encode une variable <strong>continue</strong> sans point de référence central. C’est le bon choix pour une température absolue, une concentration, un revenu, une densité de population.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1">penguins <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb1-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">drop_na</span>(body_mass_g) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb1-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> flipper_length_mm, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> bill_length_mm, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> body_mass_g)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb1-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_point</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb1-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_colour_viridis_c</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">option =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mako"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb1-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(</span>
<span id="cb1-7">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Longueur des nageoires (mm)"</span>,</span>
<span id="cb1-8">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Longueur du bec (mm)"</span>,</span>
<span id="cb1-9">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Masse (g)"</span></span>
<span id="cb1-10">  )</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-sequentiel" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-sequentiel-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202605-dataviz-1/dataviz-1_files/figure-html/fig-sequentiel-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-sequentiel-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Palette séquentielle : la masse des manchots, du plus léger au plus lourd.
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="divergent-une-variable-avec-un-centre-qui-a-du-sens" class="level3">
<h3 data-anchor-id="divergent-une-variable-avec-un-centre-qui-a-du-sens">Divergent : une variable avec un centre qui a du sens</h3>
<p>Une palette divergente encode une variable qui s’étend dans <strong>deux directions à partir d’un point central significatif</strong> : zéro, une moyenne, un seuil. Les deux extrêmes ont des couleurs distinctes, le centre est neutre. C’est la palette pour les anomalies, les corrélations, les résidus : toutes les situations où s’éloigner du centre dans un sens ou dans l’autre est porteur de sens.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1">penguins <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb2-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">drop_na</span>(body_mass_g, species) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb2-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">group_by</span>(species) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb2-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">anomalie =</span> body_mass_g <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mean</span>(body_mass_g)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb2-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ungroup</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb2-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> flipper_length_mm, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> bill_length_mm, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> anomalie)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb2-7">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_point</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb2-8">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_colour_distiller</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">palette =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"RdYlBu"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">limits =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1500</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1500</span>)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb2-9">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(</span>
<span id="cb2-10">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Longueur des nageoires (mm)"</span>,</span>
<span id="cb2-11">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Longueur du bec (mm)"</span>,</span>
<span id="cb2-12">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Anomalie de masse (g)"</span></span>
<span id="cb2-13">  )</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-divergent" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-divergent-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202605-dataviz-1/dataviz-1_files/figure-html/fig-divergent-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-divergent-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Palette divergente : anomalie de masse par rapport à la moyenne de l’espèce. Le jaune correspond à zéro : un centre qui a du sens.
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="qualitatif-des-catégories-sans-ordre" class="level3">
<h3 data-anchor-id="qualitatif-des-catégories-sans-ordre">Qualitatif : des catégories sans ordre</h3>
<p>Une palette qualitative encode des <strong>catégories distinctes sans relation d’ordre</strong> : des espèces, des pays, des groupes. Les couleurs doivent être suffisamment différentes pour qu’on distingue chaque catégorie, mais aucune ne doit sembler « plus haute » ou « plus basse » qu’une autre.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1">penguins <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb3-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">drop_na</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb3-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> flipper_length_mm, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> bill_length_mm, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> species)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb3-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_point</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb3-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_colour_brewer</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">palette =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Set2"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb3-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(</span>
<span id="cb3-7">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Longueur des nageoires (mm)"</span>,</span>
<span id="cb3-8">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Longueur du bec (mm)"</span>,</span>
<span id="cb3-9">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Espèce"</span></span>
<span id="cb3-10">  )</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-qualitatif" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-qualitatif-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202605-dataviz-1/dataviz-1_files/figure-html/fig-qualitatif-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-qualitatif-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Palette qualitative : trois espèces de manchots, trois couleurs distinctes sans hiérarchie.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Le choix entre ces trois familles n’est pas esthétique, il est conceptuel. Choisir la mauvaise famille, c’est mentir à son lecteur sans le vouloir. On verra en section 3 un exemple classique de ce glissement.</p>
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p><strong>Et les catégories ordonnées ?</strong></p>
<p>Il existe un cas intermédiaire souvent oublié : les variables <strong>ordinales</strong>, c’est-à-dire des catégories qui ont un ordre naturel mais pas de valeur numérique continue. Par exemple des tranches d’âge, des niveaux de satisfaction, ou faible / moyen / fort.</p>
<p>Ni une palette qualitative (qui ignore l’ordre) ni une palette séquentielle continue (qui suppose des intervalles réguliers) ne conviennent vraiment. La bonne réponse est une palette séquentielle discrète, comme <code>scale_fill_brewer(palette = "Blues")</code> avec des niveaux ordonnés, ou <code>scale_fill_steps()</code> pour discrétiser une variable continue.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1">penguins <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb4-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">drop_na</span>(body_mass_g) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb4-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">masse_cat =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cut</span>(</span>
<span id="cb4-4">    body_mass_g,</span>
<span id="cb4-5">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">breaks =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>,</span>
<span id="cb4-6">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">labels =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Légère"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Moyenne"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Lourde"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Très lourde"</span>))</span>
<span id="cb4-7">  ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb4-8">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> flipper_length_mm, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> bill_length_mm, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> masse_cat)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb4-9">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_point</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb4-10">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_colour_brewer</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">palette =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"RdPu"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">direction =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb4-11">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(</span>
<span id="cb4-12">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Longueur des nageoires (mm)"</span>,</span>
<span id="cb4-13">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Longueur du bec (mm)"</span>,</span>
<span id="cb4-14">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Masse"</span></span>
<span id="cb4-15">  )</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-ordinal" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-ordinal-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202605-dataviz-1/dataviz-1_files/figure-html/fig-ordinal-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-ordinal-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: Palette séquentielle discrète : la masse des manchots découpée en quatre tranches ordonnées.
</figcaption>
</figure>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</section>
<section id="jet-viridis-le-bon-outil-pour-les-données-continues" class="level2 section-2">
<h2 data-anchor-id="jet-viridis-le-bon-outil-pour-les-données-continues"><em>jet</em>, <em>viridis</em> : le bon outil pour les données continues</h2>
<p>Pour les palettes séquentielles et divergentes, le choix de la palette elle-même compte autant que le choix de la famille. Et il existe une palette qu’on retrouve partout, dans les publications scientifiques comme dans les dashboards d’entreprise, et qui pose de vrais problèmes : la palette arc-en-ciel, connue sous le nom de <em>jet</em>.</p>
<section id="pourquoi-jet-cest-mal" class="level3">
<h3 data-anchor-id="pourquoi-jet-cest-mal">Pourquoi <em>jet</em> c’est mal</h3>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-jet" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-jet-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202605-dataviz-1/dataviz-1_files/figure-html/fig-jet-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-jet-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: Température moyenne annuelle en France avec la palette jet.
</figcaption>
</figure>
</div>
</div>
</div>
<p>La quasi-totalité de la plaine française est noyée dans une masse orange-rouge indistincte, tandis que les Alpes explosent visuellement, donnant l’impression d’un contraste radical entre montagne et plaine, alors que l’écart est progressif dans les données.</p>
<p>C’est le problème fondamental de <em>jet</em> : elle n’est pas <strong>perceptuellement uniforme</strong> <span class="citation" data-cites="crameri2020Misuse">(Crameri et al. 2020)</span>. Un même écart de température ne produit pas le même saut visuel selon l’endroit où on se trouve sur le gradient. Certaines zones paraissent très contrastées, d’autres homogènes, sans que les données le justifient. Et comme on le verra dans le prochain article de la série, <em>jet</em> est aussi particulièrement problématique pour les personnes daltoniennes.</p>
</section>
<section id="viridis-la-solution-universelle" class="level3">
<h3 data-anchor-id="viridis-la-solution-universelle"><em>viridis</em> : la solution universelle</h3>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-viridis-temp" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-viridis-temp-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202605-dataviz-1/dataviz-1_files/figure-html/fig-viridis-temp-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-viridis-temp-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;6: La même carte avec viridis. Le gradient est perceptuellement uniforme : un même écart de température produit le même saut visuel partout.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Avec <a href="https://cran.r-project.org/web/packages/viridis/vignettes/intro-to-viridis.html"><em>viridis</em></a>, les faux contrastes disparaissent. On distingue maintenant clairement le gradient nord-sud, le couloir rhodanien, et la transition vers les sommets alpins. Rien n’explose, rien n’est écrasé : chaque degré compte autant visuellement que les autres.</p>
<p><em>viridis</em> n’est pas une palette unique mais une famille : cinq variantes sont disponibles dans <code>ggplot2</code> via l’argument <code>option</code>.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-palettes-viridis" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-palettes-viridis-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202605-dataviz-1/dataviz-1_files/figure-html/fig-palettes-viridis-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-palettes-viridis-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;7: Les cinq palettes de la famille viridis.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Toutes sont perceptuellement uniformes et accessibles aux daltoniens. <code>viridis</code> est la valeur par défaut ; <code>magma</code> et <code>inferno</code> conviennent bien aux fonds sombres ; <code>cividis</code> est conçue pour être identique en vision normale et en vision daltonienne.</p>
<p><em>viridis</em> est le bon réflexe par défaut, mais il existe des cas où on peut faire encore mieux. C’est ce qu’on verra avec <em>cmocean</em> en section 3.</p>
</section>
</section>
<section id="quand-la-palette-parle-avant-la-légende" class="level2 section-3">
<h2 data-anchor-id="quand-la-palette-parle-avant-la-légende">Quand la palette parle avant la légende</h2>
<p><em>viridis</em> est le choix universel et sûr. Mais il existe des cas où la palette peut faire plus que représenter correctement les données : elle peut renforcer l’intuition du lecteur. C’est la philosophie de <a href="https://cran.r-project.org/web/packages/cmocean/vignettes/cmocean.html"><em>cmocean</em></a> <span class="citation" data-cites="thyng2016True">(Thyng et al. 2016)</span> : des palettes conçues pour des variables à fort sens physique.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-palettes-cmocean" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-palettes-cmocean-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202605-dataviz-1/dataviz-1_files/figure-html/fig-palettes-cmocean-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-palettes-cmocean-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;8: Quelques palettes cmocean disponibles dans R.
</figcaption>
</figure>
</div>
</div>
</div>
<p><code>thermal</code> pour la température, <code>rain</code> pour les précipitations, <code>deep</code> pour la profondeur, <code>solar</code> pour le rayonnement : la palette encode le sens de la variable. On lit la carte avant même de regarder la légende.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-cmocean-temp" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-cmocean-temp-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202605-dataviz-1/dataviz-1_files/figure-html/fig-cmocean-temp-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-cmocean-temp-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;9: Température moyenne annuelle en France avec cmocean <code>thermal</code>. Le violet évoque le froid, l’orange évoque le chaud : la lecture est immédiate.
</figcaption>
</figure>
</div>
</div>
</div>
<section id="quand-la-palette-ne-suffit-pas-transformer-léchelle" class="level3">
<h3 data-anchor-id="quand-la-palette-ne-suffit-pas-transformer-léchelle">Quand la palette ne suffit pas : transformer l’échelle</h3>
<p>Choisir la bonne palette ne suffit pas toujours. Quand les données sont très asymétriques, c’est l’échelle qu’il faut adapter, pas la palette. Les précipitations en sont un bon exemple : beaucoup de zones avec des valeurs faibles, quelques massifs avec des valeurs très élevées. La palette <code>rain</code> est le bon choix, mais sur une échelle linéaire, les nuances dans les zones moins arrosées sont écrasées par les extrêmes. Une transformation log corrige ça.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-rain-lineaire" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-rain-lineaire-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202605-dataviz-1/dataviz-1_files/figure-html/fig-rain-lineaire-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-rain-lineaire-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;10: Précipitations annuelles en France avec une échelle linéaire (gauche) et transformée log (droite). La transformation révèle les nuances dans les zones moins arrosées, écrasées par les valeurs extrêmes des massifs montagneux.
</figcaption>
</figure>
</div>
</div>
</div>
<p>La transformation (<code>log</code>, <code>sqrt</code>…) s’applique directement dans <code>scale_fill_*</code> via l’argument <code>trans</code>. Elle ne modifie pas les données, seulement la façon dont l’échelle de couleur leur est mappée.</p>
</section>
<section id="les-palettes-divergentes-quand-le-centre-a-du-sens" class="level3">
<h3 data-anchor-id="les-palettes-divergentes-quand-le-centre-a-du-sens">Les palettes divergentes : quand le centre a du sens</h3>
<p><em>cmocean</em> propose aussi des palettes divergentes, et c’est l’occasion de revenir sur un point de la section 1 : une palette divergente n’est légitime que si <strong>le centre de la palette correspond à une valeur qui a du sens dans les données</strong>.</p>
<p>L’erreur classique : utiliser une palette bleu-blanc-rouge pour des températures absolues. Ça semble intuitif (le bleu pour le froid, le rouge pour le chaud) mais ça induit une lecture fausse. Où met-on le blanc ? À 0 °C ? À la moyenne de la période ? Le choix est arbitraire, et la carte suggère qu’il existe un seuil naturel là où il n’y en a pas.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-divergent-mauvais" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-divergent-mauvais-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202605-dataviz-1/dataviz-1_files/figure-html/fig-divergent-mauvais-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-divergent-mauvais-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;11: Palette divergente sur des températures absolues : le blanc est situé autour de 3°C, ce qui suggère faussement que cette valeur est un seuil significatif.
</figcaption>
</figure>
</div>
</div>
</div>
<p>En revanche, sur une différence ou un écart par rapport à une référence, le centre a du sens. Prenons l’écart de précipitations entre juillet et janvier : zéro signifie que les deux mois sont aussi arrosés l’un que l’autre, le brun indique un juillet plus sec, le vert un juillet plus humide. La palette divergente traduit immédiatement cette symétrie.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-divergent-bien" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-divergent-bien-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202605-dataviz-1/dataviz-1_files/figure-html/fig-divergent-bien-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-divergent-bien-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;12: Écart de précipitations juillet − janvier. Le brun indique un juillet plus sec que janvier, le vert un juillet plus arrosé.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Le choix d’une palette n’est pas esthétique. Une palette séquentielle suppose qu’il n’y a pas de centre naturel dans les données. Une palette divergente suppose qu’il y en a un. Une palette qualitative suppose que les catégories sont équivalentes. Se tromper, c’est introduire une structure dans les données qui n’existe pas.</p>
<p>Dans le prochain article de la série, on s’attaque aux palettes qualitatives : combien de couleurs peut-on vraiment distinguer, et que faire quand on a trop de catégories ?</p>
<div class="cta-group center">
<p><a href="../../formations/machine_learning_avec_r.html" class="cta-button-outline">Découvrir la formation ML avec R</a></p>
<p><a href="mailto:contact@pyranhia.eu" class="cta-button">Me contacter</a></p>
</div>
</section>
</section>




<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-bibliography"><h2 class="anchored quarto-appendix-heading">Les références</h2><div id="refs" class="references csl-bib-body hanging-indent">
<div id="ref-crameri2020Misuse" class="csl-entry">
Crameri, Fabio, Grace E. Shephard, et Philip J. Heron. 2020. <span>«&nbsp;The Misuse of Colour in Science Communication&nbsp;»</span>. <em>Nature Communications</em> 11 (1): 5444. <a href="https://doi.org/10.1038/s41467-020-19160-7">https://doi.org/10.1038/s41467-020-19160-7</a>.
</div>
<div id="ref-thyng2016True" class="csl-entry">
Thyng, Kristen M., Chad A. Greene, Robert D. Hetland, Heather M. Zimmerle, et Steven F. DiMarco. 2016. <span>«&nbsp;True <span>Colors</span> of <span>Oceanography</span>: <span>Guidelines</span> for <span>Effective</span> and <span>Accurate Colormap Selection</span>&nbsp;»</span>. <em>Oceanography</em> 29 (3): 9‑13.
</div>
</div></section></div> ]]></description>
  <category>Data viz</category>
  <category>Tutoriels R</category>
  <guid>https://www.pyranhia.eu/posts/202605-dataviz-1/dataviz-1.html</guid>
  <pubDate>Sun, 17 May 2026 22:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202605-dataviz-1/couleurs-dataviz.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Optimisation des hyperparamètres : comment trouver les bons réglages pour son modèle</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202604-hyperparametres/hyperparametres.html</link>
  <description><![CDATA[ 





<section id="optimisation-des-hyperparamètres-comment-trouver-les-bons-réglages-pour-son-modèle" class="level1 section-full section-padded">
<h1>Optimisation des hyperparamètres : comment trouver les bons réglages pour son modèle</h1>
<p><span class="section-subtitle">Paramètres, hyperparamètres, grid search : comment faire les bons choix avant d’entraîner son modèle.</span></p>
<p>Cet article fait suite aux tutoriels <a href="../../posts/202601-train-test-split/train-test-split.html">Train/test split</a> et <a href="../../posts/202604-cross-validation/cross-validation.html">Validation croisée</a>. Si ces concepts ne vous sont pas familiers, je vous conseille de les lire d’abord.</p>
<div class="note-box">
<p><strong>À propos du dataset et du modèle utilisés</strong></p>
<p>Le dataset <code>sportifs</code> est simulé, conçu spécialement pour cet article. On a aussi légèrement triché pour que les effets d’un mauvais réglage soient bien visibles : dans la vraie vie, les modèles ont des protections contre cela !</p>
</div>
</section>
<section id="paramètres-et-hyperparamètres-quelle-différence" class="level2 section-1">
<h2 data-anchor-id="paramètres-et-hyperparamètres-quelle-différence">Paramètres et hyperparamètres : quelle différence ?</h2>
<p>On va travailler sur le dataset <code>sportifs</code> : des données simulées sur 250 athlètes, où l’objectif est de prédire un score de performance à partir du nombre d’heures d’entraînement hebdomadaires et des années d’expérience.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">head</span>(sportifs)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code># A tibble: 6 × 3
  heures experience score
   &lt;dbl&gt;      &lt;dbl&gt; &lt;dbl&gt;
1   7.31       6.00  144.
2  10.6        4.19  180.
3  14.8        4.65  203.
4   6.61       4.36  116.
5   9.76       6.69  194.
6  10.4        5.97  218.</code></pre>
</div>
</div>
<p>Le modèle qu’on va utiliser ici est un <strong>arbre de décision</strong>. Le principe : pour prédire le score d’un sportif, l’algorithme découpe l’espace des variables prédictives en rectangles, et associe une valeur à chaque zone. À chaque nœud, il choisit la <strong>variable</strong> et le <strong>seuil</strong> qui réduisent le mieux l’erreur de prédiction. Ce sont ces règles de coupure que l’arbre <strong>apprend</strong> à partir des données. Ce sont ses <strong>paramètres</strong>.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-arbre-petit" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-arbre-petit-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202604-hyperparametres/hyperparametres_files/figure-html/fig-arbre-petit-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-arbre-petit-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Un arbre de décision peu profond (<code>max_depth = 2</code>). Chaque nœud montre la règle de coupure apprise par le modèle : la variable choisie et le seuil optimal.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Ces règles de coupure sont apprises pendant l’entraînement : l’algorithme a testé toutes les valeurs possibles et retenu celles qui minimisent l’erreur. On ne les fixe pas, on les découvre.</p>
<p>En revanche, <code>max_depth</code> (la profondeur maximale de l’arbre) est un réglage qu’on fixe <em>avant</em> l’entraînement. Le modèle ne le choisit pas : c’est notre décision. C’est un <strong>hyperparamètre</strong>.</p>
<p>Et ce choix a des conséquences importantes. Avec <code>max_depth = 4</code>, l’arbre peut apprendre des règles beaucoup plus fines (Figure&nbsp;2).</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-arbre-grand" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-arbre-grand-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202604-hyperparametres/hyperparametres_files/figure-html/fig-arbre-grand-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-arbre-grand-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Un arbre profond (<code>max_depth = 4</code>). L’arbre découpe l’espace en régions très petites pour coller au plus près des données d’entraînement.
</figcaption>
</figure>
</div>
</div>
</div>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Hyperparamètres ≠ paramètres
</div>
</div>
<div class="callout-body-container callout-body">
<p>Les <strong>paramètres</strong> sont appris par le modèle pendant l’entraînement : les règles de coupure, les seuils, les valeurs prédites dans chaque feuille.</p>
<p>Les <strong>hyperparamètres</strong> sont fixés par vous, avant l’entraînement. Pour un arbre de décision, on doit définir sa profondeur maximale (<code>max_depth</code>), mais ce n’est pas le seul hyperparamètre. Le modèle ne les optimise pas : c’est votre rôle.</p>
</div>
</div>
<p>Mais alors, quelle valeur de <code>max_depth</code> choisir ? Trop faible, le modèle est trop simple et rate des patterns importants. Trop élevée, il mémorise les données d’entraînement et généralise mal. C’est exactement ce qu’on va résoudre.</p>
</section>
<section id="comment-choisir-max_depth" class="level2 section-2">
<h2 data-anchor-id="comment-choisir-max_depth">Comment choisir <code>max_depth</code> ?</h2>
<p>On pourrait évaluer les performances du modèle sur les données d’entraînement pour choisir <code>max_depth</code>. Mais c’est comme corriger ses propres copies : le modèle a déjà vu ces données, les résultats seront toujours trop optimistes.</p>
<p>Le jeu de test, alors ? Pas non plus. Son rôle, c’est l’évaluation finale du modèle, une seule fois, une fois toutes les décisions prises. S’en servir pour choisir <code>max_depth</code>, c’est le contaminer.</p>
<p>La solution : réserver une troisième partie des données, le jeu de <strong>validation</strong>, uniquement dédié à comparer les valeurs de <code>max_depth</code> (Figure&nbsp;3).</p>
<div id="fig-three-split" class="quarto-float quarto-figure quarto-figure-center" style="border-radius: 12px;">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-three-split-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202604-hyperparametres/train_val_test.drawio.png" class="img-fluid figure-img" style="border-radius: 12px;">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-three-split-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Train, validation, test : trois rôles distincts.
</figcaption>
</figure>
</div>
<p>Trois ensembles, trois rôles distincts : le jeu d’<strong>entraînement</strong> pour apprendre les règles de coupure, le jeu de <strong>validation</strong> pour comparer les valeurs de <code>max_depth</code>, le jeu de <strong>test</strong> pour l’évaluation finale.</p>
<p>On teste trois valeurs de <code>max_depth</code> et on compare leurs performances sur le jeu de validation. On regarde deux métriques :</p>
<ol type="1">
<li>la <strong>RMSE</strong> (<em>root mean squared error</em>) qui mesure l’erreur en unités de la variable cible (le score des sportifs dans notre cas) ;</li>
<li>le <strong>R²</strong>, qui indique la proportion de variance expliquée (entre 0 et 1, plus c’est élevé mieux c’est).</li>
</ol>
<div class="cell" data-layout-align="center">
<div id="tbl-trois-modeles" class="cell quarto-float quarto-figure quarto-figure-center" data-layout-align="center">
<figure class="quarto-float quarto-float-tbl figure">
<figcaption class="quarto-float-caption-top quarto-float-caption quarto-float-tbl" id="tbl-trois-modeles-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Table&nbsp;1: RMSE et R² sur le jeu de validation pour trois valeurs de <code>max_depth</code>.
</figcaption>
<div aria-describedby="tbl-trois-modeles-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="cell-output-display">
<table class="cell caption-top table table-sm table-striped small">
<thead>
<tr class="header">
<th style="text-align: right;">max_depth</th>
<th style="text-align: right;">RMSE</th>
<th style="text-align: right;">R²</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: right;">2</td>
<td style="text-align: right;">33.4</td>
<td style="text-align: right;">0.640</td>
</tr>
<tr class="even">
<td style="text-align: right;">6</td>
<td style="text-align: right;">30.1</td>
<td style="text-align: right;">0.708</td>
</tr>
<tr class="odd">
<td style="text-align: right;">10</td>
<td style="text-align: right;">35.4</td>
<td style="text-align: right;">0.596</td>
</tr>
</tbody>
</table>
</div>
</div>
</figure>
</div>
</div>
<p>La table parle d’elle-même : <code>max_depth =</code> <code>6</code> donne la meilleure RMSE et le meilleur R² sur ce jeu de validation. Mais est-ce qu’on peut vraiment lui faire confiance ?</p>
<p>Cette approche a deux limites importantes.</p>
<ol type="1">
<li><p><strong>Les résultats dépendent du split.</strong> Si on tombe sur un jeu de validation particulièrement facile ou difficile, la valeur de <code>max_depth</code> sélectionnée ne sera pas forcément la meilleure en général. Avec seulement 50 observations en validation ici, l’estimation est bruitée.</p></li>
<li><p><strong>On gaspille des données.</strong> 20% des données d’entraînement ne servent qu’à évaluer l’hyperparamètre. Sur un petit jeu de données, c’est un luxe qu’on ne peut pas toujours se permettre.</p></li>
</ol>
<p>La solution à ces deux problèmes, c’est la <strong>validation croisée</strong>, <a href="../../posts/202604-cross-validation/cross-validation.html">présentée en détail dans le tutoriel précédent</a>. Plutôt que de réserver un seul jeu de validation fixe, on évalue le modèle sur plusieurs découpages successifs des données d’entraînement et on moyenne les scores. Plus stable, plus fiable, et on utilise toutes les données disponibles.</p>
<p>Et plutôt que de tester trois valeurs à la main, on peut automatiser ça sur une grille de valeurs candidates. C’est ce qu’on appelle le <strong>grid search avec CV</strong>.</p>
</section>
<section id="grid-search-avec-validation-croisée" class="level2 section-3">
<h2 data-anchor-id="grid-search-avec-validation-croisée">Grid search avec validation croisée</h2>
<p>Le <em>grid search</em> (recherche en grille) fonctionne exactement comme son nom l’indique : on dresse une liste de valeurs candidates, on les teste toutes en validation croisée, et on retient la meilleure. Ni plus, ni moins.</p>
<p>On va tester toutes les valeurs de <code>max_depth</code> de 2 à 20 par pas de 2. Pour chaque valeur, le modèle est entraîné et évalué sur 5 folds de validation croisée. Le score final est la moyenne des 5 évaluations.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-tuning" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-tuning-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202604-hyperparametres/hyperparametres_files/figure-html/fig-tuning-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-tuning-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: RMSE et R² en validation croisée selon la profondeur maximale de l’arbre. La zone grisée représente ±1 erreur standard. En dessous de l’optimum, le modèle est trop simple et rate des patterns importants. Au-delà, il commence à mémoriser les données d’entraînement.
</figcaption>
</figure>
</div>
</div>
</div>
<p>La courbe montre clairement un optimum : en dessous, le modèle est trop rigide pour capturer la relation entre les variables ; au-delà, il s’adapte trop aux données d’entraînement et perd en généralisation. On retient la valeur qui minimise le RMSE, ici <code>max_depth</code> = <code>4</code>.</p>
<p>Pour rendre ça concret, voici les surfaces de décision pour trois valeurs de <code>max_depth</code> : trop faible, optimale, trop élevée (Figure&nbsp;5).</p>
<div id="fig-surfaces" class="quarto-float quarto-figure quarto-figure-center" style="border-radius: 12px;">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-surfaces-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202604-hyperparametres/surfaces.png" class="img-fluid figure-img" style="border-radius: 12px;;width:70.0%">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-surfaces-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: Surfaces de décision pour trois valeurs de <code>max_depth</code>. En haut, l’arbre est trop simple. Au milieu, il capture bien la structure des données. En bas, il découpe l’espace en cases trop petites et mémorise le bruit.
</figcaption>
</figure>
</div>
<p>On entraîne enfin le modèle final avec la profondeur optimale sur l’ensemble du jeu d’entraînement, et on l’évalue une seule fois sur le jeu de test.</p>
<section id="quelques-règles-à-garder-en-tête" class="level3">
<h3 data-anchor-id="quelques-règles-à-garder-en-tête">Quelques règles à garder en tête</h3>
<p><strong>Le jeu de test reste intouché pendant tout le grid search</strong>. L’utiliser pour choisir <code>max_depth</code> reviendrait à tricher : les performances mesurées seraient trop optimistes.</p>
<p>Le coût computationnel peut grimper vite. Ici on a optimisé un seul hyperparamètre. Dans les faits, il y en a souvent 3 ou 4 à optimiser simultanément. Si on teste 4 valeurs pour 4 hyperparamètres, on se retrouve avec 4⁴ = 256 configurations à tester, sur chaque fold de la CV. Avec 5 folds, cela reviendrait à entraîner 1280 modèles !</p>
<p>Il existe des alternatives au grid search exhaustif. La <strong>recherche aléatoire</strong> (<em>random search</em>) tire des combinaisons au hasard : souvent presque aussi efficace pour un coût bien moindre. La <strong>recherche bayésienne</strong> cible intelligemment les zones prometteuses. Ces méthodes, ainsi que d’autres hyperparamètres et modèles bien plus puissants, font partie de ce qu’on explore ensemble dans la formation.</p>
<div class="note-box">
<p><strong>Envie d’aller plus loin ?</strong></p>
<p>Le tuning des hyperparamètres, les forêts aléatoires, le boosting, les réseaux de neurones… Tout le pipeline, de la séparation des données aux architectures avancées, on le fait ensemble en R pendant 5 jours intensifs et 100% pratiques dans la formation <a href="../../formations/machine_learning_avec_r.html">Machine Learning avec R</a>.</p>
</div>
<div class="cta-group center">
<p><a href="../../formations/machine_learning_avec_r.html" class="cta-button-outline">Découvrir la formation ML avec R</a></p>
<p><a href="../../contact.html" class="cta-button">Me contacter</a></p>
</div>
</section>
</section>



 ]]></description>
  <category>Tutoriels R</category>
  <category>Machine Learning</category>
  <guid>https://www.pyranhia.eu/posts/202604-hyperparametres/hyperparametres.html</guid>
  <pubDate>Mon, 27 Apr 2026 22:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202604-hyperparametres/trees.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Validation croisée : évaluer son modèle sans se mentir</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202604-cross-validation/cross-validation.html</link>
  <description><![CDATA[ 





<section id="validation-croisée-évaluer-son-modèle-sans-se-mentir" class="level1 section-full section-padded">
<h1>Validation croisée : évaluer son modèle sans se mentir</h1>
<p><span class="section-subtitle">Le train/test split ne suffit pas toujours. La cross-validation, si.</span></p>
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>Cet article fait suite au tutoriel <a href="../../posts/202601-train-test-split/train-test-split.html">Train/test split : séparer ses données pour le machine learning</a>. Si tu n’es pas encore à l’aise avec le principe de séparation train/test, je te conseille de le lire d’abord.</p>
</div>
</div>
</div>
</section>
<section id="le-problème-du-split-unique" class="level2 section-1">
<h2 data-anchor-id="le-problème-du-split-unique">Le problème du split unique</h2>
<p>Dans un <a href="../../posts/202601-train-test-split/train-test-split.html">tutoriel précédent</a>, on a vu pourquoi il ne faut jamais évaluer un modèle sur les données qui ont servi à l’entraîner. La solution : séparer les données en un jeu d’entraînement et un jeu de test, entraîner le modèle sur le premier, évaluer sur le second.</p>
<p>C’est un bon point de départ. Mais sur les petits jeux de données, cette approche révèle un défaut gênant : <strong>le score obtenu dépend du split choisi</strong>.</p>
<p>Pour l’illustrer, on va travailler sur le jeu de données <code>Sonar</code> du package <a href="https://cran.r-project.org/package=mlbench">mlbench</a>. Il contient 208 enregistrements sonar collectés en milieu marin : l’objectif est de distinguer les roches (R) des mines sous-marines (M) à partir de 60 variables acoustiques. Une tâche difficile, avec peu de données : le cadre idéal pour mettre en évidence le problème.</p>
<p>On entraîne un arbre de décision avec un split 80/20 stratifié. Premier essai :</p>
<div style="display: flex; justify-content: center; margin-bottom: 0.5em;">
<div class="note-box" style="text-align: center; padding: 0.5em 1.5em; width: fit-content;">
<p><span style="font-size: 1.5em;">accuracy : <strong>74.4%</strong></span></p>
</div>
</div>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.pyranhia.eu/posts/202604-cross-validation/cross-validation_files/figure-html/plot-cm-1-1.png" class="img-fluid figure-img" width="768"></p>
<figcaption>Matrice de confusion : le modèle distingue bien les mines (M) des roches (R).</figcaption>
</figure>
</div>
</div>
</div>
<p>Pas mal ! Le lendemain, on rouvre le script et on relance. Résultat :</p>
<div style="display: flex; justify-content: center; margin-bottom: 0.5em;">
<div class="note-box" style="text-align: center; padding: 0.5em 1.5em; width: fit-content;">
<p><span style="font-size: 1.5em;">accuracy : <strong>62.8%</strong></span></p>
</div>
</div>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.pyranhia.eu/posts/202604-cross-validation/cross-validation_files/figure-html/plot-cm-2-1.png" class="img-fluid figure-img" width="768"></p>
<figcaption>Matrice de confusion : cette fois, les erreurs se multiplient.</figcaption>
</figure>
</div>
</div>
</div>
<p>Aïe. Même modèle, mêmes données. Et pourtant, l’accuracy chute de <strong>74.4%</strong> à <strong>62.8%</strong>. Comment expliquer cette différence ?</p>
<p>Le coupable : le <strong>hasard du tirage</strong>. À chaque fois qu’on exécute <code>initial_split()</code> sans précaution, les observations sont réparties aléatoirement entre train et test. On ne tombe pas sur le même split d’une exécution à l’autre. C’est là qu’intervient <code>set.seed()</code> : fixer le <em>seed</em> garantit que le tirage sera identique à chaque exécution, et donc que les résultats sont reproductibles.</p>
<p>Mais même avec un seed fixé, le problème de fond reste entier : <strong>le score dépend du split choisi</strong>. Pour s’en convaincre, répétons l’opération avec 10 seeds différents :</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.pyranhia.eu/posts/202604-cross-validation/cross-validation_files/figure-html/plot-10-splits-1.png" class="img-fluid figure-img" width="1344"></p>
<figcaption>Accuracy sur le test set selon le split choisi : de 58.1% à 79.1% selon le hasard du tirage.</figcaption>
</figure>
</div>
</div>
</div>
<p>Avec seulement ~40 observations en test, il suffit que quelques cas difficiles tombent du mauvais côté pour faire basculer le score. Et c’est là qu’on se retrouve face à un dilemme :</p>
<div class="note-box">
<p><strong>Le dilemme du petit jeu de données</strong></p>
<p>Augmenter la proportion de test stabilise l’estimation, mais réduit les données d’entraînement et dégrade le modèle. Réduire la proportion de test améliore l’entraînement, mais rend l’évaluation instable. Sur un petit jeu, on ne peut pas gagner sur les deux tableaux à la fois.</p>
</div>
<p>La <strong>validation croisée</strong> sort de cette impasse. Au lieu d’un seul découpage, elle en réalise plusieurs, entraîne le modèle à chaque fois, et agrège les résultats. On obtient une estimation stable, sans sacrifier de données d’entraînement.</p>
</section>
<section id="la-k-fold-cross-validation-avec-rsample" class="level2 section-2">
<h2 data-anchor-id="la-k-fold-cross-validation-avec-rsample">La k-fold cross-validation avec <code>rsample</code></h2>
<section id="le-principe" class="level3">
<h3 data-anchor-id="le-principe">Le principe</h3>
<p>Au lieu d’un seul split, on découpe les données en <strong>k sous-ensembles de taille égale</strong>, appelés <em>folds</em>. À chaque itération, un fold différent joue le rôle de jeu de test, et les k-1 autres servent à l’entraînement. On entraîne donc <strong>k modèles</strong>, et on obtient <strong>k scores</strong> qu’on agrège ensuite.</p>
<iframe src="cv-widget/index.html" title="Widget interactif : navigation entre les folds d'une k-fold cross-validation" width="100%" height="1200" style="border:none;border-radius:12px;"></iframe>
<p><span style="color: #007B6F;"><em>Naviguez entre les itérations pour voir comment les folds se succèdent. Chaque carré représente une observation.</em></span></p>
<p>L’avantage clé : à la fin des k itérations, <strong>chaque observation a servi exactement une fois comme donnée de test</strong>. On ne gaspille rien, et l’estimation des performances n’est plus tributaire d’un seul tirage.</p>
<div class="note-box">
<p><strong>À quoi sert la CV exactement ?</strong></p>
<p>La validation croisée est avant tout un outil d’<strong>évaluation</strong> : elle fournit une estimation fiable de la capacité de généralisation d’un modèle. Une fois l’évaluation terminée, deux approches sont possibles pour prédire de nouvelles données : réentraîner un unique modèle sur toutes les données disponibles (le plus courant), ou moyenner les prédictions des k modèles pour plus de robustesse.</p>
</div>
</section>
<section id="implémenter-la-cv-avec-vfold_cv-et-fit_resamples" class="level3">
<h3 data-anchor-id="implémenter-la-cv-avec-vfold_cv-et-fit_resamples">Implémenter la CV avec <code>vfold_cv()</code> et <code>fit_resamples()</code></h3>
<p>Avec <code>rsample</code>, on crée les folds avec <code>vfold_cv()</code>. Comme pour le train/test split, on stratifie sur la variable cible.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 5 folds stratifiés</span></span>
<span id="cb1-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">set.seed</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2025</span>)</span>
<span id="cb1-3">folds <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vfold_cv</span>(Sonar, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">v =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">strata =</span> Class)</span>
<span id="cb1-4"></span>
<span id="cb1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Définition du modèle</span></span>
<span id="cb1-6">modele <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">decision_tree</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb1-7">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">set_engine</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rpart"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb1-8">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">set_mode</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"classification"</span>)</span>
<span id="cb1-9"></span>
<span id="cb1-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># fit_resamples() entraîne et évalue sur chaque fold automatiquement</span></span>
<span id="cb1-11">resultats_cv <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fit_resamples</span>(</span>
<span id="cb1-12">  modele,</span>
<span id="cb1-13">  Class <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span> .,</span>
<span id="cb1-14">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">resamples =</span> folds,</span>
<span id="cb1-15">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">metrics   =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">metric_set</span>(accuracy)</span>
<span id="cb1-16">)</span>
<span id="cb1-17"></span>
<span id="cb1-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Score moyen et erreur standard sur les 5 folds</span></span>
<span id="cb1-19"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">collect_metrics</span>(resultats_cv)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code># A tibble: 1 × 6
  .metric  .estimator  mean     n std_err .config        
  &lt;chr&gt;    &lt;chr&gt;      &lt;dbl&gt; &lt;int&gt;   &lt;dbl&gt; &lt;chr&gt;          
1 accuracy binary     0.731     5  0.0329 pre0_mod0_post0</code></pre>
</div>
</div>
<p><code>collect_metrics()</code> retourne la moyenne des scores sur les k folds et l’erreur standard (<code>std_err</code>), qui quantifie la précision de cette estimation : plus elle est faible, plus la moyenne est fiable.</p>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>CV sur le dataset complet
</div>
</div>
<div class="callout-body-container callout-body">
<p>Dans cet exemple, on applique la CV directement sur <code>Sonar</code> en entier : il n’y a pas de test set mis de côté dès le début. C’est suffisant pour évaluer la capacité de généralisation. Mais quand on veut aussi <strong>optimiser des hyperparamètres</strong>, on applique la CV uniquement sur le jeu d’entraînement et on réserve le test set pour l’évaluation finale. C’est ce qu’on verra dans le prochain tutoriel sur le tuning.</p>
</div>
</div>
</section>
<section id="combien-de-folds" class="level3">
<h3 data-anchor-id="combien-de-folds">Combien de folds ?</h3>
<p>Les valeurs usuelles sont <code>v = 5</code> ou <code>v = 10</code>. Avec k = 5, chaque modèle est entraîné sur 80% des données et évalué sur 20% : un bon compromis entre temps de calcul et précision de l’estimation. On passe à <code>v = 10</code> quand on veut serrer l’estimation au maximum et qu’on a le temps de calcul.</p>
</section>
</section>
<section id="variantes-pour-aller-plus-loin" class="level2 section-3">
<h2 data-anchor-id="variantes-pour-aller-plus-loin">Variantes pour aller plus loin</h2>
<section id="la-k-fold-répétée" class="level3">
<h3 data-anchor-id="la-k-fold-répétée">La k-fold répétée</h3>
<p>La <em>repeated k-fold cross-validation</em> consiste à <strong>répéter la k-fold plusieurs fois</strong> avec des découpages aléatoires différents. Par exemple, une 5-fold répétée 3 fois produit 15 évaluations (5 folds × 3 répétitions) dont on agrège les scores. L’intérêt : réduire encore la variance de l’estimation, surtout sur les petits jeux de données. Le coût : multiplier le temps de calcul par le nombre de répétitions. Dans <code>rsample</code>, il suffit d’ajouter l’argument <code>repeats</code> à <code>vfold_cv()</code>.</p>
</section>
<section id="le-leave-one-out" class="level3">
<h3 data-anchor-id="le-leave-one-out">Le Leave-One-Out</h3>
<p>Dans cette variante extrême, chaque observation constitue à son tour le jeu de test : on entraîne autant de modèles qu’il y a d’observations. L’estimation est quasi sans biais, mais le coût computationnel est prohibitif dès que le jeu dépasse quelques centaines d’observations. À réserver aux très petits jeux de données, et peu utilisé en pratique.</p>
</section>
<section id="cv-spatiale-et-temporelle" class="level3">
<h3 data-anchor-id="cv-spatiale-et-temporelle">CV spatiale et temporelle</h3>
<p>Si vos données ont une structure spatiale ou temporelle, un découpage aléatoire en folds introduit une fuite d’information, exactement comme pour le train/test split. Les packages <a href="https://spatialsample.tidymodels.org/">spatialsample</a> et <a href="https://rsample.tidymodels.org/">rsample</a> proposent des variantes adaptées : <code>spatial_clustering_cv()</code> pour les données géolocalisées, <code>sliding_window()</code> pour les séries temporelles. Le principe reste le même, seule la façon de construire les folds change.</p>
</section>
<section id="et-ensuite-le-tuning-des-hyperparamètres" class="level3">
<h3 data-anchor-id="et-ensuite-le-tuning-des-hyperparamètres">Et ensuite : le tuning des hyperparamètres</h3>
<p>La validation croisée est aussi le cadre standard pour <strong>optimiser les hyperparamètres</strong> d’un modèle : plutôt que d’évaluer un seul modèle, on évalue toute une série de configurations et on retient la meilleure. C’est ce qu’on appelle le <em>grid search</em> avec CV. C’est précisément l’objet d’un prochain tutoriel.</p>
<div class="note-box">
<p><strong>Envie d’aller plus loin ?</strong></p>
<p>La validation croisée, le tuning des hyperparamètres, les forêts aléatoires, le boosting, les réseaux de neurones… Tout le pipeline, de la séparation des données aux architectures avancées, on le fait ensemble en R pendant 5 jours intensifs et 100% pratiques dans ma formation <a href="../../formations/machine_learning_avec_r.html">Machine Learning avec R</a>.</p>
</div>
<div class="cta-group center">
<p><a href="../../formations/machine_learning_avec_r.html" class="cta-button-outline">Découvrir la formation ML avec R</a></p>
<p><a href="../../contact.html" class="cta-button">Me contacter</a></p>
</div>
</section>
</section>



 ]]></description>
  <category>Tutoriels R</category>
  <category>Machine Learning</category>
  <guid>https://www.pyranhia.eu/posts/202604-cross-validation/cross-validation.html</guid>
  <pubDate>Sun, 12 Apr 2026 22:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202604-cross-validation/cross-validation.png" medium="image" type="image/png" height="72" width="144"/>
</item>
<item>
  <title>Pourquoi le graphique en camembert est (presque) toujours une mauvaise idée</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202603-camembert/camembert.html</link>
  <description><![CDATA[ 





<section id="pourquoi-le-graphique-en-camembert-est-presque-toujours-une-mauvaise-idée" class="level1 section-full section-padded">
<h1>Pourquoi le graphique en camembert est (presque) toujours une mauvaise idée</h1>
<p><span class="section-subtitle">Un classique du mauvais choix de visualisation, et comment y remédier avec ggplot2.</span></p>
</section>
<div class="section-1">
<section id="le-camembert-un-graphique-trop-populaire-pour-son-propre-bien" class="level2">
<h2 data-anchor-id="le-camembert-un-graphique-trop-populaire-pour-son-propre-bien">Le camembert : un graphique trop populaire pour son propre bien</h2>
<p>Ouvrez le premier rapport PowerPoint venu, et vous en trouverez un. Le camembert (<em>pie chart</em> en anglais) est partout : dans les bilans annuels, les sondages, les dashboards d’entreprise. Il est intuitif à créer, familier pour le lecteur, et donne l’impression de synthétiser une information complexe en un coup d’œil.</p>
<p>Pourtant, les spécialistes de la visualisation de données le déconseillent quasi-unanimement. Pourquoi ? Parce que notre cerveau est particulièrement mauvais pour <strong>comparer des surfaces et des angles</strong>. Et c’est précisément ce que le camembert lui demande de faire.</p>
<div class="note-box">
<p><strong>Ce que cet article ne dit pas</strong></p>
<p>Il n’existe quasiment aucun cas où un camembert est le <em>meilleur</em> choix possible. Mais s’il est déjà dans votre rapport et que vous n’avez pas le temps de le refaire, le monde ne s’arrêtera pas. L’objectif ici, c’est de comprendre pourquoi il peut poser problème, et d’avoir les outils pour faire mieux.</p>
</div>
</section>
<section id="notre-cerveau-aime-les-longueurs-pas-les-angles" class="level2">
<h2 data-anchor-id="notre-cerveau-aime-les-longueurs-pas-les-angles">Notre cerveau aime les longueurs, pas les angles</h2>
<p>En dataviz, on distingue plusieurs <strong>encodages visuels</strong> : les façons dont on représente une valeur numérique. Voici les principaux, du plus précis au moins précis pour la perception humaine :</p>
<ol type="1">
<li><strong>Position sur un axe commun</strong> → très précis</li>
<li><strong>Longueur</strong> → précis</li>
<li><strong>Surface</strong> → approximatif</li>
<li><strong>Angle</strong> → approximatif</li>
<li><strong>Couleur / teinte</strong> → approximatif</li>
</ol>
<p>Le graphique en barres utilise la longueur et la position sur un axe. Le camembert, lui, utilise l’<strong>angle</strong> (et la surface des portions). On lui demande donc d’exploiter deux des encodages les moins précis qui soient.</p>
<p>La conséquence est immédiate : il est très difficile de distinguer deux portions proches, et quasi-impossible de comparer des portions non adjacentes.</p>
<section id="un-exemple-concret" class="level3">
<h3 data-anchor-id="un-exemple-concret">Un exemple concret</h3>
<p>Prenons des données fictives : la répartition de cinq espèces dans un inventaire marin.</p>
<p>Voici ce que donne un camembert avec <code>ggplot2</code>.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(especes, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> proportion, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> espece)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb1-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_col</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">width =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"white"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb1-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">coord_polar</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">theta =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"y"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb1-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_fill_brewer</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">palette =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Set3"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb1-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Espèce"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb1-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_void</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb1-7">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">legend.position =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>)</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-camembert" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-camembert-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202603-camembert/camembert_files/figure-html/fig-camembert-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1152">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-camembert-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Répartition de 5 espèces, version camembert
</figcaption>
</figure>
</div>
</div>
</div>
<p>Maintenant, répondez sans regarder les valeurs : quelle différence y a-t-il entre Sardine et Anchois ? Entre Thon rouge et Daurade ? Difficile à dire, n’est-ce pas, et pourtant les différences sont réelles (32% vs 28%, et 14% vs 8%).</p>
</section>
<section id="testez-votre-perception" class="level3">
<h3 data-anchor-id="testez-votre-perception">Testez votre perception</h3>
<p>Les quatre graphiques ci-dessous représentent chacun un ensemble de cinq valeurs comprises entre 43 et 61, sans valeur qui se démarque franchement. Dans chaque graphique, les lettres A à E sont réassignées indépendamment : A n’est pas la même valeur d’un graphique à l’autre. Pour chacun, essayez de classer les lettres du plus grand au plus petit avant de regarder la réponse.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-test-perception" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-test-perception-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202603-camembert/camembert_files/figure-html/fig-test-perception-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1920">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-test-perception-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Les mêmes cinq valeurs, quatre encodages différents, mais les lettres sont réassignées dans chaque graphique. Pour chacun, classez du plus grand au plus petit.
</figcaption>
</figure>
</div>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center collapsed" data-bs-toggle="collapse" data-bs-target=".callout-1-contents" aria-controls="callout-1" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Astuce</span>Réponse
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-1" class="callout-1-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p>Les valeurs de référence, du plus grand au plus petit : 61 &gt; 57 &gt; 52 &gt; 47 &gt; 43.</p>
<ul>
<li><strong>Position :</strong> A &gt; E &gt; D &gt; C &gt; B</li>
<li><strong>Longueur :</strong> D &gt; B &gt; E &gt; A &gt; C</li>
<li><strong>Surface :</strong> D &gt; A &gt; E &gt; B &gt; C</li>
<li><strong>Angle :</strong> B &gt; E &gt; C &gt; A &gt; D</li>
</ul>
</div>
</div>
</div>
</section>
</section>
</div>
<div class="section-2">
<section id="trois-problèmes-concrets-du-camembert" class="level2">
<h2 data-anchor-id="trois-problèmes-concrets-du-camembert">Trois problèmes concrets du camembert</h2>
<section id="comparer-des-portions-adjacentes-est-difficile" class="level3">
<h3 data-anchor-id="comparer-des-portions-adjacentes-est-difficile">1. Comparer des portions adjacentes est difficile</h3>
<p>Deux parts de tailles proches se ressemblent visuellement, même si les valeurs diffèrent de plusieurs points. Notre œil ne distingue pas facilement 28% d’un 32% dans un angle.</p>
</section>
<section id="comparer-des-portions-non-adjacentes-est-encore-plus-difficile" class="level3">
<h3 data-anchor-id="comparer-des-portions-non-adjacentes-est-encore-plus-difficile">2. Comparer des portions non adjacentes est encore plus difficile</h3>
<p>Si les portions Anchois et Thon rouge ne sont pas côte à côte, les comparer devient un exercice mental : il faut mentalement “déplacer” les portions pour les superposer. Avec un graphique en barres, c’est immédiat.</p>
</section>
<section id="les-camemberts-avec-beaucoup-de-catégories-sont-illisibles" class="level3">
<h3 data-anchor-id="les-camemberts-avec-beaucoup-de-catégories-sont-illisibles">3. Les camemberts avec beaucoup de catégories sont illisibles</h3>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">set.seed</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span></code></pre></div></div>
</div>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 10 catégories fictives</span></span>
<span id="cb3-2">beaucoup <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tibble</span>(</span>
<span id="cb3-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">categorie =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">paste</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Catégorie"</span>, LETTERS[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>]),</span>
<span id="cb3-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">valeur =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sample</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">18</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">13</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>))</span>
<span id="cb3-5">)</span>
<span id="cb3-6"></span>
<span id="cb3-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(beaucoup, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> valeur, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> categorie)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb3-8">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_col</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">width =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"white"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb3-9">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">coord_polar</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">theta =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"y"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb3-10">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_fill_brewer</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">palette =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Set3"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb3-11">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb3-12">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_void</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb3-13">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">legend.position =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>)</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-camembert-illisible" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-camembert-illisible-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202603-camembert/camembert_files/figure-html/fig-camembert-illisible-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-camembert-illisible-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Un camembert avec 10 catégories, très difficile à lire
</figcaption>
</figure>
</div>
</div>
</div>
<p>Dès que l’on dépasse 4 ou 5 catégories, et a fortiori quand les valeurs ne sont pas ordonnées, le camembert devient un puzzle coloré sans grande valeur informative.</p>
</section>
</section>
<section id="quand-le-camembert-est-à-peu-près-acceptable" class="level2">
<h2 data-anchor-id="quand-le-camembert-est-à-peu-près-acceptable">Quand le camembert est (à peu près) acceptable</h2>
<p>Le camembert a une vraie qualité (en dehors d’être délicieux sous sa forme fromagère) souvent ignorée : notre système visuel est particulièrement sensible aux <strong>repères angulaires canoniques</strong> : les lignes horizontales, verticales et les angles droits. Sur un camembert, cela se traduit par une capacité à détecter facilement si une part franchit le seuil des <strong>50%</strong> (la ligne de séparation coupe le cercle en deux moitiés égales) ou des <strong>25%</strong> (angle droit, un quart du disque).</p>
<p>Dans ces cas précis, le camembert peut même battre le graphique en barres. Regardons ça sur un exemple.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-seuil-50" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-seuil-50-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202603-camembert/camembert_files/figure-html/fig-seuil-50-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1152">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-seuil-50-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: Autour de 50%, sur le camembert, le franchissement du seuil se détecte visuellement même à 51/49. Sur les barres, les deux rectangles sont quasi-identiques.
</figcaption>
</figure>
</div>
</div>
</div>
<p>À exactement 50%, les deux graphiques sont lisibles. À 51/49 en revanche, la ligne de partage dépasse légèrement le centre sur le camembert : on détecte immédiatement qu’un camp a franchi la majorité. Sur les barres, les deux rectangles sont devenus quasi-identiques sans lire les valeurs.</p>
<p>Le même phénomène se produit autour de 25%, grâce à la reconnaissance de l’angle droit. Même à 23%, on perçoit intuitivement “environ un quart” grâce au repère de l’angle droit. Sur les barres, cette intuition est moins immédiate.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-seuil-25" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-seuil-25-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202603-camembert/camembert_files/figure-html/fig-seuil-25-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1152">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-seuil-25-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: Autour de 25%, l’angle droit sert de repère mental, pas de valeur exacte à atteindre. À 23%, on reconnaît encore clairement ‘environ un quart’ sur le camembert.
</figcaption>
</figure>
</div>
</div>
</div>
<p>À 25%, l’angle droit est parfaitement reconnaissable. À 23%, le repère mental “un quart” reste lisible sur le camembert, alors que les barres donnent surtout l’impression de deux rectangles de hauteurs différentes, sans ancrage intuitif.</p>
<div class="note-box">
<p><strong>Règle pratique</strong></p>
<p>Le camembert est justifié si :</p>
<ol type="1">
<li>vous avez <strong>2 à 3 catégories maximum</strong></li>
<li>votre message est centré sur le franchissement d’un seuil à 50%, 25% ou 75%</li>
<li>les valeurs tombent <em>vraiment</em> proches de ces repères.</li>
</ol>
<p>Dans tous les autres cas, les barres restent plus efficaces.</p>
</div>
<p>On sait maintenant quand éviter le camembert, et les rares cas où il se justifie. Reste à savoir par quoi le remplacer.</p>
</section>
</div>
<div class="section-3">
<section id="ce-quil-faut-utiliser-à-la-place" class="level2">
<h2 data-anchor-id="ce-quil-faut-utiliser-à-la-place">Ce qu’il faut utiliser à la place</h2>
<section id="le-graphique-en-barres-simple-efficace-toujours-juste" class="level3">
<h3 data-anchor-id="le-graphique-en-barres-simple-efficace-toujours-juste">Le graphique en barres : simple, efficace, toujours juste</h3>
<p>Le graphique en barres (<code>geom_col()</code> ou <code>geom_bar()</code> dans ggplot2) utilise la <strong>longueur</strong> comme encodage, l’un des plus précis pour notre perception. Reprenons nos données d’espèces :</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1">especes <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb4-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">espece =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fct_reorder</span>(espece, proportion)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Tri par valeur</span></span>
<span id="cb4-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> proportion, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> espece, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> espece)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb4-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_col</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">show.legend =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb4-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_fill_brewer</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">palette =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Set3"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb4-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_x_continuous</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">limits =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span>)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb4-7">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Proportion (%)"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">title =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Répartition des espèces observées"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb4-8">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_classic</span>()</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-barres-ordre" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-barres-ordre-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202603-camembert/camembert_files/figure-html/fig-barres-ordre-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1152">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-barres-ordre-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;6: Les mêmes données, version barres triées
</figcaption>
</figure>
</div>
</div>
</div>
<p>Avec ce graphique, la hiérarchie est immédiate : Sardine &gt; Anchois &gt; Maquereau &gt; Thon rouge &gt; Daurade. Les différences entre catégories sont lisibles d’un coup d’œil.</p>
<div class="note-box">
<p><strong>Astuce <code>fct_reorder()</code></strong></p>
<p>Trier les barres par valeur (plutôt que par ordre alphabétique) est presque toujours une bonne idée. Cela met en évidence la hiérarchie naturelle des données. On utilise <code>fct_reorder()</code> du package <code>forcats</code> (inclus dans le <code>tidyverse</code>) pour ça.</p>
</div>
</section>
<section id="et-si-je-veux-vraiment-montrer-des-proportions" class="level3">
<h3 data-anchor-id="et-si-je-veux-vraiment-montrer-des-proportions">Et si je veux vraiment montrer des proportions ?</h3>
<p>Si votre message est vraiment centré sur les proportions (par exemple, montrer qu’une catégorie représente “les deux tiers du total”), le graphique en barres empilées à 100% est une alternative honnête :</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1">especes <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb5-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">espece =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fct_reorder</span>(espece, proportion, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">.desc =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb5-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> proportion, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Composition"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> espece)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb5-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_col</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">position =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fill"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"white"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb5-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_x_continuous</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">labels =</span> scales<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>percent) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb5-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_fill_brewer</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">palette =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Set3"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb5-7">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Espèce"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">title =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Composition de l'inventaire"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb5-8">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_classic</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb5-9">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span>(</span>
<span id="cb5-10">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">axis.text.y =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">element_blank</span>(), </span>
<span id="cb5-11">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">axis.ticks.y =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">element_blank</span>(), </span>
<span id="cb5-12">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">panel.grid =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">element_blank</span>()</span>
<span id="cb5-13">  )</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-barres-empilees" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-barres-empilees-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202603-camembert/camembert_files/figure-html/fig-barres-empilees-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-barres-empilees-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;7: Barres empilées à 100%, une alternative aux camemberts
</figcaption>
</figure>
</div>
</div>
</div>
<p>C’est plus lisible qu’un camembert et on conserve l’idée de “parts d’un tout”.</p>
<div class="note-box">
<p><strong>Et le donut chart dans tout ça ?</strong></p>
<p>Le donut chart, c’est un camembert avec un trou au milieu : Homer Simpson approuverait la forme, pas forcément le contenu. Il souffre des mêmes limites de perception, avec un avantage : on peut glisser un chiffre clé au centre, ce qui peut avoir du sens dans un dashboard. En dehors de ce cas, les barres font mieux. Et contrairement au vrai donut, pas de glaçage pour se consoler.</p>
</div>
</section>
</section>
<section id="récapitulatif-quand-utiliser-quoi" class="level2">
<h2 data-anchor-id="récapitulatif-quand-utiliser-quoi">Récapitulatif : quand utiliser quoi ?</h2>
<table class="caption-top table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Situation</th>
<th>Graphique recommandé</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Comparer des catégories entre elles</td>
<td>Barres triées (<code>geom_col</code>)</td>
</tr>
<tr class="even">
<td>Montrer une composition (proportions)</td>
<td>Barres empilées 100%</td>
</tr>
<tr class="odd">
<td>Montrer une évolution dans le temps</td>
<td>Ligne (<code>geom_line</code>)</td>
</tr>
<tr class="even">
<td>Franchir un seuil à 50%, 25% ou 75%</td>
<td>Camembert (si vraiment pile sur le repère)</td>
</tr>
</tbody>
</table>
<p><em>Le camembert au four avec un filet de miel, en revanche, reste toujours recommandé.</em></p>
<section id="pour-aller-plus-loin" class="level3">
<h3 data-anchor-id="pour-aller-plus-loin">Pour aller plus loin</h3>
<div id="refs" class="references csl-bib-body hanging-indent">
<div id="ref-cleveland1984Graphical" class="csl-entry">
Cleveland, William S., et Robert McGill. 1984. <span>«&nbsp;Graphical <span>Perception</span>: <span>Theory</span>, <span>Experimentation</span>, and <span>Application</span> to the <span>Development</span> of <span>Graphical Methods</span>&nbsp;»</span>. <em>Journal of the American Statistical Association</em> 79 (387): 531‑54. <a href="https://doi.org/10.1080/01621459.1984.10478080">https://doi.org/10.1080/01621459.1984.10478080</a>.
</div>
<div id="ref-skau2016Arcs" class="csl-entry">
Skau, Drew, et Robert Kosara. 2016. <span>«&nbsp;Arcs, <span>Angles</span>, or <span>Areas</span>: <span>Individual Data Encodings</span> in <span>Pie</span> and <span>Donut Charts</span>&nbsp;»</span>. <em>Computer Graphics Forum</em> 35 (3): 121‑30. <a href="https://doi.org/10.1111/cgf.12888">https://doi.org/10.1111/cgf.12888</a>.
</div>
</div>
<div class="cta-group center">
<p><a href="../../formations.html" class="cta-button-outline">Voir les formations à R</a></p>
<p><a href="mailto:contact@pyranhia.eu" class="cta-button">Me contacter</a></p>
</div>
</section>
</section>
</div>



 ]]></description>
  <category>Data viz</category>
  <category>Tutoriels R</category>
  <guid>https://www.pyranhia.eu/posts/202603-camembert/camembert.html</guid>
  <pubDate>Wed, 25 Mar 2026 23:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202603-camembert/camembert.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Analyser son questionnaire marché avec R : FAMD et profils types</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202603-retour-questionnaire/retour-questionnaire.html</link>
  <description><![CDATA[ 





<section id="analyser-son-questionnaire-marché-avec-r-famd-et-profils-types" class="level1 section-full section-padded">
<h1>Analyser son questionnaire marché avec R : FAMD et profils types</h1>
<p><span class="section-subtitle">Quand on lance une activité, on diffuse un questionnaire. On récupère des données. Et puis… on se demande quoi en faire vraiment.</span></p>
</section>
<div class="section-1">
<section id="pourquoi-pas-juste-des-fréquences" class="level2">
<h2 data-anchor-id="pourquoi-pas-juste-des-fréquences">Pourquoi pas juste des fréquences ?</h2>
<p>J’ai diffusé un questionnaire marché en février 2026 pour valider l’offre de formations PYRANHIA 38 réponses, 67 variables, des questions à choix multiples, des échelles, des catégories. Voici comment j’ai procédé, et ce que l’analyse m’a appris.</p>
<p>L’objectif n’était pas de savoir “combien de personnes utilisent R”, pour ça un simple comptage suffit. L’objectif était de comprendre <strong>qui</strong> répond, et si des profils cohérents se dégagent : des groupes de personnes qui se ressemblent sur plusieurs dimensions à la fois. Parce que ce n’est pas la même chose de proposer une formation à quelqu’un qui utilise déjà R et veut apprendre le ML, et à quelqu’un qui vient d’Excel et ne sait pas par où commencer.</p>
<p>Pour ça, il faut du clustering. Mais avant, encore faut-il représenter chaque répondant dans un espace numérique commun, et c’est là que ça se complique.</p>
</section>
<section id="le-problème-des-données-mixtes" class="level2">
<h2 data-anchor-id="le-problème-des-données-mixtes">Le problème des données mixtes</h2>
<p>Dans mes données : des booléennes (cases à cocher), des ordinales (niveau en R : jamais utilisé → expert), des catégorielles nominales (domaine, statut). Une ACP ne fonctionne que sur du quantitatif. Une ACM ne gère que du catégoriel. Aucune des deux ne convient.</p>
<p>La solution : la <strong>FAMD</strong> (Factorial Analysis of Mixed Data). Elle projette chaque répondant dans un espace de faible dimension où les distances ont un sens, quelle que soit la nature des variables d’origine. En R, c’est <code>FactoMineR</code> :</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1">famd <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">FAMD</span>(df_actif, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">sup.var =</span> idx_illus, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ncp =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">graph =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>)</span></code></pre></div></div>
<p>Un piège classique : les cases non cochées encodées en <code>NA</code> au lieu de <code>FALSE</code>. La FAMD impute les NA par la moyenne, ce qui n’a aucun sens pour des booléennes. À corriger avant :</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1">df_actif <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> df_actif <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb2-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">across</span>(</span>
<span id="cb2-3">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">contains</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"outil"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">contains</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"besoin"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">contains</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"motiv"</span>),</span>
<span id="cb2-4">    <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fct_na_value_to_level</span>(., <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"FALSE"</span>)</span>
<span id="cb2-5">  ))</span></code></pre></div></div>
</section>
<section id="combien-daxes-retenir" class="level2">
<h2 data-anchor-id="combien-daxes-retenir">Combien d’axes retenir ?</h2>
<p>Avec des données d’enquête hétérogènes, le screeplot ne tranche pas : la variance se distribue de façon homogène, sans coude net (Figure&nbsp;1).</p>
<div class="cell">
<div class="cell-output-display">
<div id="fig-screeplot" class="quarto-float quarto-figure quarto-figure-center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-screeplot-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202603-retour-questionnaire/retour-questionnaire_files/figure-html/fig-screeplot-1.png" class="img-fluid figure-img" width="672">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-screeplot-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Variance expliquée par dimension
</figcaption>
</figure>
</div>
</div>
</div>
<p>J’ai donc testé toutes les combinaisons axes × k et mesuré la <strong>silhouette</strong> (indicateur de qualité du clustering, de -1 à 1) pour chaque combinaison (Figure&nbsp;2).</p>
<div class="cell">
<div class="cell-output-display">
<div id="fig-heatmap-axes-k" class="quarto-float quarto-figure quarto-figure-center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-heatmap-axes-k-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202603-retour-questionnaire/retour-questionnaire_files/figure-html/fig-heatmap-axes-k-1.png" class="img-fluid figure-img" width="672">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-heatmap-axes-k-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Silhouette moyenne selon le nombre d’axes FAMD et le nombre de clusters k
</figcaption>
</figure>
</div>
</div>
</div>
<p>Le résultat est sans ambiguïté : <strong>3 axes dominent clairement</strong> quelle que soit la valeur de k. Au-delà, la silhouette chute : les dimensions supplémentaires apportent du bruit plus que de la structure discriminante.</p>
</section>
<section id="interprétation-des-axes" class="level2">
<h2 data-anchor-id="interprétation-des-axes">Interprétation des axes</h2>
<div class="tabset-margin-container"></div><div class="panel-tabset">
<ul class="nav nav-tabs"><li class="nav-item"><a class="nav-link active" id="tabset-1-1-tab" data-bs-toggle="tab" data-bs-target="#tabset-1-1" aria-controls="tabset-1-1" aria-selected="true" href="">Dim 1 × Dim 2</a></li><li class="nav-item"><a class="nav-link" id="tabset-1-2-tab" data-bs-toggle="tab" data-bs-target="#tabset-1-2" aria-controls="tabset-1-2" aria-selected="false" href="">Dim 2 × Dim 3</a></li></ul>
<div class="tab-content">
<div id="tabset-1-1" class="tab-pane active" aria-labelledby="tabset-1-1-tab">
<div class="cell">
<div class="cell-output-display">
<div id="fig-famd-12" class="quarto-float quarto-figure quarto-figure-center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-famd-12-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202603-retour-questionnaire/retour-questionnaire_files/figure-html/fig-famd-12-1.png" class="img-fluid figure-img" width="672">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-famd-12-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Projection des variables dans le plan FAMD (Dim 1 × Dim 2)
</figcaption>
</figure>
</div>
</div>
</div>
<p><strong>Dim 1 = profil et trajectoire R</strong> : oppose les utilisateurs avancés (besoins ML/DL, Python) aux débutants (intro R, transition open-source).</p>
<p><strong>Dim 2 = contexte socio-professionnel et budget</strong> : oppose les profils avec budget institutionnel et statut établi aux profils plus contraints financièrement.</p>
</div>
<div id="tabset-1-2" class="tab-pane" aria-labelledby="tabset-1-2-tab">
<div class="cell">
<div class="cell-output-display">
<div id="fig-famd-23" class="quarto-float quarto-figure quarto-figure-center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-famd-23-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202603-retour-questionnaire/retour-questionnaire_files/figure-html/fig-famd-23-1.png" class="img-fluid figure-img" width="672">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-famd-23-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: Projection des variables dans le plan FAMD (Dim 2 × Dim 3)
</figcaption>
</figure>
</div>
</div>
</div>
<p><strong>Dim 3 = besoins de base vs avancés</strong> : structure les répondants selon leurs besoins en visualisation, manipulation et statistiques de base.</p>
</div>
</div>
</div>
</section>
</div>
<div class="section-2">
<section id="pourquoi-le-k-means" class="level2">
<h2 data-anchor-id="pourquoi-le-k-means">Pourquoi le k-means ?</h2>
<p>Le k-means a mauvaise réputation pour son instabilité. Mais plutôt que de le supposer stable, je l’ai vérifié : 7 seeds différentes, résultats identiques à chaque fois.</p>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code># A tibble: 7 × 3
   seed silhouette tailles  
  &lt;dbl&gt;      &lt;dbl&gt; &lt;chr&gt;    
1     1      0.373 2-6-14-16
2    42      0.373 2-6-14-16
3   123      0.373 2-6-14-16
4   456      0.373 2-6-14-16
5   789      0.373 2-6-14-16
6  1234      0.373 2-6-14-16
7  5678      0.373 2-6-14-16</code></pre>
</div>
</div>
<p>Avec <code>nstart = 50</code> sur un échantillon de 38 individus, il est parfaitement reproductible. Et il donne des clusters mieux équilibrés en taille, ce qui est important pour des profils actionnables.</p>
</section>
<section id="les-4-profils" class="level2">
<h2 data-anchor-id="les-4-profils">Les 4 profils</h2>
<p>J’ai testé k = 3 à 6. k=4 offre le meilleur compromis : silhouette solide (0.373) et trois clusters bien dimensionnés. Le quatrième (n=2) est un signal faible structurel : les mêmes individus atypiques isolés quel que soit le k.</p>
<div class="cell">
<div class="cell-output-display">
<div id="fig-plan-famd" class="quarto-float quarto-figure quarto-figure-center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-plan-famd-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202603-retour-questionnaire/retour-questionnaire_files/figure-html/fig-plan-famd-1.png" class="img-fluid figure-img" width="672">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-plan-famd-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: Individus dans le plan FAMD (Dim 1 × Dim 2), colorés par cluster
</figcaption>
</figure>
</div>
</div>
</div>
<div class="cell">
<div class="cell-output-display">
<div id="fig-heatmap-bool-synth" class="quarto-float quarto-figure quarto-figure-center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-heatmap-bool-synth-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202603-retour-questionnaire/retour-questionnaire_files/figure-html/fig-heatmap-bool-synth-1.png" class="img-fluid figure-img" width="672">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-heatmap-bool-synth-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;6: Profil des clusters : variables booléennes les plus discriminantes
</figcaption>
</figure>
</div>
</div>
</div>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 12%">
<col style="width: 54%">
</colgroup>
<thead>
<tr class="header">
<th>Profil</th>
<th>n</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>C1</strong> : En route vers le ML</td>
<td>14</td>
<td>Niveau R avancé, besoins ML/DL, peu intéressé par les bases</td>
</tr>
<tr class="even">
<td><strong>C2</strong> : Le futur utilisateur</td>
<td>6</td>
<td>Excel/SAS, pas R, débutant, en transition vers l’open-source</td>
</tr>
<tr class="odd">
<td><strong>C3</strong> : Le praticien</td>
<td>16</td>
<td>Visu, manip, stats, Quarto, budget institutionnel</td>
</tr>
<tr class="even">
<td><strong>C4</strong> : Signal faible</td>
<td>2</td>
<td>Reconversion, CPF, débutant</td>
</tr>
</tbody>
</table>
<p>C1 et C3 constituent le cœur de cible immédiat. C2 a besoin d’une porte d’entrée accessible avant tout.</p>
</section>
<section id="ce-que-lanalyse-a-changé" class="level2">
<h2 data-anchor-id="ce-que-lanalyse-a-changé">Ce que l’analyse a changé</h2>
<p><strong>Le frein principal n’est pas le prix.</strong> Le coût arrive derrière le manque de temps, le fait de ne pas savoir par où commencer, et les formations trop théoriques. Un résultat qui oriente les décisions de format bien plus que la tarification.</p>
<p><strong>La sensibilité prix est structurelle, pas perçue.</strong> Les profils académiques plafonnent à des budgets contraints parce que leurs budgets de laboratoire sont limités, pas parce qu’ils ne valorisent pas la formation. Ce n’est pas le même problème.</p>
<p>C’est là toute la puissance des méthodes multivariées : C2 — 6 personnes qui utilisent Excel ou SAS, pas R, et qui sont en transition vers l’open-source — n’existe pas dans un graphique univarié. “Combien n’utilisent pas R ?” et “Qui n’utilise pas R, pourquoi, et vers quoi ils se dirigent ?” sont deux questions très différentes. La deuxième est celle qui oriente les décisions : ce profil n’a pas besoin d’une formation ML, il a besoin d’une porte d’entrée.</p>
</section>
<section id="les-limites" class="level2">
<h2 data-anchor-id="les-limites">Les limites</h2>
<p>38 répondants et ~20 variables actives, c’est exploratoire. La silhouette à 0.37 reflète l’absence de groupes très distincts dans les données, ce qui est attendu avec ce volume et cette hétérogénéité. Les profils sont des tendances utiles pour orienter la stratégie, pas des segments robustes. L’analyse sera à reconduire avec plus de données.</p>
</section>
</div>
<section id="et-maintenant" class="level2 section-3">
<h2 data-anchor-id="et-maintenant">Et maintenant ?</h2>
<p>J’avais prévu de développer la formation ML en premier, et ça, le questionnaire le confirme : c’est la plus demandée, notamment par C1. Bonne nouvelle, les plans ne changent pas.</p>
<p>En revanche, je pensais enchaîner avec “Introduction à R”. Surprise : c’est la formation <strong>Statistiques multivariées</strong> qui arrive en deuxième position dans les souhaits des répondants, devant les bases. Je change donc mes plans et c’est celle-ci que je prépare ensuite.</p>
<p>Le code de l’analyse est disponible sur le <a href="https://github.com/pyranhia/enquete-marche">dépôt GitHub du projet</a>. Les données collectées ne sont pas versionnées.</p>
<div class="note-box">
<p><span style="font-size: 1.1em;"><strong>Vos données méritent mieux qu’un graphique en camembert. Apprenez à les analyser vous-même avec les formations PYRANHIA, ou confiez-moi l’analyse.</strong></span></p>
</div>
<div class="cta-group center">
<p><a href="../../formations.html" class="cta-button-outline">Voir les formations</a></p>
<p><a href="../../consulting.html" class="cta-button-outline">Consulting</a></p>
<p><a href="mailto:contact@pyranhia.eu" class="cta-button">Me contacter</a></p>
</div>
</section>



 ]]></description>
  <category>Tutoriels R</category>
  <category>Pédagogie</category>
  <guid>https://www.pyranhia.eu/posts/202603-retour-questionnaire/retour-questionnaire.html</guid>
  <pubDate>Mon, 09 Mar 2026 23:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202603-retour-questionnaire/heatmap.png" medium="image" type="image/png" height="116" width="144"/>
</item>
<item>
  <title>datapyranhia : mon data package R pour arrêter de perdre du temps en formation</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202602-datapyranhia/datapyranhia.html</link>
  <description><![CDATA[ 





<section id="datapyranhia-mon-data-package-r-pour-arrêter-de-perdre-du-temps-en-formation" class="level1 section-full section-padded">
<h1>datapyranhia : mon data package R pour arrêter de perdre du temps en formation</h1>
<p><span class="section-subtitle">Installation en une ligne, datasets prêts à l’emploi, zero friction technique. Voilà pourquoi j’ai créé mon propre datapackage.</span></p>
</section>
<div class="section-1">
<section id="le-problème" class="level2">
<h2 data-anchor-id="le-problème">Le problème</h2>
<p>Télécharger des CSV, résoudre des problèmes d’encodage, chercher où on a mis le fichier, déboguer des chemins relatifs…</p>
<p>Le scénario classique : tout le monde arrive motivé, prêt à apprendre… et on passe 30 minutes à déboguer des problèmes d’import de fichiers. L’énergie du démarrage part en fumée.</p>
<p>Et puis il y a la question de la distribution : comment faire passer les données aux apprenants ? Un dossier partagé ? Un lien Dropbox ? Les mettre dans le repo Git ? Ça marche pour les petits fichiers, mais dès qu’on a des datasets volumineux, ça coince.</p>
</section>
<section id="la-solution" class="level2">
<h2 data-anchor-id="la-solution">La solution</h2>
<p>Un package qui regroupe tous mes datasets, prêts à l’emploi :</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># install.packages("pak")</span></span>
<span id="cb1-2">pak<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">pak</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pyranhia/datapyranhia"</span>)</span>
<span id="cb1-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">data</span>(titanic)</span></code></pre></div></div>
<p>2 minutes chrono. Tout le monde a les mêmes données, propres, documentées. On peut démarrer.</p>
<p>Ça rejoint mon setup complet : Posit Cloud (zero installation), renv (packages contrôlés), repo GitHub (structure prête). L’objectif : éliminer toutes les frictions techniques possibles.</p>
</section>
</div>
<section id="contenu-du-package" class="level2 section-2">
<h2 data-anchor-id="contenu-du-package">Contenu du package</h2>
<p>7 datasets pour l’instant, que j’utilise principalement dans la formation <a href="../../formations/machine_learning_avec_r.html"><strong>Machine Learning avec R</strong></a> :</p>
<table class="caption-top table">
<colgroup>
<col style="width: 22%">
<col style="width: 32%">
<col style="width: 20%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Dataset</th>
<th>Description</th>
<th>Taille</th>
<th>Usage</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>adult</code></td>
<td>Revenus et caractéristiques démographiques</td>
<td>32561 × 15</td>
<td>Classification binaire</td>
</tr>
<tr class="even">
<td><code>ames</code></td>
<td>Prix de l’immobilier à Ames, Iowa</td>
<td>2930 × 74</td>
<td>Régression</td>
</tr>
<tr class="odd">
<td><code>housing</code></td>
<td>Prix de l’immobilier en Californie</td>
<td>20640 × 9</td>
<td>Régression</td>
</tr>
<tr class="even">
<td><code>mnist</code></td>
<td>Chiffres manuscrits 0-9 (images 28×28)</td>
<td>70000 images</td>
<td>Classification 10 classes</td>
</tr>
<tr class="odd">
<td><code>sportif</code></td>
<td>Données de performances de sportifs (exemple pédagogique)</td>
<td>500 × 3</td>
<td>Démonstration ML</td>
</tr>
<tr class="even">
<td><code>titanic</code></td>
<td>Survie des passagers du Titanic</td>
<td>891 × 12</td>
<td>Classification binaire</td>
</tr>
<tr class="odd">
<td><code>wine</code></td>
<td>Qualité des vins</td>
<td>6497 × 12</td>
<td>Régression/Classification</td>
</tr>
</tbody>
</table>
<p>Chaque dataset est documenté (<code>?titanic</code> affiche tout). Le package est évolutif : j’ajoute des datasets au fur et à mesure que je crée de nouvelles formations.</p>
<p>Pour MNIST, j’ai ajouté une fonction qui gère le preprocessing Keras :</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1">datasets <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mnist_to_dataset</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">validation_split =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">batch_size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">64</span>)</span></code></pre></div></div>
<p>Une ligne, c’est prêt pour l’entraînement.</p>
</section>
<div class="section-3">
<section id="pourquoi-cest-utile" class="level2">
<h2 data-anchor-id="pourquoi-cest-utile">Pourquoi c’est utile</h2>
<p><strong>En formation</strong> : démarrage immédiat. Pas de “ah mince ça marche pas chez moi”, pas de debug collectif de chemins de fichiers.</p>
<p><strong>Après la formation</strong> : les apprenants ont une collection de données pour continuer à pratiquer. Envie de refaire une régression ? <code>data(ames)</code>. Tester une nouvelle librairie ? <code>data(wine)</code>. Les données sont là, propres, prêtes.</p>
</section>
<section id="comment-je-lai-fait" class="level2">
<h2 data-anchor-id="comment-je-lai-fait">Comment je l’ai fait</h2>
<p>Je me suis inspirée du tutoriel <a href="https://rstudio4edu.github.io/rstudio4edu-book/data-pkg.html">R for the Rest of Us</a>. Super ressource si vous voulez créer le vôtre.</p>
<p>Créer ce package m’a forcée à :</p>
<ul>
<li>bien documenter (on écrit différemment quand d’autres vont lire) ;</li>
<li>comprendre les licences des datasets ;</li>
<li>mettre en place des tests et GitHub Actions 🚀</li>
</ul>
</section>
<section id="la-suite" class="level2">
<h2 data-anchor-id="la-suite">La suite</h2>
<p>Le package est accessible sur <a href="https://github.com/pyranhia/datapyranhia">GitHub</a>. J’ajouterai des datasets au fur et à mesure que je développe de nouvelles formations.</p>
<p>Si vous formez en R, servez-vous ou créez le vôtre. C’est vraiment pas compliqué et ça change tout.</p>
<hr>
<div class="cta-group center">
<p><a href="../../formations.html" class="cta-button-outline">Découvrir mes formations</a></p>
<p><a href="../../contact.html" class="cta-button">Me contacter</a></p>
</div>
</section>
</div>



 ]]></description>
  <category>Packages R</category>
  <category>Pédagogie</category>
  <guid>https://www.pyranhia.eu/posts/202602-datapyranhia/datapyranhia.html</guid>
  <pubDate>Tue, 10 Feb 2026 23:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202602-datapyranhia/sardines.png" medium="image" type="image/png" height="105" width="144"/>
</item>
<item>
  <title>Ce pipe n’est pas un pipe</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202602-pipe/pipe.html</link>
  <description><![CDATA[ 





<section id="ce-pipe-nest-pas-un-pipe" class="level1 section-full section-padded">
<h1>Ce pipe n’est pas un pipe</h1>
<p><span class="section-subtitle"><em>“Ceci n’est pas une pipe” – René Magritte (1929)</em></span></p>
<p>Si vous programmez en R, vous connaissez sûrement le pipe : cet opérateur magique qui permet de chaîner les opérations de manière lisible. Mais saviez-vous qu’il en existe <strong>deux versions</strong> ? Et qu’elles ne sont pas tout à fait identiques ?</p>
<p>Petit tour d’horizon de <code>%&gt;%</code> (le pipe historique du tidyverse) et <code>|&gt;</code> (le pipe natif de R) et de leurs différences.</p>
</section>
<div class="section-1">
<section id="le-pipe-cest-quoi" class="level2">
<h2 data-anchor-id="le-pipe-cest-quoi">Le pipe, c’est quoi ?</h2>
<p>Le pipe est un opérateur qui permet de <strong>chaîner les opérations</strong> de manière lisible. Au lieu d’imbriquer les fonctions ou de créer des variables intermédiaires, on “fait passer” le résultat d’une fonction à la suivante.</p>
<p><strong>Sans pipe :</strong></p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Version imbriquée (difficile à lire)</span></span>
<span id="cb1-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">summarise</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">select</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filter</span>(iris, Species <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"setosa"</span>), Sepal.Length, Sepal.Width), </span>
<span id="cb1-3">          <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">mean_length =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mean</span>(Sepal.Length))</span>
<span id="cb1-4"></span>
<span id="cb1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Ou avec variables intermédiaires (verbose)</span></span>
<span id="cb1-6">iris_filtered <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filter</span>(iris, Species <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"setosa"</span>)</span>
<span id="cb1-7">iris_selected <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">select</span>(iris_filtered, Sepal.Length, Sepal.Width)</span>
<span id="cb1-8"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">summarise</span>(iris_selected, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">mean_length =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mean</span>(Sepal.Length))</span></code></pre></div></div>
</div>
<p><strong>Avec pipe :</strong></p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1">iris <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb2-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filter</span>(Species <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"setosa"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb2-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">select</span>(Sepal.Length, Sepal.Width) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb2-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">summarise</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">mean_length =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mean</span>(Sepal.Length))</span></code></pre></div></div>
</div>
<p>C’est plus lisible : on lit le code de haut en bas, comme une recette de cuisine. Chaque ligne décrit une étape du traitement.</p>
</section>
<section id="les-deux-pipes-en-action" class="level2">
<h2 data-anchor-id="les-deux-pipes-en-action">Les deux pipes en action</h2>
<section id="le-pipe-magrittr" class="level3">
<h3 data-anchor-id="le-pipe-magrittr">Le pipe magrittr : <code>%&gt;%</code></h3>
<p>Introduit par le package <a href="https://magrittr.tidyverse.org/"><code>magrittr</code></a> et popularisé par le tidyverse, <code>%&gt;%</code> est <strong>LE</strong> pipe historique. Il a révolutionné la façon d’écrire du code R en rendant les pipelines de traitement parfaitement lisibles :</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(dplyr)</span>
<span id="cb3-2"></span>
<span id="cb3-3">iris <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb3-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filter</span>(Species <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"setosa"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb3-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">select</span>(Sepal.Length, Sepal.Width) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb3-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">summarise</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">mean_length =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mean</span>(Sepal.Length))</span></code></pre></div></div>
</section>
<section id="le-pipe-natif" class="level3">
<h3 data-anchor-id="le-pipe-natif">Le pipe natif : <code>|&gt;</code></h3>
<p>Depuis R 4.1.0 (mai 2021), R dispose de <strong>son propre pipe natif</strong> : <code>|&gt;</code>. Plus besoin d’installer <code>magrittr</code> pour l’utiliser !</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1">iris <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb4-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filter</span>(Species <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"setosa"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb4-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">select</span>(Sepal.Length, Sepal.Width) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb4-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">summarise</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">mean_length =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mean</span>(Sepal.Length))</span></code></pre></div></div>
</section>
</section>
</div>
<section id="quelle-est-la-différence" class="level2 section-2">
<h2 data-anchor-id="quelle-est-la-différence">Quelle est la différence ?</h2>
<p>Les deux pipes font <strong>presque</strong> la même chose : ils prennent le résultat de l’expression à gauche et le passent comme <strong>premier argument</strong> de la fonction à droite. Voici les différences principales.</p>
<section id="le-placeholder-." class="level3">
<h3 data-anchor-id="le-placeholder-.">1. <strong>Le placeholder <code>.</code></strong></h3>
<p>Le pipe magrittr permet d’utiliser un <strong>placeholder</strong> <code>.</code> pour contrôler où passe l’objet :</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Avec %&gt;% : on peut placer l'objet ailleurs que comme premier argument</span></span>
<span id="cb5-2">iris <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb5-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lm</span>(Sepal.Length <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span> Sepal.Width, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> .)</span></code></pre></div></div>
<p>Le pipe natif <code>|&gt;</code> <strong>ne supporte pas</strong> directement cette syntaxe, mais deux solutions s’offrent à vous.</p>
<p><strong>Option 1</strong> : utiliser le placeholder <code>_</code> (R ≥ 4.2.0)</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb6-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Avec |&gt; (R ≥ 4.2.0) : placeholder _</span></span>
<span id="cb6-2">iris <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb6-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lm</span>(Sepal.Length <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span> Sepal.Width, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> _)</span></code></pre></div></div>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Avertissement</span>Limitations du placeholder <code>_</code>
</div>
</div>
<div class="callout-body-container callout-body">
<p>Le placeholder <code>_</code> ne fonctionne que :</p>
<ul>
<li>dans les <strong>arguments nommés</strong> (pas dans les arguments positionnels) ;</li>
<li><strong>une seule fois</strong> par expression.</li>
</ul>
<p>Exemples qui ne fonctionnent pas :</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1">iris <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">head</span>(_)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># argument positionnel</span></span>
<span id="cb7-2">iris <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> _, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> _)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># plusieurs placeholders</span></span></code></pre></div></div>
<p>Pour plus de flexibilité, utilisez une fonction anonyme.</p>
</div>
</div>
<p><strong>Option 2</strong> : utiliser une fonction anonyme (R ≥ 4.1.0)</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb8-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Avec |&gt; : fonction anonyme</span></span>
<span id="cb8-2">iris <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb8-3">  (\(x) <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lm</span>(Sepal.Length <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span> Sepal.Width, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> x))()</span></code></pre></div></div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Qu’est-ce qu’une fonction anonyme ?
</div>
</div>
<div class="callout-body-container callout-body">
<p>Une fonction anonyme est une fonction sans nom, créée “à la volée”. En R, la syntaxe <code>\(x)</code> (ou <code>function(x)</code>) crée une fonction qui prend <code>x</code> comme argument.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb9-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Sans pipe : fonction classique</span></span>
<span id="cb9-2">ma_fonction  <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> fonction anonyme</span>
<span id="cb9-3"><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> (\(x) x <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)()  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># retourne aussi 10</span></span></code></pre></div></div>
<p>Les <code>()</code> à la fin exécutent immédiatement la fonction créée.</p>
</div>
</div>
</section>
<section id="compatibilité" class="level3">
<h3 data-anchor-id="compatibilité">2. <strong>Compatibilité</strong></h3>
<ul>
<li><code>%&gt;%</code> fonctionne <strong>sur toutes les versions de R</strong> (si vous chargez <code>magrittr</code>)</li>
<li><code>|&gt;</code> nécessite <strong>R ≥ 4.1.0</strong></li>
</ul>
<p>Si vous partagez du code avec des personnes utilisant des versions anciennes de R, privilégiez <code>%&gt;%</code>.</p>
</section>
</section>
<section id="alors-lequel-choisir" class="level2 section-3">
<h2 data-anchor-id="alors-lequel-choisir">Alors, lequel choisir ?</h2>
<p>Utilisez <code>|&gt;</code> si vous travaillez en R ≥ 4.1.0 et n’avez pas besoin du placeholder <code>.</code>.</p>
<p>Utilisez <code>%&gt;%</code> si vous avez besoin du placeholder <code>.</code> ou travaillez avec des versions anciennes de R.</p>
<p>Débutant ? Utilisez <code>|&gt;</code>. Déjà habitué à <code>%&gt;%</code> ? Gardez-le, ça marche très bien.</p>
<p>Depuis RStudio 2022.07, <code>Ctrl + Shift + M</code> insère <code>|&gt;</code> par défaut. Pour revenir à <code>%&gt;%</code> : <code>Tools &gt; Global Options &gt; Code</code> puis décochez “Use native pipe operator”.</p>
<hr>
<p>Envie d’approfondir R ? Découvrez mes formations pour maîtriser le tidyverse, la visualisation et le machine learning.</p>
<div class="cta-group center">
<p><a href="../../formations.html" class="cta-button-outline">Découvrir les formations R</a></p>
<p><a href="../../contact.html" class="cta-button">Me contacter</a></p>
</div>
</section>



 ]]></description>
  <category>R bits</category>
  <guid>https://www.pyranhia.eu/posts/202602-pipe/pipe.html</guid>
  <pubDate>Mon, 26 Jan 2026 23:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202602-pipe/pipe.png" medium="image" type="image/png" height="89" width="144"/>
</item>
<item>
  <title>Train/test split : séparer ses données pour le machine learning</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202601-train-test-split/train-test-split.html</link>
  <description><![CDATA[ 





<section id="traintest-split-séparer-ses-données-pour-le-machine-learning" class="level1 section-full section-padded">
<h1>Train/test split : séparer ses données pour le machine learning</h1>
<p><span class="section-subtitle">Comprendre pourquoi et comment diviser vos données</span></p>
<p>Dans ce tutoriel, on va utiliser l’écosystème <a href="https://www.tidymodels.org/">tidymodels</a> pour gérer nos splits de données. Plus précisément, on utilisera <a href="https://rsample.tidymodels.org/"><code>rsample</code></a> pour les splits classiques et <a href="https://spatialsample.tidymodels.org/"><code>spatialsample</code></a> pour les données spatiales. Ces packages s’intègrent parfaitement avec les autres éléments de tidymodels que nous verrons dans de prochains tutoriels.</p>
</section>
<section id="les-fondamentaux-du-split-avec-rsample" class="level2 section-1">
<h2 data-anchor-id="les-fondamentaux-du-split-avec-rsample">Les fondamentaux du split avec rsample</h2>
<section id="pourquoi-séparer-ses-données" class="level3">
<h3 data-anchor-id="pourquoi-séparer-ses-données">Pourquoi séparer ses données ?</h3>
<p>Lorsqu’on construit un modèle de machine learning, on a deux objectifs contradictoires :</p>
<ol type="1">
<li><strong>apprendre des patterns dans nos données</strong> (entraînement) ;</li>
<li><strong>s’assurer que ces patterns se généralisent à de nouvelles données</strong> (évaluation).</li>
</ol>
<p>Si on utilise les mêmes données pour ces deux étapes, on ne peut pas savoir si notre modèle a vraiment appris quelque chose d’utile ou s’il a simplement mémorisé nos données d’entraînement.</p>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Le principe fondamental
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>Ne jamais évaluer un modèle sur les données qui ont servi à l’entraîner.</strong></p>
<p>C’est comme corriger ses propres copies : on sera toujours trop optimiste sur nos performances réelles.</p>
</div>
</div>
</section>
<section id="notre-jeu-de-données-exemple" class="level3">
<h3 data-anchor-id="notre-jeu-de-données-exemple">Notre jeu de données exemple</h3>
<p>On va utiliser le dataset <code>palmerpenguins</code> qui contient des mesures morphologiques sur 3 espèces de manchots en Antarctique.</p>
<div class="article-image-large">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.pyranhia.eu/posts/202601-train-test-split/penguins.png" class="img-fluid figure-img" style="width:100.0%"></p>
<figcaption>Les manchots de l’archipel Palmer. Artwork by <span class="citation" data-cites="allison_horst">@allison_horst</span>.</figcaption>
</figure>
</div>
</div>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(palmerpenguins)</span>
<span id="cb1-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(tidyverse)</span>
<span id="cb1-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(rsample)</span>
<span id="cb1-4"></span>
<span id="cb1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Nettoyons les données</span></span>
<span id="cb1-6">penguins_clean <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> penguins <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> </span>
<span id="cb1-7">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">drop_na</span>()</span>
<span id="cb1-8"></span>
<span id="cb1-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Aperçu des données</span></span>
<span id="cb1-10"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">glimpse</span>(penguins_clean)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>Rows: 333
Columns: 8
$ species           &lt;fct&gt; Adelie, Adelie, Adelie, Adelie, Adelie, Adelie, Adel…
$ island            &lt;fct&gt; Torgersen, Torgersen, Torgersen, Torgersen, Torgerse…
$ bill_length_mm    &lt;dbl&gt; 39.1, 39.5, 40.3, 36.7, 39.3, 38.9, 39.2, 41.1, 38.6…
$ bill_depth_mm     &lt;dbl&gt; 18.7, 17.4, 18.0, 19.3, 20.6, 17.8, 19.6, 17.6, 21.2…
$ flipper_length_mm &lt;int&gt; 181, 186, 195, 193, 190, 181, 195, 182, 191, 198, 18…
$ body_mass_g       &lt;int&gt; 3750, 3800, 3250, 3450, 3650, 3625, 4675, 3200, 3800…
$ sex               &lt;fct&gt; male, female, female, female, male, female, male, fe…
$ year              &lt;int&gt; 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007…</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Répartition des espèces</span></span>
<span id="cb3-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(penguins_clean) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> </span>
<span id="cb3-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_bar</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> species, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> species), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">show.legend =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb3-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_text</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">stat =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"count"</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> species, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">label =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">after_stat</span>(count)), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">vjust =</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb3-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_fill_manual</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">values =</span> penguin_colours) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb3-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Espèce"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Nombre d'observations"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">title =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Répartition des espèces"</span>)</span></code></pre></div></div>
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.pyranhia.eu/posts/202601-train-test-split/train-test-split_files/figure-html/read-penguin-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344"></p>
</figure>
</div>
</div>
</div>
<p>Notre objectif sera de prédire l’espèce d’un manchot à partir de ses mesures morphologiques (longueur et profondeur du bec, longueur de la nageoire, masse corporelle).</p>
</section>
<section id="le-split-simple-8020-ou-7030" class="level3">
<h3 data-anchor-id="le-split-simple-8020-ou-7030">Le split simple : 80/20 ou 70/30 ?</h3>
<p>On entend souvent “80% train, 20% test” ou “70/30”, mais <strong>la bonne proportion dépend de votre jeu de données</strong>.</p>
<p>Le principe à retenir : <strong>l’ensemble de test doit contenir suffisamment d’observations pour être statistiquement représentatif</strong>. En pratique, on vise au minimum 30 observations par classe en classification, et idéalement 100+ observations au total pour avoir des métriques stables.</p>
<p>Quelques repères :</p>
<ul>
<li><p><strong>beaucoup de données</strong> (&gt; 5000 observations) : 80/20 ou même 90/10 ;</p>
<p>→ exemple : avec 10000 obs, 10% = 1000 en test, largement suffisant</p></li>
<li><p><strong>données moyennes</strong> (500 - 5000 observations) : 75/25 ou 70/30 ;</p>
<p>→ compromis entre apprendre suffisamment et évaluer correctement</p></li>
<li><p><strong>peu de données</strong> (&lt; 500 observations) : 70/30 minimum, ou privilégiez la validation croisée.</p>
<p>→ important d’avoir au moins 30 obs par classe dans le test</p></li>
</ul>
<p>Avec nos 333 manchots, on a relativement peu de données. Un split 70/30 nous donnera ~100 manchots en test, ce qui est le minimum pour évaluer le modèle de manière fiable. En dessous, il faudrait envisager la validation croisée.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Split 70/30</span></span>
<span id="cb4-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">set.seed</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">123</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Pour la reproductibilité</span></span>
<span id="cb4-3">split <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">initial_split</span>(penguins_clean, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">prop =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>)</span>
<span id="cb4-4"></span>
<span id="cb4-5">train_data <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">training</span>(split)</span>
<span id="cb4-6">test_data <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">testing</span>(split)</span></code></pre></div></div>
</div>
<p>Observations dans train : 233</p>
<p>Observations dans test : 100</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Astuce</span>Toujours fixer le seed !
</div>
</div>
<div class="callout-body-container callout-body">
<p><code>set.seed()</code> garantit que vous obtiendrez le même split à chaque fois. Essentiel pour la reproductibilité.</p>
</div>
</div>
</section>
<section id="le-split-stratifié-préserver-les-proportions" class="level3">
<h3 data-anchor-id="le-split-stratifié-préserver-les-proportions">Le split stratifié : préserver les proportions</h3>
<section id="le-problème-du-split-aléatoire-simple" class="level4">
<h4 data-anchor-id="le-problème-du-split-aléatoire-simple">Le problème du split aléatoire simple</h4>
<p>Regardons les proportions d’espèces dans notre jeu complet, puis dans différents splits aléatoires avec 3 seeds différents (même code, résultats différents) :</p>
<div class="tabset-margin-container"></div><div class="panel-tabset">
<ul class="nav nav-tabs"><li class="nav-item"><a class="nav-link active" id="tabset-1-1-tab" data-bs-toggle="tab" data-bs-target="#tabset-1-1" aria-controls="tabset-1-1" aria-selected="true" href="">Dataset complet</a></li><li class="nav-item"><a class="nav-link" id="tabset-1-2-tab" data-bs-toggle="tab" data-bs-target="#tabset-1-2" aria-controls="tabset-1-2" aria-selected="false" href="">Split aléatoire 1</a></li><li class="nav-item"><a class="nav-link" id="tabset-1-3-tab" data-bs-toggle="tab" data-bs-target="#tabset-1-3" aria-controls="tabset-1-3" aria-selected="false" href="">Split aléatoire 2</a></li><li class="nav-item"><a class="nav-link" id="tabset-1-4-tab" data-bs-toggle="tab" data-bs-target="#tabset-1-4" aria-controls="tabset-1-4" aria-selected="false" href="">Split aléatoire 3</a></li></ul>
<div class="tab-content">
<div id="tabset-1-1" class="tab-pane active" aria-labelledby="tabset-1-1-tab">
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.pyranhia.eu/posts/202601-train-test-split/train-test-split_files/figure-html/prop-all-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344"></p>
</figure>
</div>
</div>
</div>
</div>
<div id="tabset-1-2" class="tab-pane" aria-labelledby="tabset-1-2-tab">
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.pyranhia.eu/posts/202601-train-test-split/train-test-split_files/figure-html/prop-split-1-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344"></p>
</figure>
</div>
</div>
</div>
</div>
<div id="tabset-1-3" class="tab-pane" aria-labelledby="tabset-1-3-tab">
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.pyranhia.eu/posts/202601-train-test-split/train-test-split_files/figure-html/prop-split-2-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344"></p>
</figure>
</div>
</div>
</div>
</div>
<div id="tabset-1-4" class="tab-pane" aria-labelledby="tabset-1-4-tab">
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.pyranhia.eu/posts/202601-train-test-split/train-test-split_files/figure-html/prop-split-3-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344"></p>
</figure>
</div>
</div>
</div>
</div>
</div>
</div>
<p>On remarque que les proportions varient d’un split à l’autre ! Cela peut biaiser l’évaluation du modèle, surtout pour Chinstrap qui est déjà l’espèce la moins représentée.</p>
</section>
<section id="la-solution-le-split-stratifié" class="level4">
<h4 data-anchor-id="la-solution-le-split-stratifié">La solution : le split stratifié</h4>
<p><strong>Stratifier</strong> signifie s’assurer que les proportions de certaines variables sont identiques dans le train et le test.</p>
<ul>
<li>pour une <strong>classification</strong> : on stratifie sur la variable cible (ici, <code>species</code>) ;</li>
<li>pour une <strong>régression</strong> : on peut stratifier sur les quantiles de la variable continue <img src="https://latex.codecogs.com/png.latex?Y"> pour s’assurer d’avoir toute la gamme de valeurs dans train et test.</li>
</ul>
<p>Revenons à nos manchots :</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Split stratifié sur la variable species</span></span>
<span id="cb5-2">split_stratified <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">initial_split</span>(</span>
<span id="cb5-3">  penguins_clean, </span>
<span id="cb5-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">prop =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>,</span>
<span id="cb5-5">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">strata =</span> species  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Préserve les proportions d'espèces</span></span>
<span id="cb5-6">)</span>
<span id="cb5-7"></span>
<span id="cb5-8">train_strat <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">training</span>(split_stratified)</span>
<span id="cb5-9">test_strat <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">testing</span>(split_stratified)</span></code></pre></div></div>
</div>
<div class="tabset-margin-container"></div><div class="panel-tabset">
<ul class="nav nav-tabs"><li class="nav-item"><a class="nav-link active" id="tabset-2-1-tab" data-bs-toggle="tab" data-bs-target="#tabset-2-1" aria-controls="tabset-2-1" aria-selected="true" href="">Train set</a></li><li class="nav-item"><a class="nav-link" id="tabset-2-2-tab" data-bs-toggle="tab" data-bs-target="#tabset-2-2" aria-controls="tabset-2-2" aria-selected="false" href="">Test set</a></li></ul>
<div class="tab-content">
<div id="tabset-2-1" class="tab-pane active" aria-labelledby="tabset-2-1-tab">
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.pyranhia.eu/posts/202601-train-test-split/train-test-split_files/figure-html/prop-split-strat-train-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344"></p>
</figure>
</div>
</div>
</div>
</div>
<div id="tabset-2-2" class="tab-pane" aria-labelledby="tabset-2-2-tab">
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.pyranhia.eu/posts/202601-train-test-split/train-test-split_files/figure-html/prop-split-strat-test-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344"></p>
</figure>
</div>
</div>
</div>
</div>
</div>
</div>
<p>Les proportions sont maintenant quasi identiques entre train et test !</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Quand stratifier ?
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><strong>classification</strong> : toujours stratifier sur la variable cible ;</li>
<li><strong>régression</strong> : stratifier sur des quantiles si la distribution de <img src="https://latex.codecogs.com/png.latex?Y"> est très déséquilibrée ;</li>
<li><strong>données structurées</strong> : attention aux structures hiérarchiques (voir section suivante).</li>
</ul>
</div>
</div>
</section>
</section>
</section>
<section id="les-cas-particuliers-données-temporelles-et-spatiales" class="level2 section-2">
<h2 data-anchor-id="les-cas-particuliers-données-temporelles-et-spatiales">Les cas particuliers : données temporelles et spatiales</h2>
<section id="séries-temporelles-le-split-chronologique" class="level3">
<h3 data-anchor-id="séries-temporelles-le-split-chronologique">Séries temporelles : le split chronologique</h3>
<p>Pour les données temporelles, <strong>le split aléatoire n’a aucun sens</strong> ! On veut prédire le futur, pas des moments aléatoires du passé.</p>
<section id="pourquoi-cest-différent" class="level4">
<h4 data-anchor-id="pourquoi-cest-différent">Pourquoi c’est différent ?</h4>
<p>Imaginons qu’on suive quotidiennement des manchots sur une année (données simulées). Leur masse varie avec les saisons : ils accumulent des réserves pendant l’été austral (novembre-mars) et les perdent en hiver.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.pyranhia.eu/posts/202601-train-test-split/train-test-split_files/figure-html/ts-example-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344"></p>
</figure>
</div>
</div>
</div>
</section>
<section id="la-bonne-approche-split-chronologique" class="level4">
<h4 data-anchor-id="la-bonne-approche-split-chronologique">La bonne approche : split chronologique</h4>
<p>Pour effectuer un split chronologique, on va utiliser la fonction <code>initial_time_split()</code> du package <code>rsample</code>.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb6-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(rsample)</span>
<span id="cb6-2"></span>
<span id="cb6-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Split temporel avec rsample</span></span>
<span id="cb6-4">ts_split <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">initial_time_split</span>(penguins_ts, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">prop =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>)</span>
<span id="cb6-5"></span>
<span id="cb6-6">train_ts <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">training</span>(ts_split) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">set =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Train"</span>)</span>
<span id="cb6-7">test_ts <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">testing</span>(ts_split) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">set =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Test"</span>)</span>
<span id="cb6-8"></span>
<span id="cb6-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Combinons pour le plot</span></span>
<span id="cb6-10">penguins_split <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">bind_rows</span>(train_ts, test_ts)</span>
<span id="cb6-11"></span>
<span id="cb6-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Visualisation</span></span>
<span id="cb6-13"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(penguins_split, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(date, body_mass_g, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> set)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb6-14">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_line</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">linewidth =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb6-15">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_color_manual</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">values =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Train"</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#6BAED6"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Test"</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#F17D52"</span>)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb6-16">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(</span>
<span id="cb6-17">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">title =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Split temporel avec rsample"</span>,</span>
<span id="cb6-18">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Date"</span>, </span>
<span id="cb6-19">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Masse corporelle (g)"</span>,</span>
<span id="cb6-20">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span></span>
<span id="cb6-21">  ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb6-22">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_minimal</span>()</span></code></pre></div></div>
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.pyranhia.eu/posts/202601-train-test-split/train-test-split_files/figure-html/ts-split-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344"></p>
</figure>
</div>
</div>
</div>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Avertissement</span>Jamais de données futures dans le train !
</div>
</div>
<div class="callout-body-container callout-body">
<p>En séries temporelles, le test set doit <strong>toujours</strong> être postérieur au train set. Sinon, vous utilisez le futur pour prédire le passé (fuite de données).</p>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Astuce</span><code>rsample</code> pour les séries temporelles
</div>
</div>
<div class="callout-body-container callout-body">
<p>Le package <code>rsample</code> propose des fonctions spécialisées (<code>sliding_window()</code>, <code>sliding_index()</code>, <code>sliding_period()</code>) pour gérer automatiquement ces splits temporels. On les verra en détail dans le tutoriel sur la validation croisée.</p>
</div>
</div>
</section>
</section>
<section id="données-spatiales-spatialsample-et-autocorrélation" class="level3">
<h3 data-anchor-id="données-spatiales-spatialsample-et-autocorrélation">Données spatiales : spatialsample et autocorrélation</h3>
<section id="le-problème-de-lautocorrélation-spatiale" class="level4">
<h4 data-anchor-id="le-problème-de-lautocorrélation-spatiale">Le problème de l’autocorrélation spatiale</h4>
<p>En écologie, océanographie, ou géographie, les observations spatialement proches sont souvent similaires (autocorrélation spatiale).</p>
<p>Si vous mélangez aléatoirement des points proches entre train et test, votre modèle aura des performances artificiellement augmentées car il “voit” déjà des données très similaires.</p>
</section>
<section id="la-solution-regrouper-les-points-proches" class="level4">
<h4 data-anchor-id="la-solution-regrouper-les-points-proches">La solution : regrouper les points proches</h4>
<p>L’idée est de <strong>mettre les observations spatialement proches dans le même groupe</strong> (train ou test), plutôt que de les mélanger aléatoirement. Deux approches principales :</p>
<ul>
<li><strong>clustering spatial</strong> : algorithme qui regroupe les points proches ;</li>
<li><strong>block spatial</strong> : découpage géographique en blocs réguliers.</li>
</ul>
<p>Ici, on va utiliser le clustering spatial avec <code>spatialsample</code>.</p>
</section>
<section id="revenons-à-nos-manchots" class="level4">
<h4 data-anchor-id="revenons-à-nos-manchots">Revenons à nos manchots</h4>
<p>Ajoutons des coordonnées géographiques fictives à nos manchots, avec 3 zones géographiques distinctes.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.pyranhia.eu/posts/202601-train-test-split/train-test-split_files/figure-html/spatial-prepare-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344"></p>
</figure>
</div>
</div>
</div>
</section>
<section id="conversion-en-objet-spatial" class="level4">
<h4 data-anchor-id="conversion-en-objet-spatial">Conversion en objet spatial</h4>
<p>Pour utiliser <code>spatialsample</code>, il faut d’abord convertir nos données en objet <code>sf</code> (simple features).</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Conversion en objet sf</span></span>
<span id="cb7-2">penguins_sf <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_as_sf</span>(</span>
<span id="cb7-3">  penguins_spatial, </span>
<span id="cb7-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">coords =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"longitude"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"latitude"</span>),</span>
<span id="cb7-5">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4326</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Système de coordonnées WGS84</span></span>
<span id="cb7-6">)</span></code></pre></div></div>
</div>
</section>
<section id="split-spatial-avec-clustering" class="level4">
<h4 data-anchor-id="split-spatial-avec-clustering">Split spatial avec clustering</h4>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb8-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Split spatial : on crée 2 groupes (équivalent train/test)</span></span>
<span id="cb8-2">spatial_split <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">spatial_clustering_cv</span>(</span>
<span id="cb8-3">  penguins_sf,</span>
<span id="cb8-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">v =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2 groupes seulement pour simuler train/test</span></span>
<span id="cb8-5">)</span>
<span id="cb8-6"></span>
<span id="cb8-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Récupérons le premier split (groupe 1 = train, groupe 2 = test)</span></span>
<span id="cb8-8">train_spatial <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">analysis</span>(spatial_split<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>splits[[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]]) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">set =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Train"</span>)</span>
<span id="cb8-9">test_spatial <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">assessment</span>(spatial_split<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>splits[[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]]) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">set =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Test"</span>)</span>
<span id="cb8-10"></span>
<span id="cb8-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Combinons pour visualisation</span></span>
<span id="cb8-12">spatial_combined <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">bind_rows</span>(train_spatial, test_spatial)</span>
<span id="cb8-13"></span>
<span id="cb8-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Extrayons les coordonnées pour ggplot</span></span>
<span id="cb8-15">spatial_coords <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> spatial_combined <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb8-16">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(</span>
<span id="cb8-17">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">longitude =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_coordinates</span>(spatial_combined)[,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>],</span>
<span id="cb8-18">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">latitude =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_coordinates</span>(spatial_combined)[,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb8-19">  ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb8-20">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_drop_geometry</span>()</span>
<span id="cb8-21"></span>
<span id="cb8-22"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Visualisation</span></span>
<span id="cb8-23"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(spatial_coords, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(longitude, latitude, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> set)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-24">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_point</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.5</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">alpha =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-25">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_color_manual</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">values =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Train"</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#6BAED6"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Test"</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#F17D52"</span>)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-26">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(</span>
<span id="cb8-27">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">title =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Split spatial avec clustering"</span>,</span>
<span id="cb8-28">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Longitude"</span>,</span>
<span id="cb8-29">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Latitude"</span>,</span>
<span id="cb8-30">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">subtitle =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Les clusters géographiques sont séparés entre train et test"</span>,</span>
<span id="cb8-31">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span></span>
<span id="cb8-32">  ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-33">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">coord_quickmap</span>()</span></code></pre></div></div>
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.pyranhia.eu/posts/202601-train-test-split/train-test-split_files/figure-html/spatial-split-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344"></p>
</figure>
</div>
</div>
</div>
<p>Les points d’une même zone géographique restent ensemble dans le train ou le test, évitant ainsi le problème d’autocorrélation spatiale.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Split en 2 folds pour la démonstration
</div>
</div>
<div class="callout-body-container callout-body">
<p><code>spatialsample</code> est conçu pour la <strong>validation croisée</strong> (où on crée plusieurs splits équilibrés). Ici, on utilise <code>v = 2</code> pour simuler un simple train/test, ce qui donne un découpage basé sur les clusters spatiaux (pas forcément 50/50).</p>
<p>Dans la vraie vie, vous utiliseriez plutôt <code>v = 5</code> ou <code>v = 10</code> pour faire de la validation croisée spatiale, ce qui vous donne une évaluation plus robuste de votre modèle. On verra ça dans le tutoriel dédié à la validation croisée.</p>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Astuce</span><code>spatialsample</code> pour l’écologie
</div>
</div>
<div class="callout-body-container callout-body">
<p>Si vos données ont des coordonnées géographiques (latitude/longitude, parcelles forestières…), utilisez <strong>toujours</strong> <code>spatialsample</code> au lieu de <code>rsample</code> pour éviter de surestimer vos performances.</p>
<p>Deux méthodes principales :</p>
<ul>
<li><code>spatial_clustering_cv()</code> : clustering automatique ;</li>
<li><code>spatial_block_cv()</code> : découpage en blocs réguliers.</li>
</ul>
<p>Le split spatial gère naturellement les structures groupées (sites, parcelles, îles…) en les séparant géographiquement. Pas besoin de faire un split manuel par groupe si vous utilisez déjà <code>spatialsample</code>.</p>
</div>
</div>
</section>
</section>
</section>
<section id="erreurs-fréquentes-et-bonnes-pratiques" class="level2 section-3">
<h2 data-anchor-id="erreurs-fréquentes-et-bonnes-pratiques">Erreurs fréquentes et bonnes pratiques</h2>
<section id="trois-pièges-à-éviter" class="level3">
<h3 data-anchor-id="trois-pièges-à-éviter">Trois pièges à éviter</h3>
<section id="standardiser-avant-de-splitter" class="level4">
<h4 data-anchor-id="standardiser-avant-de-splitter">1. Standardiser avant de splitter</h4>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb9-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># MAUVAIS : fuite de données</span></span>
<span id="cb9-2">data_scaled <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> penguins_clean <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb9-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">across</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">where</span>(is.numeric), scale)) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Utilise mean/sd de TOUT le jeu (train + test)</span></span>
<span id="cb9-4">split <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">initial_split</span>(data_scaled)</span></code></pre></div></div>
</div>
<p>Pourquoi c’est problématique ? Si vous standardisez avant de splitter, vous utilisez des informations du test set (sa moyenne et son écart-type) pour transformer les données. C’est une <strong>fuite de données</strong> subtile qui biaise vos résultats.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb10-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># BON : standardiser séparément</span></span>
<span id="cb10-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. D'abord splitter les données brutes</span></span>
<span id="cb10-3">split <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">initial_split</span>(penguins_clean, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">strata =</span> species)</span>
<span id="cb10-4">train <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">training</span>(split)</span>
<span id="cb10-5">test <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">testing</span>(split)</span>
<span id="cb10-6"></span>
<span id="cb10-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. Identifier les variables numériques à standardiser</span></span>
<span id="cb10-8">num_vars <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> train <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">select</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">where</span>(is.numeric)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">names</span>()</span>
<span id="cb10-9"></span>
<span id="cb10-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. Calculer mean/sd UNIQUEMENT sur le train</span></span>
<span id="cb10-11">train_params <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> train <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb10-12">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">summarise</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">across</span>(</span>
<span id="cb10-13">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">all_of</span>(num_vars),</span>
<span id="cb10-14">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">mean =</span> mean, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">sd =</span> sd),  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Calcule moyenne et écart-type</span></span>
<span id="cb10-15">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">.names =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"{.col}_{.fn}"</span>       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Nomme les colonnes : var_mean, var_sd</span></span>
<span id="cb10-16">  ))</span>
<span id="cb10-17"></span>
<span id="cb10-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 4. Fonction pour standardiser : (x - mean) / sd</span></span>
<span id="cb10-19">standardize <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(data, params, vars) {</span>
<span id="cb10-20">  data <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb10-21">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">across</span>(</span>
<span id="cb10-22">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">all_of</span>(vars),</span>
<span id="cb10-23">      <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span> (. <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> params[[<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">paste0</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cur_column</span>(), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"_mean"</span>)]]) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Soustrait la moyenne du train</span></span>
<span id="cb10-24">        params[[<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">paste0</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cur_column</span>(), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"_sd"</span>)]]            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Divise par l'écart-type du train</span></span>
<span id="cb10-25">    ))</span>
<span id="cb10-26">}</span>
<span id="cb10-27"></span>
<span id="cb10-28"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 5. Appliquer les MÊMES paramètres (du train) au train ET au test</span></span>
<span id="cb10-29">train_scaled <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">standardize</span>(train, train_params, num_vars)</span>
<span id="cb10-30">test_scaled <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">standardize</span>(test, train_params, num_vars)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Utilise les stats du TRAIN</span></span></code></pre></div></div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span><code>recipes</code> automatise tout ça
</div>
</div>
<div class="callout-body-container callout-body">
<p>En pratique, on utilise le package <code>recipes</code> de tidymodels qui (entre autres) gère automatiquement cette logique (calcul des paramètres sur train, application sur test). On verra ça dans un prochain tutoriel dédié au preprocessing.</p>
</div>
</div>
</section>
<section id="test-set-trop-petit-pour-une-classe-minoritaire" class="level4">
<h4 data-anchor-id="test-set-trop-petit-pour-une-classe-minoritaire">2. Test set trop petit pour une classe minoritaire</h4>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb11-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Simulons un dataset déséquilibré  </span></span>
<span id="cb11-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">set.seed</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">123</span>)</span>
<span id="cb11-3">chinstrap_sample <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> penguins_clean <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> </span>
<span id="cb11-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filter</span>(species <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Chinstrap"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> </span>
<span id="cb11-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">slice_sample</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">n =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span>)</span>
<span id="cb11-6">other_species <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> penguins_clean <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> </span>
<span id="cb11-7">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filter</span>(species <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Chinstrap"</span>)</span>
<span id="cb11-8">imbalanced_penguins <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">bind_rows</span>(chinstrap_sample, other_species)</span>
<span id="cb11-9"></span>
<span id="cb11-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Split stratifié 70/30</span></span>
<span id="cb11-11">split_imbalanced <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">initial_split</span>(imbalanced_penguins, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">prop =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">strata =</span> species)</span>
<span id="cb11-12"></span>
<span id="cb11-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Combien de Chinstrap dans le test ?</span></span>
<span id="cb11-14">test_imbalanced <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">testing</span>(split_imbalanced)</span>
<span id="cb11-15"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">table</span>(test_imbalanced<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>species)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>
   Adelie Chinstrap    Gentoo 
       43         8        34 </code></pre>
</div>
</div>
<p>Avec seulement 8 Chinstrap dans le test set, peut-on vraiment évaluer la performance du modèle sur cette classe ? Non, c’est trop peu pour être statistiquement fiable.</p>
<p>Ici, une validation croisée permettrait d’évaluer le modèle sur tous les individus de la classe minoritaire tour à tour. On en parlera dans un prochain tutoriel.</p>
</section>
<section id="ignorer-lautocorrélation" class="level4">
<h4 data-anchor-id="ignorer-lautocorrélation">3. Ignorer l’autocorrélation</h4>
<p>C’est l’erreur la plus fréquente en écologie et océanographie ! Un split aléatoire sur des données spatiales ou temporelles donne des résultats beaucoup trop optimistes.</p>
<p>Utilisez toujours <code>initial_time_split()</code> pour les séries temporelles et <code>spatialsample</code> pour les données géolocalisées.</p>
</section>
</section>
<section id="guide-de-décision" class="level3">
<h3 data-anchor-id="guide-de-décision">Guide de décision</h3>
<table class="caption-top table">
<colgroup>
<col style="width: 45%">
<col style="width: 28%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Type de données</th>
<th>Principe</th>
<th>Package</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Données standard</td>
<td>Split aléatoire stratifié</td>
<td><code>rsample</code></td>
</tr>
<tr class="even">
<td>Séries temporelles</td>
<td>Split chronologique</td>
<td><code>rsample</code></td>
</tr>
<tr class="odd">
<td>Données spatiales</td>
<td>Clustering géographique</td>
<td><code>spatialsample</code></td>
</tr>
<tr class="even">
<td>Petit jeu (&lt;500 obs)</td>
<td>Validation croisée<sup>1</sup></td>
<td><code>rsample</code></td>
</tr>
</tbody>
</table>
</section>
<section id="pour-aller-plus-loin" class="level3">
<h3 data-anchor-id="pour-aller-plus-loin">Pour aller plus loin</h3>
<p>Dans les prochains tutoriels de cette série :</p>
<ul>
<li><strong>validation croisée</strong> : k-fold, leave-one-out, nested CV</li>
<li><strong>recipes</strong> : preprocessing et feature engineering</li>
</ul>
<div class="cta-group center">
<p><a href="../../formations.html" class="cta-button-outline">Découvrir la formation ML</a></p>
<p><a href="../../contact.html" class="cta-button">Me contacter</a></p>
</div>
</section>
</section>




<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Notes de bas de page</h2>

<ol>
<li id="fn1"><p>dans un prochain tutoriel !↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Tutoriels R</category>
  <category>Machine Learning</category>
  <guid>https://www.pyranhia.eu/posts/202601-train-test-split/train-test-split.html</guid>
  <pubDate>Thu, 22 Jan 2026 23:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202601-train-test-split/train-test-split.png" medium="image" type="image/png" height="86" width="144"/>
</item>
<item>
  <title>Classifier des millions d’images de plancton : pourquoi les petits modèles suffisent</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202602-classif-plankton/classif-plankton.html</link>
  <description><![CDATA[ 





<section id="petit-mais-costaud-ce-que-le-plancton-nous-apprend-sur-le-machine-learning" class="level1 section-full section-padded">
<h1>Petit mais costaud : ce que le plancton nous apprend sur le machine learning</h1>
<p><span class="section-subtitle">Quand la simplicité bat la complexité en classification d’images</span></p>
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>Cet article s’appuie sur mes travaux publiés dans <em>Earth System Science Data</em> :<br>
Panaïotis, T., Amblard, E., Boniface-Chang, G., Dulac-Arnold, G., Woodward, B., &amp; Irisson, J.-O. (2026). Benchmark of plankton images classification: Emphasizing features extraction over classifier complexity. <em>Earth System Science Data</em>, 18(2), 945–967, <a href="https://doi.org/10.5194/essd-18-945-2026">10.5194/essd-18-945-2026</a>.</p>
<p>Le code est disponible sur <a href="https://doi.org/10.5281/zenodo.14261492">Zenodo</a>.</p>
<p><em>Image d’en-tête : Visualisation interactive de réseau de neurones par <a href="https://adamharley.com/nn_vis/">A. W. Harley (2015)</a></em></p>
</div>
</div>
</div>
</section>
<div class="section-1">
<section id="le-problème-des-millions-dimages-à-trier" class="level2">
<h2 data-anchor-id="le-problème-des-millions-dimages-à-trier">Le problème : des millions d’images à trier</h2>
<p>Imaginez devoir trier 1,6 million de photos. C’est le quotidien des océanographes qui utilisent des instruments d’imagerie pour étudier le plancton. Ces organismes sont essentiels aux écosystèmes marins et incroyablement divers : des milliers d’espèces différentes, de toutes tailles et formes. Le défi ? Dans les images, ils côtoient beaucoup de « pollution » : bulles, détritus, particules de neige marine<sup>1</sup>.</p>
<p>Impossible de tout faire à la main. Le machine learning est devenu indispensable. Mais quelle approche choisir pour notre plancton ? Les réseaux de neurones à convolutions (CNN), les plus récents Vision Transformers (VIT), véritables machines de guerre, ou faut-il en rester à des méthodes plus classiques et compréhensibles ?</p>
<p>Par ailleurs, la plupart des études sur le sujet utilisent des petits jeux de données maison, jamais publiés, qui ne reflètent pas la réalité du terrain. Difficile de savoir ce qui marche le mieux.</p>
<p>Pour répondre à cette question, nous avons mené un benchmark systématique sur 6 jeux de données réels d’images de plancton.</p>
</section>
<section id="pourquoi-cest-si-difficile" class="level2">
<h2 data-anchor-id="pourquoi-cest-si-difficile">Pourquoi c’est si difficile ?</h2>
<p>Classifier des images de plancton n’est pas un problème standard de computer vision. Plusieurs spécificités rendent la tâche particulièrement ardue.</p>
<section id="des-images-pauvres-en-information" class="level3">
<h3 data-anchor-id="des-images-pauvres-en-information">1. Des images pauvres en information</h3>
<p>Les instruments d’imagerie planctonique produisent des images :</p>
<ul>
<li><strong>petites</strong> : souvent ~100×100 pixels ou moins ;</li>
<li><strong>en niveaux de gris</strong> : pas de couleur pour aider à la distinction ;</li>
<li><strong>de faible résolution</strong> : optimisées pour la vitesse, pas forcément la qualité.</li>
</ul>
<p>À titre de comparaison, les grands modèles sont entraînés sur des images couleur de bonne qualité (comme ImageNet). Nos images de plancton ? Petites, grises, pauvres en détails. On joue dans une autre catégorie.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.pyranhia.eu/posts/202602-classif-plankton/zooscan.png" class="img-fluid figure-img"></p>
<figcaption>Exemples d’images du dataset ZooScan <span class="citation" data-cites="elineau2024ZooScanNet">(Elineau et al. 2024)</span> illustrant la diversité des organismes planctoniques et des particules non vivantes. Les objets ne sont pas à l’échelle les uns par rapport aux autres. Les images du dataset ZooScan sont utilisées sous licence CC BY-NC 4.0.</figcaption>
</figure>
</div>
</section>
<section id="une-distribution-complètement-déséquilibrée" class="level3">
<h3 data-anchor-id="une-distribution-complètement-déséquilibrée">2. Une distribution complètement déséquilibrée</h3>
<p>Voici la composition réelle de nos jeux de données :</p>
<ul>
<li><strong>dataset IFCB</strong> <span class="citation" data-cites="sosik2015Annotated">(Sosik et al. 2015)</span> : 1,6 M d’images, mais 12,6% seulement de plancton (le reste : débris) ;</li>
<li><strong>dataset UVP6</strong> <span class="citation" data-cites="picheral2024UVP6Net">(Picheral et al. 2024)</span> : 634k images, 7,7% de plancton ;</li>
<li><strong>dataset ZooScan</strong> <span class="citation" data-cites="elineau2024ZooScanNet">(Elineau et al. 2024)</span> : 1,45 M d’images, 71,2% de plancton.</li>
</ul>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center collapsed" data-bs-toggle="collapse" data-bs-target=".callout-1-contents" aria-controls="callout-1" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Pourquoi tant de différence dans la composition ?
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-1" class="callout-1-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p>Les instruments <strong>in situ</strong> (IFCB, UVP6) imagent directement dans l’océan : ils capturent tout ce qui passe devant l’objectif, y compris énormément de particules non-vivantes (débris, bulles, agrégats). D’où le faible pourcentage de plancton.</p>
<p>Les instruments <strong>ex situ</strong> (ZooScan) imagent des échantillons prélevés au filet puis scannés au laboratoire : le plancton est plus concentré, mais on perd les organismes fragiles qui ne survivent pas à la capture.</p>
</div>
</div>
</div>
<p>Et parmi le plancton ? Certaines espèces apparaissent 150 000 fois, d’autres… 70 fois. C’est typique des communautés planctoniques : quelques espèces dominantes, beaucoup d’espèces rares (Figure&nbsp;1).</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-dist" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-dist-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202602-classif-plankton/classif-plankton_files/figure-html/fig-dist-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1920">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-dist-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Distribution typique d’un jeu de données de plancton (ZooScan) : quelques classes dominent massivement (à gauche), la majorité des classes sont rares (à droite). Chaque barre = un groupe taxonomique.
</figcaption>
</figure>
</div>
</div>
</div>
</section>
<section id="le-piège-de-laccuracy" class="level3">
<h3 data-anchor-id="le-piège-de-laccuracy">3. Le piège de l’accuracy</h3>
<p>Imaginez un classifieur ultra-simple qui prédit “détritus” pour toutes les images du dataset IFCB.</p>
<p><strong>Son accuracy ?</strong> 87,4% !</p>
<p>Impressionnant, non ? Sauf qu’il ne reconnaît <strong>aucun</strong> organisme planctonique.</p>
<p>C’est pourtant ce qui arrive quand on optimise uniquement l’accuracy sur des données déséquilibrées : le modèle apprend à bien classifier les classes dominantes (qui pèsent lourd dans le calcul) et ignore les classes rares (qui ne pèsent presque rien).</p>
<p>Or, ce sont justement les espèces rares qui intéressent souvent les biologistes. Un modèle à 95% d’accuracy qui rate systématiquement les larves de poissons n’est pas très utile.</p>
<p><strong>C’est pour ça que nous utilisons des métriques plus représentatives</strong> : balanced accuracy (moyenne des performances par classe), précision et rappel par classe, et des métriques calculées uniquement sur le plancton (en excluant les débris).</p>
</section>
</section>
<section id="ce-que-nous-avons-fait" class="level2">
<h2 data-anchor-id="ce-que-nous-avons-fait">Ce que nous avons fait</h2>
<p>Nous avons créé le premier <strong>benchmark réaliste</strong> pour la classification d’images de plancton :</p>
<ul>
<li><strong>6 jeux de données</strong> provenant d’instruments différents (FlowCAM, IFCB, ISIIS, UVP6, ZooCAM, ZooScan) ;</li>
<li><strong>entre 300 000 et 1,6 million d’images</strong> par jeu de données ;</li>
<li><strong>entre 32 et 120 classes</strong> par jeu de données ;</li>
<li>des données <strong>réelles et déséquilibrées</strong> : certaines classes sont très rares, d’autres dominent (souvent les débris !).</li>
</ul>
<p>Nous avons testé systématiquement plusieurs approches :</p>
<ol type="1">
<li><strong>l’approche classique</strong> : extraction manuelle de features (forme, taille, texture) + Random Forest ;</li>
<li><strong>des CNN de différentes tailles</strong> : du petit MobileNetV2 au mastodonte EfficientNetV2 XL ;</li>
<li><strong>des approches hybrides</strong> : combiner les forces des deux.</li>
</ol>
<p>Pour évaluer ces modèles, nous avons utilisé plusieurs <strong>métriques complémentaires</strong>, adaptées aux spécificités des données planctoniques (déséquilibre, classes rares, dominance des débris) : accuracy, balanced accuracy, précision moyenne des classes planctoniques, rappel moyen des classes planctoniques.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center collapsed" data-bs-toggle="collapse" data-bs-target=".callout-2-contents" aria-controls="callout-2" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Comprendre les métriques utilisées
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-2" class="callout-2-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<ul>
<li><p><strong>accuracy</strong> : proportion d’objets correctement classés. Simple mais trompeur sur des données déséquilibrées (un modèle qui prédit toujours “détritus” peut avoir 80% d’accuracy !) ;</p></li>
<li><p><strong>balanced accuracy</strong> : moyenne des performances par classe. Chaque classe compte autant, qu’elle soit rare ou abondante. Métrique plus juste pour nos données déséquilibrées ;</p></li>
<li><p><strong>précision moyenne (plancton)</strong> : parmi les objets prédits comme plancton, quelle proportion l’est vraiment ? Mesure la “pureté” de nos classes planctoniques ;</p>
<p><em>Exemple : si le modèle prédit 100 copépodes et que 80 le sont vraiment, la précision pour les copépodes est de 80%. On moyenne ensuite sur toutes les classes planctoniques.</em></p></li>
<li><p><strong>rappel moyen (plancton)</strong> : parmi tous les objets planctoniques, quelle proportion a été détectée ? Mesure notre capacité à ne pas rater d’organismes.</p>
<p><em>Exemple : s’il y a 100 copépodes dans le test set et que le modèle en détecte 75, le rappel pour les copépodes est de 75%. On moyenne ensuite sur toutes les classes planctoniques.</em></p></li>
</ul>
</div>
</div>
</div>
</section>
</div>
<section id="ce-que-nous-avons-découvert" class="level2 section-2">
<h2 data-anchor-id="ce-que-nous-avons-découvert">Ce que nous avons découvert</h2>
<section id="les-cnn-gagnent-mais-pas-toujours" class="level3">
<h3 data-anchor-id="les-cnn-gagnent-mais-pas-toujours">1. Les CNN gagnent… mais pas toujours</h3>
<p>Les CNN surpassent l’approche classique, c’est vrai. Mais creusons un peu (Figure&nbsp;2) :</p>
<ul>
<li>Sur l’<strong>accuracy globale </strong> ? L’amélioration est modeste.</li>
<li>Sur la <strong>balanced accuracy</strong> (moyenne des performances par classe) ? Là, c’est une vraie différence !</li>
<li>Sur les <strong>métriques spécifiques au plancton</strong> (précision et rappel) ? Gain significatif, surtout pour le rappel !</li>
</ul>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-cnn-vs-classic" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-cnn-vs-classic-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202602-classif-plankton/classif-plankton_files/figure-html/fig-cnn-vs-classic-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1920">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-cnn-vs-classic-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Les CNN (Mob + MLP₆₀₀) surpassent l’approche classique (Nat + RF) sur tous les datasets, mais l’écart varie selon la métrique. L’accuracy globale montre un gain modeste, tandis que la balanced accuracy et les métriques spécifiques aux classes planctoniques révèlent la vraie force des CNN : mieux classifier les classes rares. Le classifieur aléatoire sert de référence pour montrer l’importance du choix des métriques.
</figcaption>
</figure>
</div>
</div>
</div>
<p><strong>Traduction</strong> : si vous avez beaucoup d’exemples d’une classe, une méthode simple marchera très bien. Les CNN brillent surtout quand les données sont rares, précisément le cas des espèces planctoniques intéressantes pour les biologistes.</p>
<p>Notez aussi le rôle du classifieur aléatoire : il obtient 55 à 63% d’accuracy sur les datasets dominés par les débris (IFCB, ISIIS, UVP6) simplement en prédisant toujours “détritus”. C’est pour ça que nous privilégions les autres métriques, qui révèlent la vraie performance.</p>
</section>
<section id="plus-gros-nest-pas-toujours-mieux" class="level3">
<h3 data-anchor-id="plus-gros-nest-pas-toujours-mieux">2. Plus gros n’est pas toujours mieux</h3>
<p>Voici la vraie surprise : <strong>un petit CNN (MobileNetV2) performe aussi bien (voire mieux) qu’un gros (EfficientNetV2 XL)</strong> (Figure&nbsp;3).</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-model-size" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-model-size-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202602-classif-plankton/classif-plankton_files/figure-html/fig-model-size-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1920">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-model-size-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Impact de la taille du modèle sur les performances. Un petit MobileNet (4,5M paramètres) performe aussi bien qu’un EfficientNet XL (208M paramètres), 46× plus gros, sur toutes les métriques. Le gros modèle performe même moins bien sur le dataset UVP6, probablement en raison d’un sur-ajustement (le dataset est petit et très déséquilibré).
</figcaption>
</figure>
</div>
</div>
</div>
<p>Pourquoi ? Les images de plancton sont petites (~100×100 pixels) et en niveaux de gris. Il n’y a tout simplement pas autant d’information à extraire qu’on pourrait le penser.</p>
<p>Le gros modèle (208 millions de paramètres) n’a rien de plus à apprendre qu’un petit (4,5 millions). Il sur-ajuste même parfois sur le dataset UVP6, le plus petit et le plus déséquilibré de notre étude.</p>
<section id="et-si-on-compresse-encore-plus" class="level4">
<h4 data-anchor-id="et-si-on-compresse-encore-plus">Et si on compresse encore plus ?</h4>
<p>Plus étonnant encore : on peut compresser les caractéristiques extraites par le CNN de 1792 à seulement 50 valeurs via une analyse en composantes principales (ACP) <strong>sans perte de performance</strong> (Figure&nbsp;4).</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-feature-compression" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-feature-compression-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202602-classif-plankton/classif-plankton_files/figure-html/fig-feature-compression-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1920">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-feature-compression-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: Impact de la compression dans le classifieur. Les trois modèles utilisent les mêmes 1792 features extraites par MobileNet, mais avec des classifieurs différents : MLP avec couche intermédiaire de 600 neurones (référence), de 50 neurones, ou ACP réduisant à 50 features avant Random Forest.
</figcaption>
</figure>
</div>
</div>
</div>
</section>
</section>
<section id="les-features-le-classifieur" class="level3">
<h3 data-anchor-id="les-features-le-classifieur">3. Les features &gt; le classifieur</h3>
<p>Peu importe que vous utilisiez un Random Forest ou un réseau de neurones pour faire la classification finale. Ce qui compte vraiment, c’est la <strong>qualité des features</strong> (ou caractéristiques) que vous extrayez des images (Figure&nbsp;5).</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-features-classifier" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-features-classifier-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202602-classif-plankton/classif-plankton_files/figure-html/fig-features-classifier-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1920">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-features-classifier-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: Impact du type de features vs type de classifieur. Les features CNN surpassent largement les features manuelles, quel que soit le classifieur utilisé (RF ou MLP). À l’inverse, le choix du classifieur (RF vs MLP) a peu d’impact quand les features sont de bonne qualité.
</figcaption>
</figure>
</div>
</div>
</div>
<p><strong>Ce que montre cette figure :</strong></p>
<ul>
<li><strong>Features manuelles + RF</strong> : performances moyennes</li>
<li><strong>Features CNN + MLP₆₀₀</strong> : excellentes performances<br>
</li>
<li><strong>Features CNN + RF</strong> : excellentes performances, quasi identiques au MLP</li>
</ul>
<p><strong>Traduction</strong> : changer de classifieur (MLP → RF) en gardant les bonnes features (CNN) ne change presque rien. Mais garder le même classifieur (RF) en changeant de features (manuelles → CNN) change tout !</p>
<p>C’est pour ça que les CNN marchent mieux : ils apprennent automatiquement les bonnes caractéristiques, alors qu’avec l’approche classique, il faut les définir à la main (et on passe souvent à côté de détails importants comme les textures fines).</p>
</section>
<section id="limportance-des-poids-de-classes" class="level3">
<h3 data-anchor-id="limportance-des-poids-de-classes">4. L’importance des poids de classes</h3>
<p>Sur des données aussi déséquilibrées, pondérer les erreurs par l’inverse de la fréquence des classes change tout (Figure&nbsp;6).</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-class-weights" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-class-weights-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202602-classif-plankton/classif-plankton_files/figure-html/fig-class-weights-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1920">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-class-weights-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;6: Impact de la pondération par classe sur les performances. La pondération améliore significativement la balanced accuracy et le rappel plancton, en forçant les modèles à ne pas négliger les classes rares. L’effet est particulièrement marqué pour le Random Forest.
</figcaption>
</figure>
</div>
</div>
</div>
<p>La pondération améliore significativement les performances, surtout pour le Random Forest, pour les CNN le gain est plus modeste mais reste significatif.</p>
<p>Sans pondération, les modèles apprennent surtout à bien classifier les débris (majoritaires) et négligent les espèces rares. Avec pondération, on force le modèle à accorder autant d’importance à une larve de poisson rare qu’à un copépode<sup>2</sup> abondant.</p>
<p>La pondération améliore le rappel (on rate moins d’organismes rares) mais peut légèrement diminuer la précision (un peu plus de “faux positifs” parmi les classes planctoniques). C’est un compromis acceptable quand l’objectif est de ne pas manquer les espèces rares.</p>
</section>
</section>
<div class="section-3">
<section id="les-leçons-pratiques" class="level2">
<h2 data-anchor-id="les-leçons-pratiques">Les leçons pratiques</h2>
<section id="pour-ceux-qui-travaillent-avec-des-images" class="level3">
<h3 data-anchor-id="pour-ceux-qui-travaillent-avec-des-images">Pour ceux qui travaillent avec des images</h3>
<ol type="1">
<li><strong>commencez simple</strong> : un petit CNN bien entraîné bat un gros modèle mal maîtrisé ;</li>
<li><strong>comprenez vos données</strong> : si vos classes sont très déséquilibrées, pondérez vos erreurs ;</li>
<li><strong>ne vous fiez pas qu’à l’accuracy</strong> : regardez les performances par classe (balanced accuracy, précision/rappel) ;</li>
<li><strong>l’équipement compte</strong> : de meilleures images (couleur, résolution) aideront plus qu’un modèle plus gros.</li>
</ol>
</section>
<section id="le-mythe-des-transformers" class="level3">
<h3 data-anchor-id="le-mythe-des-transformers">Le mythe des transformers</h3>
<p>Les vision transformers sont à la mode en computer vision, mais pour les images de plancton le gain qu’ils apportent est marginal <span class="citation" data-cites="maracani2023Indomaina">(Maracani et al. 2023)</span>, tout en demandant <strong>beaucoup</strong> plus de données et de calcul. Ce n’est pas que les transformers soient mauvais. C’est que pour des images petites, simples et en niveaux de gris, un petit CNN capte déjà toute l’information disponible. <strong>Ajouter de la complexité n’apporte rien quand le signal est déjà entièrement extrait.</strong></p>
</section>
<section id="ce-que-ça-change-concrètement" class="level3">
<h3 data-anchor-id="ce-que-ça-change-concrètement">Ce que ça change concrètement</h3>
<ul>
<li><strong>modèles opérationnels plus légers</strong> : pas besoin de GPU énormes, des modèles qui tournent vite ;</li>
<li><strong>entraînements plus rapides</strong> : quelques heures au lieu de jours ;</li>
<li><strong>plus accessible</strong> : vous n’avez pas besoin d’une infrastructure cloud massive.</li>
</ul>
</section>
</section>
<section id="et-pour-vous" class="level2">
<h2 data-anchor-id="et-pour-vous">Et pour vous ?</h2>
<p>Ces principes s’appliquent bien au-delà du plancton. Si vous travaillez avec :</p>
<ul>
<li>des images médicales petites/simples ;</li>
<li>des images de surveillance qualité ;</li>
<li>des données où certaines catégories sont très rares ;</li>
<li>des contraintes de temps/budget.</li>
</ul>
<p>…alors la leçon est la même : <strong>commencez simple, ajustez intelligemment, et ne surdimensionnez pas vos modèles</strong>.</p>
<p>Le machine learning n’est pas de la magie noire. Comme en cuisine, souvent un bon couteau bien aiguisé vaut mieux qu’un robot à 15 fonctions : il fait le travail plus vite, plus simplement, et vous comprenez vraiment ce que vous faites.</p>
<hr>
<section id="références" class="level3">
<h3 data-anchor-id="références">Références</h3>
<div id="refs" class="references csl-bib-body hanging-indent">
<div id="ref-elineau2024ZooScanNet" class="csl-entry">
Elineau, Amanda, Corinne Desnos, Laetitia Jalabert, et al. 2024. <em><span>ZooScanNet</span>: Plankton Images Captured with the <span>ZooScan</span></em>. SEANOE. <a href="https://doi.org/10.17882/55741">https://doi.org/10.17882/55741</a>.
</div>
<div id="ref-maracani2023Indomaina" class="csl-entry">
Maracani, Andrea, Vito Paolo Pastore, Lorenzo Natale, Lorenzo Rosasco, et Francesca Odone. 2023. <span>«&nbsp;In-Domain versus out-of-Domain Transfer Learning in Plankton Image Classification&nbsp;»</span>. <em>Scientific Reports</em> 13 (1): 10443. <a href="https://doi.org/10.1038/s41598-023-37627-7">https://doi.org/10.1038/s41598-023-37627-7</a>.
</div>
<div id="ref-picheral2024UVP6Net" class="csl-entry">
Picheral, Marc, Lucas Courchet, Laetitia Jalabert, et al. 2024. <em><span>UVP6Net</span> : Plankton Images Captured with the <span>UVP6</span></em>. SEANOE. <a href="https://doi.org/10.17882/101948">https://doi.org/10.17882/101948</a>.
</div>
<div id="ref-sosik2015Annotated" class="csl-entry">
Sosik, Heidi M., Emily E. Peacock, et Emily F. Brownlee. 2015. <span>«&nbsp;Annotated Plankton Images Data Set for Developing and Evaluating Classification Methods&nbsp;»</span>. <em>URL http://darchive. mblwhoilibrary. org/handle/1912/7341</em>.
</div>
</div>
<div class="cta-group center">
<p><a href="../../formations.html" class="cta-button">Découvrir mes formations en data science</a></p>
<p><a href="../../contact.html" class="cta-button-outline">Me contacter pour un projet</a></p>
</div>
</section>
</section>
</div>




<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Notes de bas de page</h2>

<ol>
<li id="fn1"><p>Agrégats de matière organique et minérale qui tombent lentement vers les fonds marins↩︎</p></li>
<li id="fn2"><p>Fun fact : <em>Plankton</em> dans Bob l’Éponge est un copépode ! Mais contrairement à son homologue fictif, les vrais copépodes sont inoffensifs.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Computer vision</category>
  <category>ML océano</category>
  <guid>https://www.pyranhia.eu/posts/202602-classif-plankton/classif-plankton.html</guid>
  <pubDate>Tue, 20 Jan 2026 23:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202602-classif-plankton/classif.png" medium="image" type="image/png" height="80" width="144"/>
</item>
<item>
  <title>Des millions d’images de plancton, trois communautés : comment la data science révèle les patterns cachés de l’océan</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202602-typologie-plancton/typologie-plancton.html</link>
  <description><![CDATA[ 





<section id="des-millions-dimages-de-plancton-trois-communautés-comment-la-data-science-révèle-les-patterns-cachés-de-locéan" class="level1 section-full section-padded">
<h1>Des millions d’images de plancton, trois communautés : comment la data science révèle les patterns cachés de l’océan</h1>
<p><span class="section-subtitle">De la collecte de données massives à la découverte de typologies globales : retour sur une analyse qui combine imagerie sous-marine et statistiques multivariées.</span></p>
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>Cet article s’appuie sur mes travaux publiés dans <em>Global Ecology and Biogeography</em> :<br>
Panaïotis, T., et al.&nbsp;(2023). Three major mesoplanktonic communities resolved by in situ imaging in the upper 500 m of the global ocean. <em>Global Ecology and Biogeography</em>, 32(11), 1961-1977. <a href="https://doi.org/10.1111/geb.13741">https://doi.org/10.1111/geb.13741</a></p>
</div>
</div>
</div>
</section>
<section id="des-millions-dimages-aucune-structure-apparente" class="level2 section-1">
<h2 data-anchor-id="des-millions-dimages-aucune-structure-apparente">Des millions d’images, aucune structure apparente</h2>
<p>L’océan couvre 70% de la surface de la Terre, mais on connaît encore mal la biodiversité qui l’habite. Le plancton, ces organismes qui dérivent au gré des courants, joue un rôle fondamental dans les réseaux trophiques marins et le cycle du carbone. Mais comment cartographier ces communautés à l’échelle mondiale ?</p>
<p>Entre 2008 et 2019, des équipes internationales ont collecté <strong>6,8 millions d’images</strong> de plancton à travers tous les océans du globe avec l’<strong>Underwater Vision Profiler 5 (UVP5)</strong> <span class="citation" data-cites="picheral2010Underwater">(Picheral et al. 2010)</span>, un instrument d’imagerie sous-marine. Cette caméra prend des images en continu pendant sa descente dans l’eau, jusqu’à 6000 mètres de profondeur. Contrairement aux filets à plancton qui peuvent détruire les organismes fragiles, l’UVP5 les photographie sans les toucher.</p>
<p><strong>Les données :</strong></p>
<ul>
<li><strong>2 500 profils verticaux</strong> (Figure&nbsp;1) à travers le globe dans les 500 premiers mètres de l’océan ;</li>
<li><strong>330 000 organismes</strong> de plancton identifiés automatiquement grâce à des modèles de classification d’images par deep learning, entraînés et validés sur <a href="https://ecotaxa.obs-vlfr.fr/">Ecotaxa</a> (copépodes, Rhizaria, colonies de cyanobactéries…) ;</li>
<li>pour chaque profil : abondances des différents groupes taxonomiques + variables environnementales (température, salinité…).</li>
</ul>
<div class="callout callout-style-default callout-note callout-titled" title="Comment sont identifiés les organismes ?">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Comment sont identifiés les organismes ?
</div>
</div>
<div class="callout-body-container callout-body">
<p>Les millions d’images collectées sont automatiquement classifiées par des modèles de deep learning (réseaux de neurones convolutifs). J’ai consacré un article scientifique entier à benchmarker différentes approches de classification, un sujet que j’aborde dans un <a href="../../posts/202602-classif-plankton/classif-plankton.html">autre article de blog</a> !</p>
</div>
</div>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-map" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-map-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202602-typologie-plancton/typologie-plancton_files/figure-html/fig-map-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-map-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Carte des sites échantillonnés avec l’UVP5. Adapté depuis <span class="citation" data-cites="panaiotis2023Three">Panaïotis et al. (2023)</span>.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Pendant mon stage de master 2, j’ai travaillé sur l’analyse de ces données. Comment en faire émerger des patterns cohérents ? Comment identifier des typologies de communautés qui aient du sens écologiquement ?</p>
<div class="callout callout-style-default callout-note callout-titled" title="Spoiler des résultats">
<div class="callout-header d-flex align-content-center collapsed" data-bs-toggle="collapse" data-bs-target=".callout-2-contents" aria-controls="callout-2" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Spoiler des résultats
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-2" class="callout-2-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p>Trois grandes communautés planctoniques se cachaient dans les données, chacune associée à des conditions océanographiques bien spécifiques. Mais comment les révéler à partir de millions d’images ?</p>
</div>
</div>
</div>
<section id="la-stratégie-acp-clustering" class="level3">
<h3 data-anchor-id="la-stratégie-acp-clustering">La stratégie : ACP + clustering</h3>
<p>Une fois les données en main, j’ai appliqué un pipeline d’analyse classique en data science pour ce type de problème exploratoire.</p>
<p><span class="step">1. Préparer les données</span></p>
<p>J’ai d’abord agrégé les images par profil vertical. Chaque profil devient une “observation” caractérisée par les <strong>abondances de chaque groupe taxonomique</strong> (copépodes, Rhizaria, Trichodesmium, appendiculaires, etc.).</p>
<p><strong>Résultat : un tableau de contingence classique avec lignes = profils, colonnes = groupes taxonomiques.</strong></p>
<p>Avant l’analyse, j’ai appliqué une <strong>transformation de Hellinger</strong> <span class="citation" data-cites="legendre2012Numerical">(Legendre et Legendre 2012)</span> sur les abondances. C’est une étape souvent négligée mais essentielle quand on étudie des compositions de communautés. En effet, <strong>les abondances brutes posent problème</strong> : certains groupes sont naturellement très abondants (copépodes), d’autres rares (gros organismes). Sans transformation, l’ACP sera dominée par les groupes les plus abondants et on perdrait l’information sur la composition relative de la communauté.</p>
<p>La transformation de Hellinger convertit les abondances en proportions, puis applique une racine carrée. Ainsi, on analyse la <strong>composition de la communauté</strong> plutôt que les abondances brutes. Cette approche est particulièrement utile pour les analyses de communautés écologiques : elle préserve les distances écologiques tout en donnant du poids aux espèces rares.</p>
<p><span class="step">2. Réduire la dimension (ACP)</span></p>
<p>J’avais 28 groupes de plancton en variables, souvent corrélées entre elles (certains groupes cooccurrent fréquemment). Difficile de visualiser et d’interpréter tout ça simultanément.</p>
<p>L’Analyse en Composantes Principales (ACP) condense cette information en quelques axes principaux qui capturent la majorité de la variance. C’est comme compresser une photo en JPEG : on garde l’essentiel de l’information en réduisant la taille.</p>
<p>J’ai conservé les <strong>5 premières composantes principales</strong> (Figure&nbsp;2), qui capturaient environ 65% de la variance totale. Au lieu de travailler avec 28 variables, je travaille avec 5 axes synthétiques qui résument les patterns principaux de composition des communautés.</p>
<p>Le choix de 5 composantes est pragmatique : les résultats de clustering restent similaires que j’en utilise 3, 5 ou 7.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-eig" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-eig-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202602-typologie-plancton/typologie-plancton_files/figure-html/fig-eig-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-eig-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Variance expliquée par les composantes principales.
</figcaption>
</figure>
</div>
</div>
</div>
<p><span class="step">3. Identifier les groupes (clustering)</span></p>
<p>Une fois les données réduites aux 5 premières composantes principales, j’ai appliqué un clustering hiérarchique avec la méthode de Ward pour regrouper les profils similaires.</p>
<p>La méthode de Ward minimise la variance au sein de chaque cluster, ce qui produit des groupes compacts et bien séparés. Le dendrogramme (arbre de classification) a révélé une structure claire : un grand saut entre 3 et 4 clusters, indiquant que <strong>3 groupes</strong> (Figure&nbsp;3) se distinguaient naturellement dans les données.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div id="fig-dend" class="quarto-float quarto-figure quarto-figure-center" data-fig-align="center">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-dend-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202602-typologie-plancton/typologie-plancton_files/figure-html/fig-dend-1.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="1344">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-dend-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Dendrogramme obtenu, 3 groupes se distinguent clairement.
</figcaption>
</figure>
</div>
</div>
</div>
<p><span class="step">4. Valider et interpréter</span></p>
<p>La dernière étape est cruciale : les clusters ont-ils du sens écologiquement ?</p>
<p>J’ai validé le choix des 3 clusters avec :</p>
<ul>
<li>la cohérence du dendrogramme (grand saut = séparation nette) ;</li>
<li>l’interprétation écologique (est-ce que ça fait sens ?).</li>
</ul>
<p>Bonne nouvelle : les trois communautés identifiées correspondaient bien à des grands biomes océaniques distincts.</p>
</section>
</section>
<section id="trois-communautés-trois-océans" class="level2 section-2">
<h2 data-anchor-id="trois-communautés-trois-océans">Trois communautés, trois océans</h2>
<p>L’analyse a révélé <strong>trois grandes communautés planctoniques</strong> dans les 500 premiers mètres de l’océan mondial (Figure&nbsp;4).</p>
<div id="fig-pca-epi" class="article-image-large quarto-float quarto-figure quarto-figure-center" style="padding: 2rem; background: white; border-radius: 12px">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-pca-epi-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://www.pyranhia.eu/posts/202602-typologie-plancton/pca_epi.jpg" class="img-fluid figure-img" style="width:80.0%">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-pca-epi-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: Trois communautés planctoniques dans la couche de surface (épipélagique). (a) Plan factoriel de l’ACP (axes 1 et 2) : chaque point représente un profil, coloré selon son cluster. Les flèches indiquent les groupes taxonomiques les plus contributifs et les variables environnementales (température, salinité, etc.). (b) Répartition géographique des trois clusters. (c) Composition relative moyenne de chaque communauté (axe Y en racine carrée). Adapté depuis <span class="citation" data-cites="panaiotis2023Three">Panaïotis et al. (2023)</span>.
</figcaption>
</figure>
</div>
<section id="communauté-1-enrichie-en-trichodesmium" class="level3">
<h3 data-anchor-id="communauté-1-enrichie-en-trichodesmium"><span style="border-bottom: 3px solid #756bb1;">Communauté 1 : Enrichie en <em>Trichodesmium</em></span></h3>
<p><strong>Localisation :</strong> Atlantique intertropical, eaux chaudes pauvres en nutriments</p>
<p><strong>Organisme dominant :</strong> Colonies de cyanobactéries <em>Trichodesmium</em>, visibles à l’œil nu sous forme de filaments flottants</p>
<p><strong>Rôle écologique :</strong> Ces cyanobactéries fixent l’azote atmosphérique et apportent cet élément vital dans des eaux autrement pauvres en nutriments. Elles forment ainsi la base des réseaux trophiques spécifiques dans les gyres subtropicaux.</p>
</section>
<section id="communauté-2-enrichie-en-copépodes" class="level3">
<h3 data-anchor-id="communauté-2-enrichie-en-copépodes"><span style="border-bottom: 3px solid #fc8d62;">Communauté 2 : Enrichie en copépodes</span></h3>
<p><strong>Localisation :</strong> Hautes latitudes (Arctique, Antarctique, Atlantique Nord) et zones d’upwelling (remontées d’eaux profondes riches en nutriments)</p>
<p><strong>Organisme dominant :</strong> Copépodes, petits crustacés de quelques millimètres, les animaux les plus abondants de la planète</p>
<p><strong>Rôle écologique :</strong> Les copépodes sont un maillon clé des réseaux trophiques marins : ils broutent le phytoplancton et sont consommés à leur tour par de plus gros organismes. Ces zones affichent une productivité biologique exceptionnelle.</p>
</section>
<section id="communauté-3-enrichie-en-rhizaria" class="level3">
<h3 data-anchor-id="communauté-3-enrichie-en-rhizaria"><span style="border-bottom: 3px solid #a6d854;">Communauté 3 : Enrichie en Rhizaria</span></h3>
<p><strong>Localisation :</strong> Zones océaniques ouvertes pauvres en nutriments et certaines zones d’upwelling côtier</p>
<p><strong>Organisme dominant :</strong> Rhizaria, protistes unicellulaires dotés d’un squelette minéral (silice ou carbonate de calcium)</p>
<p><strong>Rôle écologique :</strong> Les Rhizaria sont des acteurs clés du cycle du carbone océanique. Leurs squelettes denses coulent rapidement, emportant le carbone organique vers les profondeurs. Ce mécanisme contribue au stockage à long terme du CO<sub>2</sub> dans l’océan.</p>
<hr>
<p>Ces trois communautés ne sont pas aléatoires : elles sont <strong>fortement corrélées aux conditions environnementales</strong> (température, nutriments…) et aux <strong>grandes régions biogéographiques</strong> de l’océan.</p>
</section>
</section>
<div class="section-3">
<section id="le-code-en-bref" class="level2">
<h2 data-anchor-id="le-code-en-bref">Le code en bref</h2>
<p>Pour les curieux, voici le pipeline simplifié en quelques lignes. Le code complet avec toutes les vérifications et visualisations est disponible sur <a href="https://github.com/ThelmaPana/planktyp">GitHub</a>.</p>
<div class="cell" data-layout-align="center">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(tidyverse)</span>
<span id="cb1-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(vegan)</span>
<span id="cb1-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(ggdendro)</span>
<span id="cb1-4"></span>
<span id="cb1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Transformation Hellinger + PCA</span></span>
<span id="cb1-6">df_hel <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">decostand</span>(df_plankton, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hellinger"</span>)</span>
<span id="cb1-7">pca <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rda</span>(df_hel)</span>
<span id="cb1-8"></span>
<span id="cb1-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Clustering hiérarchique (Ward) sur 5 premières composantes</span></span>
<span id="cb1-10">scores_pca <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scores</span>(pca, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">display =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sites"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">choices =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">scaling =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb1-11">dist_euc <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dist</span>(scores_pca, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">method =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"euclidean"</span>)</span>
<span id="cb1-12">clust <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">hclust</span>(dist_euc, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">method =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ward.D2"</span>)</span>
<span id="cb1-13">clusters <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cutree</span>(clust, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">k =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb1-14"></span>
<span id="cb1-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Composition taxonomique par cluster</span></span>
<span id="cb1-16">composition <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">bind_cols</span>(df_plankton, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">cluster =</span> clusters) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb1-17">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">pivot_longer</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>cluster, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">names_to =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"taxon"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">values_to =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"conc"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb1-18">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">group_by</span>(cluster, taxon) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb1-19">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">summarise</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">conc_mean =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mean</span>(conc), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">.groups =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"drop"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb1-20">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">group_by</span>(cluster) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb1-21">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">prop =</span> conc_mean <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>(conc_mean))</span>
<span id="cb1-22"></span>
<span id="cb1-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Carte des clusters</span></span>
<span id="cb1-24">df_results <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> df_meta <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">cluster =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.factor</span>(clusters))</span>
<span id="cb1-25"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(df_results, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> lon, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> lat)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb1-26">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_polygon</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> world, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">group =</span> group), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"grey"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb1-27">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_point</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> cluster), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">shape =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">alpha =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb1-28">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_color_manual</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">values =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#a6d854"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#fc8d62"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#756bb1"</span>)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb1-29">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">coord_quickmap</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb1-30">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_minimal</span>()</span></code></pre></div></div>
</div>
<div class="callout callout-style-default callout-tip callout-titled" title="Trois points clés à retenir">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Astuce</span>Trois points clés à retenir
</div>
</div>
<div class="callout-body-container callout-body">
<ol type="1">
<li><p><strong>Transformation Hellinger</strong> : Essentielle pour les données de composition. Elle normalise les abondances et donne du poids aux espèces rares.</p></li>
<li><p><strong>ACP puis clustering</strong> : La réduction de dimension avant le clustering améliore la robustesse et élimine le bruit.</p></li>
<li><p><strong>Méthode de Ward</strong> : Produit des clusters compacts et bien séparés, idéale pour identifier des groupes écologiques distincts.</p></li>
</ol>
</div>
</div>
</section>
<section id="ce-que-jai-appris" class="level2">
<h2 data-anchor-id="ce-que-jai-appris">Ce que j’ai appris</h2>
<p>Cette analyse m’a enseigné plusieurs choses sur l’application de la data science à l’océanographie.</p>
<p>Le big data ne suffit pas. Les 6,8 millions d’images n’auraient aucune valeur sans l’expertise océanographique pour les interpréter correctement. C’est la combinaison des deux qui crée de la connaissance.</p>
<p>L’apprentissage non supervisé révèle l’inconnu. Je ne cherchais pas trois communautés spécifiques, c’est le clustering qui les a révélées. Quand on explore des données complexes, ces méthodes sont puissantes pour découvrir des structures cachées.</p>
<p>La validation par le domaine d’expertise est essentielle. Les trois communautés correspondent à des biomes océaniques connus. Sans cette cohérence écologique, les clusters n’auraient été que des artefacts statistiques sans signification.</p>
<p>Enfin, le combo ACP + clustering est un classique pour explorer des données complexes. Il fonctionne particulièrement bien quand vous avez beaucoup de variables corrélées et que vous cherchez des groupes naturels dans vos données.</p>
<section id="références" class="level3">
<h3 data-anchor-id="références">Références</h3>
<div id="refs" class="references csl-bib-body hanging-indent">
<div id="ref-legendre2012Numerical" class="csl-entry">
Legendre, Pierre, et Louis Legendre. 2012. <em>Numerical Ecology</em>. Elsevier.
</div>
<div id="ref-panaiotis2023Three" class="csl-entry">
Panaïotis, Thelma, Marcel Babin, Tristan Biard, et al. 2023. <span>«&nbsp;Three Major Mesoplanktonic Communities Resolved by in Situ Imaging in the Upper 500 m of the Global Ocean&nbsp;»</span>. <em>Global Ecology and Biogeography</em> 32 (11): 1991‑2005. <a href="https://doi.org/10.1111/geb.13741">https://doi.org/10.1111/geb.13741</a>.
</div>
<div id="ref-picheral2010Underwater" class="csl-entry">
Picheral, Marc, Lionel Guidi, Lars Stemmann, David M. Karl, Ghizlaine Iddaoud, et Gabriel Gorsky. 2010. <span>«&nbsp;The <span>Underwater Vision Profiler</span> 5: <span>An</span> Advanced Instrument for High Spatial Resolution Studies of Particle Size Spectra and Zooplankton&nbsp;»</span>. <em>Limnology and Oceanography: Methods</em> 8 (9): 462‑73. <a href="https://doi.org/10.4319/lom.2010.8.462">https://doi.org/10.4319/lom.2010.8.462</a>.
</div>
</div>
<hr>
</section>
</section>
<section id="pour-aller-plus-loin" class="level2">
<h2 data-anchor-id="pour-aller-plus-loin">Pour aller plus loin</h2>
<p>Cette analyse fait partie de ma thèse de doctorat sur l’application de l’intelligence artificielle à l’écologie planctonique. Si vous voulez maîtriser les analyses multivariées (ACP, clustering…) et le machine learning avec R, découvrez mes formations.</p>
<div class="cta-group center">
<p><a href="../../formations.html" class="cta-button-outline">Apprendre les analyses multivariées avec R</a></p>
<p><a href="../../contact.html" class="cta-button">Me contacter</a></p>
</div>
</section>
</div>



 ]]></description>
  <category>ML océano</category>
  <category>Écologie quantitative</category>
  <category>Data viz</category>
  <guid>https://www.pyranhia.eu/posts/202602-typologie-plancton/typologie-plancton.html</guid>
  <pubDate>Mon, 19 Jan 2026 23:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202602-typologie-plancton/uvp5.png" medium="image" type="image/png" height="87" width="144"/>
</item>
<item>
  <title>De l’idée au notebook : comment je crée mes formations R</title>
  <dc:creator>Thelma Panaïotis</dc:creator>
  <link>https://www.pyranhia.eu/posts/202601-creation-formation/creation-formation.html</link>
  <description><![CDATA[ 





<section id="de-lidée-au-notebook-comment-je-crée-mes-formations-r" class="level1 section-full section-padded">
<h1>De l’idée au notebook : comment je crée mes formations R</h1>
<p><span class="section-subtitle">Les meilleures idées naissent loin de l’écran. Voici comment je les transforme en formations concrètes.</span></p>
</section>
<section id="concevoir-larchitecture-avant-tout" class="level2 section-1">
<h2 data-anchor-id="concevoir-larchitecture-avant-tout">1. Concevoir : l’architecture avant tout</h2>
<div class="text-content">
<div class="img-wrapper-right">
<p><img src="https://www.pyranhia.eu/posts/202601-creation-formation/trail.jpg" class="img-fluid" alt="Sentier de randonnée ou de trail en pleine nature"></p>
<p><em>Mes meilleures idées naissent sur les sentiers</em></p>
</div>
<p>Quand je cours ou que je pédale, mon esprit vagabonde. C’est dans ces moments-là, loin de l’écran, que les structures de formation prennent forme. Une analogie pour expliquer la validation croisée. Un exercice progressif pour introduire la vision par ordinateur. Une manière plus claire d’organiser le chapitre sur l’optimisation des hyperparamètres.</p>
</div>
<p>Je fais beaucoup de sport (course à pied, vélo, <del>natation</del>) et j’ai donc beaucoup de temps libre pour penser. Ces heures d’entraînement sont mes sessions de brainstorming les plus productives. Mais entre cette vision et un support de formation complet, il y a du travail.</p>
<section id="construire-par-petites-briques" class="level3">
<h3 data-anchor-id="construire-par-petites-briques">Construire par petites briques</h3>
<p>Je ne pars pas d’un grand plan structuré. Je pars de <strong>plein de petites idées</strong> qui émergent au fil du temps :</p>
<ul>
<li>une analogie qui m’est venue en courant pour expliquer le principe du machine learning ;</li>
<li>le piège classique que j’ai vu dix fois en formant des étudiants (oublier de standardiser les variables) ;</li>
<li>une visualisation pour comprendre la descente de gradient ;</li>
<li>une formulation claire pour expliquer précision et rappel.</li>
</ul>
<p>Pendant des semaines (parfois des mois), je collecte ces petites briques. Je les note, je les teste autour de moi, je les affine. Certaines viennent de mon expérience de recherche, d’autres d’explications à des collègues, d’autres encore de mes propres galères d’apprentissage.</p>
<p><strong>Ensuite seulement, j’assemble.</strong> Je regarde toutes ces briques et je cherche l’ordre logique : qu’est-ce qui doit venir en premier ? Quel exercice prépare le terrain pour le suivant ? Quelle analogie aide à comprendre quel concept ?</p>
<p>C’est comme un puzzle : les pièces existent déjà, il faut juste trouver comment elles s’emboîtent pour former un chemin d’apprentissage cohérent. Une fois cette structure posée, place à la création concrète.</p>
</section>
<section id="les-exercices-dabord" class="level3">
<h3 data-anchor-id="les-exercices-dabord">Les exercices d’abord</h3>
<p>Je ne commence pas par rédiger la théorie. <strong>Je commence par concevoir les exercices.</strong></p>
<p>Une fois que j’ai mes briques conceptuelles, je cherche des <strong>datasets qui fonctionnent</strong> pour les illustrer. Je commence par chercher des données réelles avec un sujet intéressant (j’utilise beaucoup l’UCI Machine Learning Repository). Si nécessaire, j’adapte : je simplifie, je nettoie, je sélectionne les variables pertinentes. En dernier recours, je crée des données synthétiques.</p>
<p>L’objectif : des données ni trop faciles ni trop difficiles, avec un <strong>sujet qui parle</strong>. On apprend mieux quand on comprend ce qu’on manipule.</p>
<p>Pour chaque dataset, je conçois des exercices progressifs (du guidé vers l’autonome) avec des solutions commentées qui expliquent le raisonnement, pas juste le code. Et surtout, <strong>je teste</strong> : l’exercice fonctionne-t-il bien avec ces données ? Est-il faisable en formation dans le temps imparti ?</p>
</section>
</section>
<section id="produire-rédaction-avec-claude" class="level2 section-2">
<h2 data-anchor-id="produire-rédaction-avec-claude">2. Produire : rédaction avec Claude</h2>
<p>Une fois la structure posée et les exercices conçus, il faut <strong>écrire</strong>. Et écrire une formation de 4 jours, c’est long. C’est là qu’intervient Claude.</p>
<p>Clarifions tout de suite : <strong>Claude ne crée pas ma formation. Je pilote, il rédige.</strong></p>
<p>Je lui donne l’architecture, les concepts à expliquer, le ton souhaité, les analogies que je veux. Il génère le contenu. Je teste chaque ligne de code, je corrige, je reformule, je restructure. On itère jusqu’à ce que ce soit juste.</p>
<p>Le gain de temps ? Énorme. Ce qui prendrait des semaines se réduit à quelques jours d’itérations intensives. Mais sans ma supervision, le contenu serait générique et potentiellement faux.</p>
<p>On se corrige mutuellement : Claude fait des erreurs de code que je détecte en testant, je fais des erreurs qu’il repère. Ni l’un ni l’autre n’est infaillible seul.</p>
<div class="callout callout-style-simple callout-tip">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p><strong>Les LLMs : une opportunité et non une menace</strong></p>
<p>L’utilisation de Claude pour rédiger mes formations peut surprendre. Pourtant, je suis convaincue que les LLMs sont une <strong>opportunité formidable</strong> pour les enseignants et formateurs. Ils permettent de :</p>
<ul>
<li>gagner un temps précieux sur la rédaction pour se concentrer sur la pédagogie ;</li>
<li>tester différentes formulations et explications rapidement ;</li>
<li>générer des variations d’exercices ;</li>
<li>se faire challenger sur ses propres contenus.</li>
</ul>
<p><strong>Mais l’expertise reste indispensable</strong> : je conçois l’architecture, je choisis les datasets, je teste chaque ligne de code, je corrige les erreurs. Claude accélère la production, il ne remplace pas la réflexion pédagogique.</p>
<p>C’est exactement ce que j’enseigne dans mes formations : utiliser les LLMs intelligemment, en gardant le contrôle et la compréhension.</p>
</div>
</div>
</div>
<section id="créer-les-supports-pédagogiques" class="level3">
<h3 data-anchor-id="créer-les-supports-pédagogiques">Créer les supports pédagogiques</h3>
<p>Je compile tout en HTML avec Quarto. Le résultat : un site web complet pour la formation.</p>
<p><strong>Pour chaque jour :</strong></p>
<ul>
<li>des notes de cours complètes et interactives (explications détaillées, code commenté) ;</li>
<li>des diapositives pour suivre pendant la session.</li>
</ul>
<p><strong>Pages transversales :</strong></p>
<ul>
<li>un glossaire des termes techniques ;</li>
<li>une page dédiée à l’utilisation des LLM.</li>
</ul>
<p>Tout est accessible dans le navigateur, consultable pendant et après la formation. Les apprenants peuvent réviser, approfondir, chercher un concept dans le glossaire.</p>
<div class="article-image-large">
<p><img src="https://www.pyranhia.eu/posts/202601-creation-formation/screenshot.png" class="img-fluid" alt="Capture d'écran d'un notebook interactif Quarto, avec notes de cours, code R exécutable et visualisations"></p>
<p><em>Exemple de notebook interactif : notes de cours, code exécutable et visualisations dans une même page.</em></p>
</div>
</section>
<section id="préparer-lenvironnement-technique" class="level3">
<h3 data-anchor-id="préparer-lenvironnement-technique">Préparer l’environnement technique</h3>
<p>Mes formations utilisent Posit Cloud Instructor. Les apprenants n’installent rien : tout fonctionne dans le navigateur, environnement identique pour tous, accès en 30 secondes.</p>
<p>Je prépare un projet R complet qu’ils chargent dans leur espace Posit Cloud :</p>
<ul>
<li>les squelettes de scripts R pour chaque exercice ;</li>
<li>un datapackage R contenant tous les datasets ;</li>
<li>les packages pré-installés et versions contrôlées grâce à <code>renv</code>.</li>
</ul>
<p>Le jour J : zéro problème technique, on se concentre sur l’apprentissage.</p>
</section>
</section>
<div class="section-3">
<section id="perfectionner-tester-et-itérer" class="level2">
<h2 data-anchor-id="perfectionner-tester-et-itérer">3. Perfectionner : tester et itérer</h2>
<p>Une formation n’est jamais parfaite dès la première version.</p>
<section id="tester-avant-de-lancer" class="level3">
<h3 data-anchor-id="tester-avant-de-lancer">Tester avant de lancer</h3>
<p>Avant la première session, je fais relire les supports par des collègues pour repérer les erreurs et les incohérences. Puis je teste la formation en conditions réelles avec une ou quelques personnes test.</p>
<p>Je vérifie que :</p>
<ul>
<li>chaque ligne de code tourne sans erreur ;</li>
<li>les exercices sont faisables dans le temps imparti ;</li>
<li>les explications sont claires ;</li>
<li>le timing global est réaliste.</li>
</ul>
<p>Cette phase révèle toujours des problèmes : section trop longue, concept introduit trop tôt, exercice ambigu. Je corrige, j’ajuste, je re-teste.</p>
</section>
<section id="améliorer-après-chaque-formation" class="level3">
<h3 data-anchor-id="améliorer-après-chaque-formation">Améliorer après chaque formation</h3>
<p>Pendant mes cours en M2, j’avais pris l’habitude de noter systématiquement les points d’amélioration. Je fais pareil avec mes formations.</p>
<p>Après chaque session, je note : quels exercices ont généré des déclics ? Quelles explications ont posé problème ? Où les apprenants ont-ils décroché ? Quelles questions n’avais-je pas anticipées ?</p>
<p>Je modifie alors les supports. J’intègre aussi les nouvelles versions de packages, les retours des apprenants, les nouvelles bonnes pratiques. Mes formations évoluent constamment.</p>
</section>
</section>
<section id="ma-philosophie-comprendre-pas-juste-exécuter" class="level2">
<h2 data-anchor-id="ma-philosophie-comprendre-pas-juste-exécuter">Ma philosophie : comprendre, pas juste exécuter</h2>
<p>Mon objectif n’est jamais que l’apprenant “sache faire tourner le code”. C’est qu’il <strong>comprenne ce qu’il fait</strong>, qu’il puisse <strong>adapter à ses propres données</strong>, qu’il soit <strong>autonome après la formation</strong>.</p>
<p>C’est pour ça que je privilégie les explications détaillées, j’intègre les LLM dans la pédagogie, je propose un suivi post-formation, et je garde de petits groupes (6 max).</p>
<hr>
<p><strong>P.S.</strong> : Cet article a été rédigé avec l’aide de Claude. J’ai conçu la structure, rédigé les grandes lignes, et nous avons itéré ensemble pour trouver les bonnes formulations. Exactement comme pour mes formations.</p>
<p><strong>Envie d’en savoir plus ?</strong></p>
<div class="cta-group center">
<p><a href="../../formations.html" class="cta-button-outline">Découvrir mes formations</a></p>
<p><a href="../../contact.html" class="cta-button">Me contacter</a></p>
</div>
</section>
</div>



 ]]></description>
  <category>Pédagogie</category>
  <guid>https://www.pyranhia.eu/posts/202601-creation-formation/creation-formation.html</guid>
  <pubDate>Thu, 15 Jan 2026 23:00:00 GMT</pubDate>
  <media:content url="https://www.pyranhia.eu/posts/202601-creation-formation/sea.jpg" medium="image" type="image/jpeg"/>
</item>
</channel>
</rss>
