Nothing
.onLoad <- function(libname, pkgname) {
rJava::.jpackage(pkgname, lib.loc=libname)
}
#' Suffix Array Kernel Smoothing
#'
#' Sarks class implements suffix array kernel smoothing for de novo
#' correlative motif discovery.
#'
#' @param fasta specification of fasta file containing sequences to be
#' analyzed; may also be a *named* character vector or
#' XStringSet whose elements are sequences to be analyzed.
#'
#' @param scores specification of scores associated with sequences in
#' fasta argument; can be provided as two column tab-delimited
#' file (should have header, first column should provide sequence
#' names identical to those in fasta argument, second column
#' should have numeric scores) or may be a named numeric vector.
#'
#' @param halfWindow half-width of smoothing window (integer).
#'
#' @param spatialLength full length of spatial smoothing window
#' (integer); use 0 to disable spatial smoothing.
#'
#' @param nThreads number of threads to use for computing permutation
#' distributions.
#'
#' @return R representation of java Sarks object.
#'
#' @references Wylie, D.C., Hofmann, H.A., and Zemelman, B.V. (2019)
#' SArKS: de novo discovery of gene expression regulatory motif
#' sites and domains by suffix array kernel smoothing,
#' Bioinformatics, Vol. 35(20), 3944-3952
#'
#' \url{https://academic.oup.com/bioinformatics/article-abstract/35/20/3944/5418797}
#'
#' @examples
#' data(simulatedSeqs, simulatedScores)
#' sarks <- Sarks(simulatedSeqs, simulatedScores, 4, 0, 1)
#'
#' @export
#' @importFrom Biostrings BStringSet
#' @importFrom Biostrings writeXStringSet
#' @importFrom rJava .jnew
#' @importFrom utils write.table
Sarks <- function(fasta, scores, halfWindow, spatialLength=0L, nThreads=1L) {
if (length(fasta) > 1) {
tmpfasta <- tempfile(pattern='seq')
writeXStringSet(BStringSet(fasta), tmpfasta, format='fasta')
fasta <- tmpfasta
}
if (length(scores) > 1) {
tmpscores <- tempfile(pattern='scores')
write.table(
data.frame(
block = names(scores),
score = scores,
stringsAsFactors = FALSE
),
tmpscores,
sep = '\t',
quote = FALSE,
row.names = FALSE
)
scores <- tmpscores
}
sarks <- .jnew(
'dcw/sarks/Sarks', fasta, scores,
as.integer(halfWindow), as.integer(spatialLength),
as.integer(nThreads), TRUE
)
if (exists('tmpfasta')) {suppressWarnings(file.remove(tmpfasta))}
if (exists('tmpscores')) {suppressWarnings(file.remove(tmpscores))}
return(sarks)
}
#' Smoothing window and Gini impurity filter settings
#'
#' Sarks methodology involves testing a range of different filter
#' parameter values; sarksFilters builds set of filters with all
#' combinations of desired halfWindow, spatialLength, and minGini
#' values.
#'
#' @param halfWindow integer vector of halfWindow values to test.
#'
#' @param spatialLength integer vector of spatialLength values to
#' test; use a single 0 value to disable spatial smoothing.
#'
#' @param minGini numeric vector giving minimum Gini impurity value(s)
#' for suffix position to be analyzed; use a value above 1 to
#' calculate minimum Gini impurity based on median of observed
#' Gini impurities so as to constrain variance under permutation
#' testing to less than minGini multiples of median variance.
#'
#' @return R representation of java object containing specified
#' combinations of filter parameters for running permutation
#' tests.
#'
#' @references Wylie, D.C., Hofmann, H.A., and Zemelman, B.V. (2019)
#' SArKS: de novo discovery of gene expression regulatory motif
#' sites and domains by suffix array kernel smoothing,
#' Bioinformatics, Vol. 35(20), 3944-3952
#'
#' \url{https://academic.oup.com/bioinformatics/article-abstract/35/20/3944/5418797}
#'
#' @examples
#' data(simulatedSeqs, simulatedScores)
#' sarks <- Sarks(simulatedSeqs, simulatedScores, 4, 0, 1)
#' filters <- sarksFilters(
#' halfWindow=c(4, 8), spatialLength=c(0, 5), minGini=1.1)
#'
#' @export
#' @importFrom rJava .jnew
sarksFilters <- function(halfWindow, spatialLength, minGini=1.1) {
filters <- .jnew('java/util/ArrayList')
for (hw in halfWindow) {for (sl in spatialLength) {for (mg in minGini) {
pars <- .jnew('java/util/HashMap')
pars$put('halfWindow', .jnew('java/lang/Integer', as.integer(hw)))
pars$put('spatialLength', .jnew('java/lang/Integer', as.integer(sl)))
pars$put('minGini', .jnew('java/lang/Double', mg))
pars$put('minSpatialGini', .jnew('java/lang/Double', mg))
filters$add(pars)
}}}
return(filters)
}
#' Estimating distribution of maximum smoothed sequence scores under
#' permutation
#'
#' Run permutation test using the specified number of repetitions,
#' keeping track of maximum observed windowed and spatially-windowed
#' smoothed scores for each combination of filter parameters for each
#' permutation.
#'
#' @param sarks Sarks object to test.
#'
#' @param reps integer specifying how many repetitions to test.
#'
#' @param filters output from sarksFilters function indicating what
#' combinations of filter parameters halfWindow, spatialLength,
#' and minGini to use.
#'
#' @param seed optional seed for random number generator (use in case
#' reproducibility of output is desired).
#'
#' @return named list with three elements: `windowed' containing a
#' data.frame with the maximum smoothed scores for each
#' permutation at each combination of filter parameter values,
#' `spatial' containing a data.frame with the maximum
#' spatially-smoothed scores for each permutation and each filter
#' parameter specification, and `.java' containing the R
#' representation of the java object encoding this information.
#'
#' @references Wylie, D.C., Hofmann, H.A., and Zemelman, B.V. (2019)
#' SArKS: de novo discovery of gene expression regulatory motif
#' sites and domains by suffix array kernel smoothing,
#' Bioinformatics, Vol. 35(20), 3944-3952
#'
#' \url{https://academic.oup.com/bioinformatics/article-abstract/35/20/3944/5418797}
#'
#' @examples
#' data(simulatedSeqs, simulatedScores)
#' sarks <- Sarks(simulatedSeqs, simulatedScores, 4, 0, 1)
#' filters <- sarksFilters(halfWindow=4, spatialLength=0, minGini=1.1)
#' permDist <- permutationDistribution(sarks, 250, filters, seed=123)
#'
#' @export
#' @importFrom rJava .jnew
#' @importFrom rJava J
#' @importFrom utils read.table
permutationDistribution <- function(sarks, reps, filters, seed=NULL) {
jreps <- .jnew('java/lang/Integer', as.integer(reps))
if (length(seed) == 0) {
obj <- sarks$permutationDistribution(jreps, filters)
} else {
obj <- sarks$permutationDistribution(jreps, filters, as.integer(seed))
}
jw <- J('dcw/sarks/Sarks')$printPermDist(filters, obj, NULL, FALSE)
js <- J('dcw/sarks/Sarks')$printPermDist(filters, obj, NULL, TRUE)
out <- list(
windowed = read.table(
textConnection(jw),
sep='\t', header=TRUE, row.names=NULL, na.strings='null',
stringsAsFactors=FALSE, check.names=FALSE
),
spatial = read.table(
textConnection(js),
sep='\t', header=TRUE, row.names=NULL, na.strings='null',
stringsAsFactors=FALSE, check.names=FALSE
),
.java = obj
)
return(out)
}
#' Set smoothed score thresholds based on permutation distribution
#'
#' Calculate thresholds for SArKS k-mer calling from permutation distribution.
#'
#' @param filters output from sarksFilters function indicating what
#' combinations of filter parameters halfWindow, spatialLength,
#' and minGini to use.
#'
#' @param permDist output from permutationDistribution function.
#'
#' @param nSigma number of standard deviations above mean of
#' permutation distribution at which to set threshold for either
#' windowed or spatially-windowed score.
#'
#' @return named list with two elements: `theta' containing a
#' data.frame with the threshold information and `.java'
#' containing an R representation of the java object with this
#' information.
#'
#' @references Wylie, D.C., Hofmann, H.A., and Zemelman, B.V. (2019)
#' SArKS: de novo discovery of gene expression regulatory motif
#' sites and domains by suffix array kernel smoothing,
#' Bioinformatics, Vol. 35(20), 3944-3952
#'
#' \url{https://academic.oup.com/bioinformatics/article-abstract/35/20/3944/5418797}
#'
#' @examples
#' data(simulatedSeqs, simulatedScores)
#' sarks <- Sarks(simulatedSeqs, simulatedScores, 4, 0, 1)
#' filters <- sarksFilters(halfWindow=4, spatialLength=0, minGini=1.1)
#' permDist <- permutationDistribution(sarks, 250, filters, seed=123)
#' thresholds <- permutationThresholds(filters, permDist, nSigma=2.0)
#'
#' @export
#' @importFrom rJava J
#' @importFrom utils read.table
permutationThresholds <- function(filters, permDist, nSigma=4.0) {
obj <- J('dcw/sarks/SarksUtilities')$thresholdsFromPermutations(
permDist$.java, nSigma)
jThresh <- J('dcw/sarks/Sarks')$printThresholds(filters, obj, NULL)
return(list(
theta = read.table(
textConnection(jThresh),
sep='\t', header=TRUE, row.names=NULL, na.strings='null',
stringsAsFactors=FALSE, check.names=FALSE
),
.java = obj
))
}
#' Call k-mer peaks
#'
#' SArKS identifies sets of short subsequences (k-mers) whose presence
#' as substrings of sequences from the input sequence set tends to be
#' associated with elevated sequence scores. Such k-mers are
#' identified as ``peaks'' where kernel-smoothed scores exceed
#' specified thresholds (generally set by permutation method).
#'
#' @param sarks Sarks object to use for k-mer peak calling.
#'
#' @param filters output from sarksFilters function indicating what
#' combinations of filter parameters halfWindow, spatialLength,
#' and minGini to use.
#'
#' @param thresholds output from permutationThresholds specifying
#' thresholds for k-mer peak calling.
#'
#' @param peakify logical value specifying whether to restrict output
#' to only spatial positions at which the smoothed score is at
#' least as high as either neighboring position or not.
#'
#' @param kMax integer value indicating the maximum k-mer length to be
#' reported.
#'
#' @return data.frame containing called k-mer peak information.
#'
#' @references Wylie, D.C., Hofmann, H.A., and Zemelman, B.V. (2019)
#' SArKS: de novo discovery of gene expression regulatory motif
#' sites and domains by suffix array kernel smoothing,
#' Bioinformatics, Vol. 35(20), 3944-3952
#'
#' \url{https://academic.oup.com/bioinformatics/article-abstract/35/20/3944/5418797}
#'
#' @examples
#' data(simulatedSeqs, simulatedScores)
#' sarks <- Sarks(simulatedSeqs, simulatedScores, 4, 0, 1)
#' filters <- sarksFilters(halfWindow=4, spatialLength=0, minGini=1.1)
#' permDist <- permutationDistribution(sarks, 250, filters, seed=123)
#' thresholds <- permutationThresholds(filters, permDist, nSigma=2.0)
#' peaks <- kmerPeaks(sarks, filters, thresholds)
#'
#' @export
#' @importFrom rJava .jnew
#' @importFrom utils read.table
kmerPeaks <- function(sarks, filters, thresholds, peakify=TRUE, kMax=12L) {
kMax <- as.integer(kMax)
eyes <- sarks$filter(
filters, thresholds$.java, .jnew('java/lang/Boolean', peakify)
)
jOut <- sarks$printPeaks(filters, thresholds$.java, eyes, kMax)
out <- read.table(
textConnection(jOut),
sep='\t', header=TRUE, row.names=NULL, na.strings='null',
stringsAsFactors=FALSE, check.names=FALSE
)
out$block <- as.character(out$block)
return(out)
}
#' Identify and merge k-mer sub-peaks within multi-motif domains
#'
#' When spatials smoothing is employed, SArKS identifies spatial
#' windows containing elevated spatially-averaged sequence-smoothed
#' scores (multi-motif domains, or MMDs). This function finds k-mers
#' within these MMDs whose sequence-smoothed scores are above the
#' threshold used for MMD calling and merges such k-mers when their
#' spatial positions overlap.
#'
#' @param sarks Sarks object to use for k-mer peak calling.
#'
#' @param filters output from sarksFilters function indicating what
#' combinations of filter parameters halfWindow, spatialLength,
#' and minGini to use.
#'
#' @param thresholds output from permutationThresholds specifying
#' thresholds for k-mer peak calling.
#'
#' @param peakify logical value specifying whether to restrict initial
#' k-mer peak calling to only spatial positions at which the
#' smoothed score is at least as high as either neighboring
#' position (or not).
#'
#' @param kMax integer value indicating the maximum k-mer length for
#' initial k-mer peak calling.
#'
#' @return data.frame containing called k-mer peak information.
#'
#' @references Wylie, D.C., Hofmann, H.A., and Zemelman, B.V. (2019)
#' SArKS: de novo discovery of gene expression regulatory motif
#' sites and domains by suffix array kernel smoothing,
#' Bioinformatics, Vol. 35(20), 3944-3952
#'
#' \url{https://academic.oup.com/bioinformatics/article-abstract/35/20/3944/5418797}
#'
#' @examples
#' data(simulatedSeqs, simulatedScores)
#' sarks <- Sarks(simulatedSeqs, simulatedScores, 4, 3, 1)
#' filters <- sarksFilters(halfWindow=4, spatialLength=3, minGini=1.1)
#' permDist <- permutationDistribution(sarks, 250, filters, seed=123)
#' thresholds <- permutationThresholds(filters, permDist, nSigma=4.0)
#' mergedSubPeaks <- mergedKmerSubPeaks(sarks, filters, thresholds)
#'
#' @export
#' @importFrom rJava .jnew
#' @importFrom utils read.table
mergedKmerSubPeaks <- function(
sarks, filters, thresholds, peakify=TRUE, kMax=12L) {
kMax <- as.integer(kMax)
eyes <- sarks$filter(
filters, thresholds$.java, .jnew('java/lang/Boolean', peakify)
)
mergedKmerIntervals <- sarks$multiMergeKmerIntervals(
eyes, thresholds$.java, filters, kMax
)
jOut <- sarks$printMergedSubPeaks(
filters, thresholds$.java, mergedKmerIntervals, NULL, kMax
)
out <- read.table(
textConnection(jOut),
sep='\t', header=TRUE, row.names=NULL, na.strings='null',
stringsAsFactors=FALSE, check.names=FALSE
)
out$block <- as.character(out$block)
return(out)
}
#' Estimating SArKS false positive rate
#'
#' Run second permutation test using the specified number of
#' repetitions, keeping track of maximum observed windowed and
#' spatially-windowed smoothed scores for each combination of filter
#' parameters for each permutation, and comparing these values to
#' thresholds determined by first round of permutation testing.
#'
#' @param sarks Sarks object to test.
#'
#' @param reps integer specifying how many repetitions to test.
#'
#' @param filters output from sarksFilters function indicating what
#' combinations of filter parameters halfWindow, spatialLength,
#' and minGini to use.
#'
#' @param thresholds output from permutationThresholds specifying
#' thresholds for k-mer peak calling.
#'
#' @param seed optional seed for random number generator (use in case
#' reproducibility of output is desired).
#'
#' NOTE: do not use the same seed passed to initial
#' permutationDistribution call used to set thresholds.
#'
#' @param conf.level level of confidence to be used in the false
#' positive rate confidence interval.
#'
#' @return named list with three elements: `permutation' containing
#' the output from permutationDistribution run.
#'
#' @references Wylie, D.C., Hofmann, H.A., and Zemelman, B.V. (2019)
#' SArKS: de novo discovery of gene expression regulatory motif
#' sites and domains by suffix array kernel smoothing,
#' Bioinformatics, Vol. 35(20), 3944-3952
#'
#' \url{https://academic.oup.com/bioinformatics/article-abstract/35/20/3944/5418797}
#'
#' @examples
#' data(simulatedSeqs, simulatedScores)
#' sarks <- Sarks(simulatedSeqs, simulatedScores, 4, 0, 1)
#' filters <- sarksFilters(halfWindow=4, spatialLength=0, minGini=1.1)
#' permDist <- permutationDistribution(sarks, 250, filters, seed=123)
#' thresholds <- permutationThresholds(filters, permDist, nSigma=2.0)
#' fpr <- estimateFalsePositiveRate(
#' sarks, 250, filters, thresholds, seed=123456)
#'
#' @export
#' @importFrom binom binom.exact
#' @importFrom rJava J
estimateFalsePositiveRate <- function(
sarks, reps, filters, thresholds, seed=NULL, conf.level=0.95) {
if (length(seed) == 0) {
permTest <- permutationDistribution(sarks, as.integer(reps), filters)
} else {
permTest <- permutationDistribution(
sarks, as.integer(reps), filters, as.integer(seed))
}
falsePositives <- J('dcw/sarks/SarksUtilities')$falsePositives(
permTest$.java,
thresholds$.java
)
return(list(
fp = falsePositives,
ci = binom.exact(falsePositives, reps, conf.level),
permutations = permTest
))
}
#' Prune nested k-mer intervals from called k-mer peak set.
#'
#' Every k-mer identified by SArKS is derived as a substring defined
#' by the interval running position i to position i+k-1 of the
#' concatenation of all input sequences. In some cases a j-mer (with j
#' < k) may be separately identified as a peak by SArKS for which the
#' j-mer interval is entirely contained within [i, i+k-1]; this
#' function removes such nested intervals from the reported collection
#' of peaks.
#'
#' @param intervals data.frame containing called k-mer peaks
#' information (format as output from kmerPeaks function).
#'
#' @param start name of column in intervals data.frame containing
#' interval start coordinates
#'
#' @param end name of column in interval data.frame containing
#' interval end coordinates; if no such column present, default
#' NULL value indicates that end coordinates should be obtained by
#' adding nchar(intervals$kmer) to the start coordinates to obtain
#' end coordinates.
#'
#' @return modified data.frame containing called k-mer peaks
#' information (format as output from kmerPeaks function).
#'
#' @references Wylie, D.C., Hofmann, H.A., and Zemelman, B.V. (2019)
#' SArKS: de novo discovery of gene expression regulatory motif
#' sites and domains by suffix array kernel smoothing,
#' Bioinformatics, Vol. 35(20), 3944-3952
#'
#' \url{https://academic.oup.com/bioinformatics/article-abstract/35/20/3944/5418797}
#'
#' @examples
#' data(simulatedSeqs, simulatedScores)
#' sarks <- Sarks(simulatedSeqs, simulatedScores, 4, 0, 1)
#' filters <- sarksFilters(halfWindow=4, spatialLength=0, minGini=1.1)
#' permDist <- permutationDistribution(sarks, 250, filters, seed=123)
#' thresholds <- permutationThresholds(filters, permDist, nSigma=2.0)
#' peaks <- kmerPeaks(sarks, filters, thresholds)
#' prunedPeaks <- pruneIntervals(peaks)
#' @export
#' @importFrom IRanges findOverlaps
#' @importFrom IRanges from
#' @importFrom IRanges IRanges
#' @importFrom IRanges NCList
pruneIntervals <- function(intervals, start='s', end=NULL) {
starts <- intervals[[start]]
if (length(end) == 0) {
ends <- starts + nchar(intervals$kmer)
} else {
ends <- intervals[[end]]
}
ncl <- NCList(IRanges(starts, ends))
nests <- findOverlaps(
ncl, type='within', drop.self=TRUE, drop.redundant=TRUE)
if (length(from(nests)) > 0) {
intervals <- intervals[-from(nests), ]
}
return(intervals)
}
#' Extend k-mers in length where possible
#'
#' Extend k-mers when adding flanking characters from region in
#' input sequence from which they are derived would result in another
#' reported l-mer string (l > k).
#'
#' @param sarks Sarks object used to obtain k-mer peak call set.
#'
#' @param sarksTable data.frame containing called k-mer peaks
#' information (format as output from kmerPeaks function).
#'
#' @return modified data.frame containing called k-mer peaks
#' information (format as output from kmerPeaks function).
#'
#' @references Wylie, D.C., Hofmann, H.A., and Zemelman, B.V. (2019)
#' SArKS: de novo discovery of gene expression regulatory motif
#' sites and domains by suffix array kernel smoothing,
#' Bioinformatics, Vol. 35(20), 3944-3952
#'
#' \url{https://academic.oup.com/bioinformatics/article-abstract/35/20/3944/5418797}
#'
#' @examples
#' data(simulatedSeqs, simulatedScores)
#' sarks <- Sarks(simulatedSeqs, simulatedScores, 4, 0, 1)
#' filters <- sarksFilters(halfWindow=4, spatialLength=0, minGini=1.1)
#' permDist <- permutationDistribution(sarks, 250, filters, seed=123)
#' thresholds <- permutationThresholds(filters, permDist, nSigma=2.0)
#' peaks <- kmerPeaks(sarks, filters, thresholds)
#' prunedPeaks <- pruneIntervals(peaks)
#' extendedPeaks <- extendKmers(sarks, prunedPeaks)
#'
#' @export
extendKmers <- function(sarks, sarksTable) {
maxLen <- max(nchar(sarksTable$kmer))
oldS <- rep(-1, nrow(sarksTable))
while (!all(oldS == sarksTable$s)) {
oldS <- sarksTable$s
kmerSet <- unique(sarksTable$kmer)
kmerLens <- nchar(sarksTable$kmer)
for (idx in which(kmerLens < maxLen)) {
doBreak <- FALSE
s <- sarksTable[idx, 's']
kmerLen <- nchar(sarksTable[idx, 'kmer'])
shiftInt <- c(s, s+kmerLen)
for (ext in (maxLen-kmerLen):0) {
for (shift in (-ext):0) {
shiftInt <- c(s+shift, s+kmerLen+ext+shift)
shiftKmer <- sarks$kmer(
as.integer(shiftInt[1]),
as.integer(shiftInt[2]-shiftInt[1])
)
if (shiftKmer %in% kmerSet) {
sarksTable[idx, 's'] <- sarksTable[idx, 's'] + shift
sarksTable[idx, 'i'] <- sarks$s2i(sarksTable[idx, 's'])
sarksTable[idx, 'wi'] <- sarksTable[idx, 'wi'] + shift
sarksTable[idx, 'kmer'] <- shiftKmer
sarksTable[idx, 'khat'] <- NA
sarksTable[idx, 'gini'] <- NA
sarksTable[idx, 'windowed'] <- NA
sarksTable[idx, 'spatial_windowed'] <- NA
doBreak <- TRUE
break
}
}
if (doBreak) {break}
}
}
}
return(sarksTable)
}
#' SArKS input scores
#'
#' Extracts vector of input scores associated with input sequences
#' from sarks object.
#'
#' @param sarks Sarks object from which information will be derived
#'
#' @return named numeric vector; names are the sequence names, values
#' are the associated scores. Note: Sarks internally sorts input
#' lexicographically by sequence name.
#'
#' @examples
#' data(simulatedSeqs, simulatedScores)
#' sarks <- Sarks(simulatedSeqs, simulatedScores, 4, 0, 1)
#' simulatedScores2 <- blockScores(sarks)
#' ## simulatedScores2 will be in different order than simulatedScores,
#' ## but contains same information.
#'
#' @export
blockScores <- function(sarks) {
return(structure(sarks$getScores(), names=sarks$getTranscripts()))
}
#' Identify associated input sequence for given position(s) in suffix
#' array
#'
#' Any positiion in a suffix array for SArKS concatenated sequence can
#' be identified either via its position i in lexicographically sorted
#' list of suffixes or by its spatial position s in the concatenated
#' sequence. This function indicates which input sequence contributed
#' the block of the concatenated sequence within which the specified
#' position(s) can be found.
#'
#' @param sarks Sarks object from which information will be derived
#'
#' @param s the spatial position(s) to query; use NULL (default value)
#' if you instead want to specify sorted suffix position i
#'
#' @param i the position(s) in the sorted suffix list to query
#'
#' @return character vector containing name(s) of corresponding input
#' sequence(s)
#'
#' @examples
#' data(simulatedSeqs, simulatedScores)
#' sarks <- Sarks(simulatedSeqs, simulatedScores, 4, 0, 1)
#' blocks <- sarks$sourceBlock(i=2253:2261)
#'
#' @export
sourceBlock <- function(sarks, s=NULL, i=NULL) {
seqs <- sarks$getTranscripts()
if (length(s) == 0) {
i <- as.integer(i)
s <- vapply(i, sarks$i2s, 0)
}
s <- as.integer(s)
return(seqs[sarks$sourceBlock(s) + 1])
}
#' Get sarks smoothed scores for input sequence
#'
#' Returns a data.frame containing the smoothed scores (including
#' spatially smoothed scores, if applicable) as well as other useful
#' sarks parameters for one or more specified input sequences
#' (blocks).
#'
#' @param sarks Sarks object from which information will be derived.
#'
#' @param block character vector of names of sequence(s) for which
#' results are desired
#'
#' @param filters output from sarksFilters function indicating what
#' combinations of filter parameters halfWindow, spatialLength,
#' and minGini were used.
#'
#' @param thresholds output from permutationThresholds specifying
#' thresholds used for k-mer peak calling.
#'
#' @param kMax integer value indicating the maximum k-mer length to be
#' reported.
#'
#' @return data.frame in same format as result of kmerPeaks giving
#' detailed information about every spatial position within
#' specified sequences/blocks.
#'
#' @references Wylie, D.C., Hofmann, H.A., and Zemelman, B.V. (2019)
#' SArKS: de novo discovery of gene expression regulatory motif
#' sites and domains by suffix array kernel smoothing,
#' Bioinformatics, Vol. 35(20), 3944-3952
#'
#' \url{https://academic.oup.com/bioinformatics/article-abstract/35/20/3944/5418797}
#'
#' @examples
#' data(simulatedSeqs, simulatedScores)
#' sarks <- Sarks(simulatedSeqs, simulatedScores, 4, 0, 1)
#' filters <- sarksFilters(halfWindow=4, spatialLength=0, minGini=1.1)
#' permDist <- permutationDistribution(sarks, 250, filters, seed=123)
#' thresholds <- permutationThresholds(filters, permDist, nSigma=2.0)
#' bi24 <- blockInfo(sarks, '24', filters, thresholds)
#'
#' @export
#' @importFrom utils read.table
blockInfo <- function(sarks, block, filters, thresholds, kMax=12L) {
if (length(block) > 1) {
return(do.call(
rbind,
lapply(
block, blockInfo, sarks=sarks,
filters=filters, thresholds=thresholds, kMax=kMax
)
))
}
kMax <- as.integer(kMax)
javaOut <- sarks$printBlockInfo(
filters, thresholds$.java, block, NULL, kMax
)
out <- read.table(
textConnection(javaOut),
sep='\t', header=TRUE, row.names=NULL, na.strings='null',
stringsAsFactors=FALSE, check.names=FALSE
)
return(out)
}
#' Count occurrences of regular expression
#'
#' Counts how often a regular expression (or vector of regular
#' expressions) occurs in each element of a character vector.
#'
#' @param regex character vector of regular expressions to search for
#'
#' @param seqs character vector or XStringSet of sequences in which to
#' search for and count occurrences of regex
#'
#' @param overlap logical value: should overlapping occurrences of
#' regex be counted as multiple hits?
#'
#' @return if length(regex) is one, returns integer vector of counts;
#' if length(regex) is more than one, returns matrix of counts:
#' one row per sequence in seqs, one column per expression in
#' regex
#'
#' @examples
#' data(simulatedSeqs)
#' reCounts1 <- regexCounts('AAAAA|TTTTT', simulatedSeqs)
#' reCounts2 <- regexCounts(c('AAAAA|TTTTT', 'CG'), simulatedSeqs)
#'
#' @export
regexCounts <- function(regex, seqs, overlap=FALSE) {
if (length(names(regex)) == 0) {names(regex) <- regex}
if (length(regex) > 1) {
return(vapply(
regex, regexCounts,
structure(rep(0, length(seqs)), names=names(seqs)),
seqs=seqs, overlap=overlap
))
}
if (overlap) {regex <- paste0('(?=', regex, ')')}
return(nchar(
gsub('[^_]+', '', gsub(regex, '_', toupper(seqs), perl=TRUE))))
}
#' Count occurrences of k-mer
#'
#' Counts how often a k-mer (or vector of k-mers) occurs in each
#' element of a character vector.
#'
#' @param kmer character vector or XStringSet of k-mers to search for.
#'
#' @param seqs character vector or XStringSet of sequences in which to
#' search for and count occurrences of kmer.
#'
#' @param directional logical value: if FALSE, counts occurrences of
#' either kmer or its reverse-complement. Makes sense only if
#' applying to DNA sequences!
#'
#' @param overlap logical value: should overlapping occurrences of
#' kmer be counted as multiple hits?
#'
#' @return if length(kmer) is one, returns integer vector of counts;
#' if length(kmer) is more than one, returns matrix of counts:
#' one row per sequence in seqs, one column per expression in
#' regex
#'
#' @examples
#' data(simulatedSeqs)
#' motifCounts <- kmerCounts('CATACTGAGA', simulatedSeqs)
#' otherCounts <- kmerCounts(
#' c('AAAAA', 'CG'),
#' simulatedSeqs,
#' directional = FALSE
#' )
#'
#' @export
#' @importFrom Biostrings DNAStringSet
#' @importFrom Biostrings reverseComplement
kmerCounts <- function(kmer, seqs, directional=TRUE, overlap=FALSE) {
if (is.numeric(kmer)) {
nucs <- c('A', 'C', 'G', 'T')
nucs <- lapply(seq(length.out=kmer), function(...) {nucs})
kmer <- apply(
X = do.call(expand.grid, nucs),
MARGIN = 1,
FUN = function(x) paste(x, collapse='')
)
names(kmer) <- kmer
}
if (!directional) {
revComps <- as.character(reverseComplement(DNAStringSet(kmer)))
kmer <- structure(
paste0(kmer, '|', revComps), names=as.character(kmer)
)
} else if (length(names(kmer)) == 0) {
kmer <- structure(as.character(kmer), names=as.character(kmer))
}
return(regexCounts(kmer, seqs, overlap))
}
#' Count occurrences of k-mer clusters
#'
#' Counts how often any k-mer from a cluster of k-mers (or list of
#' clusters of k-mers) occurs in each element of a character vector.
#'
#' @param kmers character vector or XStringSet of k-mers composing
#' cluster to search for, or a named list of such character
#' vectors or XStringSet objects to count multiple clusters.
#'
#' @param seqs character vector or XStringSet of sequences in which to
#' search for and count occurrences of kmers.
#'
#' @param directional logical value: if FALSE, counts occurrences of
#' either cluster(s) of k-mers or their reverse-complements. Makes
#' sense only if applying to DNA sequences!
#'
#' @param overlap logical value: should overlapping occurrences of
#' k-mers be counted as multiple hits?
#'
#' @return if cluster is a single character vector or XStringSet
#' (of any length), returns integer vector of counts;
#' if cluster is a list of character vectors, returns matrix of
#' counts: one row per sequence in seqs, one column per
#' character vector/XStringSet in cluster
#'
#' @examples
#' seqs <- c(
#' line1 = "My mind's got a mind of its own",
#' line2 = "Takes me out to parties when I'd rather be alone",
#' line3 = "Takes me out a-walkin' when I'd rather be at home"
#' )
#' clusters <- list(
#' antisocial = c('alone', 'at home'),
#' mind = 'mind'
#' )
#' clCounts <- clusterCounts(clusters, seqs)
#'
#' @export
#' @importFrom Biostrings DNAStringSet
#' @importFrom Biostrings reverseComplement
clusterCounts <- function(kmers, seqs, directional=TRUE, overlap=FALSE) {
if (is.list(kmers)) {
clCounts <- lapply(
kmers, clusterCounts,
seqs=seqs, directional=directional, overlap=overlap
)
out <- matrix(0, nrow=length(seqs), ncol=length(kmers))
rownames(out) <- names(seqs)
for (i in seq(length.out=length(kmers))) {
out[names(clCounts[[i]]), i] <- clCounts[[i]]
}
colnames(out) <- names(kmers)
return(out)
}
if (!directional) {
revComps <- as.character(reverseComplement(DNAStringSet(kmers)))
kmers <- sort(unique(c(as.character(kmers), revComps)))
}
pattern <- paste(kmers, collapse='|')
return(regexCounts(pattern, seqs, overlap))
}
#' Locate occurrences of regular expression
#'
#' Find locations of matches of a regular expression (or vector of
#' regular expressions) in each element of a named character
#' vector. Not case sensitive.
#'
#' @param regex character vector or XStringSet of regular expressions
#' to search for
#'
#' @param seqs named character vector or XStringSet of sequences in
#' which to locate regex
#'
#' @param showMatch logical value; if true add additional column to
#' output indicating what the exact regex match for each
#' occurrence (can be slow)
#'
#' @return If only a single regex is searched for: data.frame with two
#' columns: `seqid' containing the name of the sequence from seqs
#' in which the regex was found and `location' giving the 1-based
#' position at which the regex was found. If length(regex) greater
#' than one, adds additional column `regex' indicating the name of
#' the regex located.
#'
#' @examples
#' data(simulatedSeqs)
#' reLoci <- regexLocate('AAAAA|TTTTT', simulatedSeqs)
#'
#' @export
regexLocate <- function(regex, seqs, showMatch=FALSE) {
if (length(names(seqs)) == 0) {stop("seqs must be named vector.")}
if (length(names(regex)) == 0) {names(regex) <- regex}
if (length(regex) > 1) {
out <- lapply(regex, regexLocate, seqs=seqs, showMatch=showMatch)
for (rn in names(regex)) {out[[rn]]$regex <- rep(rn, nrow(out[[rn]]))}
outCols <- c('seqid', 'regex', 'location')
if (showMatch) {outCols <- c(outCols, 'match')}
out <- do.call(rbind, out)[ , outCols]
rownames(out) <- NULL
return(out)
}
inseqs <- seqs
frags <- strsplit(
gsub(paste0('(', tolower(regex), ')'), '_\\1', tolower(seqs)),
'_'
)
for (i in seq(length.out=length(inseqs))) {
names(frags[[i]]) <- rep(names(inseqs)[i], length(frags[[i]]))
}
names(frags) <- NULL
fragLocs <- lapply(lapply(frags, nchar), cumsum)
fragLocs <- unlist(lapply(fragLocs, function(.) {
if (length(.) > 1) {
return(.[seq(length.out=length(.)-1)] + 1)
} else {
return(integer(0))
}
}))
out <- data.frame(
seqid = names(fragLocs),
location = fragLocs,
stringsAsFactors = FALSE
)
if (showMatch) {
out$match <- mapply(out$seqid, out$location, FUN=function(s, i) {
sub(paste0('^(', toupper(regex), ').*'),
'\\1',
toupper(substr(seqs[[s]], i, nchar(seqs[[s]]))))
})
}
return(out)
}
#' Locate occurrences of specified k-mers
#'
#' Find locations of matches of vector of k-mers in each element of a
#' named character vector. Not case sensitive.
#'
#' @param kmers character vector or XStringSet of k-mers to search for
#'
#' @param seqs named character vector or XStringSet of sequences in
#' which to locate kmer
#'
#' @param directional logical value: if FALSE, counts occurrences of
#' either kmers or their reverse-complements. Makes sense only if
#' applying to DNA sequences!
#'
#' @param showMatch logical value; if true add additional column to
#' output indicating what the exact regex match for each
#' occurrence (can be slow)
#'
#' @return data.frame with three columns: `seqid' containing the name
#' of the sequence from seqs in which the k-mer was found; `kmer'
#' indicating the k-mer located; and `location' giving the 1-based
#' position at which the match was found.
#'
#' @examples
#' data(simulatedSeqs)
#' kmerLoci <- locateKmers(c('AAAAA', 'CATACTGAGA'), simulatedSeqs)
#'
#' @export
#' @importFrom Biostrings DNAStringSet
#' @importFrom Biostrings reverseComplement
locateKmers <- function(kmers, seqs, directional=TRUE, showMatch=FALSE) {
patterns <- structure(as.character(kmers), names=as.character(kmers))
if (!directional) {
revComps <- as.character(reverseComplement(DNAStringSet(kmers)))
patterns <- structure(
paste0(kmers, '|', revComps), names=as.character(kmers)
)
}
locations <- lapply(patterns, regexLocate, seqs=seqs, showMatch=showMatch)
for (kmer in as.character(kmers)) {
locations[[kmer]]$kmer <- rep(kmer, nrow(locations[[kmer]]))
}
outCols <- c('seqid', 'kmer', 'location')
if (showMatch) {outCols <- c(outCols, 'match')}
out <- do.call(rbind, locations)[ , outCols]
rownames(out) <- NULL
return(out)
}
#' Locate occurrences of specified clusters of k-mers
#'
#' Find locations of matches of list of character vectors of k-mers in
#' each element of a named character vector. Not case sensitive.
#'
#' @param clusters list of character vectors or XStringSet objects of
#' k-mers to search for
#'
#' @param seqs character vector or XStringSet of sequences in which to
#' locate kmer
#'
#' @param directional logical value: if FALSE, counts occurrences of
#' either k-mers within each cluster or their reverse-complements.
#' Makes sense only if applying to DNA sequences!
#'
#' @param showMatch logical value; if true add additional column to
#' output indicating what the exact regex match for each
#' occurrence (can be slow)
#'
#' @return data.frame with three columns: `seqid' containing the name
#' of the sequence from seqs in which the match was found;
#' `cluster' indicating the cluster from wich a k-mer was located;
#' and `location' giving the 1-based position at which the match
#' was found.
#'
#' @examples
#' seqs <- c(
#' line1 = "My mind's got a mind of its own",
#' line2 = "Takes me out to parties when I'd rather be alone",
#' line3 = "Takes me out a-walkin' when I'd rather be at home"
#' )
#' clusters <- list(
#' antisocial = c('alone', 'at home'),
#' mind = 'mind'
#' )
#' clusterLoci <- locateClusters(clusters, seqs)
#'
#' @export
#' @importFrom Biostrings DNAStringSet
#' @importFrom Biostrings reverseComplement
locateClusters <- function(clusters, seqs, directional=TRUE, showMatch=FALSE) {
out <- list()
for (cn in names(clusters)) {
kmers <- clusters[[cn]]
if (!directional) {
revComps <- as.character(reverseComplement(DNAStringSet(kmers)))
kmers <- sort(unique(c(as.character(kmers), revComps)))
}
pattern <- paste(kmers, collapse='|')
locations <- regexLocate(pattern, seqs, showMatch=showMatch)
locations$cluster <- rep(cn, nrow(locations))
out[[cn]] <- locations
}
outCols <- c('seqid', 'cluster', 'location')
if (showMatch) {outCols <- c(outCols, 'match')}
out <- do.call(rbind, out)[ , outCols]
rownames(out) <- NULL
return(out)
}
integralOptimize <- function(f, lower, upper, resolution=10) {
testPoints <- unique(round(seq(
lower, upper, by=(upper-lower)/(resolution-1))))
if (testPoints[length(testPoints)] < upper) {
testPoints <- c(testPoints, upper)
}
testVals <- vapply(testPoints, f, 0)
testMax <- which.max(testVals)
newLower <- testPoints[ifelse(testMax == 1, testMax, testMax-1)]
newUpper <- testPoints[ifelse(
testMax == length(testPoints), testMax, testMax+1)]
if (newUpper <= (newLower+2)) {
return(testPoints[testMax])
} else {
return(integralOptimize(f, newLower, newUpper, resolution))
}
}
#' Cluster k-mers
#'
#' Takes a set of k-mer sequences and returns a list of partitioning
#' the input k-mers into clusters of more similar k-mers. Hierarchical
#' clustering (average linkage) is performed based on Jaccard
#' coefficient distance metric applied treating each k-mer as the set
#' of all tetramers which can be found as substrings within it.
#'
#' @param kmers character vector or XStringSet of k-mers to partition
#' into clusters
#'
#' @param k length of sub-k-mers (default k=4 to use tetramers) with
#' which to calculate Jaccard distances for clustering
#'
#' @param nClusters number of clusters to partition kmers into; if set
#' to NULL (default value), selects number of clusters to maximize
#' the average silhouette score
#' (\url{https://en.wikipedia.org/wiki/Silhouette_(clustering)}).
#'
#' @param maxClusters if nClusters not specified, can optionally set
#' maximum number of clusters allowed in silhouette score
#' optimization.
#'
#' @param directional logical value: if FALSE, considers each kmer as
#' equivalent to its reverse-complement. Makes sense only if
#' applying to DNA sequences!
#'
#' @return list of character vectors (or XStringSet objects as per the
#' class of kmers argument) partitioning kmers into clusters: the
#' character vector at the i-th element of the output list
#' contains the elements from kmers assigned to cluster i.
#'
#' @examples
#' kmers <- c(
#' 'CAGCCTGG', 'CCTGGAA', 'CAGCCTG', 'CCTGGAAC', 'CTGGAACT',
#' 'ACCTGC', 'CACCTGC', 'TGGCCTG', 'CACCTG', 'TCCAGC',
#' 'CTGGAAC', 'CACCTGG', 'CTGGTCTA', 'GTCCTG', 'CTGGAAG', 'TTCCAGC'
#' )
#' clusterKmers(kmers, directional=FALSE)
#'
#' @export
#' @importFrom Biostrings DNAStringSet
#' @importFrom Biostrings reverseComplement
#' @importFrom cluster silhouette
#' @importFrom stats as.dist
#' @importFrom stats cutree
#' @importFrom stats hclust
clusterKmers <- function(
kmers, k=4, nClusters=NULL, maxClusters=NULL, directional=TRUE) {
outClass <- gsub('character', 'identity', class(kmers)[[1]])
kmers <- unique(kmers)
if (length(kmers) <= 3 && (length(nClusters) == 0 || nClusters > 3)) {
stop('Must specify nClusters <= 3 for length(kmers) <= 3.')
}
tetraCounts <- kmerCounts(
k, kmers, directional=directional, overlap=TRUE
)
tetraCounts <- data.frame(tetraCounts, row.names=kmers)
if (!directional) {
unorientedTetramers <- sort(unique(vapply(
colnames(tetraCounts), function(km) {
min(km, as.character(reverseComplement(DNAStringSet(km))))
}, '')))
tetraCounts <- tetraCounts[ , unorientedTetramers]
}
tetraCounts <- tetraCounts[ , colSums(tetraCounts) > 0]
pq <- as.matrix(tetraCounts) %*% t(as.matrix(tetraCounts))
p2 <- rowSums(tetraCounts^2)
d <- 1 - pq / (outer(p2, p2, `+`) - pq)
rownames(d) <- colnames(d) <- rownames(tetraCounts)
diag(d) <- Inf
d[d == 0] <- min(d[d > 0]) / 2
diag(d) <- 0
ddist <- as.dist(d)
hcout <- hclust(ddist, method='average')
if (length(nClusters) == 0) {
if (length(maxClusters) == 0) {maxClusters <- nrow(d) - 1}
ctouts <- data.frame(
cutree(hcout, k=seq(2, maxClusters, 1)),
check.names = FALSE
)
nClusters <- integralOptimize(f=function(nc) {
mean(silhouette(ctouts[ , as.character(nc)], ddist)[ , 3])
}, lower=2, upper=maxClusters)
}
ctout <- cutree(hcout, k=nClusters)
clusters <- lapply(
seq(length.out=max(ctout)),
function(cl) {d[ctout==cl, ctout==cl, drop=FALSE]}
)
names(clusters) <- vapply(
clusters, function(d) {rownames(d)[which.min(rowMeans(d))]}, '')
out <- lapply(clusters, function(d) {get(outClass)(rownames(d))})
return(out)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.