Skip to content

Commit 12ec3b5

Browse files
authored
Add files via upload
1 parent e7c47d5 commit 12ec3b5

File tree

1 file changed

+341
-0
lines changed

1 file changed

+341
-0
lines changed

GSE_analysis_microarray.Rmd

+341
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
---
2+
title: "GSE analysis for microarray data"
3+
author: "Lian Chee Foong"
4+
date: "2/3/2021"
5+
output:
6+
pdf_document:
7+
df_print: tibble
8+
toc: true
9+
number_sections: true
10+
highlight: tango
11+
fig_width: 3
12+
fig_height: 1.5
13+
---
14+
15+
16+
```{r setup, include=FALSE}
17+
knitr::opts_chunk$set(echo = TRUE)
18+
```
19+
20+
***
21+
22+
# Introduction
23+
24+
This R script is to demonstrate the steps to download GSE data from NCBI GEO database, and to obtain the differential expressed genes from GSE data.
25+
26+
## A little background of GEO
27+
28+
The Gene Expression Omnibus (GEO) is a data repository hosted by the National Center for Biotechnology Information (NCBI). NCBI contains all publicly available nucleotide and protein sequences. Presently, all records in GenBank NCBI are generated from direct submission to the DNA sequence databases from the original authors, who volunteer their records to make the data publicly available or do so as part of the publication process. The NCBI GEO is intended to house different types of expression data, covering all type of sequencing data in both raw and processed formats.
29+
30+
## Example here
31+
32+
Here, GSE63477, which is a microarray data, will be analysed. It contains an expression profile of prostate cancer cells (LNCaP) after treatment of cabazitaxel or docetaxel for 16 hr. You may read the details in NCBI GEO, under the “overall design” section. Or for better understanding, it’s always good if we read the original paper...link here: https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi
33+
34+
***
35+
# Using GEOquery to obtain microarray data
36+
37+
First, set the working directory.
38+
39+
The GEOquery package allows you to access the data from GEO. Depending on your needs, you can download only the processed data and metadata provided by the depositor. In some cases, you may want to download the raw data as well, if it was provided by the depositor.
40+
41+
The function to download a GEO dataset is ‘getGEO’ from the ‘GEOquery’ package.Check how many platforms used for the GSE data, usually there will only be one platform. We set the first object in the list and gse now is an expressionSet. You can see that it contains assayData, phenoData, feature etc.
42+
43+
We can have a look at the sample information, gene annotation, and the expression data. This allow us to have a rough idea of the information storing in this expressionSet.
44+
45+
```{R}
46+
getwd()
47+
setwd("C:/Users/Lynn/Documents/R_GEOdata")
48+
49+
###https://sbc.shef.ac.uk/geo_tutorial/tutorial.nb.html#Introduction
50+
#----import the data------------------------------------
51+
library(GEOquery)
52+
my_id <- "GSE63477"
53+
gse <- getGEO(my_id)
54+
55+
## check how many platforms used
56+
length(gse)
57+
gse <-gse[[1]]
58+
gse
59+
60+
pData(gse) ## print the sample information
61+
fData(gse) ## print the gene annotation
62+
exprs(gse)[1,] ## print the expression data
63+
```
64+
65+
# Check the normalisation and scales used
66+
67+
We can use this command to check the normalization method, to see if the data has already processed. So this expression data was RMA normalized and filtered to remove low-expressing genes. RMA means Robust Multiarray Average, it is the most common method to determine probeset expression level for Affymetrix arrays.
68+
69+
The ‘summary’ function can then be used to print the distributions of expression levels, if the data has been log transformed, typically in the range of 0 to 16. Hmm...the values are quite big and go beyond 16. It’s quite weird because RMA should already log2 transform the data at the last step, but well, let’s do it on our own and move to the next step. For a more careful analysis, we can try to run the raw data of this dataset again, by applying RMA normalization on our own, to see if there is any difference.
70+
71+
Anyway, here, let’s perform a log2 transformation. We may check the summary of expression level again. And draw a boxplot. We can see that the distributions of each sample are highly similar, which means the data have been normalised.
72+
73+
```{R}
74+
pData(gse)$data_processing[1]
75+
# For visualisation and statistical analysis, we will inspect the data to
76+
# discover what scale the data are presented in. The methods we will use assume
77+
# the data are on a log2 scale; typically in the range of 0 to 16.
78+
79+
## have a look on the expression value
80+
summary(exprs(gse))
81+
# From this output we clearly see that the values go beyond 16,
82+
# so we need to perform a log2 transformation.
83+
exprs(gse) <- log2(exprs(gse))
84+
85+
# check again the summary
86+
summary(exprs(gse))
87+
88+
boxplot(exprs(gse),outline=F)
89+
```
90+
91+
# Inspect the clinical variables
92+
93+
Now we try to look into the pData for the elements that we need for the analysis. We want to know the sample name, whether it is treatment or control...in this dataset, the info is stored in the column of 'characteristics_ch1.1'.
94+
95+
We can use the select function to subset the column of interest. It will be useful also to rename the column to something more shorter and easier.
96+
97+
To make a column of simplified group names for each sample, ‘Stringr’ is helpful. Two new columns are created, named “group” and "serum". The function ‘str_detect’ is to detect the presence of the words, and then fill the row accordingly. It totally depends on your dataset to make these necessary categories in the new columns, just modify these commands for your dataset of interest.
98+
99+
```{R}
100+
101+
library(dplyr)
102+
sampleInfo <- pData(gse)
103+
head(sampleInfo)
104+
105+
table(sampleInfo$characteristics_ch1.1)
106+
107+
#Let's pick just those columns that seem to contain factors we might
108+
#need for the analysis.
109+
sampleInfo <- select(sampleInfo, characteristics_ch1.1)
110+
111+
## Optionally, rename to more convenient column names
112+
sampleInfo <- rename(sampleInfo, sample = characteristics_ch1.1)
113+
114+
head(sampleInfo)
115+
dim(sampleInfo)
116+
sampleInfo$sample
117+
118+
library(stringr)
119+
sampleInfo$group <- ""
120+
for(i in 1:nrow(sampleInfo)){
121+
if(str_detect(sampleInfo$sample[i], "CTRL") && str_detect(sampleInfo$sample[i], "full"))
122+
{sampleInfo$group[i] <- "Conf"}
123+
124+
if(str_detect(sampleInfo$sample[i], "CTRL") && str_detect(sampleInfo$sample[i], "dextran"))
125+
{sampleInfo$group[i] <- "Cond"}
126+
127+
if(str_detect(sampleInfo$sample[i], "cabazitaxel") && str_detect(sampleInfo$sample[i], "full"))
128+
{sampleInfo$group[i] <- "cabazitaxelf"}
129+
130+
if(str_detect(sampleInfo$sample[i], "cabazitaxel") && str_detect(sampleInfo$sample[i], "dextran"))
131+
{sampleInfo$group[i] <- "cabazitaxeld"}
132+
133+
if(str_detect(sampleInfo$sample[i], "docetaxel") && str_detect(sampleInfo$sample[i], "full"))
134+
{sampleInfo$group[i] <- "docetaxelf"}
135+
136+
if(str_detect(sampleInfo$sample[i], "docetaxel") && str_detect(sampleInfo$sample[i], "dextran"))
137+
{sampleInfo$group[i] <- "docetaxeld"}
138+
}
139+
140+
sampleInfo
141+
142+
sampleInfo$serum <- ""
143+
for(i in 1:nrow(sampleInfo)){
144+
if(str_detect(sampleInfo$sample[i], "dextran"))
145+
{sampleInfo$serum[i] <- "dextran"}
146+
147+
if(str_detect(sampleInfo$sample[i], "full"))
148+
{sampleInfo$serum[i] <- "full_serum"}
149+
150+
}
151+
152+
sampleInfo <- sampleInfo[,-1]
153+
sampleInfo
154+
```
155+
156+
# Sample clustering and Principal Components Analaysis
157+
158+
We can visualize the correlations between the samples by hierarchical clustering.
159+
160+
The function ‘cor’ can calculate the correlation on the scale of 0 to 1, in a pairwise fashion between all samples, then visualise on a heatmap. There are many ways to create heatmaps in R, here I use ‘pheatmap’, the only argument it requires is a matrix of numeric values.
161+
162+
We can add more sample info onto the plot to get a better pic of the group and clustering. Here, we make use of the 'sampleInfo' file that was created earlier, to match with the columns of the correlation matrix.
163+
164+
```{R}
165+
166+
library(pheatmap)
167+
## argument use="c" stops an error if there are any missing data points
168+
169+
corMatrix <- cor(exprs(gse),use="c")
170+
pheatmap(corMatrix)
171+
172+
## Print the rownames of the sample information and check it matches the correlation matrix
173+
174+
rownames(sampleInfo)
175+
colnames(corMatrix)
176+
177+
## If not, force the rownames to match the columns
178+
#rownames(sampleInfo) <- colnames(corMatrix)
179+
180+
pheatmap(corMatrix, annotation_col= sampleInfo)
181+
```
182+
183+
Another way is to use Principal component analysis (PCA). It has to note that the data has to be transposed, so that the genelist is in the column, while rownames are the samples, so the PCA process will not run out of the memory in the oher way round.
184+
185+
Let’s add labels to plot the results, here, we use the ‘ggplots2’ package, while the ‘ggrepel’ package is used to position the text labels more cleverly so they can be read. Here we can see that the samples are divided into two groups based on the serum treatment types.
186+
187+
```{R}
188+
#make PCA
189+
library(ggplot2)
190+
library(ggrepel)
191+
## MAKE SURE TO TRANSPOSE THE EXPRESSION MATRIX
192+
193+
pca <- prcomp(t(exprs(gse)))
194+
195+
## Join the PCs to the sample information
196+
cbind(sampleInfo, pca$x) %>%
197+
ggplot(aes(x = PC1, y=PC2, col=group, label=paste("",group))) + geom_point() + geom_text_repel()
198+
```
199+
200+
# Differential expression analysis
201+
202+
In this section, we use the limma package to perform differential expressions. Limma stands for “Linear models for microarray”. Here, we need to tell limma what sample groups we want to compare. I choose sampleInfo$group. A design matrix will be created, this is a matrix of 0 and 1, one row for each sample and one column for each sample group.
203+
204+
We can rename the column names so that it is easier to see.
205+
206+
Now, let’s check if the expression data contain any lowly-expressed genes, this will affect the quality of DE analysis. A big problem in doing statistical analysis like limma is the inference of type 1 statistical errors, also called false positive. One simple way to reduce the possibility for type 1 errors is to do fewer comparisons, by filtering the data. For example, we know that not all genes are expressed in all tissues and many genes will not be expressed in any sample. As a result, in DGE analysis, it makes sense to remove the genes that are likely not expressed at all.
207+
208+
It is quite subjective how one defines a gene being expressed, here, I follow the tutorial, to make the cut off at the median of the expression values, which means to consider around 50% of the genes will not be expressed. Keep those expressed genes if they are present in more than 2 samples.
209+
210+
We can see that around half of the genes are not qualified as an “expressed” gene here, which makes sense, bcoz our cut-off is the median value.
211+
212+
```{R}
213+
library(limma)
214+
design <- model.matrix(~0 + sampleInfo$group)
215+
design
216+
217+
## the column names are a bit ugly, so we will rename
218+
colnames(design) <- c("Cabazitaxeld","Cabazitaxelf","Cond","Conf","Docetaxeld","Docetaxelf")
219+
220+
design
221+
222+
## calculate median expression level
223+
cutoff <- median(exprs(gse))
224+
225+
## TRUE or FALSE for whether each gene is "expressed" in each sample
226+
is_expressed <- exprs(gse) > cutoff
227+
228+
## Identify genes expressed in more than 2 samples
229+
230+
keep <- rowSums(is_expressed) > 3
231+
232+
## check how many genes are removed / retained.
233+
table(keep)
234+
235+
## subset to just those expressed genes
236+
gse <- gse[keep,]
237+
```
238+
239+
Here there is a little extra step to find out the outliers. This has to be done carefully so the filtered data won't be too biased. We calculate ‘weights’ to define the reliability of each sample. The ‘arrayweights’ function will assign a score to each sample, with a value of 1 implying equal weight. Samples with score less than 1 are down-weighed, or else up-weighed.
240+
241+
242+
```{R}
243+
# coping with outliers
244+
## calculate relative array weights
245+
aw <- arrayWeights(exprs(gse),design)
246+
aw
247+
```
248+
249+
Now we have a design matrix, we need to estimate the coefficients. For this design, we will essentially average the replicate arrays for each sample level. In addition, we will calculate standard deviations for each gene, and the average intensity for the genes across all microarrays.
250+
251+
We are ready to tell limma which pairwise contrasts that we want to make. For this experiment, we are going to contrast treatment (there are two types of texane drugs) and control in each serum type. So there are 4 contrasts to specify.
252+
253+
To do the statistical comparisons, Limma uses Bayesian statistics to minimize type 1 error. The eBayes function performs the tests. To summarize the results of the statistical test, 'topTable' will adjust the p-values and return the top genes that meet the cutoffs that you supply as arguments; while 'decideTests' will make calls for DEGs by adjusting the p-values and applying a logFC cutoff similar to topTable.
254+
255+
```{R}
256+
## Fitting the coefficients
257+
fit <- lmFit(exprs(gse), design,
258+
weights = aw)
259+
260+
head(fit$coefficients)
261+
262+
## Making comparisons between samples, can define multiple contrasts
263+
contrasts <- makeContrasts(Docetaxeld - Cond, Cabazitaxeld - Cond, Docetaxelf - Conf, Cabazitaxelf - Conf, levels = design)
264+
265+
fit2 <- contrasts.fit(fit, contrasts)
266+
fit2 <- eBayes(fit2)
267+
268+
269+
topTable(fit2)
270+
topTable1 <- topTable(fit2, coef=1)
271+
topTable2 <- topTable(fit2, coef=2)
272+
topTable3 <- topTable(fit2, coef=3)
273+
topTable4 <- topTable(fit2, coef=4)
274+
275+
#if we want to know how many genes are differentially expressed overall, we can use the decideTest function.
276+
summary(decideTests(fit2))
277+
table(decideTests(fit2))
278+
```
279+
280+
# Further visualization with gene annotation
281+
282+
Now we want to know the gene name associated with the gene ID. The annotation data can be retrieved with the ‘fData’ function. Let’s select the ID, GB_ACC, this is genbank accession ID. Add into fit2 table.
283+
284+
The “Volcano Plot” function is a common way of visualising the results of a DE analysis. The x axis shows the log-fold change and the y axis is some measure of statistical significance, which in this case is the log-odds, or “B” statistic. We can also change the color of those genes with p value cutoff more than 0.05, and fold change cut off more than 1.
285+
286+
```{R}
287+
288+
anno <- fData(gse)
289+
head(anno)
290+
291+
anno <- select(anno,ID,GB_ACC)
292+
fit2$genes <- anno
293+
294+
topTable(fit2)
295+
296+
## Create volcano plot
297+
full_results1 <- topTable(fit2, coef=1, number=Inf)
298+
library(ggplot2)
299+
ggplot(full_results1,aes(x = logFC, y=B)) + geom_point()
300+
301+
## change according to your needs
302+
p_cutoff <- 0.05
303+
fc_cutoff <- 1
304+
305+
306+
full_results1 %>%
307+
mutate(Significant = P.Value < p_cutoff, abs(logFC) > fc_cutoff ) %>%
308+
ggplot(aes(x = logFC, y = B, col=Significant)) + geom_point()
309+
```
310+
311+
# Further visualization of selected gene
312+
313+
I think at this point, we are quite clear about data structure of GSE data. It has an experiment data, pData; the expression data, exprs; and also annotation data, fData. And we have learned how to check the expression data, normalize them, and perform differential expression analysis.
314+
315+
Now, with the differential expression gene tables, there are some downstream analyses that we can continue, such as to export a full table of DE genes, to generate a heatmap for your selected genes, get the gene list for a particular pathway, or survival analysis (but this is only for those clinical data).
316+
317+
Here, I just want to look into the fold change data of a selected gene, whether it is significantly differential expressed or not.
318+
319+
```{R}
320+
321+
## Get the results for particular gene of interest
322+
#GB_ACC for Nkx3-1 is NM_001256339 or NM_006167
323+
##no NM_001256339 in this data
324+
full_results2 <- topTable(fit2, coef=2, number=Inf)
325+
full_results3 <- topTable(fit2, coef=3, number=Inf)
326+
full_results4 <- topTable(fit2, coef=4, number=Inf)
327+
filter(full_results1, GB_ACC == "NM_006167")
328+
filter(full_results2, GB_ACC == "NM_006167")
329+
filter(full_results3, GB_ACC == "NM_006167")
330+
filter(full_results4, GB_ACC == "NM_006167")
331+
```
332+
333+
That’s all for the walk-through, thanks for reading, I hope you have learned something new here.
334+
335+
# Acknowlegdement
336+
Many thanks to the following tutorials made publicly available:
337+
338+
1. Introduction to microarray analysis GSE15947, by Department of Statistics, Purdue Univrsity https://www.stat.purdue.edu/bigtap/online/docs/Introduction_to_Microarray_Analysis_GSE15947.html
339+
340+
2. Mark Dunning, 2020, GEO tutorial, by Sheffield Bioinformatics Core https://sbc.shef.ac.uk/geo_tutorial/tutorial.nb.html#Further_processing_and_visualisation_of_DE_results
341+

0 commit comments

Comments
 (0)