Here, we perform a window-based differential binding (DB) analysis to identify regions of differential H3K9ac enrichment between pro-B and mature B cells (Revilla-I-Domingo et al. 2012). H3K9ac is associated with active promoters and tends to exhibit relatively narrow regions of enrichment relative to other marks such as H3K27me3. We download the BAM files using the relevant function from the chipseqDBData package1 BAM files are cached upon the first call to this function, so subsequent calls do not need to re-download the files.. The experimental design contains two biological replicates for each of the two cell types.
library(chipseqDBData)
acdata <- H3K9acData()
acdata
## DataFrame with 4 rows and 3 columns
## Name Description Path
## <character> <character> <List>
## 1 h3k9ac-proB-8113 pro-B H3K9ac (8113) <BamFile>
## 2 h3k9ac-proB-8108 pro-B H3K9ac (8108) <BamFile>
## 3 h3k9ac-matureB-8059 mature B H3K9ac (8059) <BamFile>
## 4 h3k9ac-matureB-8086 mature B H3K9ac (8086) <BamFile>
We use methods from the Rsamtools package to compute some mapping statistics for each BAM file. Ideally, the proportion of mapped reads should be high (70-80% or higher), while the proportion of marked reads should be low (generally below 20%).
library(Rsamtools)
diagnostics <- list()
for (b in seq_along(acdata$Path)) {
bam <- acdata$Path[[b]]
total <- countBam(bam)$records
mapped <- countBam(bam, param=ScanBamParam(
flag=scanBamFlag(isUnmapped=FALSE)))$records
marked <- countBam(bam, param=ScanBamParam(
flag=scanBamFlag(isUnmapped=FALSE, isDuplicate=TRUE)))$records
diagnostics[[b]] <- c(Total=total, Mapped=mapped, Marked=marked)
}
diag.stats <- data.frame(do.call(rbind, diagnostics))
rownames(diag.stats) <- acdata$Name
diag.stats$Prop.mapped <- diag.stats$Mapped/diag.stats$Total*100
diag.stats$Prop.marked <- diag.stats$Marked/diag.stats$Mapped*100
diag.stats
## Total Mapped Marked Prop.mapped Prop.marked
## h3k9ac-proB-8113 10724526 8832006 434884 82.35335 4.923955
## h3k9ac-proB-8108 10413135 7793913 252271 74.84694 3.236770
## h3k9ac-matureB-8059 16675372 4670364 396785 28.00756 8.495805
## h3k9ac-matureB-8086 6347683 4551692 141583 71.70635 3.110558
Note that all csaw functions that read from a BAM file require BAM indices with .bai
suffixes.
In this case, index files have already been downloaded by H3K9acData()
,
but users supplying their own files should take care to ensure that BAM indices are available with appropriate names.
A number of genomic regions contain high artifactual signal in ChIP-seq experiments. These often correspond to genomic features like telomeres or microsatellite repeats. For example, multiple tandem repeats in the real genome are reported as a single unit in the genome build. Alignment of all (non-specifically immunoprecipitated) reads from the former will result in artificially high coverage of the latter. Moreover, differences in repeat copy numbers between conditions can lead to detection of spurious DB.
As such, these problematic regions must be removed prior to further analysis. This is done with an annotated blacklist for the mm10 build of the mouse genome, constructed by identifying consistently problematic regions from ENCODE datasets (ENCODE Project Consortium 2012). We download this BED file and save it into a local cache with the BiocFileCache package. This allows it to be used again in later workflows without being re-downloaded.
library(BiocFileCache)
bfc <- BiocFileCache("local", ask=FALSE)
black.path <- bfcrpath(bfc, file.path("https://www.encodeproject.org",
"files/ENCFF547MET/@@download/ENCFF547MET.bed.gz"))
Genomic intervals in the blacklist are loaded using the import()
method from the rtracklayer package.
All reads mapped within the blacklisted intervals will be ignored during processing in csaw by specifying the discard=
parameter (see below).
library(rtracklayer)
blacklist <- import(black.path)
blacklist
## GRanges object with 164 ranges and 0 metadata columns:
## seqnames ranges strand
## <Rle> <IRanges> <Rle>
## [1] chr10 3110061-3110270 *
## [2] chr10 22142531-22142880 *
## [3] chr10 22142831-22143070 *
## [4] chr10 58223871-58224100 *
## [5] chr10 58225261-58225500 *
## ... ... ... ...
## [160] chr9 3038051-3038300 *
## [161] chr9 24541941-24542200 *
## [162] chr9 35305121-35305620 *
## [163] chr9 110281191-110281400 *
## [164] chr9 123872951-123873160 *
## -------
## seqinfo: 19 sequences from an unspecified genome; no seqlengths
Any user-defined set of regions can be used as a blacklist in this analysis.
In the csaw package, the readParam
object determines which reads are extracted from the BAM files.
The intention is to set this up once and to re-use it in all relevant functions.
For this analysis, reads are ignored if they map to blacklist regions or do not map to the standard set of mouse nuclear chromosomes2 In this case, we are not interested in the mitochondrial genome, as these should not be bound by histones anyway..
library(csaw)
standard.chr <- paste0("chr", c(1:19, "X", "Y"))
param <- readParam(minq=20, discard=blacklist, restrict=standard.chr)
Reads are also ignored if they have a mapping quality (MAPQ) score below 203 This is more stringent than usual, to account for the fact that the short reads ued here (32-36 bp) are more difficult to accurately align.. This avoids spurious results due to weak or non-unique alignments that should be assigned low MAPQ scores by the aligner. Note that the range of MAPQ scores will vary between aligners, so some inspection of the BAM files is necessary to choose an appropriate value.
Strand bimodality is often observed in ChIP-seq experiments involving narrow binding events like H3K9ac marking. This refers to the presence of distinct subpeaks on each strand and is quantified with cross-correlation plots (Kharchenko, Tolstorukov, and Park 2008). A strong peak in the cross-correlations should be observed if immunoprecipitation was successful. The delay distance at the peak corresponds to the distance between forward- and reverse-strand subpeaks. This is identified from Figure 1 and is used as the average fragment length for this analysis.
x <- correlateReads(acdata$Path, param=reform(param, dedup=TRUE))
frag.len <- maximizeCcf(x)
frag.len
## [1] 154
plot(1:length(x)-1, x, xlab="Delay (bp)", ylab="CCF", type="l")
abline(v=frag.len, col="red")
text(x=frag.len, y=min(x), paste(frag.len, "bp"), pos=4, col="red")
Only unmarked reads (i.e., not potential PCR duplicates) are used to calculate the cross-correlations. This reduces noise from variable PCR amplification and decreases the size of the “phantom” peak at the read length (Landt et al. 2012). However, general removal of marked reads is risky as it caps the signal in high-coverage regions of the genome. This can result in loss of power to detect DB, or introduction of spurious DB when the same cap is applied to libraries of different sizes. Thus, the marking status of each read will be ignored in the rest of the analysis, i.e., no duplicates will be removed in downstream steps.
csaw uses a sliding window strategy to quantify protein binding intensity across the genome. Each read is directionally extended to the average fragment length (Figure 2) to represent the DNA fragment from which that read was sequenced. Any position within the inferred fragment is a potential contact site for the protein of interest. To quantify binding in a genomic window, the number of these fragments overlapping the window is counted. The window is then moved to its next position on the genome and counting is repeated4 Each read is usually counted into multiple windows, which will introduce correlations between adjacent windows but will not otherwise affect the analysis.. This is done for all samples such that a count is obtained for each window in each sample.
The windowCounts()
function produces a RangedSummarizedExperiment
object containing a matrix of such counts.
Each row corresponds to a window; each column represents a BAM file corresponding to a single sample5 Counting can be parallelized across files using the BPPARAM=
argument.;
and each entry of the matrix represents the number of fragments overlapping a particular window in a particular sample.
win.data <- windowCounts(acdata$Path, param=param, width=150, ext=frag.len)
win.data
## class: RangedSummarizedExperiment
## dim: 1671254 4
## metadata(6): spacing width ... param final.ext
## assays(1): counts
## rownames: NULL
## rowData names(0):
## colnames: NULL
## colData names(4): bam.files totals ext rlen
To analyze H3K9ac data, a window size of 150 bp is used here. This corresponds roughly to the length of the DNA in a nucleosome (Humburg et al. 2011), which is the smallest relevant unit for studying histone mark enrichment. The spacing between windows is set to the default of 50 bp, i.e., the start positions for adjacent windows are 50 bp apart. Smaller spacings can be used to improve spatial resolution, but will increase memory usage and runtime by increasing the number of windows required to cover the genome. This is unnecessary as increased resolution confers little practical benefit for this data set – counts for very closely spaced windows will be practically identical. Finally, windows with very low counts (by default, less than a sum of 10 across all samples) are removed to reduce memory usage. This represents a preliminary filter to remove uninteresting windows corresponding to likely background regions.
As previously mentioned, low-abundance windows contain no binding sites and need to be filtered out. This improves power by removing irrelevant tests prior to the multiple testing correction; avoids problems with discreteness in downstream statistical methods; and reduces computational work for further analyses. Here, filtering is performed using the average abundance of each window (McCarthy, Chen, and Smyth 2012), which is defined as the average log-count per million for that window. This performs well as an independent filter statistic for NB-distributed count data (Lun and Smyth 2014).
The filter threshold is defined based on the assumption that most regions in the genome are not marked by H3K9ac. Reads are counted into large bins and the median coverage across those bins is used as an estimate of the background abundance6 Large bins are necessary to obtain a precise estimate of background coverage, which would otherwise be too low in individual windows.. This estimate is then compared to the average abundances of the windows, after rescaling to account for differences in the window and bin sizes. A window is only retained if its coverage is 3-fold higher than that of the background regions, i.e., the abundance of the window is greater than the background abundance estimate by log2(3) or more. This removes a large number of windows that are weakly or not marked and are likely to be irrelevant.
bins <- windowCounts(acdata$Path, bin=TRUE, width=2000, param=param)
filter.stat <- filterWindowsGlobal(win.data, bins)
min.fc <- 3
keep <- filter.stat$filter > log2(min.fc)
summary(keep)
## Mode FALSE TRUE
## logical 982167 689087
The effect of the fold-change threshold is examined visually in Figure 3. The chosen threshold is greater than the abundances of most bins in the genome – presumably, those that contain background regions. This suggests that the filter will remove most windows lying within background regions.
hist(filter.stat$back.abundances, main="", breaks=50,
xlab="Background abundance (log2-CPM)")
threshold <- filter.stat$abundances[1] - filter.stat$filter[1] + log2(min.fc)
abline(v=threshold, col="red")
The filtering itself is done by simply subsetting the RangedSummarizedExperiment
object.
filtered.data <- win.data[keep,]
Normalization is required to eliminate confounding sample-specific biases prior to any comparisons between samples. Here, a trended bias is present between samples in Figure 4. This refers to a systematic fold-difference in per-window coverage between samples that changes according to the average abundance of the window.
win.ab <- scaledAverage(filtered.data)
adjc <- calculateCPM(filtered.data, use.offsets=FALSE)
logfc <- adjc[,4] - adjc[,1]
smoothScatter(win.ab, logfc, ylim=c(-6, 6), xlim=c(0, 5),
xlab="Average abundance", ylab="Log-fold change")
lfit <- smooth.spline(logfc~win.ab, df=5)
o <- order(win.ab)
lines(win.ab[o], fitted(lfit)[o], col="red", lty=2)
Trended biases cannot be removed by scaling methods like TMM normalization (Robinson and Oshlack 2010), as the amount of scaling required varies with the abundance of the window. Rather, non-linear normalization methods must be used. csaw implements a version of the fast loess method (Ballman et al. 2004) that has been modified to handle count data (Lun and Smyth 2015). This produces a matrix of offsets that can be used during model fitting.
filtered.data <- normOffsets(filtered.data)
offsets <- assay(filtered.data, "offset")
head(offsets)
## [,1] [,2] [,3] [,4]
## [1,] 0.5372350 0.3457091 -0.4860457 -0.3968984
## [2,] 0.5082160 0.3238545 -0.4601253 -0.3719453
## [3,] 0.5021301 0.3192584 -0.4546073 -0.3667813
## [4,] 0.6274643 0.4119203 -0.5571155 -0.4822690
## [5,] 0.7049858 0.4638779 -0.6039740 -0.5648898
## [6,] 0.7260917 0.4778642 -0.6174239 -0.5865321
The effect of non-linear normalization is visualized with another mean-difference plot. Once the offsets are applied to adjust the log-fold changes, the trend is eliminated from the plot (Figure 5). The cloud of points is also centred at a log-fold change of zero. This indicates that normalization was successful in removing the differences between samples.
norm.adjc <- calculateCPM(filtered.data, use.offsets=TRUE)
norm.fc <- norm.adjc[,4]-norm.adjc[,1]
smoothScatter(win.ab, norm.fc, ylim=c(-6, 6), xlim=c(0, 5),
xlab="Average abundance", ylab="Log-fold change")
lfit <- smooth.spline(norm.fc~win.ab, df=5)
lines(win.ab[o], fitted(lfit)[o], col="red", lty=2)
The implicit assumption of non-linear methods is that most windows at each abundance are not DB. Any systematic difference between samples is attributed to bias and is removed. The assumption of a non-DB majority is reasonable for this data set, given that the cell types being compared are quite closely related. However, it is not appropriate in cases where large-scale DB is expected, as removal of the difference would result in loss of genuine DB. An alternative normalization strategy for these situations will be described later in the CBP analysis.
Counts are modelled using negative binomial generalized linear models (NB GLMs) in the edgeR package (McCarthy, Chen, and Smyth 2012; Robinson, McCarthy, and Smyth 2010). The NB distribution is useful as it can handle low, discrete counts for each window. The NB dispersion parameter allows modelling of biological variability between replicate samples. GLMs can also accommodate complex experimental designs, though a simple design is sufficient for this study.
celltype <- acdata$Description
celltype[grep("pro", celltype)] <- "proB"
celltype[grep("mature", celltype)] <- "matureB"
celltype <- factor(celltype)
design <- model.matrix(~0+celltype)
colnames(design) <- levels(celltype)
design
## matureB proB
## 1 0 1
## 2 0 1
## 3 1 0
## 4 1 0
## attr(,"assign")
## [1] 1 1
## attr(,"contrasts")
## attr(,"contrasts")$celltype
## [1] "contr.treatment"
As a general rule, the experimental design should contain at least two replicates in each of the biological conditions. This ensures that the results for each condition are replicable and are not the result of technical artifacts such as PCR duplicates. Obviously, more replicates will provide more power to detect DB accurately and reliability, albeit at the cost of time and experimental resources.
The RangedSummarizedExperiment
object is coerced into a DGEList
object (plus offsets) for use in edgeR.
Estimation of the NB dispersion is performed using the estimateDisp
function.
Specifically, a NB dispersion trend is fitted to all windows against the average abundance.
This means that empirical mean-dispersion trends can be flexibly modelled.
library(edgeR)
y <- asDGEList(filtered.data)
str(y)
## Formal class 'DGEList' [package "edgeR"] with 1 slot
## ..@ .Data:List of 3
## .. ..$ : int [1:689087, 1:4] 6 6 7 12 15 17 24 22 25 24 ...
## .. .. ..- attr(*, "dimnames")=List of 2
## .. .. .. ..$ : chr [1:689087] "1" "2" "3" "4" ...
## .. .. .. ..$ : chr [1:4] "Sample1" "Sample2" "Sample3" "Sample4"
## .. ..$ :'data.frame': 4 obs. of 3 variables:
## .. .. ..$ group : Factor w/ 1 level "1": 1 1 1 1
## .. .. ..$ lib.size : int [1:4] 8392971 7269175 3792141 4241789
## .. .. ..$ norm.factors: num [1:4] 1 1 1 1
## .. ..$ : num [1:689087, 1:4] 16.1 16 16 16.2 16.2 ...
y <- estimateDisp(y, design)
summary(y$trended.dispersion)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.04096 0.05252 0.06165 0.06075 0.07209 0.07395
The NB dispersion trend is visualized in Figure 6 as the biological coefficient of variation (BCV), i.e., the square root of the NB dispersion. Note that only the trended dispersion will be used in the downstream steps – the common and tagwise values are only shown for diagnostic purposes. Specifically, the common BCV provides an overall measure of the variability in the data set, averaged across all windows. Data sets with common BCVs ranging from 10 to 20% are considered to have low variability, i.e., counts are highly reproducible. The tagwise BCVs should also be dispersed above and below the fitted trend, indicating that the fit was successful.
plotBCV(y)
For most sequencing count data, we expect to see a decreasing trend that plateaus with increasing average abundance. This reflects the greater reliability of large counts, where the effects of stochasticity and technical artifacts (e.g., mapping errors, PCR duplicates) are averaged out. We observe no clear trend in Figure 6 as the windows have already been filtered to the plateau. This is still a satisfactory result as it indicates that the retained windows have low variability and more power to detect DB.
Additional modelling is provided with the QL methods in edgeR (Lund et al. 2012). This introduces a QL dispersion parameter for each window, which captures variability in the NB dispersion around the fitted trend for each window. Thus, the QL dispersion can model window-specific variability, whereas the NB dispersion trend is averaged across many windows. However, with limited replicates, there is not enough information for each window to stably estimate the QL dispersion. This is overcome by sharing information between windows with empirical Bayes (EB) shrinkage. The instability of the QL dispersion estimates is reduced by squeezing the estimates towards an abundance-dependent trend (Figure 7).
fit <- glmQLFit(y, design, robust=TRUE)
plotQLDisp(fit)
The extent of shrinkage is determined by the prior degrees of freedom (d.f.). Large prior d.f. indicates that the dispersions were similar across windows, such that strong shrinkage to the trend could be performed to increase stability and power. Small prior d.f. indicates that the dispersions were more variable. In such cases, less squeezing is performed as strong shrinkage would be inappropriate.
summary(fit$df.prior)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.2238 15.4949 15.4949 15.2544 15.4949 15.4949
Also note the use of robust=TRUE
in the glmQLFit()
call, which reduces the sensitivity of the EB procedures to outlier variances.
This is particularly noticeable in Figure 7 with highly variable windows that (correctly) do not get squeezed towards the trend.
Multi-dimensional scaling (MDS) plots are used to examine the similarities between samples. The distance between a pair of samples on this plot represents the overall log-fold change between those samples. Ideally, replicates should cluster together while samples from different conditions should be separate. While the mature B replicates are less tightly grouped, samples still separate by cell type in Figure 8. This suggests that our downstream analysis will be able to detect significant differences in enrichment between cell types.
plotMDS(norm.adjc, labels=celltype,
col=c("red", "blue")[as.integer(celltype)])
Each window is tested for significant differences between cell types using the QL F-test (Lund et al. 2012). This is superior to the likelihood ratio test that is typically used for GLMs, as the QL F-test accounts for the uncertainty in dispersion estimation. One \(p\)-value is produced for each window, representing the evidence against the null hypothesis (i.e., that no DB is present in the window). For this analysis, the comparison is parametrized such that the reported log-fold change for each window represents that of the coverage in pro-B cells over their mature B counterparts.
contrast <- makeContrasts(proB-matureB, levels=design)
res <- glmQLFTest(fit, contrast=contrast)
head(res$table)
## logFC logCPM F PValue
## 1 1.3657940 0.3096885 2.305514 0.14677966
## 2 1.3564260 0.2624458 2.304629 0.14685257
## 3 2.0015383 0.2502879 3.940307 0.06304923
## 4 2.0779767 0.5033489 5.576372 0.03003408
## 5 0.8842093 0.8051419 1.651922 0.21545238
## 6 0.9678112 0.8948537 2.054733 0.16936408
One might attempt to control the FDR by applying the Benjamini-Hochberg (BH) method to the window-level \(p\)-values (Benjamini and Hochberg 1995). However, the features of interest are not windows, but the genomic regions that they represent. Control of the FDR across windows does not guarantee control of the FDR across regions (Lun and Smyth 2014). The latter is arguably more relevant for the final interpretation of the results.
We instead control the region-level FDR by aggregating windows into regions and combining the \(p\)-values.
Here, adjacent windows less than 100 bp apart are aggregated into clusters.
Each cluster represents a genomic region.
Smaller values of tol
allow distinct marking events to kept separate,
while larger values provide a broader perspective, e.g., by considering adjacent co-regulated sites as a single entity.
Chaining effects are mitigated by setting a maximum cluster width of 5 kbp.
merged <- mergeResults(filtered.data, res$table, tol=100,
merge.args=list(max.width=5000))
merged$regions
## GRanges object with 41616 ranges and 0 metadata columns:
## seqnames ranges strand
## <Rle> <IRanges> <Rle>
## [1] chr1 4775451-4775750 *
## [2] chr1 4785001-4786300 *
## [3] chr1 4807251-4807750 *
## [4] chr1 4808001-4808600 *
## [5] chr1 4857051-4858950 *
## ... ... ... ...
## [41612] chrY 73038001-73038400 *
## [41613] chrY 75445801-75446200 *
## [41614] chrY 88935951-88936350 *
## [41615] chrY 90554201-90554400 *
## [41616] chrY 90812801-90813100 *
## -------
## seqinfo: 21 sequences from an unspecified genome
A combined \(p\)-value is computed for each cluster using the method of Simes (1986), based on the \(p\)-values of the constituent windows. This represents the evidence against the global null hypothesis for each cluster, i.e., that no DB exists in any of its windows. Rejection of this global null indicates that the cluster (and the region that it represents) contains DB. Applying the BH method to the combined \(p\)-values allows the region-level FDR to be controlled.
tabcom <- merged$combined
tabcom
## DataFrame with 41616 rows and 8 columns
## num.tests num.up.logFC num.down.logFC PValue FDR direction
## <integer> <integer> <integer> <numeric> <numeric> <character>
## 1 3 0 0 0.1468526 0.2464281 up
## 2 24 0 0 0.0882967 0.1687355 up
## 3 8 0 0 0.5264245 0.6480413 mixed
## 4 10 0 0 0.7296509 0.8298757 mixed
## 5 36 5 0 0.0208882 0.0605414 up
## ... ... ... ... ... ... ...
## 41612 6 0 6 0.00587505 0.0265930 down
## 41613 6 0 6 0.03868017 0.0930955 down
## 41614 6 0 6 0.02082155 0.0604134 down
## 41615 2 0 2 0.03344646 0.0836785 down
## 41616 4 0 4 0.00147494 0.0114325 down
## rep.test rep.logFC
## <integer> <numeric>
## 1 2 1.356426
## 2 15 6.454882
## 3 34 0.433617
## 4 40 -0.272420
## 5 63 6.353706
## ... ... ...
## 41612 689066 -6.96911
## 41613 689075 -5.73312
## 41614 689081 -5.84635
## 41615 689083 -5.00404
## 41616 689087 -3.96416
Each row of the above table contains the statistics for a single cluster,
including the combined p-value before and after the BH correction.
Additional fields include num.tests
, the total number of windows in the cluster;
num.up.logFC
, the number of windows with a DB log-fold change significantly greater than zero;
and num.down.logFC
, the number of windows with a log-fold change significantly below zero.
We determine the total number of DB regions at a FDR of 5% by applying the Benjamini-Hochberg method on the combined \(p\)-values.
is.sig <- tabcom$FDR <= 0.05
summary(is.sig)
## Mode FALSE TRUE
## logical 28515 13101
Determining the direction of DB is more complicated, as clusters may contain windows that are changing in opposite directions.
One approach is to use the direction of DB from the windows that contribute most to the combined \(p\)-value,
as reported in the direction
field for each cluster.
If significance is driven by windows changing in both directions, the direction for the cluster is defined as "mixed"
.
Otherwise, the reported direction is the same as that of the windows, i.e., "up"
or "down"
.
table(tabcom$direction[is.sig])
##
## down mixed up
## 8580 154 4367
Another approach is to use the log-fold change of the most significant window as a proxy for the log-fold change of the cluster.
tabbest <- merged$best
tabbest
## DataFrame with 41616 rows and 8 columns
## num.tests num.up.logFC num.down.logFC PValue FDR direction
## <integer> <integer> <integer> <numeric> <numeric> <character>
## 1 3 0 0 0.1891477 0.335560 up
## 2 24 0 0 0.0882967 0.190583 up
## 3 8 0 0 1.0000000 1.000000 up
## 4 10 0 0 1.0000000 1.000000 down
## 5 36 2 0 0.0464346 0.121536 up
## ... ... ... ... ... ... ...
## 41612 6 0 3 0.01762514 0.0634640 down
## 41613 6 0 0 0.19568010 0.3445628 down
## 41614 6 0 0 0.06141175 0.1463585 down
## 41615 2 0 0 0.06689293 0.1549923 down
## 41616 4 0 4 0.00421597 0.0261244 down
## rep.test rep.logFC
## <integer> <numeric>
## 1 3 2.001538
## 2 15 6.454882
## 3 35 1.178400
## 4 43 -0.908825
## 5 60 6.572738
## ... ... ...
## 41612 689064 -6.96911
## 41613 689070 -5.44350
## 41614 689076 -6.68322
## 41615 689082 -5.00404
## 41616 689086 -4.08131
In the table above, the best
column is the index of the window that is the most significant in each cluster,
while the logFC
field is the log-fold change of that window.
We use this to obtain a summary of the direction of DB across all clusters.
is.sig.pos <- (tabbest$rep.logFC > 0)[is.sig]
summary(is.sig.pos)
## Mode FALSE TRUE
## logical 8664 4437
This approach is generally satisfactory, though it will not capture multiple changes in opposite directions7 Try mixedClusters()
to formally detect clusters that contain significant changes in both directions..
It also tends to overstate the magnitude of the log-fold change in each cluster.
One approach to saving results is to store all statistics in the metadata of a GRanges
object.
This is useful as it keeps the statistics and coordinates together for each cluster,
avoiding problems with synchronization in downstream steps.
We also store the midpoint and log-fold change of the most significant window in each cluster.
The updated GRanges
object is then saved to file as a serialized R object with the saveRDS
function.
out.ranges <- merged$regions
mcols(out.ranges) <- DataFrame(tabcom,
best.pos=mid(ranges(rowRanges(filtered.data[tabbest$rep.test]))),
best.logFC=tabbest$rep.logFC)
saveRDS(file="h3k9ac_results.rds", out.ranges)
For input into other programs like genome browsers, results need to be saved in a more conventional format. Here, coordinates of DB regions are saved in BED format via rtracklayer, using the log-transformed FDR as the score.
simplified <- out.ranges[is.sig]
simplified$score <- -10*log10(simplified$FDR)
export(con="h3k9ac_results.bed", object=simplified)
detailRanges
functioncsaw provides its own annotation function, detailRanges()
.
This identifies all genic features overlapping each region and reports them in a compact string form.
Briefly, features are reported as SYMBOL:STRAND:TYPE
where SYMBOL
represents the gene symbol;
STRAND
reports the strand of the gene; and TYPE
reports the type(s) of overlapped feature,
e.g., E
for exons, P
for promoters, I
for introns8 Introns are only reported if an exon is not overlapped..
library(org.Mm.eg.db)
library(TxDb.Mmusculus.UCSC.mm10.knownGene)
anno <- detailRanges(out.ranges, orgdb=org.Mm.eg.db,
txdb=TxDb.Mmusculus.UCSC.mm10.knownGene)
head(anno$overlap)
## [1] "Mrpl15:-:E" "Mrpl15:-:PE" "Lypla1:+:P"
## [4] "Lypla1:+:PE" "Lypla1:+:I,Tcea1:+:PE" "Rgs20:-:I"
Annotated features that flank the region of interest are also reported.
The description for each feature is formatted as described above but the TYPE
instead represents the distance (in base pairs) between the closest exon of the gene and the region.
By default, only flanking features within 5 kbp of each region are considered.
head(anno$left)
## [1] "Mrpl15:-:935" "Mrpl15:-:896" "" "Lypla1:+:19" ""
## [6] ""
head(anno$right)
## [1] "Mrpl15:-:627" "" "Lypla1:+:38" "" ""
## [6] ""
The annotation for each region is stored in the metadata of the GRanges
object.
The compact string form is useful for human interpretation, as it allows rapid examination of all genic features neighbouring each region.
meta <- mcols(out.ranges)
mcols(out.ranges) <- data.frame(meta, anno)
As its name suggests, the ChIPpeakAnno package is designed to annotate peaks from ChIP-seq experiments (Zhu et al. 2010).
A GRanges
object containing all regions of interest is supplied to the relevant function after removing all previous metadata fields to reduce clutter.
The gene closest to each region is then reported.
Gene coordinates are taken from the NCBI mouse 38 annotation, which is roughly equivalent to the annotation in the mm10 genome build.
library(ChIPpeakAnno)
data(TSS.mouse.GRCm38)
minimal <- out.ranges
elementMetadata(minimal) <- NULL
anno.regions <- annotatePeakInBatch(minimal, AnnotationData=TSS.mouse.GRCm38)
colnames(elementMetadata(anno.regions))
## [1] "peak" "feature"
## [3] "start_position" "end_position"
## [5] "feature_strand" "insideFeature"
## [7] "distancetoFeature" "shortestDistance"
## [9] "fromOverlappingOrNearest"
Alternatively, identification of all overlapping features within, say, 5 kbp can be achieved by setting maxgap=5000
and output="overlapping"
in annotatePeakInBatch
.
This will report each overlapping feature in a separate entry of the returned GRanges
object, i.e., each input region may have multiple output values.
In contrast, detailRanges()
will report all overlapping features for a region as a single string, i.e., each input region has one output value.
Which is preferable depends on the purpose of the annotation – the detailRanges()
output is more convenient for direct annotation of a DB list, while the annotatePeakInBatch()
output contains more information and is more convenient for further manipulation.
Another approach to annotation is to flip the problem around such that DB statistics are reported directly for features of interest like genes. This is more convenient when the DB analysis needs to be integrated with, e.g., differential expression analyses of matched RNA-seq data. In the code below, promoter coordinates and gene symbols are obtained from various annotation objects.
prom <- suppressWarnings(promoters(TxDb.Mmusculus.UCSC.mm10.knownGene,
upstream=3000, downstream=1000, columns=c("tx_name", "gene_id")))
entrez.ids <- sapply(prom$gene_id, FUN=function(x) x[1]) # Using the first Entrez ID.
gene.name <- select(org.Mm.eg.db, keys=entrez.ids, keytype="ENTREZID", column="SYMBOL")
prom$gene_name <- gene.name$SYMBOL[match(entrez.ids, gene.name$ENTREZID)]
head(prom)
## GRanges object with 6 ranges and 3 metadata columns:
## seqnames ranges strand | tx_name
## <Rle> <IRanges> <Rle> | <character>
## ENSMUST00000193812.1 chr1 3070253-3074252 + | ENSMUST00000193812.1
## ENSMUST00000082908.1 chr1 3099016-3103015 + | ENSMUST00000082908.1
## ENSMUST00000192857.1 chr1 3249757-3253756 + | ENSMUST00000192857.1
## ENSMUST00000161581.1 chr1 3463587-3467586 + | ENSMUST00000161581.1
## ENSMUST00000192183.1 chr1 3528795-3532794 + | ENSMUST00000192183.1
## ENSMUST00000193244.1 chr1 3677155-3681154 + | ENSMUST00000193244.1
## gene_id gene_name
## <CharacterList> <character>
## ENSMUST00000193812.1 <NA>
## ENSMUST00000082908.1 <NA>
## ENSMUST00000192857.1 <NA>
## ENSMUST00000161581.1 <NA>
## ENSMUST00000192183.1 <NA>
## ENSMUST00000193244.1 <NA>
## -------
## seqinfo: 66 sequences (1 circular) from mm10 genome
All windows overlapping each promoter are defined as a cluster.
We compute DB statistics are computed for each cluster/promoter using Simes’ method,
which directly yields DB results for the annotated features.
Promoters with no overlapping windows are assigned NA
values for the various fields and are filtered out below for demonstration purposes.
olap.out <- overlapResults(filtered.data, regions=prom, res$table)
olap.out
## DataFrame with 142446 rows and 3 columns
## regions combined best
## <GRanges> <DataFrame> <DataFrame>
## 1 chr1:3070253-3074252:+ NA:NA:NA:... NA:NA:NA:...
## 2 chr1:3099016-3103015:+ NA:NA:NA:... NA:NA:NA:...
## 3 chr1:3249757-3253756:+ NA:NA:NA:... NA:NA:NA:...
## 4 chr1:3463587-3467586:+ NA:NA:NA:... NA:NA:NA:...
## 5 chr1:3528795-3532794:+ NA:NA:NA:... NA:NA:NA:...
## ... ... ... ...
## 142442 chrUn_GL456381:15722-19721:- NA:NA:NA:... NA:NA:NA:...
## 142443 chrUn_GL456385:28243-32242:+ NA:NA:NA:... NA:NA:NA:...
## 142444 chrUn_GL456385:29719-33718:+ NA:NA:NA:... NA:NA:NA:...
## 142445 chrUn_JH584304:58668-62667:- NA:NA:NA:... NA:NA:NA:...
## 142446 chrUn_JH584304:58691-62690:- NA:NA:NA:... NA:NA:NA:...
simple <- DataFrame(ID=prom$tx_name, Gene=prom$gene_name, olap.out$combined)
simple[!is.na(simple$PValue),]
## DataFrame with 57380 rows and 10 columns
## ID Gene num.tests num.up.logFC num.down.logFC
## <character> <character> <integer> <integer> <integer>
## 1 ENSMUST00000134384.7 Lypla1 18 0 0
## 2 ENSMUST00000027036.10 Lypla1 18 0 0
## 3 ENSMUST00000150971.7 Lypla1 18 0 0
## 4 ENSMUST00000155020.1 Lypla1 18 0 0
## 5 ENSMUST00000119612.8 Lypla1 18 0 0
## ... ... ... ... ... ...
## 57376 ENSMUST00000150715.1 Uty 18 0 11
## 57377 ENSMUST00000154527.1 Uty 18 0 11
## 57378 ENSMUST00000091190.11 Ddx3y 17 0 17
## 57379 ENSMUST00000188484.1 Ddx3y 17 0 17
## 57380 ENSMUST00000187962.1 NA 3 0 3
## PValue FDR direction rep.test rep.logFC
## <numeric> <numeric> <character> <integer> <numeric>
## 1 0.700465 0.739135 mixed 40 -0.27242
## 2 0.700465 0.739135 mixed 40 -0.27242
## 3 0.700465 0.739135 mixed 40 -0.27242
## 4 0.700465 0.739135 mixed 40 -0.27242
## 5 0.700465 0.739135 mixed 40 -0.27242
## ... ... ... ... ... ...
## 57376 6.45130e-06 0.000324147 down 689012 -3.36536
## 57377 6.45130e-06 0.000324147 down 689012 -3.36536
## 57378 6.82321e-05 0.001421855 down 689019 -2.78424
## 57379 6.82321e-05 0.001421855 down 689019 -2.78424
## 57380 2.93752e-03 0.013802417 down 689066 -6.96911
Note that this strategy is distinct from counting reads across promoters. Using promoter-level counts would not provide enough spatial resolution to detect sharp binding events that only occur in a subinterval of the promoter. In particular, detection may be compromised by non-specific background or the presence of multiple opposing DB events in the same promoter. Combining window-level statistics is preferable as resolution is maintained for optimal performance.
Here, the Gviz package is used to visualize read coverage across the data set at regions of interest (F. and R. 2016). Coverage in each BAM file will be represented by a single track. Several additional tracks will also be included in each plot. One is the genome axis track, to display the genomic coordinates across the plotted region. The other is the annotation track containing gene models, with gene IDs replaced by symbols (where possible) for easier reading.
library(Gviz)
gax <- GenomeAxisTrack(col="black", fontsize=15, size=2)
greg <- GeneRegionTrack(TxDb.Mmusculus.UCSC.mm10.knownGene, showId=TRUE,
geneSymbol=TRUE, name="", background.title="transparent")
symbols <- unlist(mapIds(org.Mm.eg.db, gene(greg), "SYMBOL",
"ENTREZID", multiVals = "first"))
symbol(greg) <- symbols[gene(greg)]
We will also sort the DB regions by p-value for easier identification of regions of interest.
o <- order(out.ranges$PValue)
sorted.ranges <- out.ranges[o]
sorted.ranges
## GRanges object with 41616 ranges and 13 metadata columns:
## seqnames ranges strand | num.tests num.up.logFC
## <Rle> <IRanges> <Rle> | <integer> <integer>
## [1] chr17 34285101-34290050 * | 97 0
## [2] chr9 109050201-109053150 * | 57 0
## [3] chr17 34261151-34265850 * | 92 5
## [4] chr17 34306001-34308650 * | 51 0
## [5] chr18 60802751-60805750 * | 55 0
## ... ... ... ... . ... ...
## [41612] chr18 23751901-23753200 * | 22 0
## [41613] chr12 83922051-83922650 * | 10 0
## [41614] chr15 99395101-99395650 * | 8 0
## [41615] chr3 67504201-67504500 * | 4 0
## [41616] chr4 43043401-43043700 * | 4 0
## num.down.logFC PValue FDR direction rep.test
## <integer> <numeric> <numeric> <character> <integer>
## [1] 97 4.04798e-11 1.22570e-06 down 291020
## [2] 57 7.13784e-11 1.22570e-06 down 671262
## [3] 74 8.83575e-11 1.22570e-06 down 290891
## [4] 51 1.23282e-10 1.28263e-06 down 291358
## [5] 55 2.06286e-10 1.54430e-06 down 321713
## ... ... ... ... ... ...
## [41612] 0 0.999833 0.999908 mixed 313278
## [41613] 0 0.999885 0.999908 mixed 153754
## [41614] 0 0.999908 0.999908 mixed 247763
## [41615] 0 0.999908 0.999908 up 416884
## [41616] 0 0.999908 0.999908 mixed 443668
## rep.logFC best.pos best.logFC overlap
## <numeric> <integer> <numeric> <character>
## [1] -6.97365 34287575 -7.18686 H2-Aa:-:PE
## [2] -5.84054 109051575 -6.19603 Shisa5:+:PE
## [3] -7.87978 34262025 -7.70115 H2-Ab1:+:PE
## [4] -6.86030 34306075 -5.80798 H2-Eb1:+:PE
## [5] -5.13082 60804525 -5.98346 Cd74:+:PE
## ... ... ... ... ...
## [41612] -0.000502650 23752525 -0.777050 Gm15972:-:PE,Mapre2:..
## [41613] 0.000405487 83922125 0.880875 Numb:-:P
## [41614] 0.000119628 99395425 -0.411300 Tmbim6:+:I
## [41615] 0.000119628 67504275 0.491618 Rarres1:-:I
## [41616] 0.000119628 43043575 0.174254 Fam214b:-:I
## left right
## <character> <character>
## [1] H2-Aa:-:565
## [2] Atrip-trex1:-:4783,T..
## [3] H2-Ab1:+:3314 H2-Ab1:+:1252
## [4] H2-Eb1:+:925
## [5] Cd74:+:2158
## ... ... ...
## [41612] Gm15972:-:78 Mapre2:+:525
## [41613] Numb:-:117
## [41614] Tmbim6:+:1371 Tmbim6:+:4007
## [41615]
## [41616] Fam214b:-:3106 Fam214b:-:1948
## -------
## seqinfo: 21 sequences from an unspecified genome
We start by visualizing one of the top-ranking DB regions. This represents a simple DB event where the entire region changes in one direction (Figure 9). Specifically, it represents an increase in H3K9ac marking at the H2-Aa locus in mature B cells. This is consistent with the expected biology – H3K9ac is a mark of active gene expression (Karmodiya et al. 2012) and MHCII components are upregulated in mature B cells (Hoffmann et al. 2002).
cur.region <- sorted.ranges[1]
cur.region
## GRanges object with 1 range and 13 metadata columns:
## seqnames ranges strand | num.tests num.up.logFC num.down.logFC
## <Rle> <IRanges> <Rle> | <integer> <integer> <integer>
## [1] chr17 34285101-34290050 * | 97 0 97
## PValue FDR direction rep.test rep.logFC best.pos
## <numeric> <numeric> <character> <integer> <numeric> <integer>
## [1] 4.04798e-11 1.2257e-06 down 291020 -6.97365 34287575
## best.logFC overlap left right
## <numeric> <character> <character> <character>
## [1] -7.18686 H2-Aa:-:PE H2-Aa:-:565
## -------
## seqinfo: 21 sequences from an unspecified genome
One track is plotted for each sample, in addition to the coordinate and annotation tracks. Coverage is plotted in terms of sequencing depth-per-million at each base. This corrects for differences in library sizes between tracks.
collected <- list()
lib.sizes <- filtered.data$totals/1e6
for (i in seq_along(acdata$Path)) {
reads <- extractReads(bam.file=acdata$Path[[i]], cur.region, param=param)
cov <- as(coverage(reads)/lib.sizes[i], "GRanges")
collected[[i]] <- DataTrack(cov, type="histogram", lwd=0, ylim=c(0,10),
name=acdata$Description[i], col.axis="black", col.title="black",
fill="darkgray", col.histogram=NA)
}
plotTracks(c(gax, collected, greg), chromosome=as.character(seqnames(cur.region)),
from=start(cur.region), to=end(cur.region))
Complex DB refers to situations where multiple DB events are occurring within the same enriched region.
These are identified as those clusters that contain windows changing in both directions9 Technically, we should use mixedClusters()
for rigorous identification of regions with significant changes in both directions. However, for simplicity, we’ll just use a more ad hoc approach here..
Here, one of the top-ranking complex clusters is selected for visualization.
complex <- sorted.ranges$num.up.logFC > 0 & sorted.ranges$num.down.logFC > 0
cur.region <- sorted.ranges[complex][2]
cur.region
## GRanges object with 1 range and 13 metadata columns:
## seqnames ranges strand | num.tests num.up.logFC
## <Rle> <IRanges> <Rle> | <integer> <integer>
## [1] chr5 122987201-122991450 * | 83 5
## num.down.logFC PValue FDR direction rep.test rep.logFC
## <integer> <numeric> <numeric> <character> <integer> <numeric>
## [1] 37 1.30976e-08 1.33826e-05 down 508657 -5.8272
## best.pos best.logFC overlap left
## <integer> <numeric> <character> <character>
## [1] 122990925 -5.48535 A930024E05Rik:+:PE,K.. Kdm2b:-:2230
## right
## <character>
## [1] A930024E05Rik:+:2913
## -------
## seqinfo: 21 sequences from an unspecified genome
This region contains a bidirectional promoter where different genes are marked in the different cell types (Figure 10). Upon differentiation to mature B cells, loss of marking in one part of the region is balanced by a gain in marking in another part of the region. This represents a complex DB event that would not be detected if reads were counted across the entire region.
collected <- list()
for (i in seq_along(acdata$Path)) {
reads <- extractReads(bam.file=acdata$Path[[i]], cur.region, param=param)
cov <- as(coverage(reads)/lib.sizes[i], "GRanges")
collected[[i]] <- DataTrack(cov, type="histogram", lwd=0, ylim=c(0,3),
name=acdata$Description[i], col.axis="black", col.title="black",
fill="darkgray", col.histogram=NA)
}
plotTracks(c(gax, collected, greg), chromosome=as.character(seqnames(cur.region)),
from=start(cur.region), to=end(cur.region))
Both of the examples above involve differential marking within broad regions spanning several kilobases. This is consistent with changes in the marking profile across a large number of nucleosomes. However, H3K9ac marking can also be concentrated into small regions, involving only a few nucleosomes. csaw is equally capable of detecting sharp DB within these small regions. This is demonstrated by examining those clusters that contain a smaller number of windows.
sharp <- sorted.ranges$num.tests < 20
cur.region <- sorted.ranges[sharp][1]
cur.region
## GRanges object with 1 range and 13 metadata columns:
## seqnames ranges strand | num.tests num.up.logFC num.down.logFC
## <Rle> <IRanges> <Rle> | <integer> <integer> <integer>
## [1] chr16 36665551-36666200 * | 11 0 11
## PValue FDR direction rep.test rep.logFC best.pos
## <numeric> <numeric> <character> <integer> <numeric> <integer>
## [1] 1.2984e-08 1.33826e-05 down 264956 -4.65739 36665925
## best.logFC overlap left right
## <numeric> <character> <character> <character>
## [1] -4.93342 Cd86:-:PE Cd86:-:3937
## -------
## seqinfo: 21 sequences from an unspecified genome
Marking is increased for mature B cells within a 500 bp region (Figure 11), which is sharper than the changes in the previous two examples. This also coincides with the promoter of the Cd86 gene. Again, this makes biological sense as CD86 is involved in regulating immunoglobulin production in activated B-cells (Podojil and Sanders 2003).
collected <- list()
for (i in seq_along(acdata$Path)) {
reads <- extractReads(bam.file=acdata$Path[[i]], cur.region, param=param)
cov <- as(coverage(reads)/lib.sizes[i], "GRanges")
collected[[i]] <- DataTrack(cov, type="histogram", lwd=0, ylim=c(0,3),
name=acdata$Description[i], col.axis="black", col.title="black",
fill="darkgray", col.histogram=NA)
}
plotTracks(c(gax, collected, greg), chromosome=as.character(seqnames(cur.region)),
from=start(cur.region), to=end(cur.region))
Note that the window size will determine whether sharp or broad events are preferentially detected.
Larger windows provide more power to detect broad events (as the counts are higher), while smaller windows provide more resolution to detect sharp events.
Optimal detection of all features can be obtained by performing analyses with multiple window sizes and consolidating the results10 See ?consolidateWindows
and ?consolidateTests
for further information., though – for brevity – this will not be described here.
In general, smaller window sizes are preferred as strong DB events with sufficient coverage will always be detected.
For larger windows, detection may be confounded by other events within the window that distort the log-fold change in the counts between conditions.
sessionInfo()
## R version 4.0.3 (2020-10-10)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 18.04.5 LTS
##
## Matrix products: default
## BLAS: /home/biocbuild/bbs-3.12-bioc/R/lib/libRblas.so
## LAPACK: /home/biocbuild/bbs-3.12-bioc/R/lib/libRlapack.so
##
## locale:
## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=en_US.UTF-8 LC_COLLATE=C
## [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] grid stats4 parallel stats graphics grDevices utils
## [8] datasets methods base
##
## other attached packages:
## [1] ChIPpeakAnno_3.24.0
## [2] Gviz_1.34.0
## [3] org.Mm.eg.db_3.12.0
## [4] TxDb.Mmusculus.UCSC.mm10.knownGene_3.10.0
## [5] GenomicFeatures_1.42.0
## [6] AnnotationDbi_1.52.0
## [7] edgeR_3.32.0
## [8] limma_3.46.0
## [9] csaw_1.24.0
## [10] SummarizedExperiment_1.20.0
## [11] Biobase_2.50.0
## [12] MatrixGenerics_1.2.0
## [13] matrixStats_0.57.0
## [14] rtracklayer_1.50.0
## [15] BiocFileCache_1.14.0
## [16] dbplyr_1.4.4
## [17] Rsamtools_2.6.0
## [18] Biostrings_2.58.0
## [19] XVector_0.30.0
## [20] GenomicRanges_1.42.0
## [21] GenomeInfoDb_1.26.0
## [22] IRanges_2.24.0
## [23] S4Vectors_0.28.0
## [24] BiocGenerics_0.36.0
## [25] chipseqDBData_1.6.0
## [26] knitr_1.30
## [27] BiocStyle_2.18.0
##
## loaded via a namespace (and not attached):
## [1] backports_1.1.10 Hmisc_4.4-1
## [3] AnnotationHub_2.22.0 lazyeval_0.2.2
## [5] splines_4.0.3 BiocParallel_1.24.0
## [7] ggplot2_3.3.2 digest_0.6.27
## [9] ensembldb_2.14.0 htmltools_0.5.0
## [11] magick_2.5.0 magrittr_1.5
## [13] checkmate_2.0.0 memoise_1.1.0
## [15] BSgenome_1.58.0 cluster_2.1.0
## [17] askpass_1.1 prettyunits_1.1.1
## [19] jpeg_0.1-8.1 colorspace_1.4-1
## [21] blob_1.2.1 rappdirs_0.3.1
## [23] xfun_0.18 dplyr_1.0.2
## [25] crayon_1.3.4 RCurl_1.98-1.2
## [27] graph_1.68.0 survival_3.2-7
## [29] VariantAnnotation_1.36.0 glue_1.4.2
## [31] gtable_0.3.0 zlibbioc_1.36.0
## [33] DelayedArray_0.16.0 scales_1.1.1
## [35] futile.options_1.0.1 DBI_1.1.0
## [37] Rcpp_1.0.5 xtable_1.8-4
## [39] progress_1.2.2 htmlTable_2.1.0
## [41] foreign_0.8-80 bit_4.0.4
## [43] Formula_1.2-4 htmlwidgets_1.5.2
## [45] httr_1.4.2 RColorBrewer_1.1-2
## [47] ellipsis_0.3.1 pkgconfig_2.0.3
## [49] XML_3.99-0.5 nnet_7.3-14
## [51] locfit_1.5-9.4 tidyselect_1.1.0
## [53] rlang_0.4.8 later_1.1.0.1
## [55] munsell_0.5.0 BiocVersion_3.12.0
## [57] tools_4.0.3 generics_0.0.2
## [59] RSQLite_2.2.1 ExperimentHub_1.16.0
## [61] evaluate_0.14 stringr_1.4.0
## [63] fastmap_1.0.1 yaml_2.2.1
## [65] bit64_4.0.5 purrr_0.3.4
## [67] KEGGREST_1.30.0 AnnotationFilter_1.14.0
## [69] RBGL_1.66.0 mime_0.9
## [71] formatR_1.7 xml2_1.3.2
## [73] biomaRt_2.46.0 compiler_4.0.3
## [75] rstudioapi_0.11 curl_4.3
## [77] png_0.1-7 interactiveDisplayBase_1.28.0
## [79] tibble_3.0.4 statmod_1.4.35
## [81] stringi_1.5.3 futile.logger_1.4.3
## [83] highr_0.8 lattice_0.20-41
## [85] ProtGenerics_1.22.0 Matrix_1.2-18
## [87] multtest_2.46.0 vctrs_0.3.4
## [89] pillar_1.4.6 lifecycle_0.2.0
## [91] BiocManager_1.30.10 data.table_1.13.2
## [93] bitops_1.0-6 httpuv_1.5.4
## [95] R6_2.5.0 latticeExtra_0.6-29
## [97] bookdown_0.21 promises_1.1.1
## [99] KernSmooth_2.23-18 gridExtra_2.3
## [101] codetools_0.2-16 lambda.r_1.2.4
## [103] dichromat_2.0-0 MASS_7.3-53
## [105] assertthat_0.2.1 openssl_1.4.3
## [107] regioneR_1.22.0 GenomicAlignments_1.26.0
## [109] GenomeInfoDbData_1.2.4 hms_0.5.3
## [111] VennDiagram_1.6.20 rpart_4.1-15
## [113] rmarkdown_2.5 biovizBase_1.38.0
## [115] shiny_1.5.0 base64enc_0.1-3
Ballman, K. V., D. E. Grill, A. L. Oberg, and T. M. Therneau. 2004. “Faster cyclic loess: normalizing RNA arrays via linear models.” Bioinformatics 20 (16): 2778–86.
Benjamini, Y., and Y. Hochberg. 1995. “Controlling the False Discovery Rate: A Practical and Powerful Approach to Multiple Testing.” J. Royal Stat. Soc. B 57: 289–300.
ENCODE Project Consortium. 2012. “An integrated encyclopedia of DNA elements in the human genome.” Nature 489 (7414): 57–74.
F., Hahne, and Ivanek R. 2016. “Visualizing Genomic Data Using Gviz and Bioconductor.” In Statistical Genomics: Methods and Protocols, edited by Ewy Mathé and Sean Davis, 335–51. New York, NY: Springer New York. https://doi.org/10.1007/978-1-4939-3578-9_16.
Hoffmann, R., T. Seidl, M. Neeb, A. Rolink, and F. Melchers. 2002. “Changes in gene expression profiles in developing B cells of murine bone marrow.” Genome Res. 12 (1): 98–111.
Humburg, P., C. A. Helliwell, D. Bulger, and G. Stone. 2011. “ChIPseqR: analysis of ChIP-seq experiments.” BMC Bioinformatics 12: 39.
Karmodiya, K., A. R. Krebs, M. Oulad-Abdelghani, H. Kimura, and L. Tora. 2012. “H3K9 and H3K14 acetylation co-occur at many gene regulatory elements, while H3K14ac marks a subset of inactive inducible promoters in mouse embryonic stem cells.” BMC Genomics 13: 424.
Kharchenko, P. V., M. Y. Tolstorukov, and P. J. Park. 2008. “Design and analysis of ChIP-seq experiments for DNA-binding proteins.” Nat. Biotechnol. 26 (12): 1351–9.
Landt, S. G., G. K. Marinov, A. Kundaje, P. Kheradpour, F. Pauli, S. Batzoglou, B. E. Bernstein, et al. 2012. “ChIP-seq guidelines and practices of the ENCODE and modENCODE consortia.” Genome Res. 22 (9): 1813–31.
Lun, A. T., and G. K. Smyth. 2015. “csaw: a Bioconductor package for differential binding analysis of ChIP-seq data using sliding windows.” Nucleic Acids Res. https://doi.org/10.1093/nar/gkv1191.
———. 2014. “De novo detection of differentially bound regions for ChIP-seq data using peaks and windows: controlling error rates correctly.” Nucleic Acids Res. 42 (11): e95.
Lund, S. P., D. Nettleton, D. J. McCarthy, and G. K. Smyth. 2012. “Detecting differential expression in RNA-sequence data using quasi-likelihood with shrunken dispersion estimates.” Stat. Appl. Genet. Mol. Biol. 11 (5): Article 8.
McCarthy, D. J., Y. Chen, and G. K. Smyth. 2012. “Differential expression analysis of multifactor RNA-Seq experiments with respect to biological variation.” Nucleic Acids Res. 40 (10): 4288–97.
Podojil, J. R., and V. M. Sanders. 2003. “Selective regulation of mature IgG1 transcription by CD86 and beta 2-adrenergic receptor stimulation.” J. Immunol. 170 (10): 5143–51.
Revilla-I-Domingo, R., I. Bilic, B. Vilagos, H. Tagoh, A. Ebert, I. M. Tamir, L. Smeenk, et al. 2012. “The B-cell identity factor Pax5 regulates distinct transcriptional programmes in early and late B lymphopoiesis.” EMBO J. 31 (14): 3130–46.
Robinson, M. D., D. J. McCarthy, and G. K. Smyth. 2010. “edgeR: a Bioconductor package for differential expression analysis of digital gene expression data.” Bioinformatics 26 (1): 139–40.
Robinson, M. D., and A. Oshlack. 2010. “A scaling normalization method for differential expression analysis of RNA-seq data.” Genome Biol. 11 (3): R25.
Rosenbloom, K. R., J. Armstrong, G. P. Barber, J. Casper, H. Clawson, M. Diekhans, T. R. Dreszer, et al. 2015. “The UCSC Genome Browser database: 2015 update.” Nucleic Acids Res. 43 (Database issue): D670–681.
Simes, R. J. 1986. “An Improved Bonferroni Procedure for Multiple Tests of Significance.” Biometrika 73 (3): 751–54.
Zhu, L. J., C. Gazin, N. D. Lawson, H. Pages, S. M. Lin, D. S. Lapointe, and M. R. Green. 2010. “ChIPpeakAnno: a Bioconductor package to annotate ChIP-seq and ChIP-chip data.” BMC Bioinformatics 11: 237.