Introduction

This report summarizes the analysis workflow and results of an analysis of SNPs from the 1000 Genomes Project.

Data preparation

Obtaining and loading data

Single Nucleotide Polymorphism (SNPs) data in VCF format were obtained from the 1000 Genomes Project.

SNPs were downloaded using the Ensembl Data Slicer from chromosome 22 between genomic coordinates 26,006,210 and 26,246,210. This represents 100% of the chromosome. A total of 7212 variants genotyped in 505 individuals were downloaded.

The VCF file was loaded into R using the vcfR package (function read.vcfR) and converted to counts of the minor allele using the function vcfR::extract.gt().

Data subsetting

Meta-data and sample information

The super population collected for this PCA is sampled in populations from Europe. More specifically, the data is in the country of Great Britain.

No external meta-data is available for these populations. The self-identified ethnicity of each person is indicated in the row names of the dataframe. This information was extracted, cleaned with regular expressions and added as a column to the dataframe.

Data cleaning

These SNPs were then screened for any SNPs that were invariant (fixed), resulting in removal of 2504 SNPs (features). This was done using the invar_omit() function by Nathan Brouwer.

NOTE: The original workflow code for removing invariant SNPs contained and error that resulted in 1924 actually being removed (Brouwer, personal communication). The code was updated and a reduction in the size of the dataframe after omitting invariant columns confirmed by checking the dimensions of the dataframes before and after this process using dim().

The data were then screened for rows (people) with >50% NAs. There were no NAs in the data, so no rows were removed due to the presence of excessive NAs. Similarly, because no NAs were present no imputation was required.

The data were then centered and scaled using R’s scale() function. (Alternatively a SNP-specific centering technique common in other studies could have been applied).

The data were then saved in .csv format using write.csv() for PCA analysis.

After final processing the data contained 8796 SNPS and 505 samples (people).

Data Analysis

The code below carries out a PCA on the data and presents the results. The key steps are:

  1. Load the data with read.csv().
  2. Process the data with prcomp().
  3. Extract PCA scores.
  4. Carry out PCA diagnostics, including construction of a scree plot.
  5. Plot PCs 1 through 3 as scatterplots as pairwise scatterplot.
  6. Plots PCS 1 through 3 as a 3D scatterplot.

Packages

The following packages were used in this analysis:

# plotting:
library(ggplot2)
library(ggpubr)

# scores() function
library(vegan)
## Warning: package 'vegan' was built under R version 4.2.2
## Loading required package: permute
## Warning: package 'permute' was built under R version 4.2.2
## Loading required package: lattice
## This is vegan 2.6-4
# 3D scatter plot
library(scatterplot3d)

Loading data

Load the fully processed data:

NOTE: with ~6,000 SNPs, this CSV is ~83 megabytes. There are more specialized packages for doing PCA with datasets this big. I do not recommend working with more than 10,000 SNPs with basic R functions as we have done in class.

vcf_scaled <- read.csv(file = "vcf_scaled.csv")

Check the dimensions of the data to confirm this is the correct data:

dim(vcf_scaled)
## [1] 2504 7218

Principal Components Analysis

TODO: Update this paragraph to reflect how many columns of character data are in your file.

The data are scaled and ready for analysis. Only the very first column contains character data and needs to be omitted.

head(vcf_scaled[,1:10])
##    sample pop super_pop    sex      lat       lng          X1          X2
## 1 HG00096 GBR       EUR   male 52.48624 -1.890401 -0.01998402 -0.07496829
## 2 HG00097 GBR       EUR female 52.48624 -1.890401 -0.01998402 -0.07496829
## 3 HG00099 GBR       EUR female 52.48624 -1.890401 -0.01998402 -0.07496829
## 4 HG00100 GBR       EUR female 52.48624 -1.890401 -0.01998402 -0.07496829
## 5 HG00101 GBR       EUR   male 52.48624 -1.890401 -0.01998402 -0.07496829
## 6 HG00102 GBR       EUR female 52.48624 -1.890401 -0.01998402 -0.07496829
##            X3          X4
## 1 -0.01998402 -0.01998402
## 2 -0.01998402 -0.01998402
## 3 -0.01998402 -0.01998402
## 4 -0.01998402 -0.01998402
## 5 -0.01998402 -0.01998402
## 6 -0.01998402 -0.01998402

PCA

Principal Components Analysis was run using prcomp().

vcf_pca <- prcomp(vcf_scaled[,-c(1:6)])

Get the PCA scores, which will be plotted.:

vcf_pca_scores  <- vegan::scores(vcf_pca) 

Combine the scores with the sample information into a dataframe.

vcf_pca_scores2 <- data.frame(population = vcf_scaled$super_pop, #TODO
                              vcf_pca_scores)
vcf_pca_scores2$population <- factor(vcf_pca_scores2$population)

PCA diagnostics

The following steps help us understand the PCA output and determine how many PCs should be plotted and/or used in further analyses such as scans for natural selection, cluster analysis, and GWAS.

Default scree plot

A default R scree plot was created with screeplot(). This plot does not provide extra information for assessing the importance of the PCs.

screeplot(vcf_pca, 
          xlab = "Principal Components")

Advanced scree plot

The original workflow and function for making a more advanced scree plot lacked flexibility (Brouwer, personal communication). The following function and workflow simplifies things

  1. Run PC (done above)
  2. Call function PCA_variation() (below) on PCA output.
  3. (Do NOT call summary() on PCA output)
  4. Call function screeplot_snps() on the output of PCA_variation() to make an advanced scree plot
  5. Call function PCA_cumulative_var_plot() to show the cumulative variation explained as more PCs are considered
NEW Functions
PCA_variation() function

This function extacts information needed to make a more advanced, annotated scree plot.

# This is a NEW function
PCA_variation <- function(pca){
  
  # get summary information from PCA
  pca_summary <- summary(pca)
  
  # extract information from summary
  ## raw variance for each PC
  variance <- pca_summary$importance[1,]
  
  ## % variance explained by each PC
  var_explained <- pca_summary$importance[2,]*100
  var_explained <- round(var_explained,3)
  
  ## cumulative % variance  
  var_cumulative <- pca_summary$importance[3,]*100
  var_cumulative <- round(var_cumulative,3)
  
  # prepare output
  N.PCs <- length(var_explained)
  var_df <- data.frame(PC = 1:N.PCs,
            var_raw  = variance,
            var_percent = var_explained, 
            cumulative_percent = var_cumulative)
  
  # return output
  return(var_df)   
}
screeplot_snps() function

This functions makes a more advanced scree plot better suited for PCS on for SNPs.

# This is a NEW function
screeplot_snps <- function(var_df){
total_var <- sum(var_df$var_raw)
N <- length(var_df$var_raw)
var_cutoff <- total_var/N
var_cut_percent <- var_cutoff/total_var*100
var_cut_percent_rnd <- round(var_cut_percent,2)
i_above_cut <- which(var_df$var_percent > var_cut_percent)
i_cut <- max(i_above_cut) 
ti <- paste0("Cutoff = ",
            var_cut_percent_rnd,
            "%\n","Useful PCs = ",i_cut)
plot(var_df$var_percent,
        main =ti, type = "l",
     xlab = "PC",
     ylab = "Percent variation",
     col = 0)

segments(x0 = var_df$PC,
         x1 = var_df$PC,
         y0 = 0, 
         y1 = var_df$var_percent,
         col = 1)


segments(x0 = 0,
         x1 = N,
         y0 = var_cut_percent, 
         y1 = var_cut_percent,
         col = 2)

}
PCA_cumulative_var_plot() function

This makes a plot complementary to a scree plot. A scree plot plots the amount of variation explained by each PC. This plot plots a curve of cumulative amount of variation explained by the PCs.

# This is a NEW function
PCA_cumulative_var_plot <- function(var_df){
  plot(cumulative_percent ~ PC, 
       data = var_out,
       main = "Cumulative percent variation\n explained by PCs",
       xlab = "PC",
       ylab = "Cumulative %",
       type = "l")
  
  total_var <- sum(var_df$var_raw)
N <- length(var_df$var_raw)
var_cutoff <- total_var/N
var_cut_percent <- var_cutoff/total_var*100
var_cut_percent_rnd <- round(var_cut_percent,2)
i_above_cut <- which(var_df$var_percent > var_cut_percent)
i_cut <- max(i_above_cut) 

percent_cut_i <- which(var_out$PC == i_cut )
percent_cut <- var_out$cumulative_percent[percent_cut_i]
segments(x0 = i_cut,
         x1 = i_cut,
         y0 = 0, 
         y1 = 100,
         col = 2)

segments(x0 = -10,
         x1 = N,
         y0 = percent_cut, 
         y1 = percent_cut,
         col = 2)

}
Advanced screeplot analysis
Extract information

Extract information on the variance explained by each PC.

var_out <- PCA_variation(vcf_pca)

Look at the output of PCA_variation()

head(var_out)
##     PC   var_raw var_percent cumulative_percent
## PC1  1 11.781367       1.925              1.925
## PC2  2  9.070546       1.141              3.065
## PC3  3  8.676718       1.044              4.109
## PC4  4  8.073480       0.904              5.013
## PC5  5  7.510246       0.782              5.795
## PC6  6  7.369429       0.753              6.548
Advanced screeplot

This advanced scree plot shows the amount of variation explained by all PCs. It marks with a horizontal line what the cutoff is for the amount of Percent variation explained that is useful, and a vertical line for where that line interacts the curve of the scree plot. The title indicates the percentage value of the cutoff and which PC is the last PC below that value. Though only the first few PCs can be plotted, PCs below the cut off value (“useful PCs) should probably used for further machine learning algorithms.

Make the scree plot with screeplot_snps()

screeplot_snps(var_out)

Cumulative variation plot

The cumulative variation plot shows how much variation in the data explained in total as more and more PCs are considered. The vertical red line shows the cutoff value from the scree plot (above). The horizontal line indicates what the total percentage of variation explained by these useful PCs is.

Make cumulative variation plot with PCA_cumulative_var_plot()

PCA_cumulative_var_plot(var_out)

PCA Scatterplots

The object created above var_out indicates how much variation is explained by each of the Principal components. This information is often added to the axes of scatterplots of PCA output.

head(var_out)
##     PC   var_raw var_percent cumulative_percent
## PC1  1 11.781367       1.925              1.925
## PC2  2  9.070546       1.141              3.065
## PC3  3  8.676718       1.044              4.109
## PC4  4  8.073480       0.904              5.013
## PC5  5  7.510246       0.782              5.795
## PC6  6  7.369429       0.753              6.548

PC 1 explains 1.925% percent of the variation, PC2 explains. 1.141%, and PC3 explains 1.044%. In total, the first 3 PCs explain only ~4.1% of the variability in the data. The scree plot indicate that the first ~716 PCs are useful explain ~60% of the variation in the data. In further analysis such as GWAS the first 225 PCs should therefore be used.

Plot PC1 versus PC2

Plot the scores, with super-population color-coded

# make color and shape = "super_pop"
ggpubr::ggscatter(data = vcf_pca_scores2,
                  y = "PC2",
                  x = "PC1",
              color = "population",   
              shape = "population",   
              main = "PCA Scatterplot",
         ylab = "PC2 (1.141% of variation)",
         xlab = "PC1 (1.925% of variation")

warning("If this didn't work, you make need to change the color = and shape = code")
## Warning: If this didn't work, you make need to change the color = and shape =
## code
message("Be sure to also update the amount of variation explained by the PCs")
## Be sure to also update the amount of variation explained by the PCs

Note how in the plot the amount of variation explained by each PC is shown in the axis labels.

Plot PC2 versus PC3

Plot the scores, with super population color-coded

# make color and shape = "super_pop"
ggpubr::ggscatter(data = vcf_pca_scores2,
                  y = "PC3",
                  x = "PC2",
                  color = "population",   
                  shape = "population",   
                  main = "PCA Scatterplot",
          ylab = "PC3 (1.044% of variation)",
          xlab = "PC2 (1.141% of variation")

warning("Be sure to update the amount of variation explained by the PCs")
## Warning: Be sure to update the amount of variation explained by the PCs

Note how in the plot the amount of variation explained by each PC is shown in the axis labels.

Plot PC1 versus PC3

Plot the scores, with super population color-coded

# make color and shape = "population"
ggpubr::ggscatter(data = vcf_pca_scores2,
                  y = "PC3",
                  x = "PC1",
                  ellipse = T,
            color = "population",   
            shape = "population",   
            main = "PCA Scatterplot",
      ylab = "PC3 (1.044% of variation)",
      xlab = "PC1 (1.925% of variation")

warning("Be sure to update the amount of variation explained by the PCs")
## Warning: Be sure to update the amount of variation explained by the PCs

Note how in the plot the amount of variation explained by each PC is shown in the axis labels.

3D scatterplot

The first 3 principal components can be presented as a 3D scatterplot.

colors_use <- as.numeric(vcf_pca_scores2$population)
scatterplot3d(x = vcf_pca_scores2$PC1,
              y = vcf_pca_scores2$PC2,
              z = vcf_pca_scores2$PC3,
              color = colors_use,
              xlab = "PC1 (1.925%)",
              ylab = "PC2 (1.141%)",
              zlab = "PC3 (1.044%)",
              )

warning("Be sure to update the amount of variation explained by the PCs")
## Warning: Be sure to update the amount of variation explained by the PCs

In the PC1 vs. PC2 scatterplot. There seems to be not clear splits between the groups, however AFR population seems to be for the most part pretty seperated from the EAS and SAS populations that are intertwined with each other. In the PC2 vs. PC3 scatterplot, there is a split in the population as some of the EAS and SAS populations are positively correlated wtih PC2 while the reas of the data is not really correlated wtih PC1. However most of the samples in the SAS and EAS populations are in with the rest of the groups on the left side of the plot so I would say there arent two distinct groups in this plot. In the PC1 vs. PC3 scatterplot, there is a clear split between the AFR population and the other populations(AMR, EAS, EUR, and SAS). AFR seems to be negatively correlated with PC1 while the other cluster of groups seems to be more positively correlated with PC1.