Dans un précédent article, nous avions regardé le fichier d'annotation des gènes du génome humain d’après Gencode. J'avais utilisé pour cela la puissante combinaison dplyr + ggplot2 (packages centraux du tidyverse), particulièrement adaptée à tout ce qui est manipulation et visualisation de données tabulaires.
Mais notre génome n'est pas constitué que de gènes, loin s'en faut ! Les éléments répétés sont en fait bien plus majoritaires. Je ne vais pas me risquer à donner ici une définition précise de ce qu'est un élément répété, je me contenterai de rappeler que si les éléments transposables sont des éléments répétés, tout les éléments répétés ne sont pas transposables ! Comme souvent en bio-informatique, je vais me contenter de la définition pragmatique d'élément répété : un élément répété est un élément décrit dans ma table d'annotation des éléments répétés. :-p
Les sources d'annotation des éléments répétés du génome humain sont bien plus rares que pour ce qui concerne les gènes. Je vous propose d'utiliser le temps de cet article une table disponible sur le UCSC table browser. Alors oui, l'interface a mal vieilli, mais le UCSC table browser reste une formidable collection de fichiers d'annotation du génome. Pour obtenir la table en question, il suffit normalement de changer le champ group sur Repeats et de laisser le reste par défaut.
J'ai personnellement téléchargé cette table le 4 avril 2017. Peut-être la vôtre sera-t-elle plus récente, et donc légèrement différente ? En effet, les annotations du génome humain, gènes comme éléments répétés, ne sont pas encore parfaites et sont toujours activement améliorées. Cette table a été générée à l'aide de l'outil RepeatMasker, outil qui permet de masquer (en remplaçant par des N) les nucléotides d'un fichier fasta qui sont inclus dans des éléments répétés. Je trouve assez ironique qu'une des meilleures sources d'annotation des éléments répétés soit issue d'un logiciel visant à s'en débarrasser. ^^ Ce logiciel de plus de 20 ans sert notamment à faciliter l'annotation des gènes des génomes en masquant les séquences répétées.
Si vous souhaitez reproduire les analyses ci-dessous, je vous laisse donc télécharger la table, la mettre dans un répertoire de travail, et lancer R. Si vous n'en avez rien à faire de R, vous pouvez tout à fait sauter les blocs de code et autres explications pour vous contenter de regarder les jolies images. 🙂 Je détaille cependant ma démarche, en espérant qu'au moins l'une ou l'un d'entre vous puisse en retirer une astuce utile, au prix d'un alourdissement assez conséquent de ce billet.
Import et toilettage des données
Après avoir lancé R et défini un répertoire de travail approprié (via la commande
1 |
setwd() |
), je commence par charger quelques packages que j'aime bien :
1 2 3 4 5 6 7 8 9 |
library(purrr) # programmation fonctionnelle library(dplyr) # manipulation de tableaux de données library(readr) # import de fichiers txt library(ggplot2) # des jolis plots library(cowplot) # thème ggplot2 + outils pour figures multi-panneaux library(forcats) # manipulations de factor library(svglite) # export en svg library(viridis) # une palette de couleur sympa library(xtable) # export des tableaux en HTML |
J'importe la table dans R à l'aide d'une fonction du package readr, fonction qui est plus rapide et qui a des valeurs par défaut de ses paramètres plus adaptées que la fonction
1 |
read.table() |
de R base :
1 |
repeats_dirty < ;- read_tsv("repeats_rmsk_hg38.txt.gz") |
Je vais ensuite ne garder que les colonnes qui m'intéressent, que je renomme. J'en profite aussi pour ne garder que les lignes concernant les chromosomes standards, en filtrant les haplotypes alternatifs qui ne feraient qu'alourdir certaines figures par la suite.
1 2 3 4 5 |
standard_chromosomes < ;- c(paste0("chr", 1 :22), "chrX", "chrY") repeats < ;- select(repeats_dirty, genoName, genoStart, genoEnd, strand, repName, repFamily, repClass) %> ;% dplyr::rename(chr = genoName, start = genoStart, end = genoEnd, name = repName, family = repFamily, class = repClass) %> ;% filter(chr %in% standard_chromosomes) |
Ce qui me donne cette table-ci :
1 2 3 |
head(repeats) %> ;% xtable() %> ;% print(type = "html", include.rownames = FALSE) |
chr | start | end | strand | name | family | class |
---|---|---|---|---|---|---|
chr1 | 67108753 | 67109046 | + | L1P5 | L1 | LINE |
chr1 | 8388315 | 8388618 | - | AluY | Alu | SINE |
chr1 | 25165803 | 25166380 | + | L1MB5 | L1 | LINE |
chr1 | 33554185 | 33554483 | - | AluSc | Alu | SINE |
chr1 | 41942894 | 41943205 | - | AluY | Alu | SINE |
chr1 | 50331336 | 50332274 | + | HAL1 | L1 | LINE |
Nous avons donc une classifications des éléments répétés en trois niveaux hiérarchiques, dans l'ordre : class > family > name.
Avant de regarder plus en détail cette classification, j'en profite pour filtrer les quelques lignes contenant un "?", qui correspondent à des classifications incertaines. Je pourrais les garder, mais il y en a relativement peu, et elles complexifieraient l'analyse et alourdiraient les figures.
1 2 3 4 5 6 7 |
repeats < ;- filter( repeats, !(grepl("?", repeats$class, fixed = TRUE) | grepl("?", repeats$family, fixed = TRUE) | grepl("?", repeats$name, fixed = TRUE) ) ) |
Les classes d'éléments répétés
La première figure que nous allons générer s’intéresse au 1er niveau hiérarchique de la classification : les classes d'éléments répétés. Combien y en a‑t-il ? (divulgâchis : 16) Quel est l’effectif de chacune des classes ? Quelle fraction du génome chaque classe couvre-t-elle ? Quelle est la distribution des longueurs des éléments au sein de chaque classe ?
Je vais trier les classes par effectif décroissant pour rendre la figure plus jolie. Pour cela, comme j'utiliserai ggplot2, il me faut modifier l'ordre des levels de la colonne class après l'avoir transformée en factor. J'utilise quelques fonctions du package forcats.
1 |
repeats$class < ;- factor(repeats$class) %> ;% fct_infreq %> ;% fct_rev |
Pour le premier panneau, un diagramme en barres, j'utilise des astuces vues dans le précédent billet. Si vous découvrez ggplot2, pourquoi ne pas jeter un coup d’œil sur cet article star du blog ? Comme toujours, ce qui prend le plus de lignes, ce n'est pas la figure en elle-même, mais tous les petits ajustages nécessaires pour la rendre plus jolie.
1 2 3 4 5 6 7 |
plot_Class < ;- ggplot(repeats, aes(x = class)) + geom_bar(stat = "count", fill = "indianred1") + geom_text(aes(label = ..count..), y = 10000, hjust = 0, stat = "count") + labs(x = "Classe", y = "Nombre d'éléments") + scale_y_continuous(sec.axis = dup_axis()) + coord_flip() + background_grid(major = "xy", minor = "none") |
Pour le deuxième panneau, j'ai envie de voir la longueur totale couverte par chaque classe d'éléments répétés. Pour aider à la lecture, ce ne serait pas mal d'indiquer aussi la fraction du génome couverte par chaque classe. Voilà une excellente occasion d'utiliser une feature récemment ajoutée à ggplot2 : le second axe ! Avant toute chose, et comme la dernière fois, je récupère auprès de UCSC la longueur totale de chaque chromosome :
1 2 3 4 5 |
chr_length < ;- read_tsv("http://hgdownload-test.cse.ucsc.edu/goldenPath/hg38/bigZips/hg38.chrom.sizes", col_names = FALSE) %> ;% dplyr::rename(seqnames = X1, length = X2) %> ;% filter(seqnames %in% standard_chromosomes) %> ;% mutate(seqnames = factor(seqnames, levels = standard_chromosomes)) genome_length < ;- sum(as.numeric(chr_length$length)) # 3 088 269 832 |
Pour faciliter les prochains calculs, je rajoute une colonne contenant la largeur de chaque élément :
1 |
repeats < ;- mutate(repeats, width = end - start) |
Et c'est parti pour un peu de magie dplyr ! Je groupe mon tableau par classe d'éléments répétés (avec
1 |
group_by() |
), je calcule ensuite la longueur totale couverte par chaque classe (avec
1 |
summarise() |
), et je lance le tout dans ggplot2 ! Je spécifie bien que je souhaite un axe secondaire, qui est une transformation linéaire de l'axe principal (
1 |
sec.axis = sec_axis(~100 * . / genome_length, name = "% du génome") |
).
1 2 3 4 5 6 7 8 9 10 11 12 |
plot_genomeProp < ;- repeats %> ;% group_by(class) %> ;% summarise(total_length = sum(width)) %> ;% arrange(total_length) %> ;% ggplot(aes(x = class, y = total_length)) + geom_bar(stat = "identity", fill = "cornflowerblue") + geom_text(aes(label = paste0(round(100 * total_length / genome_length, digits = 1), "%")), y = 10000, hjust = 0) + labs(y = "Longueur cumulée (pb)") + scale_y_continuous(sec.axis = sec_axis(~100 * . / genome_length, name = "% du génome")) + coord_flip() + theme(axis.text.y = element_blank(), axis.title.y=element_blank()) + background_grid(major = "xy", minor = "none") |
Le troisième et dernier panneau sera un aperçu de la distribution des largeurs pour chaque classe d'éléments répétés. Des boites à moustaches générées avec ggplot2 suffisent ici :
1 2 3 4 5 6 7 |
plot_sizeDistrib < ;- ggplot(repeats, aes(x = class, y = width)) + geom_boxplot(fill = "mediumorchid", outlier.shape = NA) + labs(x = "Classe", y = "Taille des éléments (pb)") + scale_y_continuous(sec.axis = dup_axis()) + coord_flip(ylim = c(0, 2500)) + theme(axis.text.y = element_blank(), axis.title.y=element_blank()) + background_grid(major = "xy", minor = "none") |
Enfin, j'arrange laborieusement les panneaux à l'aide du package cowplot et de quelques nombres magiques qui vont bien pour rendre la figure plaisante à l’œil :
1 2 3 4 5 6 7 8 9 10 |
myoffset < ;- 0.008 firstplotsize < ;- 0.44 svglite("plots/classeER.svg", width = 10, height = 5) ggdraw() + draw_plot(plot_Class , x = 0.0, y = myoffset, w = firstplotsize, h = 0.96 - 2*myoffset) + draw_plot(plot_genomeProp , x = firstplotsize, y = 0.0, w = (1-firstplotsize)/2, h = 0.96) + draw_plot(plot_sizeDistrib, x = firstplotsize + (1-firstplotsize)/2, y = 0.0, w = (1-firstplotsize)/2, h = 0.96) + draw_plot_label(LETTERS[1 :3], x = c(0.14, firstplotsize - 0.01, firstplotsize + (1-firstplotsize)/2 - 0.01), y = 0.92) + draw_label("Classes d'éléments répétés", size = 15, x = 0.5, y = 0.97) dev.off() |
Figure 1 : Les classes d'éléments répétés du génome humain. A. Nombre d'éléments répétés pour chaque classe. B. Fraction du génome couvert par chaque classe. C. Distribution des tailles d'éléments répétés pour chaque classe.
Les plus observateurs d'entre vous auront peut être réalisé, avec stupeur, qu'en effet RepeatMasker catégorise les gènes d'ARN ribosomaux (rRNA) et de transferts (tRNA) comme étant des éléments répétés ! Ce qui est techniquement exact, mais m'a un peu surpris au début (ça va mieux maintenant, merci). Je me suis amusé à comparer le nombre de copies de gènes d'ARN ribosomaux recensé par GENCODE, vu la dernière fois (544) avec ceux repérés par RepeatMasker (1 751). Peut-être la différence est-elle due aux copies non fonctionnelles, incluses dans la liste RepeatMasker mais pas dans celle de GENCODE ? Une telle différence se retrouve pour d'autres catégories de gènes ARN. Par exemple GENCODE recense 1 900 snRNA et RepeatMasker 4 285.
Si les SINE (short interspersed nuclear elements) sont plus nombreux que les LINE (Long interspersed nuclear elements), ils sont en général plus courts, et donc constituent une fraction moindre de notre génome. La troisième classe la plus abondante, à la fois en effectif et en fraction du génome, est celle des éléments à LTR (long terminal repeat). Il s'agit donc d'éléments issus de rétrovirus endogènes.
Notez que la figure 1C ne montre pas les points oustiders. En effet, les plus longs éléments répétés le sont tellement que les montrer écraseraient le reste de la figure. Voyez plutôt :
1 2 3 4 |
arrange(repeats, desc(width)) %> ;% top_n(5) %> ;% xtable() %> ;% print(type = "html", include.rownames = FALSE) |
chr | start | end | strand | name | class | family | width |
---|---|---|---|---|---|---|---|
chr1 | 123500000 | 124000000 | + | ALR/Alpha | Satellite | centr | 500000 |
chr1 | 123000000 | 123500000 | + | ALR/Alpha | Satellite | centr | 500000 |
chr5 | 48000000 | 48500000 | + | ALR/Alpha | Satellite | centr | 500000 |
chr7 | 59000000 | 59500000 | + | ALR/Alpha | Satellite | centr | 500000 |
chr8 | 44500000 | 45000000 | + | ALR/Alpha | Satellite | centr | 500000 |
chr12 | 35000000 | 35500000 | + | ALR/Alpha | Satellite | centr | 500000 |
D’après la table d'annotation, les éléments répétés les plus longs sont donc les centromères, faisant tous exactement 500 000 paires de base. Quelle coïncidence ! En fait, à l'heure d’écriture de cet article, les centromères du génome humain ne sont toujours pas assemblés… Parce que figurez-vous qu'assembler 23 ou 24 régions d'environ 500 kb très hautement répétées, ce n'est pas de la tarte ! En attendant, les centromères sont donc annotés avec une longueur estimée arbitraire. Mais avec le rapide développement des technologies de séquençage de fragments longs, il est possible que les centromères humains soient assemblés prochainement. Les plus longs reads séquencés par la technologie Nanopore se rapprochent de la méga-base !
Notre génome est en tout cas constitué par environ :
1 |
sum(repeats$width) / genome_length |
49,4% d'éléments répétés ! Sont-ils homogènement répartis entre les chromosomes ? C'est ce que je vous propose de découvrir ensuite.
Distributions des éléments répétés entre chromosomes
Tout d'abord, souhaitant mettre en évidence les trois plus grandes catégories d'éléments répétés (LINE, SINE et LTR), je crée une nouvelle colonne via un
1 |
mutate() |
et un
1 |
if_else() |
. Je regroupe ensuite le tableau par chromosome (
1 |
group_by() |
) et par classe et somme les largeurs d'éléments répétés (
1 |
mutate(sum(width)) |
). Je joins le tableau à celui contenant la longueur des chromosomes (
1 |
left_join() |
) pour pouvoir calculer la fraction de chaque chromosome contenant des éléments répétés (le second
1 |
mutate() |
). J'en profite pour réorienter les levels de factors pour ordonner les différentes colonnes dans la figure. Et enfin j'envoie les données dans ggplot2, en ajustant tout un tas de micro-détails pour avoir une figure exactement comme j'aime :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
svglite("plots/ERparChrom.svg", width = 5, height = 3) repeats %> ;% mutate(simple_class = if_else( !(class %in% c("SINE", "LINE", "LTR")), "other", as.character(class) )) %> ;% group_by(chr, simple_class) %> ;% summarize(cum_size = sum(width)) %> ;% left_join(chr_length, by = c("chr" = "seqnames")) %> ;% ungroup %> ;% mutate( frac_repeat = cum_size/length, simple_class = factor(simple_class, levels = c("other", "LTR","SINE", "LINE")), chr = factor(chr, levels = standard_chromosomes) ) %> ;% ggplot(aes(x = chr, y = frac_repeat, fill = simple_class)) + geom_bar(stat = "identity") + theme(axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0.5)) + scale_y_continuous(labels = scales::percent) + scale_fill_manual(values = c("gray", viridis(3, begin = 0.25, end = 0.9))) + labs(x = NULL, y = "% du chromosome", title = "Contenu répété de chaque chromosome", fill = "Classe") + background_grid(major = "xy", minor = "none") dev.off() |
Figure 2 : Contenu en éléments répétés de chaque chromosome.
En première approximation, il semble que chaque autosome ait un contenu en éléments répétés à peu près équivalent, oscillant entre environ 42% pour le chromosome 22, et 59% pour le chromosome 19. Il est amusant de comparer cette figure avec celle du contenu en gènes de chaque chromosome générée la dernière fois. Ainsi le chromosome 19 est à la fois l'autosome le plus riche en gènes protéiques et le plus riche en éléments répétés ! L'énigmatique chromosome 13 est relativement pauvre en éléments répétés, et en même temps pauvre en gènes. Les chromosomes sexuels font ici leur malin, avec le chromosome X ayant le plus fort taux en éléments répétés (62%), et le chromosome Y le plus faible (28%). Étonnamment (en tout cas pour moi), notre chromosome Y est donc ni riche en pseudogènes, ni riche en éléments répétés, il est juste… petit.
Les familles d'éléments répétés
Après avoir détaillé les classes d'éléments répétés, jetons un œil aux niveaux de classifications suivants, familles et sous-familles :
1 2 3 4 5 |
group_by(repeats, class) %> ;% summarise(n_family = length(unique(family)), n_subfamily = length(unique(name)), n_element = n()) %> ;% arrange(desc(n_family), desc(n_subfamily)) %> ;% xtable() %> ;% print(type = "html", include.rownames = FALSE) |
class | n_family | n_subfamily | n_element |
---|---|---|---|
DNA | 16 | 226 | 479941 |
LINE | 7 | 171 | 1516226 |
LTR | 6 | 567 | 709475 |
SINE | 6 | 60 | 1779233 |
Satellite | 4 | 22 | 7018 |
Simple_repeat | 1 | 14162 | 678663 |
Unknown | 1 | 71 | 5531 |
tRNA | 1 | 62 | 1777 |
snRNA | 1 | 12 | 4285 |
Retroposon | 1 | 6 | 5397 |
scRNA | 1 | 5 | 1334 |
Low_complexity | 1 | 4 | 98618 |
rRNA | 1 | 3 | 1751 |
RC | 1 | 3 | 1754 |
RNA | 1 | 1 | 666 |
srpRNA | 1 | 1 | 1595 |
Nous allons essayer de représenter graphiquement cette diversité, en affichant des diagrammes en barres d'effectif de chaque famille de répétés. Je vais colorier les barres par le nombre de sous-familles pour chaque famille. Les effectifs variant énormément, je suis contraint d'utiliser une échelle logarithmique. J'ai alors été surpris de découvrir que pour l'instant,
1 |
coord_flip() |
et
1 |
annotation_logticks() |
sont mutuellement exclusif !
Je commence par préparer les données :
1 2 3 |
effectif_table < ;- group_by(repeats, class, family) %> ;% summarise(diff_name = length(unique(name)), size = n()) %> ;% ungroup() |
Générons ensuite un panneau de figure par classe d'éléments répétés possédant de multiples sous-familles (classes Satellite, LTR, LINE, SINE et DNA). J'utilise pour cela la fonction
1 |
map() |
du package purrr, une variante de
1 |
lapply() |
, en définissant une fonction anonyme via les notations quelque peu ésotériques
1 |
~ |
et
1 |
.x |
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
multiFamily < ;- c("Satellite", "LTR", "LINE", "SINE", "DNA") myFamilyPlots < ;- map( multiFamily, ~filter(effectif_table, class == .x) %> ;% arrange(size) %> ;% mutate(family = factor(family, levels = family)) %> ;% mutate(text_dark = if_else(diff_name > ; 50, TRUE, FALSE)) %> ;% ggplot(aes(x = family, y = size, fill = diff_name)) + geom_bar(stat = "identity") + geom_text(aes(label = size, color = text_dark), y = 1, hjust = 0) + scale_fill_viridis(limits=c(0, 100), oob = scales::squish) + scale_color_manual(values = c("grey80", "black"), guide = FALSE) + scale_y_log10() + coord_flip(ylim = c(10, 1e7)) + background_grid(major = "xy", minor = "none") + labs(x = NULL, y = NULL, title = .x) + theme(legend.position = "none") ) names(myFamilyPlots) < ;- multiFamily myFamilyPlots$DNA < ;- myFamilyPlots$DNA + labs(y = "Nombre d'éléments") |
Générons ensuite le même type de panneau pour toutes les classes d'éléments ayant une seule famille :
1 2 3 4 5 6 7 8 9 10 11 12 |
plot_other < ;- filter(effectif_table, !(class %in% c("Satellite", "LTR", "LINE", "SINE", "DNA"))) %> ;% arrange(size) %> ;% mutate(family = factor(family, levels = family)) %> ;% ggplot(aes(x = family, y = size, fill = diff_name)) + geom_bar(stat = "identity") + geom_text(aes(label = size), y = 1, hjust = 0, stat = "count") + scale_fill_viridis(limits=c(0, 100), oob = scales::squish) + scale_y_log10() + coord_flip(ylim = c(10, 1e7)) + background_grid(major = "xy", minor = "none") + labs(x = NULL, y = NULL, title = "Autres", fill = "Nombre de\nsous-familles") + theme(legend.position = "bottom") |
Je récupère la légende pour l'afficher à part, à l'aide d'une fonction de cowplot :
1 2 |
myLegend < ;- get_legend(plot_other) plot_other < ;- plot_other + theme(legend.position = "none") |
J'utilise un peu de magie noire pour homogénéiser les marges de mes différents panneaux et gérer l'alignement vertical. Ne me demandez pas d'expliquer, j'ai juste copié-collé un bout de code depuis internet.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
myFamilyPlots < ;- map(myFamilyPlots, ggplotGrob) plot_other < ;- ggplotGrob(plot_other) myFamilyPlots.widths < ;- map(myFamilyPlots, ~.x$widths[1 :3]) plot_other.widths < ;- plot_other$widths[1 :3] max.widths < ;- grid::unit.pmax( plot_other.widths, do.call(grid::unit.pmax, myFamilyPlots.widths) ) plot_other$widths[1 :3] < ;- max.widths myFamilyPlots < ;- map(myFamilyPlots, function(x) { x$widths[1 :3] < ;- max.widths return(x) }) |
Enfin, j'arrange les différents panneaux, et j'exporte la figure dans un .SVG :
1 2 3 4 5 6 7 8 9 10 11 12 |
svglite("plots/familleER.svg", width = 10, height = 7) ggdraw() + draw_text("Les familles\nd'éléments répétés", x = 1/6, y = 0.95, size = 18) + draw_plot(myLegend , x = 0.0, y = 0.75, w = 1/3, h = 0.2 ) + draw_plot(myFamilyPlots$DNA , x = 0.0, y = 0.0 , w = 1/3, h = 0.8 ) + draw_plot(myFamilyPlots$LINE , x = 1/3, y = 0.62, w = 1/3, h = 0.38) + draw_plot(myFamilyPlots$SINE , x = 1/3, y = 0.31, w = 1/3, h = 0.31) + draw_plot(myFamilyPlots$LTR , x = 1/3, y = 0.0 , w = 1/3, h = 0.31) + draw_plot(myFamilyPlots$LTR , x = 1/3, y = 0.0 , w = 1/3, h = 0.31) + draw_plot(myFamilyPlots$Satellite, x = 2/3, y = 0.7 , w = 1/3, h = 0.3 ) + draw_plot(plot_other , x = 2/3, y = 0.0 , w = 1/3, h = 0.7 ) dev.off() |
Figure 3 : Les familles d'éléments répétés.
Reconnaissez-vous des noms familiers ? Par exemple, nous avons 2 118 insertions d'éléments PiggyBac dans notre génome. Ce transposon est à l'origine d'une méthode de clonage de gènes dans des plasmides assez populaire.
Ce que je remarque surtout, c'est que des ARN de transferts (tRNA) se baladent dans la catégorie des SINE. MAIS POURQUOI ! POURQUOI ON NE PEUT PAS AVOIR DES CLASSIFICATIONS COHÉRENTES EN BIOINFORMATIQUE !
Hum hum, pardon.
En fait tout va bien : la classification est strictement non chevauchante au niveau des sous-familles : les tRNA de classe SINE ne contiennent pas les même sous-familles de tRNA que les tRNA de classe tRNA. Oui, je sais, ce n'est pas très clair. Mais il se trouve qu'un certain nombre de SINE dérivent de séquences d'ARN de transferts. Je pense donc que cette classification est tout à fait justifiée.
Je pourrais me perdre ensuite dans les détails des différentes sous-familles d'élément répétés, mais je préfère laisser les plus curieux d'entre vous se perdre dans ce fascinant tableau, et nous raconter leurs trouvailles en commentaires. Et c'est donc sur cette abrupte conclusion que je conclus.
Un grand merci aux super relecteurs et relectrice : Clémence, eorn, Mathurin et Max, sans qui cet article serait beaucoup moins bien.
Laisser un commentaire