The purpose of this project is to use exploratory data analysis to examine gene expression in brain tissue from a 90-year-old woman in the RUSH Alzheimer’s study. Three plots will be used to look at the distribution of gene expression, identify the most highly expressed genes, and examine the relationship between gene length and expression.
# Load tidyverse for organizing data and creating plots.
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.1 ✔ readr 2.2.0
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.3 ✔ tibble 3.3.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.2
## ✔ purrr 1.2.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# Import ENCODE data ------------------------------------
# Read the file into R and save as EncodeData
EncodeData <- read.table("ENCFF166SFX.tsv", header = TRUE)
# Show first 6 rows, display column names, show structure, and give a summary of the data
head(EncodeData)
## gene_id transcript_id.s. length effective_length expected_count TPM FPKM
## 1 10904 10904 93 0 0 0 0
## 2 12954 12954 94 0 0 0 0
## 3 12956 12956 72 0 0 0 0
## 4 12958 12958 82 0 0 0 0
## 5 12960 12960 73 0 0 0 0
## 6 12962 12962 72 0 0 0 0
## posterior_mean_count posterior_standard_deviation_of_count pme_TPM pme_FPKM
## 1 0 0 0 0
## 2 0 0 0 0
## 3 0 0 0 0
## 4 0 0 0 0
## 5 0 0 0 0
## 6 0 0 0 0
## TPM_ci_lower_bound TPM_ci_upper_bound TPM_coefficient_of_quartile_variation
## 1 0 0 0
## 2 0 0 0
## 3 0 0 0
## 4 0 0 0
## 5 0 0 0
## 6 0 0 0
## FPKM_ci_lower_bound FPKM_ci_upper_bound
## 1 0 0
## 2 0 0
## 3 0 0
## 4 0 0
## 5 0 0
## 6 0 0
## FPKM_coefficient_of_quartile_variation
## 1 0
## 2 0
## 3 0
## 4 0
## 5 0
## 6 0
names(EncodeData)
## [1] "gene_id"
## [2] "transcript_id.s."
## [3] "length"
## [4] "effective_length"
## [5] "expected_count"
## [6] "TPM"
## [7] "FPKM"
## [8] "posterior_mean_count"
## [9] "posterior_standard_deviation_of_count"
## [10] "pme_TPM"
## [11] "pme_FPKM"
## [12] "TPM_ci_lower_bound"
## [13] "TPM_ci_upper_bound"
## [14] "TPM_coefficient_of_quartile_variation"
## [15] "FPKM_ci_lower_bound"
## [16] "FPKM_ci_upper_bound"
## [17] "FPKM_coefficient_of_quartile_variation"
str(EncodeData)
## 'data.frame': 59526 obs. of 17 variables:
## $ gene_id : chr "10904" "12954" "12956" "12958" ...
## $ transcript_id.s. : chr "10904" "12954" "12956" "12958" ...
## $ length : num 93 94 72 82 73 72 74 82 73 73 ...
## $ effective_length : num 0 0 0 0 0 0 0 0 0 0 ...
## $ expected_count : num 0 0 0 0 0 0 0 0 0 0 ...
## $ TPM : num 0 0 0 0 0 0 0 0 0 0 ...
## $ FPKM : num 0 0 0 0 0 0 0 0 0 0 ...
## $ posterior_mean_count : num 0 0 0 0 0 0 0 0 0 0 ...
## $ posterior_standard_deviation_of_count : num 0 0 0 0 0 0 0 0 0 0 ...
## $ pme_TPM : num 0 0 0 0 0 0 0 0 0 0 ...
## $ pme_FPKM : num 0 0 0 0 0 0 0 0 0 0 ...
## $ TPM_ci_lower_bound : num 0 0 0 0 0 0 0 0 0 0 ...
## $ TPM_ci_upper_bound : num 0 0 0 0 0 0 0 0 0 0 ...
## $ TPM_coefficient_of_quartile_variation : num 0 0 0 0 0 0 0 0 0 0 ...
## $ FPKM_ci_lower_bound : num 0 0 0 0 0 0 0 0 0 0 ...
## $ FPKM_ci_upper_bound : num 0 0 0 0 0 0 0 0 0 0 ...
## $ FPKM_coefficient_of_quartile_variation: num 0 0 0 0 0 0 0 0 0 0 ...
summary(EncodeData)
## gene_id transcript_id.s. length effective_length
## Length :59526 Length :59526 Min. : 8 Min. : 0
## N.unique :59526 N.unique :59526 1st Qu.: 387 1st Qu.: 287
## N.blank : 0 N.blank : 0 Median : 808 Median : 708
## Min.nchar: 4 Min.nchar: 4 Mean : 1339 Mean : 1240
## Max.nchar: 24 Max.nchar: 3455 3rd Qu.: 1823 3rd Qu.: 1723
## Max. :205012 Max. :204912
## expected_count TPM FPKM
## Min. : 0.0 Min. : 0.00 Min. : 0.00
## 1st Qu.: 0.0 1st Qu.: 0.00 1st Qu.: 0.00
## Median : 0.0 Median : 0.00 Median : 0.00
## Mean : 339.3 Mean : 16.80 Mean : 19.55
## 3rd Qu.: 46.0 3rd Qu.: 1.52 3rd Qu.: 1.77
## Max. :1251092.4 Max. :267430.21 Max. :311258.58
## posterior_mean_count posterior_standard_deviation_of_count pme_TPM
## Min. : 0.0 Min. : 0.000 Min. : 0.00
## 1st Qu.: 0.0 1st Qu.: 0.000 1st Qu.: 0.09
## Median : 0.0 Median : 0.000 Median : 0.30
## Mean : 339.3 Mean : 1.965 Mean : 16.80
## 3rd Qu.: 46.0 3rd Qu.: 0.000 3rd Qu.: 3.00
## Max. :1068373.8 Max. :22576.980 Max. :217493.25
## pme_FPKM TPM_ci_lower_bound TPM_ci_upper_bound
## Min. : 0.00 Min. :0.000e+00 Min. :0.000e+00
## 1st Qu.: 0.11 1st Qu.:0.000e+00 1st Qu.:2.530e-01
## Median : 0.36 Median :3.000e-03 Median :6.790e-01
## Mean : 20.22 Mean :1.502e+01 Mean :1.923e+01
## 3rd Qu.: 3.61 3rd Qu.:1.072e+00 3rd Qu.:4.465e+00
## Max. :261833.14 Max. :2.169e+05 Max. :2.181e+05
## TPM_coefficient_of_quartile_variation FPKM_ci_lower_bound FPKM_ci_upper_bound
## Min. :0.0000 Min. :0.000e+00 Min. :0.000e+00
## 1st Qu.:0.0759 1st Qu.:0.000e+00 1st Qu.:3.040e-01
## Median :0.3873 Median :4.000e-03 Median :8.180e-01
## Mean :0.3677 Mean :1.808e+01 Mean :2.316e+01
## 3rd Qu.:0.6553 3rd Qu.:1.292e+00 3rd Qu.:5.370e+00
## Max. :0.7098 Max. :2.612e+05 Max. :2.625e+05
## FPKM_coefficient_of_quartile_variation
## Min. :0.00000
## 1st Qu.:0.07591
## Median :0.38733
## Mean :0.36768
## 3rd Qu.:0.65535
## Max. :0.70981
The ENCODE data is stored as a data frame containing gene identifiers and measurements of gene expression. Each row is one gene, while the columns are gene length, expected count, TPM, and FPKM.
The first figure shows gene length as gene size can affect the number of sequencing fragments assigned to a gene. Longer genes have more bases where sequencing fragments can align which can skew the analysis.
# Prepare gene-length data ------------------------------
# Keep genes longer than zero but shorter than 10,000 base pairs.
ShortGenes <- EncodeData %>%
filter(
length > 0,
length < 10000
)
# Plot gene-length distribution -------------------------
# Create a histogram showing the distribution of gene lengths.
GeneLength <- ggplot(
ShortGenes,
aes(x = length)
) +
# Group gene lengths into 250-base-pair intervals.
geom_histogram(
binwidth = 250,
fill = "green",
color = "white"
) +
# Add a title and axis labels.
labs(
title = "Distribution of Gene Lengths",
subtitle = "Genes shorter than 10,000 base pairs",
x = "Gene Length in Base Pairs",
y = "Number of Genes"
)
# Display the completed graph.
GeneLength
Figure 1 shows the distribution of genes that are shorter than 10,000 base pairs. Most of the genes included in the graph are found toward the shorter end of this range, while fewer genes are closer to 10,000 base pairs. Gene length is important when looking at expression data as longer genes have more locations for sequencing reads to line up which can cause longer genes to have higher expected counts.
Expected count estimates the number of sequencing fragments assigned to each gene. FPKM normalizes expression based on both gene length and the total number of sequenced fragments.
# Prepare expression data -------------------------------
# Keep genes with positive expected count and FPKM values.
ExpressedGenes <- EncodeData %>%
filter(
expected_count > 0,
FPKM > 0
)
# Compare expected count with FPKM -----------------------
# Create a scatterplot comparing expected count and FPKM.
CountvFPKM <- ggplot(
ExpressedGenes,
aes(
x = expected_count,
y = FPKM,
color = length
)
) +
# Add one point for each expressed gene.
geom_point(
alpha = 0.4,
size = 1
) +
# Use logarithmic scales because both measurements contain a large ranges of values.
scale_x_log10() +
scale_y_log10() +
# Apply a color gradient based on gene length.
scale_color_gradient(
low = "gold",
high = "darkred"
) +
# Add a line showing the overall relationship.
geom_smooth(
method = "lm",
color = "black",
se = FALSE
) +
# Add title and axis labels.
labs(
title = "Expected Gene Count Compared with FPKM",
subtitle = "Each point represents one expressed gene",
x = "Expected Count on Log10 Scale",
y = "FPKM on Log10 Scale",
color = "Gene Length"
)
# Display the completed graph.
CountvFPKM
## `geom_smooth()` using formula = 'y ~ x'
Figure 2 shows the relationship between expected count and FPKM. As expected count increases, FPKM generally increases as well. Tthe points do not form a perfect line as FPKM accounts for gene length and the total number of fragments in the samples. The colors show the lengths of the genes and help show why genes with similar expected counts can have different FPKM values.
The final figure uses FPKM to identify the 15 genes with the highest normalized expression in this brain sample.
# Select genes with the highest FPKM --------------------
# Arrange genes from highest to lowest FPKM and keep the first 15 genes.
TopGenes <- EncodeData %>%
arrange(desc(FPKM)) %>%
slice_head(n = 15)
# Plot the highest FPKM values ---------------------------
# Create a bar graph of the most highly expressed genes.
TopFPKM <- ggplot(
TopGenes,
aes(
x = reorder(gene_id, FPKM),
y = FPKM,
fill = FPKM
)
) +
# Create one bar for each gene, turn the graph so the gene IDs are easier to read and use a log scale because the FPKM values have a large range.
geom_col() +
coord_flip() +
scale_y_log10() +
# Color the bars according to their FPKM values.
scale_fill_gradient(
low = "skyblue",
high = "navy"
) +
# Add title and axis labels.
labs(
title = "Genes with Highest FPKM Values",
x = "Ensembl Gene ID",
y = "FPKM on Log10 Scale",
fill = "FPKM"
)
# Display the completed graph.
BlueGenes <- TopFPKM
BlueGenes
Figure 3 shows that a small number of genes have much higher normalized expression than other genes. These genes produced some of the most abundant RNA transcripts in this brain sample. Looking up their Ensembl gene IDs could help determine whether they are involved in brain function, energy production, protein synthesis, or processes linked to Alzheimer’s disease.