ATACseqQC Guide

Introduction

Assay for Transposase-Accessible Chromatin using sequencing (ATAC-seq) is an alternative or complementary technique to MNase-seq, DNase-seq, and FAIRE-seq for chromatin accessibility analysis. The results obtained from ATAC-seq are similar to those from DNase-seq and FAIRE-seq. ATAC-seq is gaining popularity because it does not require cross-linking, has higher signal to noise ratio, requires a much smaller amount of biological material and is faster and easier to perform, compared to other techniques1.

To help researchers quickly assess the quality of ATAC-seq data, we have developed the ATACseqQC package for easily making diagnostic plots following the published guidelines1. In addition, it has functions to preprocess ATACseq data for subsequent peak calling.

Quick start

Here is an example using ATACseqQC with a subset of published ATAC-seq data1. Currently, only bam input file format is supported.

First install ATACseqQC and other packages required to run the examples. Please note that the example dataset used here is from human. To run analysis with dataset from a different species or differnt assembly, please install the corresponding BSgenome, TxDb and phastCons. For example, to analyze mouse data aligned to mm10, please install BSgenome.Mmusculus.UCSC.mm10, TxDb.Mmusculus.UCSC.mm10.knownGene and phastCons60way.UCSC.mm10. Please note that phstCons60way.UCSC.mm10 is optional, which can be obtained according to the vignettes of GenomicScores.

library(BiocManager)
BiocManager::install(c("ATACseqQC", "ChIPpeakAnno", "MotifDb", "GenomicAlignments",
           "BSgenome.Hsapiens.UCSC.hg19", "TxDb.Hsapiens.UCSC.hg19.knownGene",
           "phastCons100way.UCSC.hg19"))
## load the library
library(ATACseqQC)
## input the bamFile from the ATACseqQC package 
bamfile <- system.file("extdata", "GL1.bam", 
                        package="ATACseqQC", mustWork=TRUE)
bamfile.labels <- gsub(".bam", "", basename(bamfile))

IGV snapshot

Source code of IGVSnapshot function is available in extdata folder. To call the function, please try

source(system.file("extdata", "IGVSnapshot.R", package = "ATACseqQC"))

Estimate the library complexity

#bamQC(bamfile, outPath=NULL)
estimateLibComplexity(readsDupFreq(bamfile))

Fragment size distribution

First, there should be a large proportion of reads with less than 100 bp, which represents the nucleosome-free region. Second, the fragment size distribution should have a clear periodicity, which is evident in the inset figure, indicative of nucleosome occupacy (present in integer multiples).

## generate fragement size distribution
fragSize <- fragSizeDist(bamfile, bamfile.labels)

Nucleosome positioning

Adjust the read start sites

Tn5 transposase has been shown to bind as a dimer and inserts two adaptors into accessible DNA locations separated by 9 bp2.

Therefore, for downstream analysis, such as peak-calling and footprinting, all reads in input bamfile need to be shifted. The function shiftGAlignmentsList can be used to shift the reads. By default, all reads aligning to the positive strand are offset by +4bp, and all reads aligning to the negative strand are offset by -5bp1.

The adjusted reads will be written into a new bamfile for peak calling or footprinting.

## bamfile tags to be read in
possibleTag <- list("integer"=c("AM", "AS", "CM", "CP", "FI", "H0", "H1", "H2", 
                                "HI", "IH", "MQ", "NH", "NM", "OP", "PQ", "SM",
                                "TC", "UQ"), 
                 "character"=c("BC", "BQ", "BZ", "CB", "CC", "CO", "CQ", "CR",
                               "CS", "CT", "CY", "E2", "FS", "LB", "MC", "MD",
                               "MI", "OA", "OC", "OQ", "OX", "PG", "PT", "PU",
                               "Q2", "QT", "QX", "R2", "RG", "RX", "SA", "TS",
                               "U2"))
library(Rsamtools)
bamTop100 <- scanBam(BamFile(bamfile, yieldSize = 100),
                     param = ScanBamParam(tag=unlist(possibleTag)))[[1]]$tag
tags <- names(bamTop100)[lengths(bamTop100)>0]
tags
##    integer2   integer13 character16 
##        "AS"        "NM"        "MD"
## files will be output into outPath
outPath <- "splited"
dir.create(outPath)
## shift the coordinates of 5'ends of alignments in the bam file
library(TxDb.Hsapiens.UCSC.hg19.knownGene)
## if you don't have an available TxDb, please refer
## GenomicFeatures::makeTxDbFromGFF to create one from gff3 or gtf file.
seqlev <- "chr1" ## subsample data for quick run
seqinformation <- seqinfo(TxDb.Hsapiens.UCSC.hg19.knownGene)
which <- as(seqinformation[seqlev], "GRanges")
gal <- readBamFile(bamfile, tag=tags, which=which, asMates=TRUE, bigFile=TRUE)
shiftedBamfile <- file.path(outPath, "shifted.bam")
gal1 <- shiftGAlignmentsList(gal, outbam=shiftedBamfile)
##   |                                                                              |                                                                      |   0%  |                                                                              |==============                                                        |  20%  |                                                                              |============================                                          |  40%  |                                                                              |==========================================                            |  60%  |                                                                              |========================================================              |  80%  |                                                                              |======================================================================| 100%

Promoter/Transcript body (PT) score

PT score is calculated as the coverage of promoter divided by the coverage of its transcript body. PT score will show if the signal is enriched in promoters.

library(TxDb.Hsapiens.UCSC.hg19.knownGene)
txs <- transcripts(TxDb.Hsapiens.UCSC.hg19.knownGene)
pt <- PTscore(gal1, txs)
plot(pt$log2meanCoverage, pt$PT_score, 
     xlab="log2 mean coverage",
     ylab="Promoter vs Transcript")

Nucleosome Free Regions (NFR) score

NFR score is a ratio between cut signal adjacent to TSS and that flanking the corresponding TSS. Each TSS window of 400 bp is first divided into 3 sub-regions: the most upstream 150 bp (n1), the most downstream of 150 bp (n2), and the middle 100 bp (nf). Then the number of fragments with 5’ ends overlapping each region are calculated for each TSS. The NFR score for each TSS is calculated as NFR-score = log2(nf) - log2((n1+n2)/2). A plot can be generated with the NFR scores as Y-axis and the average signals of 400 bp window as X-axis, very like a MA plot for gene expression data.

nfr <- NFRscore(gal1, txs)
plot(nfr$log2meanCoverage, nfr$NFR_score, 
     xlab="log2 mean coverage",
     ylab="Nucleosome Free Regions score",
     main="NFRscore for 200bp flanking TSSs",
     xlim=c(-10, 0), ylim=c(-5, 5))

Transcription Start Site (TSS) Enrichment Score

TSS enrichment score is a raio between aggregate distribution of reads centered on TSSs and that flanking the corresponding TSSs. TSS score = the depth of TSS (each 100bp window within 1000 bp each side) / the depth of end flanks (100bp each end). TSSE score = max(mean(TSS score in each window)). TSS enrichment score is calculated according to the definition at https://www.encodeproject.org/data-standards/terms/#enrichment. Transcription start site (TSS) enrichment values are dependent on the reference files used; cutoff values for high quality data are listed in the following table from https://www.encodeproject.org/atac-seq/.

tsse <- TSSEscore(gal1, txs)
tsse$TSSEscore
## [1] 14.99366
plot(100*(-9:10-.5), tsse$values, type="b", 
     xlab="distance to TSS",
     ylab="aggregate TSS score")

Split reads

The shifted reads will be split into different bins, namely nucleosome free, mononucleosome, dinucleosome, and trinucleosome. Shifted reads that do not fit into any of the above bins will be discarded. Splitting reads is a time-consuming step because we are using random forest to classify the fragments based on fragment length, GC content and conservation scores3.

By default, we assign the top 10% of short reads (reads below 100_bp) as nucleosome-free regions and the top 10% of intermediate length reads as (reads between 180 and 247 bp) mononucleosome. This serves as the training set to classify the rest of the fragments using random forest. The number of the tree will be set to 2 times of square root of the length of the training set.

library(BSgenome.Hsapiens.UCSC.hg19)
library(phastCons100way.UCSC.hg19)
## run program for chromosome 1 only
txs <- txs[seqnames(txs) %in% "chr1"]
genome <- Hsapiens
## split the reads into NucleosomeFree, mononucleosome, 
## dinucleosome and trinucleosome.
## and save the binned alignments into bam files.
objs <- splitGAlignmentsByCut(gal1, txs=txs, genome=genome, outPath = outPath,
                              conservation=phastCons100way.UCSC.hg19)
## list the files generated by splitGAlignmentsByCut.
dir(outPath)
##  [1] "NucleosomeFree.bam"     "NucleosomeFree.bam.bai" "dinucleosome.bam"      
##  [4] "dinucleosome.bam.bai"   "inter1.bam"             "inter1.bam.bai"        
##  [7] "inter2.bam"             "inter2.bam.bai"         "inter3.bam"            
## [10] "inter3.bam.bai"         "mononucleosome.bam"     "mononucleosome.bam.bai"
## [13] "others.bam"             "others.bam.bai"         "shifted.bam"           
## [16] "shifted.bam.bai"        "trinucleosome.bam"      "trinucleosome.bam.bai"

You can also perform shifting, splitting and saving in one step by calling splitBam.

objs <- splitBam(bamfile, tags=tags, outPath=outPath,
                 txs=txs, genome=genome,
                 conservation=phastCons100way.UCSC.hg19)

Conservation is an optional parameter. If you do not have the conservation score or you would like to simply split the bam files using the fragment length, then you will just need to run the command without providing the conservation argument. Without setting the conservation parameter, it will run much faster.

## split reads by fragment length
## NOT RUN IN THIS example
objs <- splitGAlignmentsByCut(gal1, txs=txs, outPath = outPath)

Heatmap and coverage curve for nucleosome positions

By averaging the signal across all active TSSs, we should observe that nucleosome-free fragments are enriched at the TSSs, whereas the nucleosome-bound fragments should be enriched both upstream and downstream of the active TSSs and display characteristic phasing of upstream and downstream nucleosomes. Because ATAC-seq reads are concentrated at regions of open chromatin, users should see a strong nucleosome signal at the +1 nucleosome, but the signal decreases at the +2, +3 and +4 nucleosomes.

library(ChIPpeakAnno)
bamfiles <- file.path(outPath,
                     c("NucleosomeFree.bam",
                     "mononucleosome.bam",
                     "dinucleosome.bam",
                     "trinucleosome.bam"))
## Plot the cumulative percentage of tag allocation in nucleosome-free 
## and mononucleosome bam files.
cumulativePercentage(bamfiles[1:2], as(seqinformation["chr1"], "GRanges"))

TSS <- promoters(txs, upstream=0, downstream=1)
TSS <- unique(TSS)
## estimate the library size for normalization
(librarySize <- estLibSize(bamfiles))
## splited/NucleosomeFree.bam splited/mononucleosome.bam 
##                      33227                       2149 
##   splited/dinucleosome.bam  splited/trinucleosome.bam 
##                       2047                        472
## calculate the signals around TSSs.
NTILE <- 101
dws <- ups <- 1010
sigs <- enrichedFragments(gal=objs[c("NucleosomeFree", 
                                     "mononucleosome",
                                     "dinucleosome",
                                     "trinucleosome")], 
                          TSS=TSS,
                          librarySize=librarySize,
                          seqlev=seqlev,
                          TSS.filter=0.5,
                          n.tile = NTILE,
                          upstream = ups,
                          downstream = dws)
## log2 transformed signals
sigs.log2 <- lapply(sigs, function(.ele) log2(.ele+1))
#plot heatmap
featureAlignedHeatmap(sigs.log2, reCenterPeaks(TSS, width=ups+dws),
                      zeroAt=.5, n.tile=NTILE)

## get signals normalized for nucleosome-free and nucleosome-bound regions.
out <- featureAlignedDistribution(sigs, 
                                  reCenterPeaks(TSS, width=ups+dws),
                                  zeroAt=.5, n.tile=NTILE, type="l", 
                                  ylab="Averaged coverage")
## rescale the nucleosome-free and nucleosome signals to 0~1
range01 <- function(x){(x-min(x))/(max(x)-min(x))}
out <- apply(out, 2, range01)
matplot(out, type="l", xaxt="n", 
        xlab="Position (bp)", 
        ylab="Fraction of signal")
axis(1, at=seq(0, 100, by=10)+1, 
     labels=c("-1K", seq(-800, 800, by=200), "1K"), las=2)
abline(v=seq(0, 100, by=10)+1, lty=2, col="gray")

plot Footprints

ATAC-seq footprints infer factor occupancy genome-wide. The factorFootprints function uses matchPWM to predict the binding sites using the input position weight matrix (PWM). Then it calculates and plots the accumulated coverage for those binding sites to show the status of the occupancy genome-wide. Unlike CENTIPEDE4, the footprints generated here do not take the conservation (PhyloP) into consideration. factorFootprints function could also accept the binding sites as a GRanges object.

## foot prints
library(MotifDb)
CTCF <- query(MotifDb, c("CTCF"))
CTCF <- as.list(CTCF)
print(CTCF[[1]], digits=2)
##      1    2    3    4    5    6     7     8     9      10    11      12      13
## A 0.17 0.23 0.29 0.10 0.33 0.06 0.052 0.037 0.023 0.00099 0.245 0.00099 0.00099
## C 0.42 0.28 0.30 0.32 0.11 0.33 0.562 0.005 0.960 0.99702 0.670 0.68901 0.99702
## G 0.25 0.23 0.26 0.27 0.42 0.55 0.052 0.827 0.013 0.00099 0.027 0.00099 0.00099
## T 0.16 0.27 0.15 0.31 0.14 0.06 0.334 0.131 0.004 0.00099 0.058 0.30900 0.00099
##      14    15    16    17      18    19   20
## A 0.050 0.253 0.004 0.172 0.00099 0.019 0.19
## C 0.043 0.073 0.418 0.150 0.00099 0.063 0.43
## G 0.017 0.525 0.546 0.055 0.99702 0.865 0.15
## T 0.890 0.149 0.032 0.623 0.00099 0.053 0.23
sigs <- factorFootprints(shiftedBamfile, pfm=CTCF[[1]], 
                         genome=genome, ## Don't have a genome? ask ?factorFootprints for help
                         min.score="90%", seqlev=seqlev,
                         upstream=100, downstream=100)

featureAlignedHeatmap(sigs$signal, 
                      feature.gr=reCenterPeaks(sigs$bindingSites,
                                               width=200+width(sigs$bindingSites[1])), 
                      annoMcols="score",
                      sortBy="score",
                      n.tile=ncol(sigs$signal[[1]]))

sigs$spearman.correlation
## $`+`
## 
##  Spearman's rank correlation rho
## 
## data:  predictedBindingSiteScore and highest.sig.windows
## S = 6179324, p-value = 1.241e-06
## alternative hypothesis: true rho is not equal to 0
## sample estimates:
##       rho 
## 0.2499378 
## 
## 
## $`-`
## 
##  Spearman's rank correlation rho
## 
## data:  predictedBindingSiteScore and highest.sig.windows
## S = 6521273, p-value = 5.731e-05
## alternative hypothesis: true rho is not equal to 0
## sample estimates:
##       rho 
## 0.2084312
sigs$Profile.segmentation
##           pos   distal_abun proximal_abun       binding 
##   51.75000000    0.08269190    0.11405437    0.06159158

Here is the CTCF footprints for the full dataset. CTCF footprints

V-plot

V-plot is a plot to visualize fragment midpoint vs length for a given transcription factors.

vp <- vPlot(shiftedBamfile, pfm=CTCF[[1]], 
            genome=genome, min.score="90%", seqlev=seqlev,
            upstream=200, downstream=200, 
            ylim=c(30, 250), bandwidth=c(2, 1))

distanceDyad(vp, pch=20, cex=.5)

Here is the CTCF vPlot for the full dataset. CTCF footprints

Plot correlations for multiple samples

path <- system.file("extdata", package="ATACseqQC", mustWork=TRUE)
bamfiles <- dir(path, "*.bam$", full.name=TRUE)
gals <- lapply(bamfiles, function(bamfile){
               readBamFile(bamFile=bamfile, tag=character(0), 
                          which=GRanges("chr1", IRanges(1, 1e6)), 
                          asMates=FALSE)
         })
library(TxDb.Hsapiens.UCSC.hg19.knownGene)
txs <- transcripts(TxDb.Hsapiens.UCSC.hg19.knownGene)
library(GenomicAlignments)
plotCorrelation(GAlignmentsList(gals), txs, seqlev="chr1")

Session Info

sessionInfo()
## R version 4.4.2 (2024-10-31)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 24.04.1 LTS
## 
## Matrix products: default
## BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
## LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0
## 
## 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       
## 
## time zone: Etc/UTC
## tzcode source: system (glibc)
## 
## attached base packages:
## [1] stats4    stats     graphics  grDevices utils     datasets  methods  
## [8] base     
## 
## other attached packages:
##  [1] GenomicAlignments_1.43.0               
##  [2] Rsamtools_2.23.1                       
##  [3] SummarizedExperiment_1.37.0            
##  [4] MatrixGenerics_1.19.0                  
##  [5] matrixStats_1.4.1                      
##  [6] MotifDb_1.49.0                         
##  [7] phastCons100way.UCSC.hg19_3.7.2        
##  [8] GenomicScores_2.19.0                   
##  [9] TxDb.Hsapiens.UCSC.hg19.knownGene_3.2.2
## [10] GenomicFeatures_1.59.1                 
## [11] AnnotationDbi_1.69.0                   
## [12] Biobase_2.67.0                         
## [13] BSgenome.Hsapiens.UCSC.hg19_1.4.3      
## [14] BSgenome_1.75.0                        
## [15] rtracklayer_1.67.0                     
## [16] BiocIO_1.17.1                          
## [17] Biostrings_2.75.3                      
## [18] XVector_0.47.0                         
## [19] ChIPpeakAnno_3.41.0                    
## [20] GenomicRanges_1.59.1                   
## [21] GenomeInfoDb_1.43.2                    
## [22] IRanges_2.41.2                         
## [23] ATACseqQC_1.31.0                       
## [24] S4Vectors_0.45.2                       
## [25] BiocGenerics_0.53.3                    
## [26] generics_0.1.3                         
## 
## loaded via a namespace (and not attached):
##   [1] splines_4.4.2               bitops_1.0-9               
##   [3] filelock_1.0.3              tibble_3.2.1               
##   [5] R.oo_1.27.0                 graph_1.85.0               
##   [7] XML_3.99-0.17               DirichletMultinomial_1.49.0
##   [9] lifecycle_1.0.4             httr2_1.0.7                
##  [11] pwalign_1.3.1               edgeR_4.5.1                
##  [13] lattice_0.22-6              ensembldb_2.31.0           
##  [15] MASS_7.3-61                 magrittr_2.0.3             
##  [17] limma_3.63.2                sass_0.4.9                 
##  [19] rmarkdown_2.29              jquerylib_0.1.4            
##  [21] yaml_2.3.10                 grImport2_0.3-3            
##  [23] DBI_1.2.3                   buildtools_1.0.0           
##  [25] CNEr_1.43.0                 ade4_1.7-22                
##  [27] abind_1.4-8                 zlibbioc_1.52.0            
##  [29] purrr_1.0.2                 R.utils_2.12.3             
##  [31] preseqR_4.0.0               AnnotationFilter_1.31.0    
##  [33] RCurl_1.98-1.16             pracma_2.4.4               
##  [35] rappdirs_0.3.3              GenomeInfoDbData_1.2.13    
##  [37] maketools_1.3.1             seqLogo_1.73.0             
##  [39] BiocStyle_2.35.0            annotate_1.85.0            
##  [41] codetools_0.2-20            DelayedArray_0.33.3        
##  [43] xml2_1.3.6                  tidyselect_1.2.1           
##  [45] futile.logger_1.4.3         UCSC.utils_1.3.0           
##  [47] universalmotif_1.25.1       base64enc_0.1-3            
##  [49] BiocFileCache_2.15.0        jsonlite_1.8.9             
##  [51] multtest_2.63.0             motifStack_1.51.0          
##  [53] survival_3.8-3              tools_4.4.2                
##  [55] progress_1.2.3              TFMPvalue_0.0.9            
##  [57] Rcpp_1.0.13-1               glue_1.8.0                 
##  [59] SparseArray_1.7.2           xfun_0.49                  
##  [61] dplyr_1.1.4                 HDF5Array_1.35.2           
##  [63] formatR_1.14                BiocManager_1.30.25        
##  [65] fastmap_1.2.0               rhdf5filters_1.19.0        
##  [67] caTools_1.18.3              digest_0.6.37              
##  [69] R6_2.5.1                    colorspace_2.1-1           
##  [71] GO.db_3.20.0                gtools_3.9.5               
##  [73] poweRlaw_0.80.0             jpeg_0.1-10                
##  [75] biomaRt_2.63.0              RSQLite_2.3.9              
##  [77] R.methodsS3_1.8.2           tidyr_1.3.1                
##  [79] data.table_1.16.4           prettyunits_1.2.0          
##  [81] InteractionSet_1.35.0       httr_1.4.7                 
##  [83] htmlwidgets_1.6.4           S4Arrays_1.7.1             
##  [85] TFBSTools_1.45.0            regioneR_1.39.0            
##  [87] pkgconfig_2.0.3             gtable_0.3.6               
##  [89] blob_1.2.4                  sys_3.4.3                  
##  [91] htmltools_0.5.8.1           RBGL_1.83.0                
##  [93] ProtGenerics_1.39.1         scales_1.3.0               
##  [95] png_0.1-8                   knitr_1.49                 
##  [97] lambda.r_1.2.4              tzdb_0.4.0                 
##  [99] reshape2_1.4.4              rjson_0.2.23               
## [101] curl_6.0.1                  cachem_1.1.0               
## [103] rhdf5_2.51.1                stringr_1.5.1              
## [105] BiocVersion_3.21.1          KernSmooth_2.23-24         
## [107] parallel_4.4.2              restfulr_0.0.15            
## [109] pillar_1.10.0               grid_4.4.2                 
## [111] vctrs_0.6.5                 randomForest_4.7-1.2       
## [113] dbplyr_2.5.0                xtable_1.8-4               
## [115] evaluate_1.0.1              readr_2.1.5                
## [117] VennDiagram_1.7.3           cli_3.6.3                  
## [119] locfit_1.5-9.10             compiler_4.4.2             
## [121] futile.options_1.0.1        rlang_1.1.4                
## [123] crayon_1.5.3                plyr_1.8.9                 
## [125] stringi_1.8.4               BiocParallel_1.41.0        
## [127] munsell_0.5.1               lazyeval_0.2.2             
## [129] Matrix_1.7-1                hms_1.1.3                  
## [131] bit64_4.5.2                 ggplot2_3.5.1              
## [133] Rhdf5lib_1.29.0             KEGGREST_1.47.0            
## [135] statmod_1.5.0               AnnotationHub_3.15.0       
## [137] memoise_2.0.1               bslib_0.8.0                
## [139] bit_4.5.0.1                 splitstackshape_1.4.8      
## [141] polynom_1.4-1

References

1.
Buenrostro, J. D., Giresi, P. G., Zaba, L. C., Chang, H. Y. & Greenleaf, W. J. Transposition of native chromatin for fast and sensitive epigenomic profiling of open chromatin, DNA-binding proteins and nucleosome position. Nature methods 10, 1213–1218 (2013).
2.
Adey, A. et al. Rapid, low-input, low-bias construction of shotgun fragment libraries by high-density in vitro transposition. Genome biology 11, R119 (2010).
3.
Chen, K. et al. DANPOS: Dynamic analysis of nucleosome position and occupancy by sequencing. Genome research 23, 341–351 (2013).
4.
Pique-Regi, R. et al. Accurate inference of transcription factor binding from DNA sequence and chromatin accessibility data. Genome research 21, 447–455 (2011).