In this case study, we perform differential peak calling on ChIP-seq data for a trnscription factor, CREB-binding protein (CBP), from Kasper et al. (2014), used in Lun et al. (2015) to demonstrate the usage of csaw. As described in a separate case study analyzing ChIP-seq data for H3K4Me3, csaw is a package for differential peak calling based on counting reads in sliding windows across the genome. Code and steps for initial processing and analysis of this data with csaw are taken directly from Lun et al. (2015).
library(dplyr)
library(ggplot2)
library(SummarizedBenchmark)
library(BiocParallel)
library(rtracklayer)
library(Rsamtools)
library(Rsubread)
library(csaw)
library(edgeR)
library(GenomicAlignments)
## load helper functions
for (f in list.files("../R", "\\.(r|R)$", full.names = TRUE)) {
source(f)
}
## project data/results folders
datdir <- "data"
resdir <- "results"
sbdir <- "../../results/ChIPseq"
dir.create(datdir, showWarnings = FALSE)
dir.create(resdir, showWarnings = FALSE)
dir.create(sbdir, showWarnings = FALSE)
## intermediary files we create below
count_file <- file.path(resdir, "cbp-csaw-counts.rds")
filtered_file <- file.path(resdir, "cbp-csaw-counts-filtered.rds")
normfacs_file <- file.path(resdir, "cbp-csaw-counts-normfacs.rds")
result_file <- file.path(resdir, "cbp-csaw-results.rds")
bench_file <- file.path(sbdir, "cbp-csaw-benchmark.rds")
bench_file_cov <- file.path(sbdir, "cbp-csaw-cov-benchmark.rds")
bench_file_uninf <- file.path(sbdir, "cbp-csaw-uninf-benchmark.rds")
## set up parallel backend
cores <- as.numeric(Sys.getenv("SLURM_NTASKS"))
multicoreParam <- MulticoreParam(workers = cores)
We download the fastq files directly from the European Nucleotide Archive (ENA).
fqurls <- c("007/SRR1145787/SRR1145787.fastq.gz",
"008/SRR1145788/SRR1145788.fastq.gz",
"009/SRR1145789/SRR1145789.fastq.gz",
"000/SRR1145790/SRR1145790.fastq.gz")
fqurls <- paste0("ftp://ftp.sra.ebi.ac.uk/vol1/fastq/SRR114/", fqurls)
for (i_fq in fqurls) {
out_fq <- file.path(datdir, basename(i_fq))
if (!file.exists(out_fq)) {
download.file(i_fq, destfile = out_fq)
}
}
We download the mm10 reference genome used for ENCODE3 and build the index for alignment if not already available.
ref_url <- paste0("https://www.encodeproject.org/files/",
"mm10_no_alt_analysis_set_ENCODE/@@download/",
"mm10_no_alt_analysis_set_ENCODE.fasta.gz")
ref_fastagz <- file.path(datdir, basename(ref_url))
ref_fasta <- gsub("\\.gz$", "", ref_fastagz)
if (!file.exists(file.path(datdir, "mm_ref_index.00.b.tab"))) {
if (!file.exists(ref_fasta)) {
download.file(ref_url, destfile = ref_fastagz)
system(paste("gunzip", ref_fastagz))
}
buildindex(basename = file.path(datdir, "mm_ref_index"),
reference = ref_fasta)
}
Sample metadata is stored in a data.frame.
fqfiles <- file.path(datdir, basename(fqurls))
meta <- data.frame(genotype = factor(c("wt", "wt", "ko", "ko")),
fqurl = fqurls, fqfile = fqfiles,
bamfile = gsub("\\.fastq\\.gz", ".bam", fqfiles),
sortedfile = gsub("\\.fastq\\.gz", ".sorted.bam", fqfiles),
stringsAsFactors = FALSE)
We also download blacklisted regions for mm10 from ENCODE (https://www.encodeproject.org/annotations/ENCSR636HFF/).
blacklist_url <- "https://www.encodeproject.org/files/ENCFF547MET/@@download/ENCFF547MET.bed.gz"
if (!file.exists(file.path(datdir, basename(blacklist_url)))) {
download.file(blacklist_url, destfile = file.path(datdir, basename(blacklist_url)))
}
bl <- import(file.path(datdir, basename(blacklist_url)))
Standard set of parameters are defined for the analysis. Only the canonical set of chromosomes are used in the analysis.
std_chr <- paste0("chr", c(1:19, "X", "Y"))
param <- readParam(minq = 20, discard = bl, restrict = std_chr)
We count reads within sliding windows across the genome.
if (file.exists(count_file)) {
win_cnts <- readRDS(count_file)
} else {
unaligned <- !file.exists(meta$bamfile)
if (any(unaligned)) {
align(index = file.path(datdir, "mm_ref_index"),
readfile1 = meta$fqfile[unaligned],
type = 1, phredOffset = 64,
input_format = "FASTQ",
output_file = meta$bamfile[unaligned])
}
## sort bam files
for (i in 1:nrow(meta)) {
if (!file.exists(meta$sortedfile[i])) {
sortBam(meta$bamfile[i], gsub("\\.bam$", "", meta$sortedfile[i]))
}
}
## mark duplicates w/ picard and index bam files
if (any(!file.exists(paste0(meta$sortedfile, ".bai")))) {
temp_bam <- file.path(datdir, "temp_dups_mm.bam")
temp_file <- file.path(datdir, "temp_mets_mm.txt")
temp_dir <- file.path(datdir, "temp_dups_mm")
dir.create(temp_dir)
for (i_bam in meta$sortedfile) {
code <- paste0("java -jar ${PICARD_TOOLS_HOME}/picard.jar ",
"MarkDuplicates I=%s O=%s M=%s ",
"TMP_DIR=%s AS=true REMOVE_DUPLICATES=false ",
"VALIDATION_STRINGENCY=SILENT")
code <- sprintf(code, i_bam, temp_bam, temp_file, temp_dir)
code <- system(code)
stopifnot(code == 0L)
file.rename(temp_bam, i_bam)
}
unlink(temp_file)
unlink(temp_dir, recursive = TRUE)
indexBam(meta$sortedfile)
}
## use correlateReads to determine fragment length (remove dups)
x <- correlateReads(meta$sortedfile, param = reform(param, dedup = TRUE))
frag_len <- which.max(x) - 1
## count reads in sliding windows (keep dups)
win_cnts <- windowCounts(meta$sortedfile, param = param, width = 10, ext = frag_len)
## save unfiltered counts
saveRDS(win_cnts, file = count_file)
}
We can apply prefiltering on windows with low abundance as described in Lun and Smyth (2016).
if (file.exists(filtered_file)) {
filtered_cnts <- readRDS(filtered_file)
normfacs <- readRDS(normfacs_file)
} else {
## filter windows by abundance
bins <- windowCounts(meta$sortedfile, bin = TRUE, width = 10000, param = param)
filter_stat <- filterWindows(win_cnts, bins, type = "global")
keep <- filter_stat$filter > log2(3)
filtered_cnts <- win_cnts[keep, ]
## calculate normalizing offsets based on larger windows
normfacs <- normOffsets(bins, se.out = FALSE)
## save filtered counts, normalizing factors
saveRDS(filtered_cnts, file = filtered_file)
saveRDS(normfacs, file = normfacs_file)
}
We use edgeR to test for differential binding with the filtered data.
if (file.exists(result_file)) {
res_ranges <- readRDS(result_file)
} else {
## create model diesign
design <- model.matrix(~ 0 + genotype, data = meta)
colnames(design) <- levels(meta$genotype)
## run quasi-likelihood F-test
y <- asDGEList(filtered_cnts, norm.factors = normfacs)
y <- estimateDisp(y, design)
fit <- glmQLFit(y, design, robust = TRUE)
res <- glmQLFTest(fit, contrast = makeContrasts(wt-ko, levels = design))
## merge p-values across regions
merged <- mergeWindows(rowRanges(filtered_cnts), tol = 100, max.width = 5000)
tab_comb <- combineTests(merged$id, res$table)
tab_best <- getBestTest(merged$id, res$table)
## save results
res_ranges <- merged$region
elementMetadata(res_ranges) <-
data.frame(tab_comb,
best_pos = mid(ranges(rowRanges(filtered_cnts[tab_best$best]))),
best_logFC = tab_best$logFC)
## get overall mean counts for each window
merged_cnts <- summarizeOverlaps(res_ranges, BamFileList(meta$sortedfile))
res_ranges$meancnt <- rowMeans(assays(merged_cnts)$counts)
saveRDS(res_ranges, file = result_file)
}
## covert to df for downstream analysis
res_df <- as.data.frame(res_ranges)
## add random covariate
set.seed(11245)
res_df$uninf_covar = rnorm(nrow(res_df))
rank_scatter(res_df, pvalue = "PValue", covariate = "nWindows") +
ggtitle("Number of windows as independent covariate") +
xlab("Number of Windows")
strat_hist(res_df, pvalue = "PValue", covariate = "nWindows", maxy = 15)
##
## Attaching package: 'cowplot'
## The following object is masked from 'package:ggplot2':
##
## ggsave
##
## Attaching package: 'magrittr'
## The following object is masked from 'package:rlang':
##
## set_names
## The following object is masked from 'package:tidyr':
##
## extract
rank_scatter(res_df, pvalue = "PValue", covariate = "width") +
ggtitle("Region width as independent covariate") +
xlab("Region Width")
strat_hist(res_df, pvalue = "PValue", covariate = "width", maxy = 15)
rank_scatter(res_df, pvalue = "PValue", covariate = "meancnt") +
ggtitle("Mean coverage as independent covariate") +
xlab("Mean coverage")
strat_hist(res_df, pvalue = "PValue", covariate = "meancnt", maxy = 15)
rank_scatter(res_df, pvalue = "PValue", covariate = "uninf_covar") +
ggtitle("Random independent covariate") +
xlab("Region Width")
strat_hist(res_df, pvalue = "PValue", covariate = "uninf_covar", maxy = 15)
We use the common BenchDesign
with the set of multiple testing correction methods already included. We investigate both the width of the regions and their mean coverage as the independent covariate. We won’t assess the number of windows, since this is very tightly correlated with the width of the region.
cor(res_df[,c("width", "nWindows", "meancnt")])
## width nWindows meancnt
## width 1.0000000 0.9982230 0.1023338
## nWindows 0.9982230 1.0000000 0.1029068
## meancnt 0.1023338 0.1029068 1.0000000
First, we’ll use the region width covariate.
if (!file.exists(bench_file)) {
res_df$ind_covariate <- res_df$width
res_df$pval <- res_df$PValue
bd <- initializeBenchDesign()
sb <- buildBench(bd, data = res_df, ftCols = "ind_covariate")
saveRDS(sb, file = bench_file)
} else {
sb <- readRDS(bench_file)
}
Next, we’ll use the mean coverage covariate.
if (!file.exists(bench_file_cov)) {
res_df$ind_covariate <- res_df$meancnt
res_df$pval <- res_df$PValue
bd <- initializeBenchDesign()
sbC <- buildBench(bd, data = res_df, ftCols = "ind_covariate")
saveRDS(sbC, file = bench_file_cov)
} else {
sbC <- readRDS(bench_file_cov)
}
We’ll also compare to the random covariate.
if (!file.exists(bench_file_uninf)) {
res_df$ind_covariate <- res_df$uninf_covar
res_df$pval <- res_df$PValue
bd <- initializeBenchDesign()
sbU <- buildBench(bd, data = res_df, ftCols = "ind_covariate")
saveRDS(sbU, file = bench_file_uninf)
} else {
sbU <- readRDS(bench_file_uninf)
}
Next, we’ll add the default performance metric for q-value assays. First, we have to rename the assay to ‘qvalue’.
assayNames(sb) <- "qvalue"
sb <- addDefaultMetrics(sb)
Now, we’ll plot the results.
rejections_scatter(sb, supplementary = FALSE)
rejection_scatter_bins(sb, covariate = "ind_covariate",
bins = 4, supplementary = FALSE)
plotFDRMethodsOverlap(sb, alpha = 0.05, nsets = ncol(sb),
order.by = "freq", decreasing = TRUE,
supplementary = FALSE)
covariateLinePlot(sb, alpha = 0.05, covname = "ind_covariate")
## Warning: Removed 25 rows containing missing values (geom_path).
We’ll do the same for the mean coverage covariate.
assayNames(sbC) <- "qvalue"
sbC <- addDefaultMetrics(sbC)
Now, we’ll plot the results.
rejections_scatter(sbC, supplementary = FALSE)
rejection_scatter_bins(sbC, covariate = "ind_covariate",
bins = 4, supplementary = FALSE)
plotFDRMethodsOverlap(sbC, alpha = 0.05, nsets = ncol(sbC),
order.by = "freq", decreasing = TRUE,
supplementary = FALSE)
covariateLinePlot(sbC, alpha = 0.05, covname = "ind_covariate")
## Warning: Removed 25 rows containing missing values (geom_path).
We’ll do the same for the random (uninformative covariate).
assayNames(sbU) <- "qvalue"
sbU <- addDefaultMetrics(sbU)
Now, we’ll plot the results.
rejections_scatter(sbU, supplementary = FALSE)
rejection_scatter_bins(sbU, covariate = "ind_covariate",
bins = 4, supplementary = FALSE)
plotFDRMethodsOverlap(sbU, alpha = 0.05, nsets = ncol(sbU),
order.by = "freq", decreasing = TRUE,
supplementary = FALSE)
covariateLinePlot(sbU, alpha = 0.05, covname = "ind_covariate")
## Warning: Removed 25 rows containing missing values (geom_path).
Here we compare the method ranks for the two covariates at alpha = 0.10.
plotMethodRanks(c(bench_file, bench_file_cov, bench_file_uninf),
colLabels = c("Region width", "Mean Coverage", "Random"),
alpha = 0.10, xlab = "Covariate",
excludeMethods = NULL)
sessionInfo()
## R version 3.5.0 (2018-04-23)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: CentOS Linux 7 (Core)
##
## Matrix products: default
## BLAS: /usr/lib64/libblas.so.3.4.2
## LAPACK: /usr/lib64/liblapack.so.3.4.2
##
## locale:
## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
## [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
## [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
## [9] LC_ADDRESS=C LC_TELEPHONE=C
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
##
## attached base packages:
## [1] parallel stats4 stats graphics grDevices utils datasets
## [8] methods base
##
## other attached packages:
## [1] bindrcpp_0.2.2 magrittr_1.5
## [3] cowplot_0.9.2 hexbin_1.27.2
## [5] GenomicAlignments_1.16.0 edgeR_3.22.3
## [7] limma_3.36.2 csaw_1.14.1
## [9] Rsubread_1.30.3 Rsamtools_1.32.0
## [11] Biostrings_2.48.0 XVector_0.20.0
## [13] rtracklayer_1.40.3 SummarizedBenchmark_0.99.1
## [15] mclust_5.4 stringr_1.3.1
## [17] rlang_0.2.1 UpSetR_1.3.3
## [19] SummarizedExperiment_1.10.1 DelayedArray_0.6.1
## [21] BiocParallel_1.14.2 matrixStats_0.53.1
## [23] Biobase_2.40.0 GenomicRanges_1.32.3
## [25] GenomeInfoDb_1.16.0 IRanges_2.14.10
## [27] S4Vectors_0.18.3 BiocGenerics_0.26.0
## [29] tidyr_0.8.1 ggplot2_3.0.0
## [31] dplyr_0.7.5
##
## loaded via a namespace (and not attached):
## [1] httr_1.3.1 bit64_0.9-7 assertthat_0.2.0
## [4] blob_1.1.1 GenomeInfoDbData_1.1.0 yaml_2.1.19
## [7] progress_1.1.2 pillar_1.2.3 RSQLite_2.1.1
## [10] backports_1.1.2 lattice_0.20-35 glue_1.2.0
## [13] digest_0.6.15 RColorBrewer_1.1-2 colorspace_1.3-2
## [16] htmltools_0.3.6 Matrix_1.2-14 plyr_1.8.4
## [19] XML_3.98-1.11 pkgconfig_2.0.1 biomaRt_2.36.1
## [22] zlibbioc_1.26.0 purrr_0.2.5 scales_0.5.0
## [25] tibble_1.4.2 withr_2.1.2 GenomicFeatures_1.32.0
## [28] lazyeval_0.2.1 memoise_1.1.0 evaluate_0.10.1
## [31] prettyunits_1.0.2 tools_3.5.0 Rhtslib_1.12.1
## [34] munsell_0.4.3 locfit_1.5-9.1 AnnotationDbi_1.42.1
## [37] compiler_3.5.0 grid_3.5.0 RCurl_1.95-4.10
## [40] labeling_0.3 bitops_1.0-6 rmarkdown_1.10
## [43] gtable_0.2.0 DBI_1.0.0 R6_2.2.2
## [46] gridExtra_2.3 knitr_1.20 bit_1.1-14
## [49] bindr_0.1.1 rprojroot_1.3-2 stringi_1.2.2
## [52] Rcpp_0.12.17 tidyselect_0.2.4