Module 8.2 Lab: Defined gene list (g:Profiler)

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 Unported License. This means that you are able to copy, share and modify the work, as long as the result is distributed under the same license.

Authors: Veronique Voisin, Ruth Isserlin

Presenter: Veronique Voisin

Introduction

During this practical lab, we will perform pathway enrichment analysis using a defined gene list.

Goal of the exercise 1

For this exercise, our goal is to run pathway enrichment analysis using g:Profiler and explore the results. g:Profiler performs a gene-set enrichment analysis using a hypergeometric test (Fisher’s exact test). The Gene Ontology Biological Process, Reactome and WikiPathways sources are going to be used as pathway databases. We wil also upload our custom geneset file.

g:Profiler can be used using the website at https://biit.cs.ut.ee/gprofiler/gost. However, for this practical lab, we will run it from the g:Profiler R package.

We will run the query and explore the table of results and visualize the results as bar and dot plots.

One of the greatest features of g:Profiler is that it is updated on a regular basis and most of the previous versions are available online ont the gprofiler archive.

The gprofielr2 -g:Profiler R implementation is a wrapper for the web version. You require an internet connection to get enrichment results.

Data

g:Profiler requires a list of genes. For this analysis, we used genes we found to be differentially expressed in palbociclib-resistant samples compared with parental samples from the breast cancer cell line MCF7 (see chapter 1.

Exercise 1 - prepare the gene lists

This exercise requires the file “edgeR_resistant_vs_parental_all_genes.csv” located in the data folder.

Step 1 - Install and Load libraries

# List of CRAN packages
cran_packages <- c(
  "tidyverse",
  "knitr",
  "kableExtra",
  "glue",
  "patchwork",
  "ggdendro",
  "pheatmap",
  "gprofiler2"
)
# Install CRAN packages if not already installed
for (pkg in cran_packages) {
  if (!requireNamespace(pkg, quietly = TRUE)) {
    install.packages(pkg)
  }
}
# Load the libraries
library(tidyverse)
library(knitr)
library(kableExtra)
library(glue)
library(patchwork)
library(ggdendro)
library(pheatmap)
library(gprofiler2)

# create the results folder (if it does not exist yet) to save the outputs
# dir.create("results", showWarnings = FALSE)
# Location of the Module 8 files (data/, BYODdata/, image/):
# - on the course server: ~/CourseData/Module8
# - when the book is built from the repository: Module8
data_dir <- if (dir.exists("~/CourseData/Module8")) "~/CourseData/Module8" else "Module8"

Step 2 - Prepare the define gene lists

res = read.csv(file.path(data_dir, "data/edgeR_resistant_vs_parental_all_genes.csv"))

UPgenes <- res$gene[res$logFC>0 &  res$FDR <= 0.001]
DOWNgenes <- res$gene[res$logFC<0 &  res$FDR <= 0.001]

Step 3 - Run g:Profiler

The next step is to run pathway enrichment analysis using gost g:Profiler using the g:Profiler2 R package. For detailed descriptions of all the parameters that can be specified for the gost g:profiler function see the R package information - here and here.

For this query we are specifying -

  • query - the set of genes of interest, as loaded in from the gene list file (query_set).
  • significant - set to TRUE so that g:Profiler returns only the pathways that it deems significant (adjusted p-value below its default threshold of 0.05). We will filter these results further in a next step.
  • ordered_query - set to FALSE (because we are not taking into account the order of the list)
  • exclude_iea - set to TRUE. We are removing the electronic inferred annotations from the GO database
  • correction_method - set to fdr. by default g:Profiler uses g:Scs
  • organism - set to “hsapiens” for homo sapiens. Organism names are constructed by concatenating the first letter of the name and the family name (according to gprofiler2 documentation)
  • source - the geneset source databases to use for the analysis. We recommend using GO biological process (GO:BP), WikiPathways (WP) and Reactome (Reac) but there are additional sources you can add (GO molecular function or cellular component(GO:MF, GO:CC), KEGG, transcription factors (TF), microRNA targets (MIRNA), corum complexes (CORUM), Human protein atlas (HPA),Human phenotype ontology (HP) )
  • evcodes - set to TRUE so that g:Profiler also returns the genes of our list found in each pathway (column intersection, genes separated by a comma). It makes the query a little slower.

run g:Profiler with the up genes

query_set = UPgenes 

gprofiler_results_UPgenes <- gost(query = query_set ,
                          significant=TRUE,
                          ordered_query = FALSE,
                          exclude_iea=TRUE,
                          correction_method = "fdr",
                          organism = "hsapiens",
                          source = c("REAC","WP","GO:BP"),
                          evcodes = TRUE)

observe the results

#get the gprofiler results table
enrichment_results <- gprofiler_results_UPgenes$result

#display the names of all columns
colnames(enrichment_results)
##  [1] "query"                 "significant"           "p_value"              
##  [4] "term_size"             "query_size"            "intersection_size"    
##  [7] "precision"             "recall"                "term_id"              
## [10] "source"                "term_name"             "effective_domain_size"
## [13] "source_order"          "parents"               "evidence_codes"       
## [16] "intersection"
#display the top rows
head(enrichment_results)
##     query significant     p_value term_size query_size intersection_size
## 1 query_1        TRUE 0.005977645       635         74                13
## 2 query_1        TRUE 0.005977645       509         74                11
## 3 query_1        TRUE 0.005977645        61         74                 5
## 4 query_1        TRUE 0.005977645      1013         74                16
## 5 query_1        TRUE 0.005977645       188         74                 7
## 6 query_1        TRUE 0.005977645        73         74                 5
##    precision     recall    term_id source
## 1 0.17567568 0.02047244 GO:0035295  GO:BP
## 2 0.14864865 0.02161100 GO:0001944  GO:BP
## 3 0.06756757 0.08196721 GO:0050818  GO:BP
## 4 0.21621622 0.01579467 GO:2000026  GO:BP
## 5 0.09459459 0.03723404 GO:0050817  GO:BP
## 6 0.06756757 0.06849315 GO:0032963  GO:BP
##                                            term_name effective_domain_size
## 1                                   tube development                 16180
## 2                            vasculature development                 16180
## 3                          regulation of coagulation                 16180
## 4 regulation of multicellular organismal development                 16180
## 5                                        coagulation                 16180
## 6                         collagen metabolic process                 16180
##   source_order                            parents
## 1         8374             GO:0007275, GO:0048856
## 2          542             GO:0048731, GO:0072359
## 3        12418             GO:0050817, GO:0051239
## 4        23538 GO:0007275, GO:0050793, GO:0051239
## 5        12417                         GO:0032501
## 6         7434                         GO:0008152
##                                                                            evidence_codes
## 1                 ISS,IMP IGI IEP IBA,IDA,IDA,IEP,IMP IBA TAS,ISS,ISS,IMP,ISS,TAS,IDA,IMP
## 2                     ISS,IMP IGI IEP IBA,IDA,IDA,IEP,IMP IBA TAS,IMP ISS,ISS,ISS,TAS,IDA
## 3                                           IMP,IDA IMP IBA TAS NAS IC,IC,IDA IBA,IMP IBA
## 4 ISS,IDA,IDA IMP IGI IBA,IDA,IDA,IMP,IDA,IMP,IMP,IDA,ISS,ISS IBA,IBA,TAS,IDA,IDA IMP IBA
## 5                               IMP,IBA,IDA IMP IBA TAS NAS IC,IDA IC,IDA IBA,IDA,IMP IBA
## 6                                                             IDA IBA,ISS IBA,IMP,IDA,IDA
##                                                                                       intersection
## 1                     CAV1,SERPINE1,F3,ADGRB1,TGFBI,ITGA5,CCBE1,LOX,WNK4,ERRFI1,ANGPTL4,RAMP1,CLMP
## 2                               CAV1,SERPINE1,F3,ADGRB1,TGFBI,ITGA5,CCBE1,LOX,ERRFI1,ANGPTL4,RAMP1
## 3                                                                       CAV1,SERPINE1,F3,F2R,PROCR
## 4 CAV1,AXL,SERPINE1,F3,ADGRB1,DPYSL5,ADAMTS7,BIN1,ITGA5,FBN1,L1CAM,ERRFI1,CSF3,ANGPTL4,SEMA7A,TESC
## 5                                                             CAV1,AXL,SERPINE1,F3,F2R,FBLN1,PROCR
## 6                                                                        P3H2,P3H3,COL5A1,F2R,MRC2

YOUR TURN: run g:Profiler with the down genes (use same parameters as above)

##Tip: remove eval=FALSE chunk option to run the code
query_set = XXXX

gprofiler_results_DOWNgenes <- gost(query = XXXX,
                          significant = XXXX,
                          ordered_query = XXXX,
                          exclude_iea = XXXX,
                          correction_method = XXXX,
                          organism = XXXX,
                          source = c("XXXX","XXXX","XXXX"),
                          evcodes = XXXX)

##observe the results (same as for the UP genes)

Clear visualization of the results

The enrichment_results dataframe contains different columns. Let’s rearrange the table to display the most important information. We will arrange the table so it contains: the database origin, the name of each of the pathway, term size , query size, intersection size and adjusted pvalue. -

  • Term size: number of genes in the pathway as it is in the database
  • Query size: number of genes in our gene list
  • Intersection size: number of genes overlapping between our gene list and the tested pathway
  • pvalue: adjusted pvalue (corrected for multiple hypothesis testing using the Benjamini-Hochberg method)

The results are ordered by significance, from the most significant pathway (lowest p-value) to the least significant.

enrichment_results <- gprofiler_results_UPgenes$result
enrichment_results$p_value <- formatC(enrichment_results$p_value, format = "e", digits = 2)

enrichment_results = enrichment_results[ , c("source", "term_name", "term_size", "query_size", "intersection_size", "p_value")]

kable(head(enrichment_results, n=20), caption = "Enrichment Result") %>%
kable_styling(bootstrap_options = c("striped", "condensed"), font_size = 10)
Table 1: Table 2: Enrichment Result
source term_name term_size query_size intersection_size p_value
GO:BP tube development 635 74 13 5.98e-03
GO:BP vasculature development 509 74 11 5.98e-03
GO:BP regulation of coagulation 61 74 5 5.98e-03
GO:BP regulation of multicellular organismal development 1013 74 16 5.98e-03
GO:BP coagulation 188 74 7 5.98e-03
GO:BP collagen metabolic process 73 74 5 5.98e-03
GO:BP anatomical structure morphogenesis 1847 74 22 5.98e-03
GO:BP cell adhesion mediated by integrin 80 74 5 7.04e-03
GO:BP blood vessel morphogenesis 457 74 10 8.11e-03
GO:BP tube morphogenesis 562 74 11 8.11e-03
GO:BP regulation of multicellular organismal process 2293 74 24 8.15e-03
GO:BP blood vessel development 487 74 10 1.04e-02
GO:BP positive regulation of blood coagulation 21 74 3 1.44e-02
GO:BP positive regulation of hemostasis 21 74 3 1.44e-02
GO:BP circulatory system development 745 74 12 1.45e-02
GO:BP regulation of blood coagulation 57 74 4 1.45e-02
GO:BP regulation of hemostasis 58 74 4 1.46e-02
GO:BP wound healing 340 74 8 1.46e-02
GO:BP positive regulation of coagulation 23 74 3 1.46e-02
GO:BP blood coagulation 182 74 6 1.61e-02

YOUR TURN: visualize the results with the down genes (use same parameters as above)

##Tip: remove eval=FALSE chunk option to run the code

enrichment_results <- XXXX
enrichment_results$p_value <- formatC(XXXX)

enrichment_results = enrichment_results[ , c("XXXX", "XXXX", "XXXX", "XXXX", "XXXX", "XXXX")]

kable(head(enrichment_results, n=20), caption = "Enrichment Result") %>%
kable_styling(bootstrap_options = c("striped", "condensed"), font_size = 10)

Step 4 - Explore top table of results

For this analysis, we chose to use 3 databases: GO:BP, Reactome and Wikipathways. If we look at the top table only, we are only going to see the results of GO:BP but it is interesting to look at the top results for each of the 3 databunique(enrichment_results$source)

enrichment_results <- gprofiler_results_UPgenes$result
##Get the name of the databases
unique(enrichment_results$source) 
## [1] "GO:BP" "REAC"  "WP"
##Filter the dataframe to retrieve only the Reactome results
enrichment_results_Reac <- enrichment_results %>%
  filter(source == "REAC")

##Now display the top 20 pathways
kable(head(enrichment_results_Reac, n=20), caption = "Enrichment Result") %>%
kable_styling(bootstrap_options = c("striped", "condensed"), font_size = 10)
Table 3: Table 4: Enrichment Result
query significant p_value term_size query_size intersection_size precision recall term_id source term_name effective_domain_size source_order parents evidence_codes intersection
query_1 TRUE 0.0000077 298 57 12 0.2105263 0.0402685 REAC:R-HSA-1474244 REAC Extracellular matrix organization 11056 930 REAC:0000000 REAC,REAC,REAC,REAC,REAC,REAC,REAC,REAC,REAC,REAC,REAC,REAC COL6A1,SERPINE1,P3H2,TIMP2,P3H3,COL6A2,COL5A1,ITGA5,FBLN1,FBN1,LOX,BGN
query_1 TRUE 0.0008208 89 57 6 0.1052632 0.0674157 REAC:R-HSA-1474290 REAC Collagen formation 11056 403 REAC:R-HSA-1474244 REAC,REAC,REAC,REAC,REAC,REAC COL6A1,P3H2,P3H3,COL6A2,COL5A1,LOX
query_1 TRUE 0.0021054 67 57 5 0.0877193 0.0746269 REAC:R-HSA-1650814 REAC Collagen biosynthesis and modifying enzymes 11056 400 REAC:R-HSA-1474290 REAC,REAC,REAC,REAC,REAC COL6A1,P3H2,P3H3,COL6A2,COL5A1
query_1 TRUE 0.0029160 76 57 5 0.0877193 0.0657895 REAC:R-HSA-3000178 REAC ECM proteoglycans 11056 851 REAC:R-HSA-1474244 REAC,REAC,REAC,REAC,REAC COL6A1,SERPINE1,COL6A2,COL5A1,BGN
query_1 TRUE 0.0033756 44 57 4 0.0701754 0.0909091 REAC:R-HSA-1566948 REAC Elastic fibre formation 11056 875 REAC:R-HSA-1474244 REAC,REAC,REAC,REAC ITGA5,FBLN1,FBN1,LOX
query_1 TRUE 0.0033756 84 57 5 0.0877193 0.0595238 REAC:R-HSA-216083 REAC Integrin cell surface interactions 11056 1267 REAC:R-HSA-1474244 REAC,REAC,REAC,REAC,REAC COL6A1,COL6A2,COL5A1,ITGA5,FBN1
query_1 TRUE 0.0097759 60 57 4 0.0701754 0.0666667 REAC:R-HSA-2022090 REAC Assembly of collagen fibrils and other multimeric structures 11056 186 REAC:R-HSA-1474290 REAC,REAC,REAC,REAC COL6A1,COL6A2,COL5A1,LOX
query_1 TRUE 0.0256174 140 57 5 0.0877193 0.0357143 REAC:R-HSA-1474228 REAC Degradation of the extracellular matrix 11056 748 REAC:R-HSA-1474244 REAC,REAC,REAC,REAC,REAC COL6A1,TIMP2,COL6A2,COL5A1,FBN1
query_1 TRUE 0.0316913 39 57 3 0.0526316 0.0769231 REAC:R-HSA-140877 REAC Formation of Fibrin Clot (Clotting Cascade) 11056 986 REAC:R-HSA-109582 REAC,REAC,REAC F3,F2R,PROCR
query_1 TRUE 0.0322168 42 57 3 0.0526316 0.0714286 REAC:R-HSA-419037 REAC NCAM1 interactions 11056 1577 REAC:R-HSA-375165 REAC,REAC,REAC COL6A1,COL6A2,COL5A1
query_1 TRUE 0.0322168 516 57 9 0.1578947 0.0174419 REAC:R-HSA-9006934 REAC Signaling by Receptor Tyrosine Kinases 11056 2399 REAC:R-HSA-162582 REAC,REAC,REAC,REAC,REAC,REAC,REAC,REAC,REAC CAV1,FOSL1,AXL,COL6A1,F3,TCIRG1,COL6A2,COL5A1,CYBA
query_1 TRUE 0.0338224 44 57 3 0.0526316 0.0681818 REAC:R-HSA-8948216 REAC Collagen chain trimerization 11056 401 REAC:R-HSA-1650814 REAC,REAC,REAC COL6A1,COL6A2,COL5A1
query_1 TRUE 0.0449470 675 57 10 0.1754386 0.0148148 REAC:R-HSA-109582 REAC Hemostasis 11056 1168 REAC:0000000 REAC,REAC,REAC,REAC,REAC,REAC,REAC,REAC,REAC,REAC CAV1,SERPINE1,F3,PDE9A,ITGA5,EHD2,F2R,PROCR,L1CAM,SERPINA1

YOUR TURN: DISPLAY THE TOP 20 PATHWAYS FOR THE WIKIPATHWAY DATABASE (“WP”) and the down genes

enrichment_results <- gprofiler_results_DOWNgenes$result
##Get the name of the databases
unique(enrichment_results$source) 

##Filter the dataframe to retrieve only the Reactome results
enrichment_results_WP <- enrichment_results %>%
  XXXX(source == "XXXX")

##Now display the top 20 pathways
kable(head(enrichment_results_WP, n=20), caption = "Enrichment Result") %>%
kable_styling(bootstrap_options = c("striped", "condensed"), font_size = 10)

Step 5 - Filter results by geneset size

Restrict the results to just the ones that have at maximum gene-set size of 1000 and a minimum gene-set size of 10 and with an adjusted pvalue of 0.05.-

  • maximum gene-set size of 1000: will remove very generic pathway terms that are less informative like “regulation of gene expression”
  • minimum gene-set size of 10: will remove small pathways where the pvalue is significant athough the overlap size is very small
  • p_thres: aim is to retrieve only pathways that are significant under this adjusted pvalue threshold of 0.01 (1% false positive)
enrichment_results <- gprofiler_results_UPgenes$result 

min_gs_size = 10
max_gs_size = 1000
p_thres = 0.01
# filter by params defined above
# by default we have set the max and min gs size to 250 and 3, respectively.


##the pvalue was stored as a character vector, we have to transform back into a numerical vector to be able to use it to filter values
enrichment_results$p_value <- as.numeric(enrichment_results$p_value) 

enrichment_results_mxgssize_1000_min_10_adjp_0_01 <- 
                        subset(enrichment_results,
                                   term_size >= min_gs_size & 
                                   term_size <= max_gs_size & 
                                   p_value <= p_thres )

#
myrows = nrow(enrichment_results_mxgssize_1000_min_10_adjp_0_01)

print(glue("It results in {myrows} selected pathways with maximum gene-set size of {max_gs_size} and minimum gene-set of {min_gs_size} with an adjusted p-value of {p_thres} . "))
## It results in 15 selected pathways with maximum gene-set size of 1000 and minimum gene-set of 10 with an adjusted p-value of 0.01 .

YOUR TURN: using the enrichment results for the down genes, use gene-set size filters and FDR filters and display the results

In a second step, try different thresholds of maximum gene-set size and adjusted pvalue threshold: 500 and 250 for * max_gs_size and 0.05 or 0.001 for pvalue-

  • Option 1
    • max_gs_size = 500
    • p_thres = 0.001

or

  • Option 2
    • max_gs_size = 250
    • p_thres = 0.05

How many pathways do you get for each option (nrow())??

enrichment_results <- gprofiler_results_DOWNgenes$result 

min_gs_size = XXXX
max_gs_size = XXXX
p_thres = XXXX
# filter by params defined above
# by default we have set the max and min gs size to 250 and 3, respectively.


##the pvalue was stored as a character vector, we have to transform back into a numerical vector to be able to use it to filter values
enrichment_results$p_value <- as.numeric(enrichment_results$p_value) 

enrichment_results <- XXXXX
                        
#
myrows = nrow(enrichment_results)

print(glue("It results in {myrows} selected pathways with maximum gene-set size of {max_gs_size} and minimum gene-set of {min_gs_size} with an adjusted p-value of {p_thres} . "))

Step 6 - Run g:profiler with your own genesets (example using BaderLab genesets)

With regards to pathway sets there are two options when using g:Profiler -

  • Use the genesets that are supplied by g:Profiler as we just did.
  • Upload your own genesets.

The most common reasons for supplying your own genesets is the ability to use up to date annotations or in-house annotations that might not be available in the public sphere yet. It can be used to test the enrichment in a particular pathway of interest that might not be in the general pathway database but that you obtained for example by extracting the data from a published paper. You need to format this pathway in a .gmt file.

In this example, we will upload the Baderlab gene set file that contains multiple database sources. We filtered the gene-set prior to uploading the gene-set file to g:Profiler. The Baderlab gene set file combines different sources of databases and you can find more information at: https://baderlab.org/GeneSets

’‘’Why do we filter the geneset before running gprofiler’’’

The g:Profiler interface only allows for filtering genesets by size only after the analysis is complete. After the analysis is complete means the filtering is happening after Multiple hypothesis testing. Filtering prior to the analysis will generate more robust results because we exclude the uninformative large genesets prior to testing changing the sets that multiple hypothesis filtering will get rid of.

We have 1 example of gmt files with different filtering thresholds : * genesets less of equal than 500 genes

The format of the GMT file is described https://software.broadinstitute.org/cancer/software/gsea/wiki/index.php/Data_formats#GMT:Gene_Matrix_Transposed_file_format.28.2A.gmt.29 and consists of rows with the following

  • Name
  • Description
  • tab delimited list of genes a part of this geneset

Write out the gmt file with genenames

In order to use your own genesets with g:Profiler you need to upload the the file to g:Profiler server first. The function will return an ID that you need to specify in the organism parameter of the g:Profiler gost function call. It is done by using the upload_GMT_file() function which is included in the gprofiler2 package. Note: it took about 2 minutes to run this chunck of code.

dest_gmt_file_500 =file.path(data_dir, "data/Human_GOBP_AllPathways_filtered_500orLessV2.gmt")

custom_gmt_max500 <- upload_GMT_file(
                        gmtfile=dest_gmt_file_500)
## Your custom annotations ID is gp__1EtK_ZBaC_KKk.
## You can use this ID as an 'organism' name in all the related enrichment tests against this custom source.
## Just use: gost(my_genes, organism = 'gp__1EtK_ZBaC_KKk')
UPgenes <- res$gene[res$logFC>0 &  res$FDR <= 0.001]
DOWNgenes <- res$gene[res$logFC<0 &  res$FDR <= 0.001]


gprofiler_results_custom_max500_UPgenes <- gost(query = UPgenes  ,
                                     significant=TRUE,
                                      ordered_query = FALSE,
                                      exclude_iea=TRUE,
                                     correction_method = "fdr",
                                 organism = custom_gmt_max500,
                                     evcodes = TRUE
                                     )
## Detected custom GMT source request
enrichment_results_UPgenes <- gprofiler_results_custom_max500_UPgenes$result

enrichment_results_UPgenes$parents <- sapply(
  enrichment_results_UPgenes$parents,
  function(x) paste(x, collapse = ", ")
)
 
##Now display the top 20 pathways
kable(head(enrichment_results_UPgenes, n=20), caption = "Enrichment Result") %>%
kable_styling(bootstrap_options = c("striped", "condensed"), font_size = 10)
Table 5: Table 6: Enrichment Result
query significant p_value term_size query_size intersection_size precision recall term_id source term_name effective_domain_size source_order parents evidence_codes intersection
query_1 TRUE 0.0000025 308 81 13 0.1604938 0.0422078 EXTRACELLULAR MATRIX ORGANIZATION%REACTOME%R-HSA-1474244.5 Human_GOBP_AllPathways_filtered_500orLessV2 Extracellular matrix organization 17710 3114 X,X,X,X,X,X,X,X,X,X,X,X,X COL6A1,SERPINE1,SNTB1,P3H2,TIMP2,P3H3,COL6A2,COL5A1,ITGA5,FBLN1,FBN1,LOX,BGN
query_1 TRUE 0.0005125 66 81 6 0.0740741 0.0909091 BETA1 INTEGRIN CELL SURFACE INTERACTIONS%PATHWAY INTERACTION DATABASE NCI-NATURE CURATED DATA%BETA1 INTEGRIN CELL SURFACE INTERACTIONS Human_GOBP_AllPathways_filtered_500orLessV2 Beta1 integrin cell surface interactions 17710 1083 X,X,X,X,X,X COL6A1,COL6A2,TGFBI,COL5A1,ITGA5,FBN1
query_1 TRUE 0.0020101 89 81 6 0.0740741 0.0674157 COLLAGEN FORMATION%REACTOME DATABASE ID RELEASE 97%1474290 Human_GOBP_AllPathways_filtered_500orLessV2 Collagen formation 17710 2582 X,X,X,X,X,X COL6A1,P3H2,P3H3,COL6A2,COL5A1,LOX
query_1 TRUE 0.0043121 62 81 5 0.0617284 0.0806452 REGULATION OF COAGULATION%GOBP%GO:0050818 Human_GOBP_AllPathways_filtered_500orLessV2 regulation of coagulation 17710 14756 X,X,X,X,X CAV1,SERPINE1,F3,F2R,PROCR
query_1 TRUE 0.0050573 67 81 5 0.0617284 0.0746269 COLLAGEN BIOSYNTHESIS AND MODIFYING ENZYMES%REACTOME DATABASE ID RELEASE 97%1650814 Human_GOBP_AllPathways_filtered_500orLessV2 Collagen biosynthesis and modifying enzymes 17710 3202 X,X,X,X,X COL6A1,P3H2,P3H3,COL6A2,COL5A1
query_1 TRUE 0.0067961 422 81 10 0.1234568 0.0236967 TUBE DEVELOPMENT%GOBP%GO:0035295 Human_GOBP_AllPathways_filtered_500orLessV2 tube development 17710 9957 X,X,X,X,X,X,X,X,X,X CAV1,SERPINE1,TGFBI,ITGA5,CCBE1,LOX,WNK4,ERRFI1,RAMP1,CLMP
query_1 TRUE 0.0086573 40 81 4 0.0493827 0.1000000 ELASTIC FIBRE FORMATION%REACTOME DATABASE ID RELEASE 97%1566948 Human_GOBP_AllPathways_filtered_500orLessV2 Elastic fibre formation 17710 4843 X,X,X,X ITGA5,FBLN1,FBN1,LOX
query_1 TRUE 0.0104444 283 81 8 0.0987654 0.0282686 VASCULATURE DEVELOPMENT%GOBP%GO:0001944 Human_GOBP_AllPathways_filtered_500orLessV2 vasculature development 17710 8945 X,X,X,X,X,X,X,X CAV1,SERPINE1,TGFBI,ITGA5,CCBE1,LOX,ERRFI1,RAMP1
query_1 TRUE 0.0137184 57 81 4 0.0493827 0.0701754 COAGULATION PATHWAY%REACTOME DATABASE ID RELEASE 97%9769740 Human_GOBP_AllPathways_filtered_500orLessV2 Coagulation pathway 17710 2450 X,X,X,X SERPINE1,F3,F2R,PROCR
query_1 TRUE 0.0137184 98 81 5 0.0617284 0.0510204 POSITIVE REGULATION OF MONOATOMIC ION TRANSMEMBRANE TRANSPORT%GOBP%GO:0034767 Human_GOBP_AllPathways_filtered_500orLessV2 positive regulation of monoatomic ion transmembrane transport 17710 7284 X,X,X,X,X CAV1,F2R,FXYD5,TESC,FHL1
query_1 TRUE 0.0137184 50 81 4 0.0493827 0.0800000 REGULATION OF CLOTTING CASCADE%REACTOME DATABASE ID RELEASE 97%9769739 Human_GOBP_AllPathways_filtered_500orLessV2 Regulation of clotting cascade 17710 4167 X,X,X,X SERPINE1,F3,F2R,PROCR
query_1 TRUE 0.0137184 57 81 4 0.0493827 0.0701754 REGULATION OF BLOOD COAGULATION%GOBP%GO:0030193 Human_GOBP_AllPathways_filtered_500orLessV2 regulation of blood coagulation 17710 17655 X,X,X,X CAV1,SERPINE1,F3,F2R
query_1 TRUE 0.0137184 21 81 3 0.0370370 0.1428571 POSITIVE REGULATION OF HEMOSTASIS%GOBP%GO:1900048 Human_GOBP_AllPathways_filtered_500orLessV2 positive regulation of hemostasis 17710 15624 X,X,X SERPINE1,F3,F2R
query_1 TRUE 0.0137184 240 81 7 0.0864198 0.0291667 BLOOD VESSEL MORPHOGENESIS%GOBP%GO:0048514 Human_GOBP_AllPathways_filtered_500orLessV2 blood vessel morphogenesis 17710 12918 X,X,X,X,X,X,X CAV1,SERPINE1,TGFBI,ITGA5,CCBE1,LOX,RAMP1
query_1 TRUE 0.0137184 94 81 5 0.0617284 0.0531915 POSITIVE REGULATION OF CATION TRANSMEMBRANE TRANSPORT%GOBP%GO:1904064 Human_GOBP_AllPathways_filtered_500orLessV2 positive regulation of cation transmembrane transport 17710 11849 X,X,X,X,X CAV1,F2R,FXYD5,TESC,FHL1
query_1 TRUE 0.0137184 56 81 4 0.0493827 0.0714286 ASSEMBLY OF COLLAGEN FIBRILS AND OTHER MULTIMERIC STRUCTURES%REACTOME DATABASE ID RELEASE 97%2022090 Human_GOBP_AllPathways_filtered_500orLessV2 Assembly of collagen fibrils and other multimeric structures 17710 4570 X,X,X,X COL6A1,COL6A2,COL5A1,LOX
query_1 TRUE 0.0137184 21 81 3 0.0370370 0.1428571 POSITIVE REGULATION OF BLOOD COAGULATION%GOBP%GO:0030194 Human_GOBP_AllPathways_filtered_500orLessV2 positive regulation of blood coagulation 17710 18367 X,X,X SERPINE1,F3,F2R
query_1 TRUE 0.0137184 52 81 4 0.0493827 0.0769231 COLLAGEN METABOLIC PROCESS%GOBP%GO:0032963 Human_GOBP_AllPathways_filtered_500orLessV2 collagen metabolic process 17710 16593 X,X,X,X P3H2,P3H3,COL5A1,MRC2
query_1 TRUE 0.0139108 58 81 4 0.0493827 0.0689655 REGULATION OF HEMOSTASIS%GOBP%GO:1900046 Human_GOBP_AllPathways_filtered_500orLessV2 regulation of hemostasis 17710 7751 X,X,X,X CAV1,SERPINE1,F3,F2R
query_1 TRUE 0.0141423 23 81 3 0.0370370 0.1304348 POSITIVE REGULATION OF COAGULATION%GOBP%GO:0050820 Human_GOBP_AllPathways_filtered_500orLessV2 positive regulation of coagulation 17710 12228 X,X,X SERPINE1,F3,F2R
# write.csv( enrichment_results_UPgenes, "results/enrichment_results_gprofiler_UPgenes.csv")

YOUR TURN: perform the same task with the down genes

gprofiler_results_custom_max500_DNgenes <- gost(
                                      query = XXXX  ,
                                     significant=XXXX,
                                      ordered_query = XXXX,
                                      exclude_iea=XXXX,
                                     correction_method = "XXX",
                                 organism = XXXX,
                                     evcodes = XXXX
                                     )

enrichment_results_DNgenes <- gprofiler_results_custom_max500_DNgenes$result


enrichment_results_DNgenes$parents <- sapply(
  enrichment_results_DNgenes$parents,
  function(x) paste(x, collapse = ", ")
)

kable(head(enrichment_results_DNgenes, n=20), caption = "Enrichment Result") %>%
kable_styling(bootstrap_options = c("striped", "condensed"), font_size = 10)

# write.csv( enrichment_results_DNgenes, "results/enrichment_results_gprofiler_DNgenes.csv")

Exercise 2 - visualization

We will use clusterProfiler style visualization. ### Step 1 - Bar plot

##Calculate a score (small very significant adjusted p value will turn into a large score)

enrichment_results_UPgenes <- gprofiler_results_custom_max500_UPgenes$result

enrichment_results_UPgenes$score = -log10(as.numeric(enrichment_results_UPgenes$p_value))

enrichment <- enrichment_results_UPgenes %>%
  as_tibble() %>%
  arrange(desc(score)) %>%
  slice_head(n = 20)

p = ggplot(enrichment, aes(reorder(term_name, score), score)) +
  geom_col(aes(fill = score), width=0.8) +
  scale_fill_gradient(low = "#C1F6F8", high = "#00BFC4")+
  coord_flip() +
  labs(x="", y="Normalized Enrichment Score",title="Pathway enrichment analysis ") + 
  theme_minimal()

p

Step 2 - Dot plot with segment

enrichmentTop20 <- enrichment %>%
  arrange(desc(score)) %>%
  slice_head(n = 20)

p = ggplot(enrichmentTop20, aes(
  y = fct_reorder(term_name, score),
  x = score,
  color = score,
  size = score
)) +
  geom_point() +
  scale_color_gradientn(
    colours = c("#56A443", "#8AA443", "#f7ca64"),
    trans = "log10",
    guide = guide_colorbar(reverse = FALSE, order = 1)
  ) +
  scale_size_continuous(range = c(4, 8)) +
  theme_bw(base_size = 12) +
  xlab("score -log10(adj pvalue)") +
  ylab(NULL) +
  ggtitle("Pathway Enrichment Analysis") +
  theme(
    axis.text.y = element_text(size = 10)
  )
p

Step 4 - Hierarchical Cluster

The problem with the earlier visualization, which represents the top 20 pathways, is that it does not account for the overlap between gene sets. As you can observe, many pathways have similar names because they share a substantial number of genes. To provide a more accurate and efficient representation of the results, and to obtain a quicker and clearer overview, it would be more appropriate to aggregate and summarize pathways with substantial gene overlap, thereby reducing redundancy.

Our first approach is to use hierarchical clustering based on a distance matrix that measures the similarity between pathways according to the number of genes they have in common, allowing us to visualize their relationships.

enrichment_results_UPgenes <- gprofiler_results_custom_max500_UPgenes$result

enrichment_results_UPgenes$score = -log10(as.numeric(enrichment_results_UPgenes$p_value))

# Top 20 enriched terms
enrichmentTop20 <- enrichment_results_UPgenes %>%
  arrange(desc(score)) %>%
  slice_head(n = 20)

Calculating the distance matrix using the jaccard coefficient

  • Many pathways share genes, so similar biological processes can appear under different pathway names. To group similar pathways, we look at how many genes they have in common.
  • The Jaccard coefficient measures the shared genes relative to all unique genes in the two pathways, from 0 (no overlap) to 1 (identical).
  • The overlap coefficient measures the shared genes relative to the smaller pathway and shows when one pathway is mostly contained within another, from 0 (no overlap) to 1 (smaller pathway contained into larger one).
Jaccard and overlap coefficients for two gene sets.

Figure 3: Jaccard and overlap coefficients for two gene sets.

We convert these scores into distances (1 − score) and use them to group similar pathways into broader biological themes.

##Illustration with Jaccard Coefficient

# Convert genes to lists
# (the intersection column, returned by g:Profiler with evcodes = TRUE, contains
# the genes of our list found in each pathway, separated by a comma)
gene_lists <- strsplit(enrichment_results_UPgenes$intersection, ",")

# Calculate Jaccard similarity matrix
n <- length(gene_lists)
jaccard_mat <- matrix(0, n, n)
rownames(jaccard_mat) <- enrichment_results_UPgenes$term_name
colnames(jaccard_mat) <- enrichment_results_UPgenes$term_name

for (i in seq_len(n)) {
  for (j in seq_len(n)) {
    intersection <- length(intersect(gene_lists[[i]], gene_lists[[j]]))
    union        <- length(union(gene_lists[[i]],     gene_lists[[j]]))
    jaccard_mat[i, j] <- intersection / union
  }
}


labs <- enrichment_results_UPgenes$term_name
labs <- ifelse(nchar(labs) > 30, paste0(substr(labs, 1, 27), "..."), labs)
rownames(jaccard_mat) <- colnames(jaccard_mat) <- make.unique(labs)

keep <- sample(seq_len(nrow(jaccard_mat)), min(20, nrow(jaccard_mat)))
jaccard_sub <- jaccard_mat[keep, keep]


# Heatmap
p <- pheatmap(
  jaccard_sub,
  color           = colorRampPalette(c("white", "#C6DBEF", "#6BAED6", "#2171B5", "#08306B"))(100),
  breaks          = seq(0, 1, length.out = 101),
  display_numbers = F,
  #number_format   = "%.2f",
  #number_color    = "black",
  #fontsize_number = 8,
  clustering_distance_rows = as.dist(1 - jaccard_sub),
  clustering_distance_cols = as.dist(1 - jaccard_sub),
  border_color    = "grey90",
  fontsize_row    = 7,
  fontsize_col    = 7,
  angle_col       = 45,
  main            = "Jaccard coefficient (20 random gene sets)"
)

##Illustration with Overlap Coefficient

# Calculate Overlap coefficient matrix (reuses gene_lists and n from above)
overlap_mat <- matrix(0, n, n)

for (i in seq_len(n)) {
  for (j in seq_len(n)) {
    intersection <- length(intersect(gene_lists[[i]], gene_lists[[j]]))
    smaller      <- min(length(gene_lists[[i]]), length(gene_lists[[j]]))
    overlap_mat[i, j] <- intersection / smaller
  }
}

rownames(overlap_mat) <- colnames(overlap_mat) <- make.unique(labs)

# Use the same 20 gene sets as the Jaccard heatmap so the two can be compared
overlap_sub <- overlap_mat[keep, keep]


# Heatmap
p <- pheatmap(
  overlap_sub,
  color           = colorRampPalette(c("white", "#C6DBEF", "#6BAED6", "#2171B5", "#08306B"))(100),
  breaks          = seq(0, 1, length.out = 101),
  display_numbers = F,
  clustering_distance_rows = as.dist(1 - overlap_sub),
  clustering_distance_cols = as.dist(1 - overlap_sub),
  border_color    = "grey90",
  fontsize_row    = 7,
  fontsize_col    = 7,
  angle_col       = 45,
  main            = "Overlap coefficient (20 random gene sets)"
)

##Hierarchical Clustering

plot_hclust_enrichment: helper function to cluster the top enriched pathways by gene overlap (Jaccard distance) and plot them next to the dendrogram

plot_hclust_enrichment = function(enrichment_results, n_top = 20, n_clusters = 8) {

  enrichment_results$score <- -log10(as.numeric(enrichment_results$p_value))

  # Top n_top enriched terms
  enrichmentTop <- enrichment_results %>%
    arrange(desc(score)) %>%
    slice_head(n = n_top)

  top_terms <- enrichmentTop %>%
    arrange(as.numeric(p_value))

  # Jaccard similarity matrix
  # (the intersection column contains the genes of our list found in each pathway)
  gene_lists <- strsplit(top_terms$intersection, ",")
  n          <- length(gene_lists)

  jaccard_mat <- matrix(0, n, n,
                        dimnames = list(top_terms$term_name, top_terms$term_name))

  for (i in seq_len(n)) {
    for (j in seq_len(n)) {
      inter <- length(intersect(gene_lists[[i]], gene_lists[[j]]))
      uni   <- length(union(gene_lists[[i]],     gene_lists[[j]]))
      jaccard_mat[i, j] <- inter / uni
    }
  }

  # Hierarchical clustering 
  dist_mat   <- as.dist(1 - jaccard_mat)
  hc         <- hclust(dist_mat, method = "average")
  term_order <- hc$labels[hc$order]

  # The first level is drawn at the bottom of the bar plot, matching the
  # first dendrogram leaf (y = 1)
  top_terms$term_name <- factor(top_terms$term_name, levels = term_order)
  top_terms$neglog10p <- -log10(as.numeric(top_terms$p_value))

  #  Cut tree into k branches (cannot have more branches than terms)
  k        <- min(n_clusters, n)
  clusters <- cutree(hc, k = k)

  top_terms$cluster <- factor(clusters[as.character(top_terms$term_name)])

  # Bar plot (full, coloured by cluster)
  base_colours <- c("#1D9E75", "#3A7DC9", "#E07B39", "#9B59B6",
                    "#E74C3C", "#F1C40F", "#1ABC9C", "#E91E63")
  # Interpolate extra colours when more than 8 clusters are requested
  if (k <= length(base_colours)) {
    cluster_colours <- base_colours
  } else {
    cluster_colours <- colorRampPalette(base_colours)(k)
  }

  p_bar <- ggplot(top_terms, aes(x = neglog10p, y = term_name, fill = cluster)) +
    geom_col(alpha = 0.85, width = 0.7) +
    scale_y_discrete(expand = c(0, 0.5)) +
    scale_fill_manual(values = cluster_colours[seq_len(k)],
                      name   = "Branch") +
    labs(x = expression(-log[10](p-value)), y = NULL) +
    theme_bw(base_size = 10) +
    theme(
      panel.grid.major.y = element_blank(),
      axis.text.y        = element_text(size = 8),
      legend.position    = "right"
    )

  #  Dendrogram 
  dend_data <- dendro_data(hc, type = "rectangle")
  seg       <- dend_data$segments

  p_dend <- ggplot(seg) +
    geom_segment(aes(x = y, y = x, xend = yend, yend = xend),
                 colour = "grey40", linewidth = 0.45) +
    scale_x_reverse(expand = expansion(mult = c(0.02, 0))) +
    scale_y_continuous(limits = c(0.5, n + 0.5), expand = c(0, 0)) +
    labs(x = "Jaccard distance") +
    theme_void() +
    theme(
      axis.title.x = element_text(size = 8, colour = "grey50", margin = margin(t = 4)),
      axis.text.x  = element_text(size = 7, colour = "grey60"),
      plot.margin  = margin(0, 4, 0, 0)
    )

  #  Combined plot 
  p_combined <- p_dend + p_bar +
    plot_layout(widths = c(3, 1))

  #  Keep only the top hit per branch 
  top_per_cluster <- top_terms %>%
    group_by(cluster) %>%
    slice_min(as.numeric(p_value), n = 1, with_ties = FALSE) %>%
    ungroup() %>%
    arrange(as.numeric(p_value)) %>%
    mutate(term_name = factor(as.character(term_name),
                              levels = rev(as.character(term_name))))

  #  Simple barplot — one representative term per branch 
  p_simple <- ggplot(top_per_cluster,
                     aes(x = neglog10p, y = term_name, fill = cluster)) +
    geom_col(alpha = 0.88, width = 0.6) +
    geom_text(aes(label = sprintf("p = %.2e", as.numeric(p_value))),
              hjust = -0.1, size = 3, colour = "grey30") +
    scale_fill_manual(values = cluster_colours[seq_len(k)],
                      name   = "Branch") +
    scale_x_continuous(expand = expansion(mult = c(0, 0.25))) +
    labs(
      x     = expression(-log[10](p-value)),
      y     = NULL,
      title = "Top term per branch"
    ) +
    theme_bw(base_size = 11) +
    theme(
      panel.grid.major.y = element_blank(),
      axis.text.y        = element_text(size = 9),
      legend.position    = "bottom",
      plot.title         = element_text(size = 11, face = "bold")
    )

  # n_terms and n_clusters are returned so the figure height can be set
  # from them in the chunk options
  return(list(combined = p_combined, top_per_branch = p_simple,
              n_terms = n, n_clusters = k))
}

Running the function on the top 20 pathways

enrichment_results_UPgenes <- gprofiler_results_custom_max500_UPgenes$result

hc_plots <- plot_hclust_enrichment(enrichment_results = enrichment_results_UPgenes, n_top = 20, n_clusters = 10)

The figure height grows with the number of pathways (combined plot) and the number of clusters (top term per branch), so the bars do not overlap.

hc_plots$combined

hc_plots$top_per_branch

YOUR TURN Running the function with your defined number of pathways and clusters

NOTE: In the next module, you will learn a better approach to address redundancy and visualize enriched gene sets as networks using Cytoscape and EnrichmentMap.