---
title: 'Principal Component Analysis of Genotypic Data'
author: "International Statical Genetics Workshop"
#date: '2023-03-07'
output:
html_document: default
pdf_document: default
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```
**This practical runs on the workshop RStudio server.**
First login to RStudio
```{bash, eval=FALSE}
https://workshop.colorado.edu/rstudio/
```
Copy the Practical document into your home directory using the following `R` commands.
```{R, eval=TRUE}
setwd("~/.")
system("mkdir -p Day1")
system("cp /home/practicals/1.4.PrincipalComponentAnalysis_LoicYengo/final/Practical_PCA.html ~/Day1/.")
## Now copy the data
system("mkdir -p Day1/pca_prac")
system("cp /home/practicals/1.4.PrincipalComponentAnalysis_LoicYengo/final/data/1kg_* ~/Day1/pca_prac/.")
system("cd Day1/pca_prac")
setwd("Day1/pca_prac")
```
## Data description
In this practical we use data from the 1000 Genomes Project (Phase 3). The dataset consists of 2,504 individuals from 26 diverse populations grouped into 5 super-population (AFR: African; AMR: Admixed American; EAS: East-Asian; EUR: European; SAS: South-Asian). More details about the 1000 Genomes data can be found at https://www.internationalgenome.org/. For the practical, we will QC genotypes for 80,244 SNPs.
Check that all the files are copied correctly using `R`
```{R, eval=TRUE}
system("ls -lt")
```
or directly via the terminal
```{bash, eval=TRUE}
ls -lt
```
We now read the labels for each individual in the data.
```{R, eval=TRUE}
labels <- read.table("1kg_pop2504.txt",h=T)
head(labels)
```
## Part 1: PCA with PLINK and ancestry calling
Run the following PLINK command (called from `R`, i.e., ghe actual command is everything between `system(\" PLINK COMMAND \")`) to calculate 10 PCs from the set of genotypes stored in the `1kg_hm3_qced.*`files.
```{R, eval=TRUE}
system("plink --bfile 1kg_hm3_qced --pca 10 --out myFirstPCA")
```
Now let's read and plot the results in `R`
```{R, eval=TRUE}
## Read the results
pca_res <- read.table("myFirstPCA.eigenvec")
colnames(pca_res) <- c("FID","IID",paste0("PC",1:10))
## Read eigen values
eigenvals <- read.table("myFirstPCA.eigenval")[,1]
## Check the results
head(pca_res)
npc <- 10
for(k in 1:npc){
pca_res[,paste0("PC",k)] <- pca_res[,paste0("PC",k)] * sqrt(eigenvals[k])
}
## Add labels
pca_res <- merge(pca_res,labels,by.x="IID",by.y="sample")
plot(pca_res[,"PC1"],pca_res[,"PC2"],pch=19,axes=FALSE,xlab="PC1",ylab="PC2",col=1,main="")
axis(1);axis(2)
abline(h=0,v=0,col="grey",lty=2)
```
*Question 1a. How many (ancestry) clusters do you see?*
*Question 1b. What is the ancestry of someone with a PC1 value of 0? Same question for a PC2 value 0.*
Let's use k-means approach to determine the clusters
```{R, eval=TRUE}
ncluster <- 5
cluster1_2 <- kmeans(pca_res[,c("PC1","PC2")],ncluster,nstart = 10)$cluster
plot(pca_res[,"PC1"],pca_res[,"PC2"],pch=19,axes=FALSE,xlab="PC1",ylab="PC2",col=cluster1_2,main="")
axis(1);axis(2)
abline(h=0,v=0,col="grey",lty=2)
```
Now let's use the geographic labels...
```{R, eval=TRUE}
Colors <- c("dodgerblue","coral1","goldenrod","seagreen4","purple3")
Pops <- names(table(labels$super_pop))
names(Colors) <- Pops
plot(pca_res[,"PC1"],pca_res[,"PC2"],pch=19,axes=FALSE,xlab="PC1",ylab="PC2",col=Colors[pca_res[,"super_pop"]],main="")
axis(1);axis(2)
abline(h=0,v=0,col="grey",lty=2)
legend("topright",legend=Pops,fill=Colors,box.lty=0)
```
*Question 2. Are the k-means clusters concordant with geographical labels? If not then how can you improve concordance?*
To answer the question you can run the `R` code below
```{R, eval=TRUE}
## Credit to Museful (stackoverflow)
permutations <- function(n){
if(n==1){
return(matrix(1))
} else {
sp <- permutations(n-1)
p <- nrow(sp)
A <- matrix(nrow=n*p,ncol=n)
for(i in 1:n){
A[(i-1)*p+1:p,] <- cbind(i,sp+(sp>=i))
}
return(A)
}
}
concordance <- function(x,y){
tab <- table(x,y)
n <- nrow(tab)
m <- ncol(tab)
if(n != m){
cat("Error: Number of clusters must be the same for x and y.")
return(NULL)
}
perms <- permutations(n)
conc <- sapply(1:nrow(perms),function(i) sum( diag(tab[perms[i,],]) ) ) / sum(tab)
return(max(conc))
}
concordance(x=cluster1_2,y=pca_res[,"super_pop"])
```
Let's now visualize for PCs...
```{R, eval=TRUE}
for(j in 1:4){
for(k in (j+1):5){
plot(pca_res[,paste0("PC",j)],pca_res[,paste0("PC",k)],pch=19,axes=FALSE,
xlab=paste0("PC",j),ylab=paste0("PC",k),col=Colors[pca_res[,"super_pop"]],
main=paste0("PC",j," vs PC",k))
axis(1);axis(2)
abline(h=0,v=0,col="grey",lty=2)
}
}
```
So let's redefine k-mean clusters with more PCs...
```{R, eval=TRUE}
ncluster <- 5
cluster1_5 <- kmeans(pca_res[,paste0("PC",1:5)],ncluster,nstart = 10)$cluster
plot(pca_res[,"PC1"],pca_res[,"PC2"],pch=19,axes=FALSE,xlab="PC1",ylab="PC2",col=cluster1_5,main="")
axis(1);axis(2)
abline(h=0,v=0,col="grey",lty=2)
## Test concordance again
concordance(x=cluster1_5,y=pca_res[,"super_pop"])
```
*Question 3. Can we improve concordance between PC-based clusters and geographical labels when using more than 5 PCs?*
*Question 4. What can you conclude overall?*
## Part 2: Projected PCA using GCTA
In this part of the practical, we will calculate PCs without AMR samples, then project back to the PCA map. We will use the software tool `GCTA`. This procedure has multiple steps. First, step run PCA in without AMR samples.
We first create a list of ID for AMR samples
```{R, eval=TRUE}
## Create a list AMR sample
write.table(pca_res[which(pca_res[,"super_pop"]=="AMR"),c("FID","IID")],"AMR.id",quote=F,col.names=F,row.names=F)
## Check the file
system("head AMR.id")
```
Step 1. Run PCA without AMR samples...
```{R, eval=TRUE}
system("gcta64 --bfile 1kg_hm3_qced --remove AMR.id --make-grm --out myGRM_noAMR")
system("gcta64 --grm myGRM_noAMR --pca 2 --out myPCA_noAMR")
```
Step 2. Calculate SNP loadings...
```{R, eval=TRUE}
system("gcta64 --bfile 1kg_hm3_qced --remove AMR.id --pc-loading myPCA_noAMR --out myPCA_noAMR_snp_loading")
```
Check that the loading file named `myPCA_noAMR_snp_loading.pcl` has been created. You can use `R`
```{R, eval=TRUE}
system("head myPCA_noAMR_snp_loading.pcl")
```
or the terminal
```{bash, eval=TRUE}
head myPCA_noAMR_snp_loading.pcl
```
Step 3. Project AMR samples...
```{R, eval=TRUE}
system("gcta64 --bfile 1kg_hm3_qced --keep AMR.id --project-loading myPCA_noAMR_snp_loading 2 --out AMR_PCA")
```
Check that the loading file named `AMR_PCA.proj.eigenvec` has been created. You can use `R`
```{R, eval=TRUE}
system("head AMR_PCA.proj.eigenvec")
```
or using the terminal
```{bash, eval=TRUE}
head AMR_PCA.proj.eigenvec
```
Let's now load the results in `R` and visualize them.
```{R, eval=TRUE}
ref <- read.table("myPCA_noAMR.eigenvec",stringsAsFactors=FALSE)
amr <- read.table("AMR_PCA.proj.eigenvec",stringsAsFactors=FALSE)
colnames(ref) <- colnames(amr) <- c("FID","IID","PC1","PC2")
## Read eigenvalues
eigenvals <- read.table("myPCA_noAMR.eigenval")[,1]
npc <- 2
for(k in 1:npc){
ref[,paste0("PC",k)] <- ref[,paste0("PC",k)] * sqrt(eigenvals[k])
amr[,paste0("PC",k)] <- amr[,paste0("PC",k)] * sqrt(eigenvals[k])
}
ref <- merge(ref,labels,by.x="IID",by.y="sample")
plot(ref[,"PC1"],ref[,"PC2"],pch=19,axes=FALSE,xlab="PC1",ylab="PC2",col=Colors[ref[,"super_pop"]],main="")
axis(1);axis(2)
abline(h=0,v=0,col="grey",lty=2)
legend("topright",legend=Pops,fill=Colors,box.lty=0)
## Plot AMR projections
points(amr[,"PC1"],amr[,"PC2"],col=Colors["AMR"],pch=17)
```
*Question 5. Compare PC coordinates of AMR samples when they are included versus excluded from PCA*
```{R, eval=TRUE}
comparison <- merge(pca_res[,c("IID","PC1","PC2")],amr[,c("IID","PC1","PC2")],by="IID")
apply(comparison[,-1],2,mean)
apply(comparison[,-1],2,sd)
cor(comparison[,"PC1.x"],comparison[,"PC1.y"])
cor(comparison[,"PC2.x"],comparison[,"PC2.y"])
```
*Question 6. Assign an ancestry cluster to the AMR samples*
```{R, eval=TRUE}
clus_centers <- aggregate(cbind(PC1,PC2)~super_pop,data=ref,FUN=mean)
## Distance calculate distance to each cluster
Distances <- matrix(NA,nrow=nrow(amr),ncol=4)
for(i in 1:nrow(amr)){
for(k in 1:4){
Distances[i,k] <- sum( ( as.numeric(amr[i,c("PC1","PC2")]) - as.numeric(clus_centers[k,-1]) )^2 )
}
}
clus_amr <- clus_centers[apply(Distances,1,which.min),1]
table(clus_amr)
```