Calling genome-wide differential interactions
Learning objectives
We will use diffHic to:
- Count the read pairs into bins and filter uninteresting bin pairs
- Normalize our libraries to account for different biases
- Estimate biological variability of the replicates
- Obtain our list of differential interactions (DI)
1. Getting HiCUP-mapped data into diffhic
Copy the sorted bam files and the digested genome in your host:
scp lbc@10.10.30.15:/Users/lbc/prac7_data.tar.gz .
tar xvzf prac7_data.tar.gz #uncompress
If you don´t have terminal you first need to enter the docker container:
docker run -i -t lizfernandez/hic-langebio:your_tag /bin/bash
scp lbc@10.10.30.15:/Users/lbc/prac7_data.tar.gz .
tar xvzf prac7_data.tar.gz#uncompress
#In your shell outside of docker:
docker cp <containerId>:/path/file /your/path/
Working on RStudio:
Set your working directory to where your files are
#example
setwd("/Users/americaramirezcolmenero/Documents/hic_workshop_langebio")
# or do: session > set working directory > choose directory
Install the required packages
Packages <- c("diffHic","csaw", "edgeR", "GenomicRanges")
# install biocmanager
if (!requireNamespace("BiocManager", quietly = TRUE))
install.packages("BiocManager")
BiocManager::install()
#bioconductor libraries
BiocManager::install(Packages)
#statmod
install.packages("statmod")
lapply(Packages, library, character.only=T)
library(statmod)
Import HiCUP digest file into R and generate digest genome object
digest <- read.csv("Digest_maize_mini2_DpnII_None_21-28-22_04-11-2019.txt", header=T, sep="\t", skip=1) #change name of the file
#The object zm.frag has the digested genome in the diffHic required format
zm.frag <- with(digest,GRanges(Chromosome, IRanges(Fragment_Start_Position,Fragment_End_Position)))
#lets look at it
zm.frag
## GRanges object with 42569 ranges and 0 metadata columns:
## seqnames ranges strand
## <Rle> <IRanges> <Rle>
## [1] 2:120000001-132000000 1-73 *
## [2] 2:120000001-132000000 74-93 *
## [3] 2:120000001-132000000 94-203 *
## [4] 2:120000001-132000000 204-713 *
## [5] 2:120000001-132000000 714-761 *
## ... ... ... ...
## [42565] 2:120000001-132000000 11999122-11999197 *
## [42566] 2:120000001-132000000 11999198-11999474 *
## [42567] 2:120000001-132000000 11999475-11999740 *
## [42568] 2:120000001-132000000 11999741-11999814 *
## [42569] 2:120000001-132000000 11999815-12000000 *
## -------
## seqinfo: 1 sequence from an unspecified genome; no seqlengths
#Generate pair.param, to store the restriction fragments.
#zm.param will hold other parameters later
zm.param <- pairParam(zm.frag)
zm.param
## Genome contains 42569 restriction fragments across 1 chromosome
## No discard regions are specified
## No limits on chromosomes for read extraction
## No cap on the read pairs per pair of restriction fragments
preparePairs
creates the h5 files needed for counting data into bins, it matches the mapping location of each read with a restriction fragment:
#Do this for every bam file
# Zea mays endosperm
preparePairs("ZmEn_HiC_1_1_2.hicup.sorted.bam", zm.param, file="En1_Zm.h5")
preparePairs("ZmEn_HiC_2_1_2.hicup.sorted.bam", zm.param, file="En2_Zm.h5")
# Zea mays mesophyll
preparePairs("ZmMC_HiC_1_1_2.hicup.sorted.bam", zm.param, file="MC1_Zm.h5")
preparePairs("ZmMC_HiC_2_1_2.hicup.sorted.bam", zm.param, file="MC2_Zm.h5")
Create input, an object that has the name of our files
input <- c("En1_Zm.h5","En2_Zm.h5","MC1_Zm.h5","MC2_Zm.h5")
input
## [1] "En1_Zm.h5" "En2_Zm.h5" "MC1_Zm.h5" "MC2_Zm.h5"
2. COUNTING READS INTO BINS
First we will indicate the size (in base pairs) of our bin
bin.size <- 50000
SquareCounts
is the function that counts our read pairs between the pairs of bins, for all of our libraries, using input, and will store this information in zm_data.
zm_data <- squareCounts(input, zm.param, width=bin.size, filter=1)
Check the structure of zm_data:
zm_data
## class: InteractionSet
## dim: 28909 4
## metadata(2): param width
## assays(1): counts
## rownames: NULL
## rowData names(0):
## colnames: NULL
## colData names(1): totals
## type: ReverseStrictGInteractions
## regions: 240
zm_data contains 515,645 interactions (bin pairs) in all 4 libraries.
head(anchors(zm_data))
## $first
## GRanges object with 28909 ranges and 1 metadata column:
## seqnames ranges strand | nfrags
## <Rle> <IRanges> <Rle> | <integer>
## [1] 2:120000001-132000000 1-50246 * | 163
## [2] 2:120000001-132000000 50247-99992 * | 169
## [3] 2:120000001-132000000 50247-99992 * | 169
## [4] 2:120000001-132000000 99993-150558 * | 186
## [5] 2:120000001-132000000 99993-150558 * | 186
## ... ... ... ... . ...
## [28905] 2:120000001-132000000 11950034-12000000 * | 178
## [28906] 2:120000001-132000000 11950034-12000000 * | 178
## [28907] 2:120000001-132000000 11950034-12000000 * | 178
## [28908] 2:120000001-132000000 11950034-12000000 * | 178
## [28909] 2:120000001-132000000 11950034-12000000 * | 178
## -------
## seqinfo: 1 sequence from an unspecified genome; no seqlengths
##
## $second
## GRanges object with 28909 ranges and 1 metadata column:
## seqnames ranges strand | nfrags
## <Rle> <IRanges> <Rle> | <integer>
## [1] 2:120000001-132000000 1-50246 * | 163
## [2] 2:120000001-132000000 1-50246 * | 163
## [3] 2:120000001-132000000 50247-99992 * | 169
## [4] 2:120000001-132000000 1-50246 * | 163
## [5] 2:120000001-132000000 50247-99992 * | 169
## ... ... ... ... . ...
## [28905] 2:120000001-132000000 11750020-11799897 * | 195
## [28906] 2:120000001-132000000 11799898-11850047 * | 175
## [28907] 2:120000001-132000000 11850048-11900292 * | 191
## [28908] 2:120000001-132000000 11900293-11950033 * | 180
## [28909] 2:120000001-132000000 11950034-12000000 * | 178
## -------
## seqinfo: 1 sequence from an unspecified genome; no seqlengths
Each row is an interaction, each column is a library.
Each bin has nfrags fragments.
Note: The boundary of each bin is rounded to the closest restriction site.
This object also has a count matrix with the number of read pairs for each interaction, in each library
dim(assay(zm_data))
## [1] 28909 4
head(assay(zm_data))
## [,1] [,2] [,3] [,4]
## [1,] 238 309 383 511
## [2,] 149 125 179 187
## [3,] 219 388 290 356
## [4,] 58 39 61 55
## [5,] 117 119 122 117
## [6,] 145 298 224 259
zm_data$totals # total read pairs per library
## [1] 726265 606426 584113 485457
3. FILTERING BIN PAIRS
Visualize the distribution of the average abundance
ave.ab <- aveLogCPM(asDGEList(zm_data))
hist(ave.ab, xlab="Average abundance", col="cadetblue3", main="Zea mays data before filtering")
We filter bin pairs with very low absolute counts
count.keep <- ave.ab >= aveLogCPM(2, lib.size=mean(zm_data$totals))
summary(count.keep)
## Mode FALSE TRUE
## logical 551 28358
zm_data_or <- zm_data # backup the non-filtered data
zm_data <- zm_data[count.keep,] # apply the filter
zm_data
## class: InteractionSet
## dim: 28358 4
## metadata(2): param width
## assays(1): counts
## rownames: NULL
## rowData names(0):
## colnames: NULL
## colData names(1): totals
## type: ReverseStrictGInteractions
## regions: 240
Plot again to check changes
ave.ab <- aveLogCPM(asDGEList(zm_data))
hist(ave.ab, xlab="Average abundance", col="blueviolet", main="Zea mays data after filtering")
Other strategies (apply when having genome-wide data):
#Retain only those bin pairs with abundances x-times higher than the median abundance across inter-chromosomal bin pairs.
direct <- filterDirect(zm_data)
keep <- direct$abundances > log2(3) + direct$threshold
data <- data[keep, ]
4. NORMALIZATION
Generate a MA plot comparing one library of each group
ab <- aveLogCPM(asDGEList(zm_data))
o <- order(ab)
adj.counts <- cpm(asDGEList(zm_data), log=TRUE)
mval <- adj.counts[,3]-adj.counts[,2]
smoothScatter(ab, mval, xlab="A", ylab="M", main="En (2) vs MC (1)")
fit <- loessFit(x=ab, y=mval)
lines(ab[o], fit$fitted[o], col="red")
Perform non-linear normalization
zm_data <- normOffsets(zm_data, se.out=TRUE)
The matrix of offsets has same dimensions as count matrix and is stored as an element of assays slot of the InteractionSet object.
Create object with matrix of offsets
nb.off <- assay(zm_data, "offset")
head(nb.off)
## [,1] [,2] [,3] [,4]
## [1,] -0.232805996 0.2620612 -0.05675096 0.0274957257
## [2,] -0.008384201 0.1884264 -0.06019441 -0.1198478186
## [3,] -0.190152329 0.2487886 -0.05786232 -0.0007739079
## [4,] 0.278478329 0.1172279 -0.08193442 -0.3137718484
## [5,] 0.079734145 0.1595868 -0.06153263 -0.1777883368
## [6,] -0.109757560 0.2230147 -0.05949599 -0.0537611676
Plot after normalization
ab <- aveLogCPM(asDGEList(zm_data))
o <- order(ab)
adj.counts <- cpm(log2(assay(zm_data) + 0.5) - nb.off/log(2))
mval <- adj.counts[,3]-adj.counts[,2]
smoothScatter(ab, mval, xlab="A", ylab="M", main="En (2) vs MC (1) after NLN")
fit <- loessFit(x=ab, y=mval)
lines(ab[o], fit$fitted[o], col="red")
5. MODELING BIOLOGICAL VARIABILITY
The NB model also consider extra-Poisson variability between biological replicates of the same conditon.
Variability is modelled by estimating the dispersion parameter of the NB distribution
Specify design matrix to describe the experimental setup
design <- model.matrix(~factor(c("En", "En", "MC", "MC")))
colnames(design) <- c("Intercept", "MC")
design
## Intercept MC
## 1 1 0
## 2 1 0
## 3 1 1
## 4 1 1
## attr(,"assign")
## [1] 0 1
## attr(,"contrasts")
## attr(,"contrasts")$`factor(c("En", "En", "MC", "MC"))`
## [1] "contr.treatment"
y <- asDGEList(zm_data)
y
## An object of class "DGEList"
## $counts
## Sample1 Sample2 Sample3 Sample4
## 1 238 309 383 511
## 2 149 125 179 187
## 3 219 388 290 356
## 4 58 39 61 55
## 5 117 119 122 117
## 28353 more rows ...
##
## $samples
## group lib.size norm.factors
## Sample1 1 726265 1
## Sample2 1 606426 1
## Sample3 1 584113 1
## Sample4 1 485457 1
##
## $offset
## [,1] [,2] [,3] [,4]
## [1,] 13.06262 13.55749 13.23868 13.32292
## [2,] 13.28704 13.48385 13.23523 13.17558
## [3,] 13.10527 13.54421 13.23756 13.29465
## [4,] 13.57390 13.41265 13.21349 12.98165
## [5,] 13.37516 13.45501 13.23389 13.11764
## 28353 more rows ...
Estimate NB dispersion
y <- estimateDisp(y, design)
y$common.dispersion
## [1] 0.02241369
plotBCV(y)
Estimate QL dispersion
Estimation of QL dispersion is performed to model variability of the dispersions. Note: robust=TRUE protect EB shrinkage against positive outliers (highly variable counts).
library(statmod)
fit <- glmQLFit(y, design, robust=TRUE)
plotQLDisp(fit)
summary(fit$df.prior)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.3139 94.8057 94.8057 93.8711 94.8057 94.8057
plotQLDisp(fit)
6. TESTING FOR SIGNIFICANT INTERACTIONS
QL F-test
result <- glmQLFTest(fit, coef=2)
topTags(result)
## Coefficient: MC
## logFC logCPM F PValue FDR
## 13346 -1.1231542 10.680540 40.05655 7.685422e-09 0.0001291583
## 9865 -1.1290963 10.190604 39.58786 9.109124e-09 0.0001291583
## 19439 -1.0759957 10.492930 36.64549 2.686483e-08 0.0002539443
## 7984 -1.7116681 6.387260 34.67566 5.622975e-08 0.0003986408
## 6493 1.9548499 5.787290 31.51096 2.013291e-07 0.0011418579
## 7997 -0.9821595 10.745390 30.43788 2.873087e-07 0.0013579166
## 16246 -1.9021883 5.909386 28.86006 5.355809e-07 0.0020249001
## 6268 2.0172868 5.567084 28.69803 5.712392e-07 0.0020249001
## 4749 -0.9134636 9.951511 26.16918 1.581572e-06 0.0049833576
## 14507 -1.5570337 6.166139 25.48952 2.088012e-06 0.0057935872
Save significance statistics in rowData of InteractionSet object
rowData(zm_data) <- cbind(rowData(zm_data), result$table)
Plot to visualize
de <- decideTestsDGE(result, p.value=0.05, adjust.method="BH")
debins <- rownames(result)[as.logical(de)]
plotSmear(result, de.tags=debins)
Clustering based on significant bin pairs
clustered.sig <- diClusters(zm_data, result$table, target=0.05, cluster.args=list(tol=1))
length(clustered.sig$interactions)
## [1] 36
head(clustered.sig$interactions)
## ReverseStrictGInteractions object with 6 interactions and 0 metadata columns:
## seqnames1 ranges1 seqnames2
## <Rle> <IRanges> <Rle>
## [1] 2:120000001-132000000 1-50246 --- 2:120000001-132000000
## [2] 2:120000001-132000000 249863-300231 --- 2:120000001-132000000
## [3] 2:120000001-132000000 1549705-1599984 --- 2:120000001-132000000
## [4] 2:120000001-132000000 2749835-2799978 --- 2:120000001-132000000
## [5] 2:120000001-132000000 2900413-2949355 --- 2:120000001-132000000
## [6] 2:120000001-132000000 4800039-4850036 --- 2:120000001-132000000
## ranges2
## <IRanges>
## [1] 1-50246
## [2] 1-50246
## [3] 1450000-1500065
## [4] 199996-249862
## [5] 2900413-2949355
## [6] 4800039-4850036
## -------
## regions: 41 ranges and 0 metadata columns
## seqinfo: 1 sequence from an unspecified genome; no seqlengths
clustered.sig$FDR
## [1] 0.02777778
Create objects for bin pairs identities
‘combineTest’ combines p-values
tabcomdata <- combineTests(clustered.sig$indices[[1]], result$table)
head(tabcomdata)
## DataFrame with 6 rows and 6 columns
## nWindows logFC.up logFC.down PValue FDR
## <integer> <integer> <integer> <numeric> <numeric>
## 1 1 1 0 6.98300823925361e-05 6.98300823925361e-05
## 2 1 1 0 5.19655380695768e-05 5.85404213192927e-05
## 3 1 1 0 4.55319410551155e-06 1.09276658532277e-05
## 4 1 1 0 5.11079907655807e-05 5.85404213192927e-05
## 5 1 1 0 2.03040674036126e-05 3.17802794143501e-05
## 6 1 0 1 1.58157197900456e-06 6.32628791601824e-06
## direction
## <character>
## 1 up
## 2 up
## 3 up
## 4 up
## 5 up
## 6 down
#getBestTest finds bin pair with lowest p-values, to find the strongest change in clusters
tabbestdata <- getBestTest(clustered.sig$indices[[1]], result$table)
head(tabbestdata)
## DataFrame with 6 rows and 6 columns
## best logFC logCPM F
## <integer> <numeric> <numeric> <numeric>
## 1 1 0.755133710812747 9.31224585499152 17.2767111629158
## 2 16 1.02581706935246 6.68036637000534 18.0278849330954
## 3 526 1.06287622995692 6.99529897295963 23.6052430478237
## 4 1545 1.67208692036701 5.43050462963205 17.978084998199
## 5 1770 0.813585444719294 9.66647056081663 20.1118927392774
## 6 4749 -0.913463593687237 9.95151138153677 26.1691841649811
## PValue FDR
## <numeric> <numeric>
## 1 6.98300823925361e-05 6.98300823925361e-05
## 2 5.19655380695768e-05 5.85404213192927e-05
## 3 4.55319410551155e-06 1.09276658532277e-05
## 4 5.11079907655807e-05 5.85404213192927e-05
## 5 2.03040674036126e-05 3.17802794143501e-05
## 6 1.58157197900456e-06 6.32628791601824e-06
Save statistics
tabstat <- data.frame(tabcomdata[,2:6], logFC=tabbestdata$logFC, FDR=clustered.sig$FDR)
result.d <- as.data.frame(clustered.sig$interactions)[,c("seqnames1","start1","end1","seqnames2","start2","end2")]
result.d <- cbind(result.d, tabstat)
o.d <- order(result.d$PValue)
write.table(result.d[o.d,], file="DIclustersData.txt", sep="\t", quote=FALSE, row.names=FALSE)