L’objectif de ce TP est de vous familiariser avec les packages R utilisés pour la manipulation et la visualisation de variations génomiques à partir de fichiers VCF.
Le jeu de données choisi pour ce TP est le même que celui utilisé pour le TP sur les variants du niveau 1.
Nom d’échantillon | Phénotype |
---|---|
SRR1262731 | QTL- |
SRR1205992 | QTL+ |
SRR1205973 | QTL+ |
Le point d’entrée de ce TP sera le fichier multi-VCF généré par le workflow décrit dans le cours/TP “variant-calling et annotation niveau 1”.
# le chemain vers le vcf
vcf_file <- "data/pool_GATK_annot.vcf"
La fonction read.vcfR() du package vcfR permet de lire un fichier VCF/multi-VCF et retourne un objet de la classe vcfR.
library(vcfR) # extraire les informations stockées dans un VCF
# pour chercher l'aide d'une fonction, utiliser la fonction help() ou ?
# help(read.vcfR)
# ?read.vcfR()
vcf <- vcfR::read.vcfR(file = vcf_file)
## Scanning file to determine attributes.
## File attributes:
## meta lines: 43
## header_line: 44
## variant count: 2826
## column count: 12
##
Meta line 43 read in.
## All meta lines processed.
## gt matrix initialized.
## Character matrix gt created.
## Character matrix gt rows: 2826
## Character matrix gt cols: 12
## skip: 0
## nrows: 2826
## row_num: 0
##
Processed variant 1000
Processed variant 2000
Processed variant: 2826
## All variants processed
Pour vérifier le type ou la classe d’un objet, on utilise summary(), class(), ou is()
summary(vcf)
## Length Class Mode
## 1 vcfR S4
La fonction vcfR2tidy() du package vcfR permet de convertir l’objet vcfR en un data frame de type tibble (un format compatible avec les fonctions tidyverse).
vcf.tidy.list <- vcfR2tidy(vcf)
# on peut specifier les champs qu'on veux garder (ex ci-dessous)
# vcf.tidy.list <- vcfR2tidy(vcf.snv, format_fields = c("GT", "AD", "DP"), info_fields = c("AC", "AN", "MQ"))
Cette fonction retourne une liste de 3 tibbles fix, gt, meta
is(vcf.tidy.list)
## [1] "list" "vector"
Pour accéder aux noms de ces tibbles (les noms des élements d’une liste), on utilise la foction name.
names(vcf.tidy.list)
## [1] "fix" "gt" "meta"
Pour accéder à un tibble (un élement de la liste), on utilise $.
# vcf.tidy.list$meta
head(vcf.tidy.list$meta)
La fonction head permet d’afficher que les 6 premières lignes d’un tableau. Le tibble meta contient les informations présentent dans l’entête du fichier vcf.
Le tibble fix contient les informations liées à chaque variation génomique.
head(vcf.tidy.list$fix)
Le nombre de ligne de ce tableau correspond au nombre de variations génomiques.
dim(vcf.tidy.list$fix) # nrow(vcf.tidy.list$fix)) pour afficher que le nombre de lignes
## [1] 2826 28
Le tibble gt contient les inforamtions liées au génotype de chaque échantillon et pour chaque variation génomique.
head(vcf.tidy.list$gt)
Le nombre de ligne de ce tableau correspond au nombre de variations génomiques multiplier par le nombre d’échantillons.
nrow(vcf.tidy.list$gt) # dim(vcf.tidy.list$fix)) pour afficher le nombre de lignes et le nombre de colonnes.
## [1] 8478
Pour facilter l’analyse de nos variations génomiques on a besoin de processer nos tibbles
Pour cela on va utiliser la fonction full_join() du package dplyr pour futionner les deux tableaux. Il faut commencer par definir les colonnes communes entre les deux tibbles. Dans notre cas c’est la colonne ChromKey et la colonne POS.
library(dplyr) # manipulation des data
vcf.tidy <- dplyr::full_join(vcf.tidy.list$fix , vcf.tidy.list$gt, by = c("ChromKey", "POS"))
Afficher les 6 premières lignes du tibble fusionné.
head(vcf.tidy)
La taille de ce tibble :
dim(vcf.tidy)
## [1] 8478 41
Dans ce TP on va s’intéresser uniquement aux variations avec un allele alternatif. Dans le cas des variants multi-alléliques les valeurs de la colonne ALT contiennent une “,” pour séparer les différents allèles alternatifs.
Pour detecter ces cas on va utiliser la fonction str_detect() du package stringr qui permet de detecter un pattern dans un vecteur de chaines de caractères.
Example :
library(stringr)
fruits <- c("banane", "pomme", "mangue", "raisin", "kiwi")
index <- stringr::str_detect(string = fruits , pattern = "a")
index
## [1] TRUE FALSE TRUE TRUE FALSE
La fonction str_detect() renvoie un vecteur bouléen.
Pour récupérer la liste des fruits dont le nom contient la lettre ‘a’ :
fruits[index]
## [1] "banane" "mangue" "raisin"
Pour récuperer les noms de fruits qui ne contiennent pas la lettre ‘a’, on utilise le signe ! pour inverser la condition.
fruits[!index]
## [1] "pomme" "kiwi"
On va appliquer le même principe pour récupérer les variations génomiques avec un allèle unique. On va d’abord repérer les variations multi-alleliques grâce à la présence de “,” dans la colonne ALT.
index <- stringr::str_detect(string = vcf.tidy$ALT , pattern = ",")
index[1:6]
## [1] FALSE FALSE FALSE FALSE FALSE FALSE
Le nombre de variations multi-allèles :
length(index)/3
## [1] 2826
On va ensuite les retirer du tableau des variants. La fonction filter() du package dplyr permet de filtrer les lignes selon une ou plusieurs conditions.
vcf.u.tidy <- dplyr::filter(vcf.tidy, !index)
head(vcf.u.tidy)
# ecriture simplifiée
# vcf.u.tidy <- dplyr::filter(vcf.tidy, !stringr::str_detect(string = ALT , pattern = ","))
Le champs ID renseigne un identifiant unique pour chaque variant. S’il n’est pas renseigné il est recommandé d’en créer un pour faciliter la manipulation des tableaux. Une variation est définie par sa position et par son allèle.
On va utiliser la fonction unite() du package tidyr, pour créer un identifiant qui est le résultat de la concaténation des colonnes, CHROM, POS, REF et ALT.
library(tidyr)
vcf.u.tidy <- tidyr::unite(vcf.u.tidy, CHROM, POS, REF, ALT, col="ID", sep="_", remove = FALSE)
head(vcf.u.tidy)
Certains outils de variant-calling donnent l’info du type de variants dans le VCF. Ce n’est pas le cas de tous les outils!
Pour différencier les SNV des INDEL on teste le nombre de bases dans les colonnes REF et ALT. Si le nombre de bases est supéreur à 1 cela signifie que le variant est de type INDEL, sinon c’est un SNV. Pour effectuer ce test on va utiliser la fonction if_else() du package dplyr combiner à la fonction mutate afin de rajouter une colonne avec le type de variations.
vcf.u.tidy <- dplyr::mutate(vcf.u.tidy, var_type = dplyr::if_else(str_length(REF) > 1 | str_length(ALT) > 1, "INDEL", "SNV"))
La fonction select() du package dplyr permet de selectionner les colonnes que on souhaite garder ou visualiser dans ce cas.
dplyr::select(vcf.u.tidy, ID, var_type, Indiv) %>% head
L’AR à une position donnée est le ratio entre le nombre de reads (depth) qui couvrent l’allèle variant/alternatif et le nombre de reads total qui couvrent cette position.
L’information liée au nombre de reads par allèle est stockée dans la colonne gt_AD (Allele depth) sous forme de (ref AD,alt AD). Pour accèder à “alt AD”, on va utiliser la fonction separate() du package tidyr pour separer la colonne gt_AD en 2 colonnes, gt_ref.AD et gt_alt.AD.
vcf.u.tidy <- tidyr::separate(data = vcf.u.tidy, col = gt_AD, c("gt_ref.AD", "gt_alt.AD"), sep = ",", remove=FALSE)
dplyr::select(vcf.u.tidy, ID, Indiv, gt_AD, gt_ref.AD, gt_alt.AD) %>% head()
On va definir les colonnes ”gt_ref.AD” et “gt_alt.AD” comme numériques, pour pouvoir calculer le ratio.
vcf.u.tidy <- dplyr::mutate(vcf.u.tidy, gt_ref.AD = as.integer(gt_ref.AD), gt_alt.AD = as.integer(gt_alt.AD) )
dplyr::select(vcf.u.tidy, ID, gt_AD, gt_ref.AD, gt_alt.AD, gt_DP, gt_GT, Indiv) %>% head()
On va utiliser la fonction mutate() aussi pour ajouter une colonne avec l’allèle ratio à chaque ligne
vcf.u.tidy <- dplyr::mutate(vcf.u.tidy, gt_AR = gt_alt.AD/gt_DP)
select(vcf.u.tidy, ID, gt_AD, gt_ref.AD, gt_alt.AD, gt_DP, gt_AR, gt_GT, Indiv) %>% head()
BONUS : gt_DP peut contenir des 0 ou des NA. Dans ces cas là on ne pourra pas calculer le ratio. La solution serait :
# dplyr::mutate(vcf.u.tidy, gt_AR = if_else(gt_DP %in% c(0, NA), 0, gt_alt.AD/gt_DP))
Le champs INFO contient plusieurs métriques qui vont nous permettre d’évaluer la qualité de nos variants, comme par exemple QUAL, MQ, DP…etc
Le meilleur moyen d’accèder à ces metriques est d’afficher leur distribution sous forme de graphe. Pour cela on va utiliser les fonctions du package ggplot2. La visualisation des différents métriques nous permet de voir la qualité de nos données mais aussi de définir les filtres et les seuils à appliquer.
tb <- dplyr::select(vcf.u.tidy, ID, QUAL)
head(tb)
Pour visualiser la distribution de la qualité on va utiliser les fonctions du package ggplot2.
ggplot() est une fonction clé du package et permet de initialiser la figure.
library(ggplot2)
p <- ggplot(data = tb, mapping = aes(QUAL))
p
La fonction geom_density() permet de représenté la densité de nos scores de qualité
library(ggplot2)
p <- ggplot(data = tb, mapping = aes(QUAL)) + geom_density()
p
Le fonction xlim() permet de définir la feunettre à visualiser et les fonctions xlab()/ylab() permettent de changer les noms des axes X et Y.
p <- p + xlim(0,500) + xlab("score de qualité")
p
Il est possible de superposer des graphes sur le même fond. La fonction geom_vline() nous permet de tracer un ligne verticale pour représenter le cut-off qui sera utilisé pour filtrer.
p <- p + geom_vline(xintercept = 40, color = "red", linetype = "longdash")
p
On utilise les même fonction que ci-dessus.
p <- ggplot(data=vcf.u.tidy, aes(gt_DP, color=Indiv)) + geom_density() +
xlab("Variant position coverage") + xlim(c(0,100)) +
geom_vline(xintercept = 4, color = "red", linetype = "longdash")
p
p <- ggplot(data=vcf.u.tidy, aes(gt_AR, color=Indiv)) + geom_density() +
labs(y="Alternative allele ratio")
p
Ches les organismes diploide hétérozygote on observe un pic à 0.5 (qui correspond au variants hétérozygotes) et 2 pics à 1 et 0 (qui corresponent au variants homozygotes).
Il y a plusieurs filtre qui pourrraient être appliqué pour réduire la présence de faux positifs et de variations de mauvaises qualités. Ci-dessous on vous donne quelques exemples.
On va utiliser la fonction filtre() pour filtrer que les variations sur la base de la colonne QUAL.
# filtrer les variations avec un score qualité inférieur à 30
dplyr::filter(vcf.u.tidy, QUAL >= 40) %>% head()
L’information de la couverture est stockée dans la colonne gt_DP.
dplyr::filter(vcf.u.tidy, gt_DP >= 4) %>% head()
Cette onformation est stockée dans la colonne gt_alt.AD.
dplyr::filter(vcf.u.tidy, gt_alt.AD >= 2) %>% head()
Il est possible de regrouper plusieurs conditions avec la fonction filter().
vcf.q.tidy <- dplyr::filter(vcf.u.tidy, QUAL >= 30, gt_DP >= 4, gt_alt.AD >= 2)
head(vcf.q.tidy)
Compter le nombre de variations génomiques par échantillon.
La fonction group_by() du package dplyr permet de grouper un tibble afin d’appliquer une opération sur chacun des groupe. La fonction count() du même package permet de compter le nombre de ligne par groupe (par individu dans notre cas).
tb.nbr.var <- dplyr::group_by(vcf.q.tidy, Indiv) %>% dplyr::count(name = "nb_var")
tb.nbr.var
Pour représenter ces comptages sous forme de barres on va utiliser la fonction geom_bar() du package ggplot.
ggplot(tb.nbr.var, aes(x = Indiv, y = nb_var)) + geom_bar(stat = "identity") +
labs(y="nombre de variants par individu", x="")
Dans l’échantillon ‘SRR1262731’, il y a moins de variations que dans les autres. Dans notre cas, cela est dû à la profondeur de couverture qui est plus faible pour cet échantillon. Dans d’autres cas, cela peut être dû à la distance entre les variants de l’espèce et le génome de référence.
BONUS
Il est possible de compter le nombre de variations par type de variants et par échantillon. Cette fois on groupe le tableau sur deux niveaux (par undividu et par type de variant).
tb.nbr.var <- dplyr::group_by(vcf.q.tidy, Indiv, var_type) %>% dplyr::count(name = "nb_var")
tb.nbr.var
Le paramêtre fill permet de distinguer les deux types de variants
ggplot(tb.nbr.var, aes(x = Indiv, y = nb_var, fill = var_type)) + geom_bar(stat="identity") +
labs(y="nombre de variants par individu", x="", fill = "Type de variants")
On peut comparer les variants des 3 échantiilons avec un diagramme de venn.
Pour porduire un diagramme de venn, on a besoin d’extraire une matrice binaire de presence absence des variants dans chaque échantillon. Avec les variants en lignes et les échantillons en colonnes. La fonction dcast() du package reshape2 permet de construire ce type de matrice.
library(reshape2)
# transformer la matrice de données en format "wide" -> ID en lignes et Indiv en colonne
vcf.mat.AR <- reshape2::dcast(vcf.q.tidy, ID~Indiv, value.var="gt_AR", fun.aggregate = sum)
# l'option (fun.aggregate = sum) permet de remplacer NA par 0.
head(vcf.mat.AR)
Pour obtenir une matrice binaire avec 0/1, on utilise la fonction if_else() vu précédemment et la fonction mutate_at() du package dplyr pour remplacer les AR supérieurs à 0 par 1.
vcf.mat.bin <- dplyr::mutate_at(vcf.mat.AR, .vars = c("SRR1205973", "SRR1205992", "SRR1262731"), .funs = function(x){dplyr::if_else(x == 0, 0, 1)})
head(vcf.mat.bin)
La fonction venn() du package venn permet de représenter les intersections entre les échantillons.
library(venn)
# mat[,-1] : retire la première colonne.
venn::venn(vcf.mat.bin[,-1], zcolor = "style")
On va reprendre labelau “vcf.mat.AR”.
head(vcf.mat.AR)
Calculer la distance euclidienne entre les lignes de la matrice
dist <- dist(t(vcf.mat.AR[,-1]), method = "euclidean")
dist
## SRR1205973 SRR1205992
## SRR1205992 13.76642
## SRR1262731 15.61776 16.05623
Effectuer une classification hiérarchique des échantillons à partir des distances.
# clustering hiérarchique
hierarchical_result <- hclust(dist)
Convertir la sortie de hclust() en objets de classe “dendrogram”.
dendrogram_result <- as.dendrogram(hierarchical_result)
Afficher le dendrogramme avec la fonction plot()
plot(dendrogram_result, main = "Sample clustering using all variants")
La mutation recherchée :
On va commencer par appliquer les filtres nécessaires pour cerner la mutation d’interêt.
La colonne ANN contient les informations d’annotation des variants.
vcf.q.tidy[1,"ANN"] # afficher la 1ere ligne de la colonne "ANN"
Pour filtrer les variants qui se trouve sur un gène codant pour une protéine, on va utiliser la fonction str_detect() pour rechercher le motif “protein_coding”.
dplyr::filter(vcf.q.tidy, stringr::str_detect(string = ANN, pattern = "protein_coding")) %>% head()
Pour filtrer les variants qui ont un effet “missense”, on va utiliser la même fonction str_detect() pour rechercher le motif “missense_variant” dans la colonne ANN.
dplyr::filter(vcf.q.tidy, grepl("missense_variant", ANN)) %>% head()
Il est possible de combiner plusieurs condition dans la même commande.
vcf.filt.tidy <- dplyr::filter(vcf.q.tidy, grepl("protein_coding" ,ANN), grepl("missense_variant" ,ANN))
Pour sélectionner que les variants qui portent l’allèle référent sur les échantillons QTL+ et l’allèle alternatif sur l’echantillons QTL-. On va utiliser de nouveau la fonction dcast() pour obtenir un tabeau au format large (matrice ID~Indiv).
vcf.filt.AR <- reshape2::dcast(vcf.filt.tidy, ID~Indiv, value.var="gt_AR", fun.aggregate = sum)
vcf.filt.AR
Grace à ce format large et à l’aide de la fonction filter(), on va pouvoir identifier les mutations absentes (qui ont un AR égale à 0) chez les échantillons QTL+ et présentes (AR différent de 0) chez l’échantillons QTL-.
mutations <- dplyr::filter(vcf.filt.AR, SRR1262731 != 0 & SRR1205973 == 0 & SRR1205992 == 0)
mutations
L’identifiant de la mutation recherchée :
mutations$ID
## [1] "6_38027010_A_C"
Il est possible d’extraire l’information complète de cette mutation du tableau d’origine :
resultats <- dplyr::filter(vcf.u.tidy, ID == mutations$ID)
resultats
Ecrire les résultats dans un fichier txt tabulé avec une extention “xls” pour le visualiser facilement sous excel.
write.table(file = "QTLminus_mutation.xls", x = resultats, quote = FALSE, sep = "\t", row.names = FALSE, col.names = TRUE)