---
title: "SeSAMe Basic Usage"
date: "`r BiocStyle::doc_date()`"
package: sesame
output: BiocStyle::html_document
fig_width: 6
fig_height: 6
vignette: >
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteIndexEntry{"0. Basic Usage"}
  %\VignetteEncoding{UTF-8}
---

```{r message=FALSE, warning=FALSE, include=FALSE}
library(sesame)
sesameDataCacheAll()
library(dplyr)
options(rmarkdown.html_vignette.check_title = FALSE)
```

# Installation

To install SeSAMe from Bioconductor,
```{r, eval=FALSE}
if (!requireNamespace("BiocManager", quietly=TRUE))
    install.packages("BiocManager")
BiocManager::install("sesame")
```

To install the development version directly from github
```{r, eval=FALSE}
BiocManager::install('zwdzwd/sesameData')
BiocManager::install('zwdzwd/sesame')
```

**CRITICAL:** After a new installation, one needs to cache the associated
annotation data using the following command.
This needs to be done only once per SeSAMe installation.
```{r eval=FALSE}
sesameDataCacheAll()
```
This function caches the needed SeSAMe annotation for all the supported
platforms. One optionally caches only one specific platform (e.g., EPIC)
by calling
```{r eval=FALSE}
sesameDataCache("EPIC")
```

SeSAMe annotation data is managed by the
[sesameData](https://bioconductor.org/packages/release/
data/experiment/html/sesameData.html) package which is based on the
[ExperimentHub](https://bioconductor.org/packages/release/
bioc/html/ExperimentHub.html) package. You can find the path of
the annotation data storage on your local computer using

```{r}
tools::R_user_dir("ExperimentHub", which="cache")
```

# Beta Value Extraction

The raw Infinium BeadChip data are stored in IDAT files. Each sample has
two IDAT files and they correspond to the red and green signal respectively.
Green and red files for the same samples should always share the same sample
name prefix.
For example, `204529320035_R06C01_Red.idat` and
`204529320035_R06C01_Grn.idat` correspond to the red and green signal
of one sample. In SeSAMe, we will use the **common prefix**,
i.e. `204529320035_R06C01`, to refer to that sample.
Sesame recognize both the raw IDAT as well as gzipped IDATs which are common
in data stored in [GEO](https://www.ncbi.nlm.nih.gov/geo/).
For example, in addition to the example above, SeSAMe also recognizes
`GSM2178224_Red.idat.gz` and `GSM2178224_Grn.idat.gz`.

The function `readIDATpair` function reads in the signal intensity data from
the IDAT pairs. The function takes the **common prefix** as input and outputs
a `SigDF` object. Using the two examples above, one would run the following
commands.
```{r eval=FALSE}
sdf = readIDAT("204529320035_R06C01")  # Example 1
sdf = readIDAT("GSM2178224")           # Example 2
```
Note that SeSAMe automatically detects and matches up the green and red signal
files for the same sample.
We will get back to the structure of `SigDF` below.

DNA methylation level (aka beta values) are defined as
$$\beta = M / (M+U)$$
with $M$ representing the signal from methylated allele and $U$
representing the unmethylated signal.
It can be retrieved calling the `getBetas` function on the `SigDF`.
The output is a named vector with probe ID as names.
For example, the following commands read in one
sample and convert it to $\beta$ values.
```{r echo=FALSE}
sdf = sesameDataGet('EPIC.1.SigDF')
```

```{r eval=FALSE}
sdf = readIDAT("GSM2178224")
```

```{r}
print(sdf)
head(as.data.frame(sdf)) # peek into the internals
betas = getBetas(sdf)
head(betas)
```

**CRITICAL:** `getBetas` takes a single `SigDF`
object as input instead of a list of them. A common mistake is to `c`-merge
multiple `SigDF`s. To combine multiple `SigDF`s, one can use `list()` instead.
To have it process many `SigDF`s, we
should combine that with looping functions `lapply` or `mclapply`s, or using
the `openSesame` pipeline (see below).

## Search IDAT Prefixes

Most often we will be working with a folder that contains many IDATs. Here is
where the `searchIDATprefixes` function comes in handy. It lets us search all
the IDATs in a folder and its subfolders recursively. Combine this with the
R looping functions lets you process many IDATs without having to specify
all IDAT names. `searchIDATprefixes` returns a named vector of prefixes with
associated `Red` and `Grn` files, for `readIDATpair`:
```{r}
sdfs = lapply(
    searchIDATprefixes(system.file("extdata/", package = "sesameData")),
    readIDATpair)
sdfs
```
A simple list of "SigSet"s are returned.

## Custom-made Array

If you need to deal with a custom-made array instead of the standard
array (MM285, EPIC, HM450 etc) supported natively by SeSAMe, you would need
to provide a manifest that describe the probe information. You should be
able to obtain that from the Illumina support website.
The manifest should be made into a simple data frame with a minimum of
four columns: `Probe_ID`, `M`, `U` and `col`.
The easiest way to make the the manifest compatible with SeSAMe is by
following internal manifests for a SeSAME-supported platform. They can be
retrieved with the `sesameDataGet` function:
```{r}
mft = sesameDataGet("MM285.address")$ordering
```

The `col` is either `G` (stand for Green) or `R` (stand for Red) or `2` (
stand for both in the case of Infinium II design).
For Infinium-II probes, the `M` column and `col` column is left to be `NA`.
For example, one can check that both `M` and `col` columns are filled with
the Infinium-I probes:
```{r}
head(mft[!is.na(mft$col),])
```
The last column `mask` is a logical vector that defines the default masking
behavior of SeSAMe for the platform (see below for discussion of NA-masking).

With the manifest, your data can be processed using
```{r eval = FALSE}
sdf = readIDATpair("your_sample_name", manifest = mft)
betas = getBetas(sdf)
```

## Sum Two-channels

$\beta$ values for Infinium-I probes can also be obtained by summing up the two
in-band channel and out-of-band channel. This rescues probes with SNP hitting
the extension base and hence switching color channel. More details can be
found in
[Zhou et al 2017](https://academic.oup.com/nar/article/45/4/e22/2290930).
```{r}
library(sesame)
## show case using an example without mask, then add mask with qualityMask
sdf = qualityMask(sesameDataGet('EPIC.1.SigDF'))
betas = getBetas(sdf, sum.TypeI = TRUE)
```

For color-channel-switching probes, extra SNP allele frequencies can be obtained
by summing up methylated and umethylated alleles:
```{r}
extraSNPAFs = getAFTypeIbySumAlleles(sdf)
```

# NA-Masking

If you call getBetas as is, you may notice that some of the beta values
show up having NA values. This NA-masking is controlled internally using
the `mask` column in `SigDF`. E.g.,
```{r message = FALSE}
betas = getBetas(sdf)
head(betas)
```

To check probes to be NA-masked in a `SigSet`, one can use the `mask`
function
```{r}
head(sdf$mask)
sum(sdf$mask)   # number of probes to be NA-masked
sum(is.na(betas)) # should be the same as above
```

Please be noted that `mask` in `SigDF` does not actually remove the probe
reading but only specify how SeSAMe currently views the measurement. One can
add more probes to the mask with the `addMask` function. Other functions
such as the detection p-value calculation, also modifies `mask`. NA-masking
influences other normalization and preprocessing functions. Therefore one
should set it carefully. If one does not do any explicit masking, one simply
gets the masking specified in the `mask` column of the manifest. This defines
the default masking behavior of SeSAMe. For more details of masking, one can
refer to
[Zhou et al 2017](https://academic.oup.com/nar/article/45/4/e22/2290930).
One can override this masking by the `resetMask` function
```{r}
sum(sdf$mask)  # before resetting
sum(resetMask(sdf)$mask)  # after resetting, expect 0
```
The `getBetas` function can also ignore NA-masking while extracting beta
values:
```{r}
sum(is.na(getBetas(sdf, mask=FALSE)))  # expect 0
```

We recommend two types of probe masking:

1) **Experiment-dependent Probe Masking** based on signal detection p-value
([Zhou et al. 2018](https://www.ncbi.nlm.nih.gov/pubmed/30085201)). Probes with
p-value higher than a threshold (default: 0.05) are masked (see following for
detection p-value calculation using the `pOOBAH` method).

2) **Experiment-independent Probe Masking** due to design issues. This is
typically designated in the `mask` column of the manifest (see 
[Zhou et al. 2017](https://www.ncbi.nlm.nih.gov/pubmed/27924034)): This masking
supports EPIC, MM285, HM450 and HM27 and is turned on by default and can also be
explicitly added by the function `qualityMask`.

# Preprocessing

## Detection p-value

As mentioned above, experiment-dependent masking based on signal detection
p-values is effective in excluding artifactual methylation level reading
and probes with too much influence from signal background. We recommend the
`pOOBAH` algorithm that was based on Infinium-I probe out-of-band signal for
calibrating the distribution of the signal background:
```{r}
sum(sdf$mask)  # before pOOBAH
sum(pOOBAH(sdf)$mask) # after pOOBAH, expect more probes
sum(pOOBAH(resetMask(sdf))$mask) # pOOBAH-only masking
```

Sometimes one would want to calculation detection p-value without creating
masking like in the case of having to upload the p-value to GEO. In those cases
one can use the `return.pval` option and add pvalue-based mask later.
```{r}
pvals = pOOBAH(sdf, return.pval=TRUE)
sdf2 = addMask(sdf, pvals > 0.05) # default, recommend between 0.05 and 0.2
```

## Background Subtraction

SeSAMe implements the
background subtraction based on normal-exponential deconvolution using
out-of-band probes `noob`
([Triche et al. 2013](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3627582/))
and optionally with extra bleed-through subtraction. Signal bleed-through
happens when measurement from one channel affects the measurement in the other
channel. SeSAMe's `noob` further removes residual background by regressing
out the green-to-red and red-to-green relationship using Type-I probes.
```{r}
sdf.nb = noob(sdf)          # noob background subtraction
sdf.sb = scrub(sdf)         # more aggressive background subtraction
```

One can use following beta to total signal intensity to check the effect of
background subtraction.
```{r fig.width=5}
sesamePlotIntensVsBetas(sdf)
sesamePlotIntensVsBetas(sdf.nb) # with noob
sesamePlotIntensVsBetas(sdf.sb) # with scrub
```

## Dye Bias Correction

Dye bias refers to the difference in signal intensity between the two color
channel. SeSAMe offers two flavors of dye bias correction: linear scaling
(`dyeBiasCorr`) and nonlinear scaling (`dyeBiasCorrTypeINorm`). Linear scaling
equalize the mean of all probes from the two color channel.
```{r fig.width=5}
library(sesame)
sdf.dbLinear = dyeBiasCorr(sdf)
sesamePlotRedGrnQQ(sdf.dbLinear)
```

Residual dye bias can be corrected using nonlinear quantile interpolation with
Type-I probes.
```{r}
sdf.dbNonlinear = dyeBiasCorrTypeINorm(sdf)
```
Under this correction, Infinium-I Red probes and
Infinium-I Grn probes have the same distribution of signal.
```{r fig.width=5}
sesamePlotRedGrnQQ(sdf.dbNonlinear)
```

Note that linear scaling does not shift beta values of Type-I probes while
nonlinear scaling does shift beta values of Type-I probes.

## Channel Inference
Sometimes Infinium-I channel spec is inaccurate in the manifest. We can
infer the channel using data.
```{r}
sdf.InfICorrected = inferInfiniumIChannel(sdf, verbose=TRUE)
```
# The openSesame Pipeline

We have discussed noob, nonlinear dye bias correction and pOOBAH. Put
together, this can be implemented as follows
```{r eval = FALSE}
idat_dir = system.file("extdata/", package = "sesameData")
betas = do.call(cbind, mclapply(searchIDATprefixes(idat_dir), function(pfx) {
    getBetas(dyeBiasNL(noob(pOOBAH(readIDATpair(pfx)))))
}), mc.cores=2)
```
Here we use two cores with `mc.cores=2`.

Equivalently, sesame provides the **openSesame** pipeline
```{r eval = FALSE}
idat_dir = system.file("extdata/", package = "sesameData")
betas = openSesame(idat_dir)
```
as a quickstart default. Here `idat_dir` is the directory containing all
the IDAT files. Multi-core processing can be invoked using the BiocParallel:
```{r eval = FALSE}
betas = openSesame(idat_dir, BPPARAM=BiocParallel::MulticoreParam(2))
```
Like readIDATpair, `openSesame` also works with custom-made array with a
manifest file (see above):

```{r eval = FALSE}
betas = openSesame(idat_dir, manifest = manifest)
```

# The SigDF Class

SeSAMe design includes alight-weight full exposure of internal signal
intensities (essential information for users of Illumina methylation array data,
as demonstrated in Zhou et al 2018), which permits sensitive and specific joint
inference on copy number and DNA methylation.

Central to the SeSAMe platform is the `SigDF` data structure,
a `data.frame` subclass with the following column names
```{r}
colnames(sdf)
```
The `col` column specifies the color channel and takes `G`, `R` and `2`.
The Infinium-I probes carry `G` and `R`
```{r}
head(sdf[sdf$col != "2",])      # Infinium-I probes
head(sdf[sdf$col == "2",])      # Infinium-II probes
```

The `controls` attributes may contain the control probe information. This is
not a mandatory field for valid `SigDF`
```{r}
## same as head(attr(sdf, "controls"))
head(controls(sdf))  # the last column: the type of the control.
```

For control probes, signal intensities are stored as an `Nx2` numeric
matrix, with `N` representing the number of probes in the class. The two
columns of the matrix represent the methylated probe intensity and the 
unmethylated probe intensity.

## IO with tsv/csv files

`SigDF` can be written as and read from plain text file with the compliant
column names (i.e., `r colnames(sdf)`).
```{r}
txt_file_path = sprintf("%s/sigdf.txt", tempdir())
sdf_write_table(sdf, file=txt_file_path) # save as space-delimited txt
sdf2 = sdf_read_table(txt_file_path)     # read back

tsv_file_path = sprintf("%s/sigdf.tsv", tempdir())
sdf_write_table(sdf, file=tsv_file_path, sep="\t", quote=FALSE) # save as tsv
sdf2 = sdf_read_table(tsv_file_path)                            # read back

csv_file_path = sprintf("%s/sigdf.csv", tempdir())
sdf_write_table(sdf, file=csv_file_path, sep=",") # save as csv
sdf2 = sdf_read_table(csv_file_path, sep=",")     # read back
```

## Interaction with SigSet

Previously, the signal was implemented an S4 implementation in `SigSet`
complies with Bioconductor guidelines, and for backwards compatibility,
`SigSet` can be transformed to a `SigDF` using the `SigSetToSigDF` function
`sesame:::SigSetToSigDF(sset)`.

## Interaction with Minfi

`SigSet` can be converted back and forth from Minfi `RGChannelSet` in multiple
ways. One can sesamize a minfi `RGChannelSet` which returns a `GenomicRatioSet`.
Here we are illustrating using the `FlowSorted.Blood.450k` object, which is 
distributed in the `minfi::RGChannelSet`.
```{r message = FALSE}
library(sesame)
library(FlowSorted.Blood.450k)
options(rmarkdown.html_vignette.check_title = FALSE)
```

```{r}
grSet <- sesamize(FlowSorted.Blood.450k[,1:4])
grSet
```

# Session Info

```{r}
sessionInfo()
```
