\(^1\) Hub de Bioinformatique et Biostatistique, Département de Biologie Computationnelle, Institut Pasteur
\(^2\) Plate-forme Métabolomique, Centre de Ressources et Recherches Technologiques, Institut Pasteur
Ce fichier Rmarkdown contient tout le matériel de l’atelier RNA-Seq de l’école de Bioinformatique Aviesan/IFB/Inserm niveau 2. Diverses notions seront abordées à travers plusieurs types de designs expérimentaux :
chacune d’entre-elles pouvant nécessiter plusieurs heures d’approfondissement d’un point de vue statistique/méthodologique. Nous les aborderons donc dans les grandes lignes tout en insistant sur certains concepts et détails clés.
Le code ci-dessous permet de charger les packages R dont nous allons avoir besoin :
library(DESeq2) # analyse differentielle
library(limma) # removeBatchEffect et CAMERA
library(fgsea) # gene-set enrichment analysis
library(biomaRt) # faire la correspondance ensembl id - gene name
library(EnrichmentBrowser) # recuperer gene-sets KEGG
library(msigdbr) # recuperer gene-sets KEGG
library(ggplot2) # plots
library(plotly) # plots interactifs
library(ggrepel) # ajouter des labels sur les plots
library(ggbeeswarm) # plots avancés
library(scales) # transformation log2 dans ggplot
library(dplyr) # manipulation de donnees
library(ggVennDiagram) # diagrammes de Venn
library(UpSetR) # UpSet plot
library(FactoMineR) # ACP
library(factoextra) # plot de l'ACP
library(pheatmap) # pretty heatmap
library(RColorBrewer) # palette pour les heatmaps
library(ExploreModelMatrix) # application shiny interpretation design
library(devtools) # session info
Les données utilisées proviennent de diverses expériences RNA-Seq et sont disponibles dans des fichiers au format .RData
que nous chargerons avec la fonction load()
. Chaque fichier .RData
contient deux objets :
counts
: matrice de comptages avec les gènes en lignes et les échantillons en colonnes (après nettoyage, mapping et comptage des reads sur les gènes),target
: data.frame
décrivant le design expérimental.On suppose que les données sont de bonne qualité et qu’il n’y a pas eu de problème durant l’extraction des ARN, la préparation des librairies et le séquençage. Aussi, il n’y a pas d’outlier ni d’inversion d’échantillon. En résumé, tous les contrôles ont été faits grâce à des statistiques/figures descriptives simples. Enfin, les échantillons sont nommés et ordonnés de la même manière dans le tableau de design et dans les comptages.
Remarque : certaines données ont été anonymisées en modifiant le contexte du design expérimental et en utilisant des identifiants de gènes du type gene1
, gene2
, …, geneN
.
Le jeu de données utilisé ici provient d’une expérience sur un champignon dont l’objectif était de suivre l’évolution transcriptomique au cours du temps. Pour cela, nous avons séquencé l’ARN de 3 échantillons à T=0h, T=4h et à T=8h. On commence par afficher le design de l’expérience ainsi que la matrice de comptages obtenue :
load("data_3_levels.RData")
ls()
## [1] "counts" "target"
print(target)
## label time replicate
## T0-1 T0-1 T0 r1
## T0-2 T0-2 T0 r2
## T0-3 T0-3 T0 r3
## T4-1 T4-1 T4 r1
## T4-2 T4-2 T4 r2
## T4-3 T4-3 T4 r3
## T8-1 T8-1 T8 r1
## T8-2 T8-2 T8 r2
## T8-3 T8-3 T8 r3
print(head(counts))
## T0-1 T0-2 T0-3 T4-1 T4-2 T4-3 T8-1 T8-2 T8-3
## gene1 2331 3079 3513 2250 3268 3141 1785 3488 2107
## gene2 772 1677 270 1023 1973 479 1165 1318 700
## gene3 2467 3655 3204 2442 3998 2595 1786 3032 1640
## gene4 4177 4632 5667 3798 5428 5543 3159 4880 3907
## gene5 4844 5501 6222 3731 4857 4522 3356 4906 2578
## gene6 3522 5206 3438 2643 5209 3239 1962 3163 1834
Question : que peut-on faire à partir de ces données et comment pouvons-nous les analyser ?
Les commandes suivantes permettent de créer un objet dds
(qui est central et spécifique à l’analyse différentielle avec DESeq2) puis de réaliser les étapes suivantes :
dds <- DESeqDataSetFromMatrix(countData=counts,
colData=target,
design=~time)
## Warning in DESeqDataSet(se, design = design, ignoreRank): some variables in design formula are characters, converting to
## factors
dds <- estimateSizeFactors(dds)
dds <- estimateDispersions(dds)
dds <- nbinomWaldTest(dds)
Remarque : nous utilisons ici DESeq2 avec le paramétrage par défaut, mais il existe de nombreuses subtilités mathématiques/statistiques. La vignette du package est extrêmement bien faite et explique les différentes options possibles.
L’analyse en composantes principales (ACP) est une méthode de réduction de la dimension qui permet d’explorer la structure du jeu de données et de le visualiser facilement sur un sous-espace bien choisi. Les figures générées permettent de détecter des échantillons outliers, des inversions, des effets “batch”, etc…
Les deux figures ci-dessous permettent de visualiser le caractère hétéroscédastique des données de comptages, et l’homoscédasticité après transformation rlog
. Ce sont ces données transformées qui sont utilisées par les méthodes exploratoires telles que l’ACP car le calcul des distances entre les échantillons ne doit pas être drivé par seulement quelques gènes fortement exprimés.
data.frame(m=apply(counts, 1, mean) + 1, v=apply(counts, 1, var) + 1) %>%
ggplot(aes(m, v)) +
scale_x_log10() +
scale_y_log10() +
geom_point(alpha=0.2) +
geom_abline(slope=1, intercept=0, linetype="dashed", col="red") +
xlab("Mean") +
ylab("Variance") +
ggtitle("Comptages bruts : un point = un gène") +
theme_light()
rld <- rlog(dds)
data.frame(m=apply(assay(rld), 1, mean), v=apply(assay(rld), 1, var)) %>%
ggplot(aes(m, v)) +
geom_point(alpha=0.2) +
xlab("Mean") +
ylab("Variance") +
ggtitle("Comptages transformés : un point = un gène") +
theme_light()
On utilise donc les données rendues homoscédastiques grâce à la méthode rlog()
puis la fonction plotPCA()
disponible dans DESeq2 :
plotPCA(rld, intgroup="time")
Une alternative est d’utiliser les packages R FactoMineR et factoextra afin d’avoir plus de flexibilité :
pca <- PCA(t(assay(rld)), scale.unit=FALSE, ncp=3, graph=FALSE)
fviz_pca_ind(pca, axes=c(1, 2), repel=TRUE, habillage=factor(target$time), mean.point=FALSE)
fviz_pca_ind(pca, axes=c(1, 3), repel=TRUE, habillage=factor(target$time), mean.point=FALSE)
Question : pourquoi y a-t-il une différence entre les deux approches (plotPCA()
de DESeq2 et PCA()
de FactoMineR) ?
Le code ci-dessous permet d’extraire toutes les comparaisons 2 à 2 :
et d’afficher un MA-plot :
resultsNames(dds)
## [1] "Intercept" "time_T4_vs_T0" "time_T8_vs_T0"
# interpretation des coefficients
# ExploreModelMatrix() pour lancer l'application Shiny
res_4vs0 <- results(dds, contrast=c(0, 1, 0))
res_8vs0 <- results(dds, contrast=c(0, 0, 1))
res_8vs4 <- results(dds, contrast=c(0, -1, 1))
summary(res_4vs0, alpha=0.05)
##
## out of 2169 with nonzero total read count
## adjusted p-value < 0.05
## LFC > 0 (up) : 348, 16%
## LFC < 0 (down) : 186, 8.6%
## outliers [1] : 0, 0%
## low counts [2] : 0, 0%
## (mean count < 0)
## [1] see 'cooksCutoff' argument of ?results
## [2] see 'independentFiltering' argument of ?results
summary(res_8vs0, alpha=0.05)
##
## out of 2169 with nonzero total read count
## adjusted p-value < 0.05
## LFC > 0 (up) : 378, 17%
## LFC < 0 (down) : 319, 15%
## outliers [1] : 0, 0%
## low counts [2] : 0, 0%
## (mean count < 0)
## [1] see 'cooksCutoff' argument of ?results
## [2] see 'independentFiltering' argument of ?results
summary(res_8vs4, alpha=0.05)
##
## out of 2169 with nonzero total read count
## adjusted p-value < 0.05
## LFC > 0 (up) : 36, 1.7%
## LFC < 0 (down) : 57, 2.6%
## outliers [1] : 0, 0%
## low counts [2] : 0, 0%
## (mean count < 0)
## [1] see 'cooksCutoff' argument of ?results
## [2] see 'independentFiltering' argument of ?results
# MA-plot de la comparaison 4h vs 0h
DESeq2::plotMA(res_4vs0)
Ce MA-plot est produit grâce à une fonction du package DESeq2. Il est possible de le reproduire manuellement et en ajoutant une couche d’interactivité :
p <- res_4vs0 %>%
as.data.frame() %>%
mutate(DE = ifelse(!is.na(padj) & padj <= 0.05, "DE", "Not DE")) %>%
mutate(id=rownames(res_4vs0)) %>%
ggplot(aes(x=baseMean, y=log2FoldChange, color=DE, label=id)) +
geom_point(alpha=0.4) +
labs(color="4h vs 0h") +
geom_hline(yintercept = 0, linetype="dashed", color="grey") +
scale_color_manual(values=c("DE"="red", "Not DE"="black")) +
scale_x_log10() +
theme_light()
toWebGL(ggplotly(p, tooltip="label"))
On peut aussi ajouter les noms de certains gènes bien choisis :
res_4vs0 %>%
as.data.frame() %>%
mutate(DE = ifelse(!is.na(padj) & padj <= 0.05, "DE", "Not DE")) %>%
mutate(id=ifelse(log2FoldChange > 3, rownames(res_4vs0), "")) %>%
ggplot(aes(x=baseMean, y=log2FoldChange, color=DE, label=id)) +
geom_point(alpha=0.4) +
labs(color="4h vs 0h") +
geom_text_repel(show.legend=FALSE, size=3, point.padding=0.2, max.overlaps=20) +
geom_hline(yintercept = 0, linetype="dashed", color="grey") +
scale_color_manual(values=c("DE"="red", "Not DE"="black")) +
scale_x_log10() +
theme_light()
Etant donné le design expérimental, nous avons obtenus les résultats d’une analyse différentielle pour 3 comparaisons. L’objectif est ici de croiser ces résultats sous la meilleure forme possible, mais nous devons pour cela nous assurer que les gènes sont ordonnés de la même manière dans les 3 objets res_4vs0
, res_8vs0
et res_8vs4
:
all(rownames(res_4vs0) == rownames(res_8vs0)) & all(rownames(res_4vs0) == rownames(res_8vs4))
## [1] TRUE
Une manière assez naturelle est de croiser les listes de gènes différentiellement exprimés, en fixant par exemple un seuil de 5% sur la p-valeur ajustée. Ainsi, nous pouvons détecter des gènes dérégulés spécifiquement à 8h mais qui ne l’étaient pas à 4h. Les diagrammes de Venn sont souvent utilisés pour faire cela :
d <- data.frame(gene = rownames(res_4vs0),
log2FC_4vs0 = res_4vs0$log2FoldChange, padj_4vs0 = res_4vs0$padj,
log2FC_8vs0 = res_8vs0$log2FoldChange, padj_8vs0 = res_8vs0$padj,
log2FC_8vs4 = res_8vs4$log2FoldChange, padj_8vs4 = res_8vs4$padj)
d <- d %>%
mutate(DE_4vs0 = 1 * I(!is.na(padj_4vs0) & padj_4vs0 <= 0.05),
DE_8vs0 = 1 * I(!is.na(padj_8vs0) & padj_8vs0 <= 0.05),
DE_8vs4 = 1 * I(!is.na(padj_8vs4) & padj_8vs4 <= 0.05))
print(head(d), digits=3)
## gene log2FC_4vs0 padj_4vs0 log2FC_8vs0 padj_8vs0 log2FC_8vs4 padj_8vs4 DE_4vs0 DE_8vs0 DE_8vs4
## 1 gene1 0.01621 0.9649 0.0585 0.8398 0.04224 0.952 0 0 0
## 2 gene2 0.35520 0.7625 0.5354 0.5879 0.18021 0.951 0 0 0
## 3 gene3 -0.00173 0.9950 -0.2121 0.3724 -0.21042 0.621 0 0 0
## 4 gene4 0.08433 0.8084 0.0921 0.7501 0.00779 0.998 0 0 0
## 5 gene5 -0.27832 0.0608 -0.2955 0.0366 -0.01720 0.972 0 1 0
## 6 gene6 -0.11239 0.7622 -0.4873 0.0805 -0.37488 0.411 0 0 0
x <- list("4h vs 0h"=d %>% filter(DE_4vs0 == 1) %>% pull(gene),
"8h vs 0h"=d %>% filter(DE_8vs0 == 1) %>% pull(gene),
"8h vs 4h"=d %>% filter(DE_8vs4 == 1) %>% pull(gene))
ggVennDiagram(x)
Le diagramme généré est plutôt lisible car nous avons croisé seulement 3 listes. Pour croiser davantage de données, les UpSet plots ont récemment vu le jour et sont souvent plus adaptés :
upset(d[, c("DE_4vs0", "DE_8vs0", "DE_8vs4")], order.by="freq")
Question : comment interpréter les chiffres issus du diagramme de Venn et de l’UpSet plot ?
Les UpSet plots sont souvent plus lisibles que les diagrammes de Venn mais ont toujours l’inconvénient d’avoir des “effets de seuil”. L’exemple ci-dessous permet de comprendre ce qu’il peut se passer :
d %>%
filter(abs(log2FC_8vs0 - log2FC_4vs0) < 0.1) %>%
filter(padj_4vs0 > 0.05 & padj_8vs0 < 0.05) %>%
dplyr::select(contains(c("gene", "log2FC", "padj"))) %>%
head()
## gene log2FC_4vs0 log2FC_8vs0 log2FC_8vs4 padj_4vs0 padj_8vs0 padj_8vs4
## 1 gene5 -0.2783232 -0.2955247 -0.01720146 0.06083904 0.03657322 0.9722079
## 2 gene157 -0.4395321 -0.5374405 -0.09790837 0.07001014 0.01821062 0.8820284
## 3 gene262 1.2994053 1.3980105 0.09860521 0.05681905 0.03167935 0.9639143
## 4 gene374 -0.3802865 -0.4236334 -0.04334692 0.06849556 0.03167935 0.9484213
## 5 gene409 -0.5537782 -0.6432927 -0.08951448 0.07503017 0.02781289 0.9190701
## 6 gene467 -0.3363274 -0.4032035 -0.06687612 0.07968930 0.02544719 0.8991029
Par exemple, au seuil de 5%, le gène 5 est différentiellement exprimé à 8h mais pas à 4h. Pourtant, les log2(Fold-Change) à 4h et à 8h sont très proches (\(-0,27\) et \(-0,29\)), tout comme les P-valeurs ajustées (\(0,06\) et \(0,04\)). On constate d’ailleurs que la différence entre 4h et 8h n’est pas significative (P-valeur ajustée = \(0,97\)). Nous allons donc construire une figure qui permet de visualiser au mieux tous les résultats simultanément :
d <- d %>%
mutate(DE_8vs4=ifelse(!is.na(padj_8vs4) & padj_8vs4 <= 0.05, "DE", "Not DE"),
DE_both_times=ifelse(!is.na(padj_4vs0) & padj_4vs0 <= 0.05 & !is.na(padj_8vs0) & padj_8vs0 <= 0.05, "Yes", "No"))
ggplot(d, aes(x=log2FC_4vs0, y=log2FC_8vs0, color=DE_8vs4, size=DE_both_times)) +
geom_point(alpha=0.5) +
scale_color_manual(values=c("DE"="red", "Not DE"="black")) +
scale_size_manual(values=c("Yes"=1, "No"=0.4)) +
geom_hline(yintercept=0, lty=2, color="grey") +
geom_vline(xintercept=0, lty=2, color="grey") +
geom_abline(slope=1, intercept=0, lty=2, color="grey") +
labs(color="8h vs 4h", size="DE at 4h and 8h") +
xlab(bquote(log[2](FC)~"4h vs 0h")) +
ylab(bquote(log[2](FC)~"8h vs 0h")) +
ggtitle("Comparison of the 3 conditions") +
theme_light()
Cette figure permet de repérer certains gènes pour lesquels l’effet à 4h est différent de l’effet à 8h, chacun de ces deux effets pouvant être significatifs ou non. On peut alors construire des filtres en fonction de la question biologique, par exemple :
d %>%
filter(padj_8vs4 <= 0.05 & abs(log2FC_8vs4) > 2) %>%
dplyr::select(contains(c("gene", "log2FC", "padj")))
## gene log2FC_4vs0 log2FC_8vs0 log2FC_8vs4 padj_4vs0 padj_8vs0 padj_8vs4
## 1 gene27 4.2341606 8.032530 3.798370 9.962520e-21 3.678433e-76 3.074497e-16
## 2 gene135 4.1902551 6.242643 2.052388 5.035197e-20 2.978423e-45 1.998221e-04
## 3 gene322 0.6631198 -11.553934 -12.217054 1.529902e-02 1.448189e-61 5.244541e-68
## 4 gene898 1.7858818 5.239265 3.453383 1.314652e-09 2.950421e-83 1.085497e-35
## 5 gene901 3.9035829 6.048712 2.145130 1.180844e-20 1.862678e-50 1.356926e-05
## 6 gene970 4.4565744 8.141452 3.684878 2.723993e-25 6.628782e-86 9.362211e-17
## 7 gene1546 3.8053746 5.849055 2.043681 6.852751e-19 2.586579e-45 6.264307e-05
## 8 gene1702 3.9451013 5.978606 2.033505 1.094818e-17 2.787538e-41 2.636428e-04
## 9 gene1866 3.7946393 5.883242 2.088603 2.702374e-17 3.333002e-42 8.798819e-05
Les heatmaps sont également souvent utilisées pour visualiser les résultats. Pour autant, celles-ci sont plus complexes qu’il n’y parait et il existe de nombreux paramètres à définir :
Le code R ci-dessous permet de sélectionner les gènes différentiellement exprimés dans les 3 comparaisons (avec un seuil de 0.001 sur la P-valeur ajustée) et d’afficher une heatmap (i) sans paramétrage spécifique et (ii) en spécifiant certains paramètres.
DE_genes <- d %>%
as.data.frame() %>%
filter(padj_4vs0 <= 0.001 | padj_8vs0 <= 0.001 | padj_8vs4 <= 0.001) %>%
pull(gene)
mat_hm <- assay(rlog(dds))
pheatmap(mat_hm[DE_genes, ])
Question : quel message renvoie cette heatmap ? Pourquoi ?
pheatmap(mat_hm[DE_genes, ],
scale="row",
show_rownames=FALSE,
clustering_distance_rows="euclidean",
clustering_distance_cols="euclidean",
clustering_method="ward.D",
color=colorRampPalette(rev(brewer.pal(9, "RdBu")))(200))
Questions :
On peut également visualiser les log2(Fold-Change) plutôt que les données d’expression. Le code R ci-dessous permet de faire cela pour un certain nombre de gènes pré-sélectionnés :
rownames(d) <- d$gene
d <- d %>%
filter(padj_8vs4 <= 0.05 & (padj_4vs0 <= 0.05 | padj_8vs0 <= 0.05)) %>%
filter(abs(log2FC_8vs0 - log2FC_4vs0) > 1) %>%
dplyr::select(log2FC_4vs0, log2FC_8vs0) %>%
as.matrix()
print(head(d))
## log2FC_4vs0 log2FC_8vs0
## gene27 4.2341606 8.03253034
## gene135 4.1902551 6.24264310
## gene213 1.6094624 0.40294799
## gene220 1.4038623 -0.02242903
## gene277 -1.1078211 -0.02902005
## gene287 0.2845613 -1.27985078
pheatmap(d,
scale="none",
cluster_cols=FALSE,
clustering_distance_rows="euclidean",
angle_col=45,
fontsize_row=5,
color=colorRampPalette(rev(brewer.pal(9, "RdBu")))(100),
breaks=seq(-max(abs(d)), max(abs(d)), length=101))
La heatmap produite est influencée par quelques gènes ayant des log2(Fold-Change) très élevés ou très faibles. Dans ce cas, on peut borner l’échelle de couleur de manière à faire ressortir les autres gènes plus facilement. Toutefois, il faut faire attention à ne pas déformer la figure :
pheatmap(d,
scale="none",
cluster_cols=FALSE,
clustering_distance_rows="euclidean",
angle_col=45,
fontsize_row=5,
color=colorRampPalette(rev(brewer.pal(9, "RdBu")))(100),
breaks=seq(-3, 3, length=101))
Nous utilisons ici un jeu de données RNA-Seq humain : un traitement expérimental T et un traitement contrôle C ont été appliqués sur des cultures cellulaires provenant de 3 donneurs indépendants, l’objectif étant de détecter les gènes dérégulés par l’effet du traitement.
rm(list=ls())
load("data_batch_effect.RData")
ls()
## [1] "counts" "target"
print(target)
## label condition donor
## C-d03 C-d03 C d03
## C-d08 C-d08 C d08
## C-d12 C-d12 C d12
## T-d03 T-d03 T d03
## T-d08 T-d08 T d08
## T-d12 T-d12 T d12
print(head(counts))
## C-d03 C-d08 C-d12 T-d03 T-d08 T-d12
## ENSG00000000003 64 55 37 62 45 50
## ENSG00000000005 0 0 0 0 0 0
## ENSG00000000419 8370 5420 6154 7823 5283 5849
## ENSG00000000457 970 811 567 950 669 545
## ENSG00000000460 1689 1113 981 2065 1082 1264
## ENSG00000000938 44 25 10 24 5 1
Commençons par créer l’objet dds
à partir des comptages et du tableau de design :
dds <- DESeqDataSetFromMatrix(countData=counts,
colData=target,
design=~condition)
## Warning in DESeqDataSet(se, design = design, ignoreRank): some variables in design formula are characters, converting to
## factors
Question : que révèle l’analyse en composante principale ?
rld <- rlog(dds)
pca <- PCA(t(assay(rld)), scale.unit=FALSE, ncp=3, graph=FALSE)
fviz_pca_ind(pca, axes=c(1, 2), repel=TRUE, habillage=factor(target$condition), mean.point=FALSE)
fviz_pca_ind(pca, axes=c(1, 3), repel=TRUE, habillage=factor(target$condition), mean.point=FALSE)
Le code ci-dessous permet de réaliser l’analyse différentielle et visualiser la distribution des p-valeurs brutes :
dds <- DESeq(dds)
res <- results(dds)
summary(res, alpha=0.05)
##
## out of 32453 with nonzero total read count
## adjusted p-value < 0.05
## LFC > 0 (up) : 17, 0.052%
## LFC < 0 (down) : 69, 0.21%
## outliers [1] : 106, 0.33%
## low counts [2] : 10723, 33%
## (mean count < 3)
## [1] see 'cooksCutoff' argument of ?results
## [2] see 'independentFiltering' argument of ?results
ggplot(as.data.frame(res), aes(x=pvalue)) +
geom_histogram(color = "white", breaks = seq(0, 1, by = 0.025)) +
xlab("Raw p-value") +
ylab("Frequency") +
theme_light()
## Warning: Removed 32870 rows containing non-finite values (stat_bin).
La distribution n’est pas celle attendue car la variabilité (information) générée par l’effet “donneur” n’est pas prise en compte dans le modèle. Nous pouvons visualiser ci-dessous cette variabilité pour un seul gène (bien choisi) :
ncounts <- counts(dds, norm=TRUE)
d <- data.frame(target, ncounts=ncounts["ENSG00000066279",])
print(d)
## label condition donor ncounts
## C-d03 C-d03 C d03 575.2294
## C-d08 C-d08 C d08 3654.0259
## C-d12 C-d12 C d12 378.6506
## T-d03 T-d03 T d03 1095.8348
## T-d08 T-d08 T d08 4831.8431
## T-d12 T-d12 T d12 598.5342
ggplot(d, aes(x=condition, y=ncounts, color=donor, shape=donor)) +
geom_point(size=4) +
scale_y_continuous(trans = log2_trans(),
breaks = trans_breaks("log2", function(x) 2^x),
labels = trans_format("log2", math_format(2^.x))) +
xlab("Condition") +
ylab("Normalized counts (log2 scale)") +
labs(shape="Donor", color="Donor") +
ggtitle("Illustration of the donor effect for gene ENSG00000066279") +
theme_light()
Il est nécessaire de spécifier dans le design du modèle cet effet “donneur” :
dds <- DESeqDataSetFromMatrix(countData=counts,
colData=target,
design=~donor+condition)
## Warning in DESeqDataSet(se, design = design, ignoreRank): some variables in design formula are characters, converting to
## factors
dds <- DESeq(dds)
res <- results(dds)
summary(res, alpha=0.05)
##
## out of 32453 with nonzero total read count
## adjusted p-value < 0.05
## LFC > 0 (up) : 577, 1.8%
## LFC < 0 (down) : 729, 2.2%
## outliers [1] : 0, 0%
## low counts [2] : 13702, 42%
## (mean count < 10)
## [1] see 'cooksCutoff' argument of ?results
## [2] see 'independentFiltering' argument of ?results
ggplot(as.data.frame(res), aes(x=pvalue)) +
geom_histogram(color = "white", breaks = seq(0, 1, by = 0.025)) +
xlab("Raw p-value") +
ylab("Frequency") +
theme_light()
## Warning: Removed 32764 rows containing non-finite values (stat_bin).
Question : quelles sont les améliorations apportées par la prise en compte de cet effet “donneur” ?
Maintenant que l’analyse différentielle est correctement réalisée, nous pouvons visualiser les résultats sous la forme d’une heatmap, en sélectionnant les gènes différentiellement exprimés au seuil de 5% :
DE_genes <- res %>%
as.data.frame() %>%
filter(padj <= 0.05) %>%
rownames()
mat_hm <- assay(rlog(dds))
pheatmap(mat_hm[DE_genes,],
scale="row",
show_rownames=FALSE,
clustering_distance_rows="euclidean",
clustering_distance_cols="euclidean",
clustering_method="ward.D",
color=colorRampPalette(rev(brewer.pal(9, "RdBu")))(200))
Dans cette heatmap, les échantillons clusterisent par donneur et pas par traitement alors que nous avons pourtant sélectionné les gènes selon ce critère. Une solution est d’ajuster la matrice avant de produire la figure grâce à la fonction removeBatchEffect()
:
mat_hm_adj <- removeBatchEffect(mat_hm, batch=target$donor)
pheatmap(mat_hm_adj[DE_genes,],
scale="row",
show_rownames=FALSE,
clustering_distance_rows="euclidean",
clustering_distance_cols="euclidean",
clustering_method="ward.D",
color=colorRampPalette(rev(brewer.pal(9, "RdBu")))(200),
main="Heatmap based on the *adjusted* variance-stabilized counts")
Remarques :
Les identifiants utilisés pour l’analyse différentielle sont issus d’ensembl (et du fichier d’annotation au format GFF ou GTF) et ne sont pas toujours pratiques pour l’interprétation. Le code ci-dessous est une manière parmi tant d’autres de récupérer les noms des gènes correspondants :
ensembl <- useEnsembl(biomart="ensembl", dataset="hsapiens_gene_ensembl", version=92)
# attr <- listAttributes(ensembl)$name
# attr[grepl("ensembl_gene_id", attr)]
# attr[grepl("entrezgene", attr)]
# attr[grepl("external_gene_name", attr)]
bm <- getBM(attributes=c("ensembl_gene_id", "external_gene_name"), mart=ensembl)
head(bm)
## ensembl_gene_id external_gene_name
## 1 ENSG00000276626 RF00100
## 2 ENSG00000201317 RNU4-59P
## 3 ENSG00000200823 SNORD114-2
## 4 ENSG00000221598 MIR1249
## 5 ENSG00000199595 RF00019
## 6 ENSG00000200839 RNA5SP48
Le tableau bm
permet de faire le lien entre les identifiants ensembl sous la forme ENSG000...
et les noms humainement interprétables. Ici, il est nécessaire d’explorer ce tableau afin de vérifier la présence de doublons dans chacune des colonnes. Commençons par les gene names :
bm %>%
filter(!is.na(external_gene_name)) %>%
pull(external_gene_name) %>%
duplicated() %>%
any()
## [1] TRUE
dup_gene_names <- bm %>%
filter(!is.na(external_gene_name)) %>%
filter(duplicated(external_gene_name)) %>%
pull(external_gene_name)
bm %>%
filter(external_gene_name %in% dup_gene_names) %>%
arrange(external_gene_name) %>%
head(n=10)
## ensembl_gene_id external_gene_name
## 1 ENSG00000197953 AADACL2
## 2 ENSG00000261846 AADACL2
## 3 ENSG00000242908 AADACL2-AS1
## 4 ENSG00000262466 AADACL2-AS1
## 5 ENSG00000276072 AATF
## 6 ENSG00000275700 AATF
## 7 ENSG00000274099 ABCB10P1
## 8 ENSG00000280461 ABCB10P1
## 9 ENSG00000281173 ABCB10P1
## 10 ENSG00000261524 ABCB10P3
Plusieurs identifiants ensembl correspondent à un même gene name ! Regardons ensuite les identifiants ensembl :
bm %>%
filter(!is.na(ensembl_gene_id)) %>%
pull(ensembl_gene_id) %>%
duplicated() %>%
any()
## [1] FALSE
Les identifiants ensembl sont uniques et nous avons un seul gene name par identifiant ensembl. Nous allons donc pouvoir les remplacer facilement :
DE_genes <- res %>%
as.data.frame() %>%
filter(padj <= 0.0001 & (log2FoldChange > 2.5 | log2FoldChange < -3.5)) %>%
rownames() %>%
sort()
DE_gene_names <- bm %>%
arrange(ensembl_gene_id) %>%
filter(ensembl_gene_id %in% DE_genes) %>%
pull(external_gene_name)
print(cbind(DE_genes, DE_gene_names))
## DE_genes DE_gene_names
## [1,] "ENSG00000065833" "ME1"
## [2,] "ENSG00000108702" "CCL1"
## [3,] "ENSG00000111537" "IFNG"
## [4,] "ENSG00000112116" "IL17F"
## [5,] "ENSG00000115008" "IL1A"
## [6,] "ENSG00000115523" "GNLY"
## [7,] "ENSG00000124191" "TOX2"
## [8,] "ENSG00000124766" "SOX4"
## [9,] "ENSG00000136237" "RAPGEF5"
## [10,] "ENSG00000136634" "IL10"
## [11,] "ENSG00000152104" "PTPN14"
## [12,] "ENSG00000154734" "ADAMTS1"
## [13,] "ENSG00000166025" "AMOTL1"
## [14,] "ENSG00000169860" "P2RY1"
## [15,] "ENSG00000271503" "CCL5"
pheatmap(mat_hm_adj[DE_genes,],
scale="row",
labels_row=DE_gene_names,
clustering_distance_rows="euclidean",
clustering_distance_cols="euclidean",
clustering_method="ward.D",
color=colorRampPalette(rev(brewer.pal(9, "RdBu")))(200),
main="Heatmap based on the *adjusted* variance-stabilized counts")
Remarque : nous avons utilisé le package R biomaRt pour associer les identifiants ensembl aux gene names, mais les packages AnnotationHub et AnnotationDbi (entre autres) peuvent être intéressants. Ce document recense différents outils disponibles et leurs fonctionnements.
L’analyse conduite ci-dessus nous a permis de détecter un certain nombre de gènes dérégulés par l’effet du traitement. Nous pouvons alors nous demander si ces gènes appartiennent préférentiellement à des pathways/voies/catégories (i.e. ensembles de gènes cohérents biologiquement) connus dans la littérature. Autrement dit, nous allons rechercher des gene-sets enrichis en gènes différentiellement exprimés, i.e. des gene-sets perturbés par l’effet du traitement.
De nombreuses méthodes et plusieurs packages R et applications web existent pour réaliser cette analyse fonctionnelle de manière automatisée. Ici, nous allons la construire pas-à-pas en utilisant trois méthodes différentes et en illustrant les points importants.
La première étape est d’importer dans R les gene-sets que l’on souhaite tester : par exemple les pathways KEGG, les GO terms de la catégorie Molecular Function, etc… Ici, nous importons uniquement les pathways KEGG en utilisant le package R EnrichmentBrowser :
kegg <- getGenesets(org="hsa", db="kegg")
print(head(kegg, 3))
## $`hsa00010_Glycolysis_/_Gluconeogenesis`
## [1] "10327" "124" "125" "126" "127" "128" "130" "130589" "131" "160287" "1737" "1738"
## [13] "2023" "2026" "2027" "217" "218" "219" "2203" "221" "222" "223" "224" "226"
## [25] "229" "230" "2538" "2597" "26330" "2645" "2821" "3098" "3099" "3101" "387712" "3939"
## [37] "3945" "3948" "441531" "501" "5105" "5106" "5160" "5161" "5162" "5211" "5213" "5214"
## [49] "5223" "5224" "5230" "5232" "5236" "5313" "5315" "55276" "55902" "57818" "669" "7167"
## [61] "80201" "83440" "84532" "8789" "92483" "92579" "9562"
##
## $`hsa00020_Citrate_cycle_(TCA_cycle)`
## [1] "1431" "1737" "1738" "1743" "2271" "3417" "3418" "3419" "3420" "3421" "4190" "4191" "47" "48"
## [15] "4967" "50" "5091" "5105" "5106" "5160" "5161" "5162" "55753" "6389" "6390" "6391" "6392" "8801"
## [29] "8802" "8803"
##
## $hsa00030_Pentose_phosphate_pathway
## [1] "132158" "2203" "221823" "226" "229" "22934" "230" "2539" "25796" "2821" "414328" "51071"
## [13] "5211" "5213" "5214" "5226" "5236" "55276" "5631" "5634" "6120" "64080" "6888" "7086"
## [25] "729020" "8277" "84076" "8789" "9104" "9563"
length(kegg)
## [1] 343
Les pathways KEGG sont importés sous la forme d’une liste : chaque élément est un vecteur (ensemble) de gènes représentant un pathway. Aussi, les gènes sont caractérisés par des numéros qui correspondent à leur identifiant Entrez (NCBI). On peut vérifier que chaque gene-set contient des identifiants uniques (i.e. pas de doublons) et qu’aucun gene-set n’est présent deux fois :
any(sapply(kegg, function(x) any(duplicated(x))))
## [1] FALSE
any(duplicated(lapply(kegg, function(x) sort(x))))
## [1] FALSE
L’étape suivante consiste à transformer les identifiants ensembl utilisés pour l’analyse différentielle en identifiants de type Entrez. Pour cela, nous pouvons utiliser la même méthode que précédemment et vérifier la présence de doublons :
bm <- getBM(attributes=c("ensembl_gene_id", "entrezgene"), mart=ensembl)
head(bm)
## ensembl_gene_id entrezgene
## 1 ENSG00000210049 NA
## 2 ENSG00000211459 4549
## 3 ENSG00000210077 NA
## 4 ENSG00000210082 4550
## 5 ENSG00000209082 NA
## 6 ENSG00000198888 4535
bm %>%
filter(!is.na(ensembl_gene_id)) %>%
pull(ensembl_gene_id) %>%
duplicated() %>%
any()
## [1] TRUE
dup_ensembl <- bm %>%
filter(!is.na(ensembl_gene_id)) %>%
filter(duplicated(ensembl_gene_id)) %>%
pull(ensembl_gene_id)
bm %>%
filter(ensembl_gene_id %in% dup_ensembl) %>%
arrange(ensembl_gene_id) %>%
head(n=10)
## ensembl_gene_id entrezgene
## 1 ENSG00000011454 2844
## 2 ENSG00000011454 23637
## 3 ENSG00000023171 57476
## 4 ENSG00000023171 100128242
## 5 ENSG00000058600 55718
## 6 ENSG00000058600 101060521
## 7 ENSG00000072195 10290
## 8 ENSG00000072195 100996693
## 9 ENSG00000079974 11158
## 10 ENSG00000079974 11159
On remarque qu’il est possible d’avoir différents identifiants Entrez qui pointent vers un même identifiant ensembl…
bm %>%
filter(!is.na(entrezgene)) %>%
pull(entrezgene) %>%
duplicated() %>%
any()
## [1] TRUE
dup_entrez <- bm %>%
filter(!is.na(entrezgene)) %>%
filter(duplicated(entrezgene)) %>%
pull(entrezgene)
bm %>%
filter(entrezgene %in% dup_entrez) %>%
arrange(entrezgene) %>%
head(n=10)
## ensembl_gene_id entrezgene
## 1 ENSG00000236149 23
## 2 ENSG00000204574 23
## 3 ENSG00000232169 23
## 4 ENSG00000231129 23
## 5 ENSG00000236342 23
## 6 ENSG00000206490 23
## 7 ENSG00000225989 23
## 8 ENSG00000175164 28
## 9 ENSG00000281879 28
## 10 ENSG00000159842 29
On remarque que plusieurs identifiants ensembl (et donc plusieurs log2(Fold-Change), P-valeurs…) correspondent à un même identifiant Entrez. Les pathways étant définis par des identifiants Entrez, nous devons choisir quel identifiant ensembl associer à chaque Entrez. Ici, nous choisons l’identifiant ensembl ayant l’expression la plus forte, mais nous pourrions choisir tout autre critère :
res <- res %>%
as.data.frame() %>%
mutate(ensembl_gene_id=rownames(res)) %>%
full_join(bm, by="ensembl_gene_id") %>%
arrange(desc(baseMean)) %>%
filter(!duplicated(entrezgene))
print(head(res))
## baseMean log2FoldChange lfcSE stat pvalue padj ensembl_gene_id entrezgene
## 1 615460.0 0.26714260 0.10288000 2.5966428 0.009413978 0.10519755 ENSG00000075624 60
## 2 598624.2 0.10777324 0.10825857 0.9955169 0.319484931 0.75184010 ENSG00000100453 3002
## 3 428040.3 -0.18019105 0.11389827 -1.5820350 0.113641578 0.48043456 ENSG00000166710 567
## 4 409505.7 0.13099269 0.09991101 1.3110937 0.189826113 0.60658307 ENSG00000074800 2023
## 5 407566.1 0.29093899 0.09923912 2.9316967 0.003371158 0.04877515 ENSG00000184009 71
## 6 400827.1 0.07520714 0.10268790 0.7323856 0.463933242 0.83995735 ENSG00000111640 2597
Nous pouvons maintenant tester si le gene-set hsa05418_Fluid_shear_stress_and_atherosclerosis
(par exemple) est enrichi en gènes différentiellement exprimés. Nous utilisons la méthode ORA (Over-Representation Analysis) avec un test de Fisher :
res <- res %>%
mutate(geneset = ifelse(entrezgene %in% kegg$hsa05418_Fluid_shear_stress_and_atherosclerosis, "in", "out")) %>%
mutate(DE = ifelse(padj <= 0.05, "DE", "notDE"))
print(head(res), digits=3)
## baseMean log2FoldChange lfcSE stat pvalue padj ensembl_gene_id entrezgene geneset DE
## 1 615460 0.2671 0.1029 2.597 0.00941 0.1052 ENSG00000075624 60 in notDE
## 2 598624 0.1078 0.1083 0.996 0.31948 0.7518 ENSG00000100453 3002 out notDE
## 3 428040 -0.1802 0.1139 -1.582 0.11364 0.4804 ENSG00000166710 567 out notDE
## 4 409506 0.1310 0.0999 1.311 0.18983 0.6066 ENSG00000074800 2023 out notDE
## 5 407566 0.2909 0.0992 2.932 0.00337 0.0488 ENSG00000184009 71 in DE
## 6 400827 0.0752 0.1027 0.732 0.46393 0.8400 ENSG00000111640 2597 out notDE
tab <- table(res$geneset, res$DE)
print(tab)
##
## DE notDE
## in 25 81
## out 1152 11488
print(fisher.test(tab), alternative="greater")
##
## Fisher's Exact Test for Count Data
##
## data: tab
## p-value = 8.861e-06
## alternative hypothesis: true odds ratio is not equal to 1
## 95 percent confidence interval:
## 1.874578 4.893869
## sample estimates:
## odds ratio
## 3.077626
Le gene-set hsa05418_Fluid_shear_stress_and_atherosclerosis
est enrichi en gènes dérégulés car son Odds-Ratio défini comme :
\[ \text{OR} = \frac{\text{Odd in gene-set}}{\text{Odd out gene-set}} = \frac{25/81}{1152/11488} = 3.07 \] est significativement supérieur à 1 (P-valeur = \(8.86 \times 10^{-6}\)). On peut également calculer le Fold-Enrichment :
\[ \text{FE} = \frac{\text{% gene DE in gene-set}}{\text{% gene DE out gene-set }} = \frac{25/(25+81)}{1152/(1152+11488)} = \frac{23,6}{9,1} = 2,59\] On peut maintenant répéter l’opération grâce à une boucle sur tous les pathways KEGG :
res_kegg <- data.frame(kegg=names(kegg), ngenes=sapply(kegg, length), OR=NA, pvalue=NA)
for (name in names(kegg)){
res <- res %>%
mutate(geneset = ifelse(entrezgene %in% kegg[[name]], "in", "out")) %>%
mutate(DE = ifelse(padj <= 0.05, "DE", "notDE"))
tab <- table(res$geneset, res$DE)
if (nrow(tab) < 2 | ncol(tab) < 2) stop("tab is not a 2x2 table, can't perform the statistical test")
f <- fisher.test(table(res$geneset, res$DE), alternative="greater")
res_kegg[which(res_kegg$kegg == name), c("OR", "pvalue")] <- c(f$estimate, f$p.value)
}
print(head(res_kegg))
## kegg ngenes OR
## hsa00010_Glycolysis_/_Gluconeogenesis hsa00010_Glycolysis_/_Gluconeogenesis 67 0.9588108
## hsa00020_Citrate_cycle_(TCA_cycle) hsa00020_Citrate_cycle_(TCA_cycle) 30 0.0000000
## hsa00030_Pentose_phosphate_pathway hsa00030_Pentose_phosphate_pathway 30 0.0000000
## hsa00040_Pentose_and_glucuronate_interconversions hsa00040_Pentose_and_glucuronate_interconversions 34 0.0000000
## hsa00051_Fructose_and_mannose_metabolism hsa00051_Fructose_and_mannose_metabolism 33 0.0000000
## hsa00052_Galactose_metabolism hsa00052_Galactose_metabolism 31 0.4676265
## pvalue
## hsa00010_Glycolysis_/_Gluconeogenesis 0.6071986
## hsa00020_Citrate_cycle_(TCA_cycle) 1.0000000
## hsa00030_Pentose_phosphate_pathway 1.0000000
## hsa00040_Pentose_and_glucuronate_interconversions 1.0000000
## hsa00051_Fructose_and_mannose_metabolism 1.0000000
## hsa00052_Galactose_metabolism 0.8815650
Etant donné le nombre important de pathways testés, il est probable que certaines p-valeurs soient significatives seulement par chance : on doit les ajuster de manière à prendre en compte la multiplicité des tests et ainsi contrôler le taux de faux positifs.
res_kegg <- res_kegg %>%
mutate(FDR = p.adjust(pvalue)) %>%
arrange(FDR)
print(head(res_kegg))
## kegg ngenes OR pvalue FDR
## 1 hsa04060_Cytokine-cytokine_receptor_interaction 295 6.628291 1.146409e-26 3.932182e-24
## 2 hsa05323_Rheumatoid_arthritis 93 6.685674 4.865160e-12 1.663885e-09
## 3 hsa04061_Viral_protein_interaction_with_cytokine_and_cytokine_receptor 100 7.361277 1.484583e-11 5.062429e-09
## 4 hsa05321_Inflammatory_bowel_disease 65 7.502807 2.833697e-11 9.634569e-09
## 5 hsa04630_JAK-STAT_signaling_pathway 162 4.194984 8.949376e-10 3.033838e-07
## 6 hsa04062_Chemokine_signaling_pathway 192 3.641915 1.297898e-09 4.386897e-07
sum(res_kegg$FDR <= 0.05)
## [1] 37
Au seuil de 5%, on détecte 37 pathways KEGG enrichis en gènes dérégulés. On peut les représenter de cette manière :
res_kegg %>%
filter(FDR <= 0.05) %>%
mutate(kegg = gsub("_", " ", kegg)) %>%
ggplot(aes(x=OR, y=reorder(kegg, OR), color=FDR, size=ngenes)) +
geom_point() +
labs(size="# genes", color="FDR") +
xlab("Odds-ratio") +
ylab("Gene-set") +
ggtitle("Pathways KEGG enrichis") +
theme_light()
Remarque : on aurait pu visualiser les Fold-Enrichment à la place des Odds-Ratios.
Cette analyse d’enrichissement utilise la méthode ORA (Over-Representation Analysis) mais il existe beaucoup d’autres méthodes dont les objectifs et hypothèses sont légèrement différents.
La méthode CAMERA du package R limma
utilise l’ensemble des résultats plutôt que de filtrer en utilisant un seuil sur la P-valeur ajustée (par exemple). Le code R ci-dessous permet de réaliser une telle analyse, en utilisant en entrée l’ensemble des statistiques de tests.
Remarque : la statistique de test d’un gène est défini comme :
\[ \text{stat} = \frac{\log_2(\text{Fold-Change})}{\text{standard-error}} \]
statistic <- res %>% pull(stat)
names(statistic) <- res %>% pull(entrezgene)
statistic <- statistic[!is.na(statistic)]
print(head(statistic))
## 60 3002 567 2023 71 2597
## 2.5966428 0.9955169 -1.5820350 1.3110937 2.9316967 0.7323856
res.cam <- cameraPR(statistic=statistic, index=kegg)
print(head(res.cam))
## NGenes Direction PValue FDR
## hsa05332_Graft-versus-host_disease 38 Down 8.591389e-22 2.938255e-19
## hsa04060_Cytokine-cytokine_receptor_interaction 259 Down 1.158750e-20 1.591790e-18
## hsa05323_Rheumatoid_arthritis 84 Down 1.396307e-20 1.591790e-18
## hsa04061_Viral_protein_interaction_with_cytokine_and_cytokine_receptor 87 Down 1.766405e-19 1.510276e-17
## hsa04940_Type_I_diabetes_mellitus 39 Down 1.018705e-15 6.967943e-14
## hsa05321_Inflammatory_bowel_disease 63 Down 5.009742e-12 2.855553e-10
Le gene-set hsa04060_Cytokine-cytokine_receptor_interaction
est détecté comme étant plutôt down-régulé, et on peut visualiser les statistiques de test des gènes qui le composent :
res %>%
filter(entrezgene %in% kegg[["hsa04060_Cytokine-cytokine_receptor_interaction"]]) %>%
ggplot(aes(x=stat)) +
geom_histogram(bins=50, color="white", fill="grey") +
geom_vline(xintercept=0, linetype="dashed") +
xlab("Statistique de test (par gène)") +
ylab("Frequency") +
ggtitle("Pathway hsa04060") +
theme_light()
## Warning: Removed 35 rows containing non-finite values (stat_bin).
La méthode GSEA est également souvent utilisée et permet, comme CAMERA, de détecter si un gene-set est plutôt up-régulé ou down-régulé. Nous utilisons ici le package fgsea
pour l’illustrer, avec en entrée l’ensemble des statistiques de tests :
res.gsea <- fgseaMultilevel(pathways=kegg, stats=statistic, eps=0) %>%
arrange(padj)
## Warning in preparePathwaysAndStats(pathways, stats, minSize, maxSize, gseaParam, : There are ties in the preranked stats (9.35% of the list).
## The order of those tied genes will be arbitrary, which may produce unexpected results.
print(head(res.gsea))
## pathway pval padj log2err
## 1: hsa04060_Cytokine-cytokine_receptor_interaction 9.885149e-26 3.380721e-23 1.3188888
## 2: hsa05323_Rheumatoid_arthritis 2.774582e-13 4.744536e-11 0.9325952
## 3: hsa05332_Graft-versus-host_disease 1.323968e-10 1.509324e-08 0.8266573
## 4: hsa04940_Type_I_diabetes_mellitus 2.593657e-10 2.217577e-08 0.8140358
## 5: hsa03013_RNA_transport 9.469439e-10 5.989887e-08 0.7881868
## 6: hsa04061_Viral_protein_interaction_with_cytokine_and_cytokine_receptor 1.050857e-09 5.989887e-08 0.7881868
## ES NES size leadingEdge
## 1: -0.6635860 -2.750132 259 112744,3562,414062,6349,1437,388372,...
## 2: -0.7331574 -2.610718 84 414062,6349,1437,6348,6364,3552,...
## 3: -0.8231509 -2.514241 38 3558,3552,3458,941,3824,3122,...
## 4: -0.8205691 -2.512186 39 3558,3552,3458,941,3122,942,...
## 5: 0.5406519 2.152058 157 10460,9883,10940,25929,60528,55706,...
## 6: -0.6857816 -2.448202 87 414062,6349,388372,9560,6348,6364,...
On peut à nouveau illustrer l’enrichissement du pathway hsa04060_Cytokine-cytokine_receptor_interaction
:
plotEnrichment(kegg[["hsa04060_Cytokine-cytokine_receptor_interaction"]], statistic) +
labs(title="hsa04060_Cytokine-cytokine_receptor_interaction")
Enfin, la figure ci-dessous permet de visualiser les résultats pour les 10 pathways les plus up- et down-régulés :
topPathwaysUp <- res.gsea[ES > 0][head(order(pval), n=10), pathway]
topPathwaysDown <- res.gsea[ES < 0][head(order(pval), n=10), pathway]
topPathways <- c(topPathwaysUp, rev(topPathwaysDown))
plotGseaTable(kegg[topPathways], statistic, res.gsea, gseaParam=0.5)
Questions/remarques :
Pour aller plus loin :
Dans les sections précédentes, nous avons utilisé des pathways KEGG importés à partir du package R EnrichmentBrowser. Il est également possible de les importer en utilisant le package msigdbr, lui même basé sur ces données :
kegg.msigdb <- msigdbr(species="Homo sapiens",
category="C2",
subcategory="KEGG")
print(head(as.data.frame(kegg.msigdb)))
## gs_cat gs_subcat gs_name entrez_gene gene_symbol human_entrez_gene human_gene_symbol gs_id gs_pmid
## 1 C2 CP:KEGG KEGG_ABC_TRANSPORTERS 19 ABCA1 19 ABCA1 M11911
## 2 C2 CP:KEGG KEGG_ABC_TRANSPORTERS 10349 ABCA10 10349 ABCA10 M11911
## 3 C2 CP:KEGG KEGG_ABC_TRANSPORTERS 26154 ABCA12 26154 ABCA12 M11911
## 4 C2 CP:KEGG KEGG_ABC_TRANSPORTERS 154664 ABCA13 154664 ABCA13 M11911
## 5 C2 CP:KEGG KEGG_ABC_TRANSPORTERS 20 ABCA2 20 ABCA2 M11911
## 6 C2 CP:KEGG KEGG_ABC_TRANSPORTERS 21 ABCA3 21 ABCA3 M11911
## gs_geoid gs_exact_source gs_url gs_description species_name
## 1 hsa02010 http://www.genome.jp/kegg/pathway/hsa/hsa02010.html ABC transporters Homo sapiens
## 2 hsa02010 http://www.genome.jp/kegg/pathway/hsa/hsa02010.html ABC transporters Homo sapiens
## 3 hsa02010 http://www.genome.jp/kegg/pathway/hsa/hsa02010.html ABC transporters Homo sapiens
## 4 hsa02010 http://www.genome.jp/kegg/pathway/hsa/hsa02010.html ABC transporters Homo sapiens
## 5 hsa02010 http://www.genome.jp/kegg/pathway/hsa/hsa02010.html ABC transporters Homo sapiens
## 6 hsa02010 http://www.genome.jp/kegg/pathway/hsa/hsa02010.html ABC transporters Homo sapiens
## species_common_name ortholog_sources num_ortholog_sources
## 1 human <NA> NA
## 2 human <NA> NA
## 3 human <NA> NA
## 4 human <NA> NA
## 5 human <NA> NA
## 6 human <NA> NA
length(unique(kegg.msigdb$gs_name))
## [1] 186
On constate que 186 pathways KEGG sont répertoriés dans le tableau alors qu’EnrichmentBrower a permis d’en importer 343.
Question : comment faire pour comparer le contenu des pathways importés grâce à msigdbr et EnrichmentBrowser ?
Dans les sections ci-dessus, nous avons analysé des données provenant de designs expérimentaux simples. Or, pour répondre à certaines questions biologiques, il est nécessaire de créer des designs dits complexes, i.e. incluants plusieurs facteurs.
Nous allons étudier ici des données RNA-Seq provenant d’un tel design : l’objectif est d’évaluer l’effet d’un traitement (T vs C) sur deux souches d’une bactérie (WT et KO). Les deux facteurs/variables étudié(e)s sont donc le traitement et la souche, chacun prenant deux niveaux/modalités.
Question : que peut-on faire à partir de ces données et comment pouvons-nous les analyser ?
rm(list=ls())
load("data_interaction.RData")
ls()
## [1] "counts" "target"
print(target)
## label strain treatment replicate
## WT-C-1 WT-C-1 WT C r1
## WT-C-2 WT-C-2 WT C r2
## WT-C-3 WT-C-3 WT C r3
## WT-T-1 WT-T-1 WT T r1
## WT-T-2 WT-T-2 WT T r2
## WT-T-3 WT-T-3 WT T r3
## KO-C-1 KO-C-1 KO C r1
## KO-C-2 KO-C-2 KO C r2
## KO-C-3 KO-C-3 KO C r3
## KO-T-1 KO-T-1 KO T r1
## KO-T-2 KO-T-2 KO T r2
## KO-T-3 KO-T-3 KO T r3
print(head(counts))
## WT-C-1 WT-C-2 WT-C-3 WT-T-1 WT-T-2 WT-T-3 KO-C-1 KO-C-2 KO-C-3 KO-T-1 KO-T-2 KO-T-3
## gene1 412 590 656 698 537 737 35 47 36 9 21 19
## gene2 7483 11099 9825 20678 13621 19940 13170 14153 11198 8475 9479 8464
## gene3 7005 11450 10400 11932 9691 14038 8533 10837 7824 6814 8318 6668
## gene4 6774 12938 9603 13754 11554 16687 4405 4446 3661 4045 6133 4075
## gene5 2706 4018 3604 4927 3839 5663 4526 5084 4326 4435 4985 4502
## gene6 25276 42273 38634 49580 40575 56964 36755 40690 30480 28131 32439 27851
# mettre WT en reference de strain et C en reference de treatment
target$strain <- factor(target$strain, levels=c("WT", "KO"))
target$treatment <- factor(target$treatment, levels=c("C", "T"))
Le code R ci-dessous permet de créer l’objet dds
avec le design adéquat et de réaliser les 3 étapes de l’analyse différentielle (normalisation, estimation de la dispersion et modèle linéaire généralisé) :
dds <- DESeqDataSetFromMatrix(countData=counts,
colData=target,
design=~replicate + strain + treatment + strain:treatment)
## Warning in DESeqDataSet(se, design = design, ignoreRank): some variables in design formula are characters, converting to
## factors
dds <- estimateSizeFactors(dds)
dds <- estimateDispersions(dds)
dds <- nbinomWaldTest(dds)
resultsNames(dds)
## [1] "Intercept" "replicate_r2_vs_r1" "replicate_r3_vs_r1" "strain_KO_vs_WT" "treatment_T_vs_C"
## [6] "strainKO.treatmentT"
L’analyse en composante principale permet de visualiser la structure des données :
rld <- rlog(dds)
pca <- PCA(t(assay(rld)), scale.unit=FALSE, ncp=3, graph=FALSE)
fviz_pca_ind(pca, axes=c(1, 2), repel=TRUE, mean.point=FALSE,
habillage=factor(paste(target$strain, target$treatment, sep="-")))
fviz_pca_ind(pca, axes=c(1, 3), repel=TRUE, mean.point=FALSE,
habillage=factor(paste(target$strain, target$treatment, sep="-")))
Sur le premier plan factoriel formé par les axes 1 et 2 nous visualisons l’effet du traitement (axe 1) et l’effet de la souche (axe 2). Ensuite, l’axe 3 permet de visualiser l’interaction souche-traitement : l’effet du traitement sur les WT (haut vers le bas) est opposé à l’effet du traitement sur KO (bas vers le haut).
On peut également visualiser l’interaction pour un gène spécifique (bien choisi) :
ncounts <- counts(dds, norm=TRUE)
d <- data.frame(target, ncounts=ncounts["gene914",])
print(d)
## label strain treatment replicate ncounts
## WT-C-1 WT-C-1 WT C r1 57.68749
## WT-C-2 WT-C-2 WT C r2 43.65947
## WT-C-3 WT-C-3 WT C r3 44.75770
## WT-T-1 WT-T-1 WT T r1 491.84167
## WT-T-2 WT-T-2 WT T r2 276.67419
## WT-T-3 WT-T-3 WT T r3 338.98342
## KO-C-1 KO-C-1 KO C r1 275.29097
## KO-C-2 KO-C-2 KO C r2 116.71415
## KO-C-3 KO-C-3 KO C r3 266.30771
## KO-T-1 KO-T-1 KO T r1 44.58641
## KO-T-2 KO-T-2 KO T r2 23.61180
## KO-T-3 KO-T-3 KO T r3 27.25240
ggplot(d, aes(x=strain, y=ncounts, color=treatment, shape=replicate)) +
geom_beeswarm(dodge.width=0.3, size=4) +
scale_y_continuous(trans = log2_trans(),
breaks = trans_breaks("log2", function(x) 2^x),
labels = trans_format("log2", math_format(2^.x))) +
xlab("Condition") +
ylab("Normalized counts (log2 scale)") +
labs(shape="Replicate", color="Treatment") +
ggtitle("Illustration of the donor effect for gene gene914") +
theme_light()
Nous pouvons ensuite utiliser DESeq2 et des contrastes adaptés pour evaluer l’effet du traitement sur chaque souche et ensuite comparer ces deux effets :
# ExploreModelMatrix() pour lancer l'application Shiny
res_TvsC.WT <- results(dds, contrast=c(0, 0, 0, 0, 1, 0))
res_TvsC.KO <- results(dds, contrast=c(0, 0, 0, 0, 1, 1))
res_interaction <- results(dds, contrast=c(0, 0, 0, 0, 0, 1))
summary(res_TvsC.WT, alpha=0.05)
##
## out of 1716 with nonzero total read count
## adjusted p-value < 0.05
## LFC > 0 (up) : 487, 28%
## LFC < 0 (down) : 451, 26%
## outliers [1] : 0, 0%
## low counts [2] : 0, 0%
## (mean count < 0)
## [1] see 'cooksCutoff' argument of ?results
## [2] see 'independentFiltering' argument of ?results
summary(res_TvsC.KO, alpha=0.05)
##
## out of 1716 with nonzero total read count
## adjusted p-value < 0.05
## LFC > 0 (up) : 463, 27%
## LFC < 0 (down) : 506, 29%
## outliers [1] : 0, 0%
## low counts [2] : 0, 0%
## (mean count < 0)
## [1] see 'cooksCutoff' argument of ?results
## [2] see 'independentFiltering' argument of ?results
summary(res_interaction, alpha=0.05)
##
## out of 1716 with nonzero total read count
## adjusted p-value < 0.05
## LFC > 0 (up) : 398, 23%
## LFC < 0 (down) : 430, 25%
## outliers [1] : 0, 0%
## low counts [2] : 0, 0%
## (mean count < 0)
## [1] see 'cooksCutoff' argument of ?results
## [2] see 'independentFiltering' argument of ?results
Afin de comparer les effets du traitement chez les WT et chez les KO, nous assemblons les résultats au sein d’un même tableau et nous créons de nouvelles variables indiquant d’une part si un gène est en interaction (DE_int
) et d’autre part si les deux effets sont significatifs (DE_both_strains
). Ensuite nous pouvons tracer la figure :
d <- data.frame(gene=rownames(res_TvsC.WT),
log2FC_TvsC.WT=res_TvsC.WT$log2FoldChange,
padj_TvsC.WT=res_TvsC.WT$padj,
log2FC_TvsC.KO=res_TvsC.KO$log2FoldChange,
padj_TvsC.KO=res_TvsC.KO$padj,
log2FC_int=res_interaction$log2FoldChange,
padj_int=res_interaction$padj)
d <- d %>%
mutate(DE_int=ifelse(!is.na(padj_int) & padj_int <= 0.05, "Yes", "No"),
DE_both_strains=ifelse(!is.na(padj_TvsC.WT) & padj_TvsC.WT <= 0.05 & !is.na(padj_TvsC.KO) & padj_TvsC.KO <= 0.05, "Yes", "No"))
print(head(d))
## gene log2FC_TvsC.WT padj_TvsC.WT log2FC_TvsC.KO padj_TvsC.KO log2FC_int padj_int DE_int DE_both_strains
## 1 gene1 -0.12265527 5.178970e-01 -0.8514421 6.810186e-03 -0.72878688 0.052770555 No No
## 2 gene2 0.54287663 8.312332e-06 -0.1234842 3.601136e-01 -0.66636086 0.000169735 Yes No
## 3 gene3 -0.06527749 6.122993e-01 0.1031446 4.114685e-01 0.16842212 0.357576667 No No
## 4 gene4 0.16625328 3.794633e-01 0.5820048 6.742476e-04 0.41575149 0.112832737 No No
## 5 gene5 0.09476675 4.220296e-01 0.4144172 7.871748e-05 0.31965050 0.044938613 Yes No
## 6 gene6 0.10150445 3.921496e-01 0.1323122 2.488123e-01 0.03080776 0.882849983 No No
ggplot(d, aes(x=log2FC_TvsC.WT, y=log2FC_TvsC.KO, color=DE_int, size=DE_both_strains)) +
geom_point(alpha=0.5) +
scale_color_manual(values=c("Yes"="red", "No"="black")) +
scale_size_manual(values=c("Yes"=1, "No"=0.4)) +
geom_hline(yintercept=0, lty=2, color="grey") +
geom_vline(xintercept=0, lty=2, color="grey") +
geom_abline(slope=1, intercept=0, lty=2, color="grey") +
labs(color="Strain-condition\ninteraction", size="DE for both\nWT and KO") +
xlab(bquote(log[2](FC)~"T vs C for WT")) +
ylab(bquote(log[2](FC)~"T vs C for KO")) +
ggtitle("Illustration of the interaction") +
theme_light()
Sur cette figure, les points éloignés de la diagonale (droite \(Y = X\)) correspondent aux gènes en interaction, i.e. pour lesquels l’effet du traitement diffère selon la souche. Autrement dit, le log2(Fold-Change) chez les WT est très différent du log2(Fold-Change) chez les KO. Cette différence d’effets est significative lorsque le point est rouge (au seuil de 5%), et les effets individuels (chez les WT et KO) sont significatifs simultanément si le point est agrandi.
On peut sélectionner ces gènes et les visualiser sous la forme d’une heatmap (en ajustant sur l’effet du réplicat) :
int_genes <- d %>%
as.data.frame() %>%
filter(padj_int <= 0.05) %>%
pull(gene)
mat_hm <- assay(rlog(dds))
mat_hm_adj <- removeBatchEffect(mat_hm, batch=target$replicate)
pheatmap(mat_hm_adj[int_genes,],
scale="row",
show_rownames=FALSE,
clustering_distance_rows="euclidean",
clustering_distance_cols="euclidean",
clustering_method="ward.D",
color=colorRampPalette(rev(brewer.pal(9, "RdBu")))(200))
On voit sur cette heatmap que pour chaque ligne l’effet du traitement est opposé entre les deux souches (e.g. rouge vs bleu pour les WT contre bleu vs rouge pour les KO).
Le code ci-dessous permet de visualiser les log2(Fold-Change) :
rownames(d) <- d$gene
d <- d %>%
filter(padj_int < 0.005 & sign(log2FC_TvsC.WT) != sign(log2FC_TvsC.KO)) %>%
dplyr::select(log2FC_TvsC.WT, log2FC_TvsC.KO) %>%
as.matrix()
pheatmap(d,
scale="none",
show_rownames=FALSE,
cluster_cols=FALSE,
clustering_distance_rows="euclidean",
angle_col=45,
fontsize_row=5,
color=colorRampPalette(rev(brewer.pal(9, "RdBu")))(100),
breaks=seq(-4, 4, length=101))
Question : comment analyser un design expérimental comportant plus de 3 facteurs d’intérêt ?
Livre R for Data Science
Livre R Cookbook
Site de référence ggplot2
Site extensions ggplot2
Package R patchwork
Livre ComplexHeatmap
Palettes de couleurs
Dans un souci de reproductibilité, la commande ci-dessous permet d’afficher l’ensemble des packages utilisés ainsi que leurs versions :
devtools::session_info()
## ─ Session info ───────────────────────────────────────────────────────────────────────────────────────────────────────
## setting value
## version R version 4.0.3 (2020-10-10)
## os macOS Big Sur 10.16
## system x86_64, darwin17.0
## ui X11
## language (EN)
## collate fr_FR.UTF-8
## ctype fr_FR.UTF-8
## tz Europe/Paris
## date 2021-05-07
##
## ─ Packages ───────────────────────────────────────────────────────────────────────────────────────────────────────────
## package * version date lib source
## abind 1.4-5 2016-07-21 [1] CRAN (R 4.0.0)
## annotate 1.68.0 2020-10-27 [1] Bioconductor
## AnnotationDbi 1.52.0 2020-10-27 [1] Bioconductor
## askpass 1.1 2019-01-13 [1] CRAN (R 4.0.0)
## assertthat 0.2.1 2019-03-21 [1] CRAN (R 4.0.0)
## backports 1.2.1 2020-12-09 [1] CRAN (R 4.0.2)
## beeswarm 0.2.3 2016-04-25 [1] CRAN (R 4.0.2)
## Biobase * 2.50.0 2020-10-27 [1] Bioconductor
## BiocFileCache 1.14.0 2020-10-27 [1] Bioconductor
## BiocGenerics * 0.36.0 2020-10-27 [1] Bioconductor
## BiocParallel 1.24.1 2020-11-06 [1] Bioconductor
## biomaRt * 2.46.0 2020-10-27 [1] Bioconductor
## Biostrings 2.58.0 2020-10-27 [1] Bioconductor
## bit 4.0.4 2020-08-04 [1] CRAN (R 4.0.2)
## bit64 4.0.5 2020-08-30 [1] CRAN (R 4.0.2)
## bitops 1.0-6 2013-08-17 [1] CRAN (R 4.0.0)
## blob 1.2.1 2020-01-20 [1] CRAN (R 4.0.0)
## broom 0.7.3 2020-12-16 [1] CRAN (R 4.0.2)
## Cairo 1.5-12.2 2020-07-07 [1] CRAN (R 4.0.2)
## callr 3.5.1 2020-10-13 [1] CRAN (R 4.0.2)
## car 3.0-10 2020-09-29 [1] CRAN (R 4.0.2)
## carData 3.0-4 2020-05-22 [1] CRAN (R 4.0.0)
## cellranger 1.1.0 2016-07-27 [1] CRAN (R 4.0.0)
## class 7.3-17 2020-04-26 [1] CRAN (R 4.0.3)
## classInt 0.4-3 2020-04-07 [1] CRAN (R 4.0.2)
## cli 2.2.0 2020-11-20 [1] CRAN (R 4.0.2)
## cluster 2.1.0 2019-06-19 [1] CRAN (R 4.0.3)
## codetools 0.2-16 2018-12-24 [1] CRAN (R 4.0.3)
## colorspace 2.0-0 2020-11-11 [1] CRAN (R 4.0.2)
## cowplot 1.1.1 2020-12-30 [1] CRAN (R 4.0.2)
## crayon 1.3.4 2017-09-16 [1] CRAN (R 4.0.0)
## crosstalk 1.1.0.1 2020-03-13 [1] CRAN (R 4.0.0)
## curl 4.3 2019-12-02 [1] CRAN (R 4.0.0)
## data.table 1.13.6 2020-12-30 [1] CRAN (R 4.0.2)
## DBI 1.1.0 2019-12-15 [1] CRAN (R 4.0.0)
## dbplyr 2.0.0 2020-11-03 [1] CRAN (R 4.0.2)
## DelayedArray 0.16.0 2020-10-27 [1] Bioconductor
## desc 1.2.0 2018-05-01 [1] CRAN (R 4.0.0)
## DESeq2 * 1.30.0 2020-10-27 [1] Bioconductor
## devtools * 2.3.2 2020-09-18 [1] CRAN (R 4.0.2)
## digest 0.6.27 2020-10-24 [1] CRAN (R 4.0.2)
## dplyr * 1.0.2 2020-08-18 [1] CRAN (R 4.0.2)
## DT 0.17 2021-01-06 [1] CRAN (R 4.0.2)
## e1071 1.7-6 2021-03-18 [1] CRAN (R 4.0.2)
## ellipsis 0.3.1 2020-05-15 [1] CRAN (R 4.0.0)
## EnrichmentBrowser * 2.20.7 2020-12-10 [1] Bioconductor
## evaluate 0.14 2019-05-28 [1] CRAN (R 4.0.0)
## ExploreModelMatrix * 1.2.0 2020-10-27 [1] Bioconductor
## factoextra * 1.0.7 2020-04-01 [1] CRAN (R 4.0.0)
## FactoMineR * 2.4 2020-12-11 [1] CRAN (R 4.0.2)
## fansi 0.4.1 2020-01-08 [1] CRAN (R 4.0.0)
## farver 2.0.3 2020-01-16 [1] CRAN (R 4.0.0)
## fastmap 1.0.1 2019-10-08 [1] CRAN (R 4.0.2)
## fastmatch 1.1-0 2017-01-28 [1] CRAN (R 4.0.0)
## fgsea * 1.16.0 2020-10-27 [1] Bioconductor
## flashClust 1.01-2 2012-08-21 [1] CRAN (R 4.0.0)
## forcats 0.5.0 2020-03-01 [1] CRAN (R 4.0.0)
## foreign 0.8-80 2020-05-24 [1] CRAN (R 4.0.3)
## formatR 1.7 2019-06-11 [1] CRAN (R 4.0.0)
## fs 1.5.0 2020-07-31 [1] CRAN (R 4.0.2)
## futile.logger 1.4.3 2016-07-10 [1] CRAN (R 4.0.0)
## futile.options 1.0.1 2018-04-20 [1] CRAN (R 4.0.0)
## genefilter 1.72.0 2020-10-27 [1] Bioconductor
## geneplotter 1.68.0 2020-10-27 [1] Bioconductor
## generics 0.1.0 2020-10-31 [1] CRAN (R 4.0.2)
## GenomeInfoDb * 1.26.2 2020-12-08 [1] Bioconductor
## GenomeInfoDbData 1.2.4 2021-01-08 [1] Bioconductor
## GenomicRanges * 1.42.0 2020-10-27 [1] Bioconductor
## ggbeeswarm * 0.6.0 2017-08-07 [1] CRAN (R 4.0.2)
## ggplot2 * 3.3.3 2020-12-30 [1] CRAN (R 4.0.2)
## ggpubr 0.4.0 2020-06-27 [1] CRAN (R 4.0.2)
## ggrepel * 0.9.0 2020-12-16 [1] CRAN (R 4.0.2)
## ggsignif 0.6.0 2019-08-08 [1] CRAN (R 4.0.0)
## ggVennDiagram * 0.5.0 2021-04-08 [1] Github (gaospecial/ggVennDiagram@c64b878)
## glue 1.4.2 2020-08-27 [1] CRAN (R 4.0.2)
## graph * 1.68.0 2020-10-27 [1] Bioconductor
## gridExtra 2.3 2017-09-09 [1] CRAN (R 4.0.0)
## GSEABase 1.52.1 2020-12-11 [1] Bioconductor
## gtable 0.3.0 2019-03-25 [1] CRAN (R 4.0.0)
## haven 2.3.1 2020-06-01 [1] CRAN (R 4.0.0)
## hms 0.5.3 2020-01-08 [1] CRAN (R 4.0.0)
## htmltools 0.5.1.1 2021-01-22 [1] CRAN (R 4.0.2)
## htmlwidgets 1.5.3 2020-12-10 [1] CRAN (R 4.0.2)
## httpuv 1.5.4 2020-06-06 [1] CRAN (R 4.0.2)
## httr 1.4.2 2020-07-20 [1] CRAN (R 4.0.2)
## IRanges * 2.24.1 2020-12-12 [1] Bioconductor
## jsonlite 1.7.2 2020-12-09 [1] CRAN (R 4.0.2)
## KEGGgraph 1.50.0 2020-10-27 [1] Bioconductor
## KEGGREST 1.30.1 2020-11-23 [1] Bioconductor
## KernSmooth 2.23-17 2020-04-26 [1] CRAN (R 4.0.3)
## knitr 1.30 2020-09-22 [1] CRAN (R 4.0.2)
## labeling 0.4.2 2020-10-20 [1] CRAN (R 4.0.2)
## lambda.r 1.2.4 2019-09-18 [1] CRAN (R 4.0.0)
## later 1.1.0.1 2020-06-05 [1] CRAN (R 4.0.0)
## lattice 0.20-41 2020-04-02 [1] CRAN (R 4.0.3)
## lazyeval 0.2.2 2019-03-15 [1] CRAN (R 4.0.0)
## leaps 3.1 2020-01-16 [1] CRAN (R 4.0.0)
## lifecycle 0.2.0 2020-03-06 [1] CRAN (R 4.0.0)
## limma * 3.46.0 2020-10-27 [1] Bioconductor
## locfit 1.5-9.4 2020-03-25 [1] CRAN (R 4.0.0)
## magrittr 2.0.1 2020-11-17 [1] CRAN (R 4.0.2)
## MASS 7.3-53 2020-09-09 [1] CRAN (R 4.0.3)
## Matrix 1.2-18 2019-11-27 [1] CRAN (R 4.0.3)
## MatrixGenerics * 1.2.0 2020-10-27 [1] Bioconductor
## matrixStats * 0.57.0 2020-09-25 [1] CRAN (R 4.0.2)
## memoise 1.1.0 2017-04-21 [1] CRAN (R 4.0.0)
## mime 0.9 2020-02-04 [1] CRAN (R 4.0.0)
## msigdbr * 7.2.1 2020-10-02 [1] CRAN (R 4.0.2)
## munsell 0.5.0 2018-06-12 [1] CRAN (R 4.0.0)
## openssl 1.4.3 2020-09-18 [1] CRAN (R 4.0.2)
## openxlsx 4.2.3 2020-10-27 [1] CRAN (R 4.0.2)
## pheatmap * 1.0.12 2019-01-04 [1] CRAN (R 4.0.0)
## pillar 1.4.7 2020-11-20 [1] CRAN (R 4.0.2)
## pkgbuild 1.2.0 2020-12-15 [1] CRAN (R 4.0.2)
## pkgconfig 2.0.3 2019-09-22 [1] CRAN (R 4.0.0)
## pkgload 1.1.0 2020-05-29 [1] CRAN (R 4.0.0)
## plotly * 4.9.2.2 2020-12-19 [1] CRAN (R 4.0.2)
## plyr 1.8.6 2020-03-03 [1] CRAN (R 4.0.0)
## png 0.1-7 2013-12-03 [1] CRAN (R 4.0.0)
## prettyunits 1.1.1 2020-01-24 [1] CRAN (R 4.0.0)
## processx 3.4.5 2020-11-30 [1] CRAN (R 4.0.2)
## progress 1.2.2 2019-05-16 [1] CRAN (R 4.0.0)
## promises 1.1.1 2020-06-09 [1] CRAN (R 4.0.0)
## proxy 0.4-25 2021-03-05 [1] CRAN (R 4.0.2)
## ps 1.5.0 2020-12-05 [1] CRAN (R 4.0.2)
## purrr 0.3.4 2020-04-17 [1] CRAN (R 4.0.0)
## R6 2.5.0 2020-10-28 [1] CRAN (R 4.0.2)
## rappdirs 0.3.1 2016-03-28 [1] CRAN (R 4.0.0)
## RColorBrewer * 1.1-2 2014-12-07 [1] CRAN (R 4.0.0)
## Rcpp 1.0.5 2020-07-06 [1] CRAN (R 4.0.2)
## RCurl 1.98-1.2 2020-04-18 [1] CRAN (R 4.0.0)
## readxl 1.3.1 2019-03-13 [1] CRAN (R 4.0.0)
## remotes 2.2.0 2020-07-21 [1] CRAN (R 4.0.2)
## rintrojs 0.2.2 2019-05-29 [1] CRAN (R 4.0.2)
## rio 0.5.16 2018-11-26 [1] CRAN (R 4.0.0)
## rlang 0.4.10 2020-12-30 [1] CRAN (R 4.0.2)
## rmarkdown 2.6 2020-12-14 [1] CRAN (R 4.0.2)
## rprojroot 2.0.2 2020-11-15 [1] CRAN (R 4.0.2)
## RSQLite 2.2.1 2020-09-30 [1] CRAN (R 4.0.2)
## rstatix 0.6.0 2020-06-18 [1] CRAN (R 4.0.1)
## S4Vectors * 0.28.1 2020-12-09 [1] Bioconductor
## scales * 1.1.1 2020-05-11 [1] CRAN (R 4.0.0)
## scatterplot3d 0.3-41 2018-03-14 [1] CRAN (R 4.0.0)
## sessioninfo 1.1.1 2018-11-05 [1] CRAN (R 4.0.0)
## sf 0.9-8 2021-03-17 [1] CRAN (R 4.0.2)
## shiny 1.5.0 2020-06-23 [1] CRAN (R 4.0.2)
## shinydashboard 0.7.1 2018-10-17 [1] CRAN (R 4.0.2)
## shinyjs 2.0.0 2020-09-09 [1] CRAN (R 4.0.2)
## stringi 1.5.3 2020-09-09 [1] CRAN (R 4.0.2)
## stringr 1.4.0 2019-02-10 [1] CRAN (R 4.0.0)
## SummarizedExperiment * 1.20.0 2020-10-27 [1] Bioconductor
## survival 3.2-7 2020-09-28 [1] CRAN (R 4.0.3)
## testthat 3.0.1 2020-12-17 [1] CRAN (R 4.0.2)
## tibble 3.0.4 2020-10-12 [1] CRAN (R 4.0.2)
## tidyr 1.1.2 2020-08-27 [1] CRAN (R 4.0.2)
## tidyselect 1.1.0 2020-05-11 [1] CRAN (R 4.0.0)
## units 0.7-1 2021-03-16 [1] CRAN (R 4.0.2)
## UpSetR * 1.4.0 2019-05-22 [1] CRAN (R 4.0.0)
## usethis * 2.0.0 2020-12-10 [1] CRAN (R 4.0.2)
## vctrs 0.3.6 2020-12-17 [1] CRAN (R 4.0.2)
## VennDiagram 1.6.20 2018-03-28 [1] CRAN (R 4.0.0)
## vipor 0.4.5 2017-03-22 [1] CRAN (R 4.0.2)
## viridisLite 0.3.0 2018-02-01 [1] CRAN (R 4.0.0)
## withr 2.3.0 2020-09-22 [1] CRAN (R 4.0.2)
## xfun 0.20 2021-01-06 [1] CRAN (R 4.0.2)
## XML 3.99-0.5 2020-07-23 [1] CRAN (R 4.0.2)
## xml2 1.3.2 2020-04-23 [1] CRAN (R 4.0.0)
## xtable 1.8-4 2019-04-21 [1] CRAN (R 4.0.0)
## XVector 0.30.0 2020-10-28 [1] Bioconductor
## yaml 2.2.1 2020-02-01 [1] CRAN (R 4.0.0)
## zip 2.1.1 2020-08-27 [1] CRAN (R 4.0.2)
## zlibbioc 1.36.0 2020-10-28 [1] Bioconductor
##
## [1] /Library/Frameworks/R.framework/Versions/4.0/Resources/library
Remarque : la sortie est plus lisible qu’avec le traditionnel sessionInfo()
.