R/kernels.R

Defines functions poisson find.best.transform goodness.of.fit null.model phylogenetic abundance gaussian.radial.basis linear

#############################################################################################################
# Author :
#   Jerome Mariette, MIAT, Universite de Toulouse, INRA 31326 Castanet-Tolosan France
#   Nathalie Vialaneix, MIAT, Universite de Toulouse, INRA 31326 Castanet-Tolosan France
#
# Copyright (C) 2017
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#############################################################################################################

#############################################################################################################
#
#############################################################################################################

# Checks whether a matrix is a kernel
is.kernel <- function (K, test.pos.semidef = FALSE) {
  
  if (!is.matrix(K)) {
    K <- as.matrix(K)
  }
  
  if (test.pos.semidef) {
    eval <- eigen(K, only.values = TRUE, symmetric = TRUE)$values
    tolerance <- 10^(-10)
    
    if (is.complex( eval )) {
      stop("Kernel matrix has complex eigenvalues")
    }
    
    if (isSymmetric(K)) {
      return(all(eval[which(eval > tolerance)] > 0))
    } else {
      return(FALSE)
    }
  } else {
    return(isSymmetric(K))
  }
  
}


#############################################################################################################
# linear kernel
#############################################################################################################

linear <- function(X, scale = TRUE) {

  #-- checking general input parameters --#
  if (any(is.na(X))) {
    stop("'X' should not contain NAs")
  }
  if (! is.logical(scale)) {
  	stop("'scale' should be a logical value")
  }
  if (is.data.frame(X)) {
    X <- as.matrix(X)
  }
  if (! is.matrix(X) || is.character(X)) {
    stop("'X' must be a numeric matrix.", call. = FALSE)
  }
  if (any(apply(X, 1, is.infinite))) {
    stop("infinite values in 'X'.", call. = FALSE)
  }
  if (scale) {
	  X.scaled <- scale(X)
  } else {
    X.scaled <- X
  }
  
  similarities <- tcrossprod(X.scaled)
  
  return(invisible(similarities))
  
}


#############################################################################################################
# gaussian.radial.basis kernel
#############################################################################################################

gaussian.radial.basis <- function(X, scale = TRUE, sigma = 1) {
  
  #-- checking general input parameters --#
  if (any(is.na(X))) {
    stop("'X' should not contain NAs")
  }
  if (! is.logical(scale)) {
    stop("'scale' should be a logical value")
  }
  if (is.data.frame(X)) {
    X <- as.matrix(X)
  }
  if (! is.matrix(X) || is.character(X)) {
    stop("'X' must be a numeric matrix.", call. = FALSE)
  }
  if (any(apply(X, 1, is.infinite))) {
    stop("infinite values in 'X'.", call. = FALSE)
  }
  if (scale) {
    X.scaled <- scale(X)
  } else {
    X.scaled <- X
  }

  n <- dim(X.scaled)[1]
  dota <- rowSums(X.scaled * X.scaled) / 2
  
  similarities <- tcrossprod(X.scaled)
  for (i in 1:n) {
    similarities[i, ] <- exp(2 * sigma * (similarities[i,] - dota - rep(dota[i], n)))
  }
  return(invisible(similarities))
  
}


#############################################################################################################
# abundance kernel
#############################################################################################################

abundance <- function(X, method = c("bray", "manhattan", "euclidean", "canberra", 
                                    "kulczynski", "jaccard", "gower", "altGower", 
                                    "morisita", "horn",	"mountford", "raup" , 
                                    "binomial", "chao", "cao")) {

  #-- checking general input parameters --#
  method <- match.arg(method)
  if (any(is.na(X))) {
    stop("'X' should not contain NAs")
  }
  if (is.data.frame(X)) {
    X = as.matrix(X)
  }
  if (!is.matrix(X) || is.character(X)) {
    stop("'X' must be a numeric matrix.", call. = FALSE)
  }
  if (any(apply(X, 1, is.infinite))) {
    stop("infinite values in 'X'.", call. = FALSE)
  }
  
  dissimilarities <- as.matrix(vegdist(X, method=method))
    
  # convert dissimilarities into similarities
  tolerance <- 10^(-10)
  similarities <- -.5* (diag(1, nrow(dissimilarities)) - 1 / nrow(dissimilarities)) %*%
    dissimilarities %*% (diag(1, nrow(dissimilarities)) - 1 / nrow(dissimilarities))
  similarities <- round(similarities, -log10(tolerance))
  
  return(invisible(similarities))

}


#############################################################################################################
# phylogenetic kernel
#############################################################################################################

phylogenetic <- function(X, method = c("wunifrac", "unifrac"), 
                         phylogenetic.tree = NULL) {

  #-- checking general input parameters --#
  method <- match.arg(method)
  if (any(is.na(X))) {
    stop("'X' should not contain NAs")
  }
  if (is.data.frame(X)) {
    X <- as.matrix(X)
  }
  if (!is.matrix(X) || is.character(X)) {
    stop("'X' must be a numeric matrix.", call. = FALSE)
  }
  if (any(apply(X, 1, is.infinite))) {
    stop("infinite values in 'X'.", call. = FALSE)
  }
  if (is.null(phylogenetic.tree)) {
  	stop("'phylogenetic.tree' is required when computing a 'phylogenetic' kernel.", 
  	     call. = FALSE)
  }
  if (!inherits(phylogenetic.tree, "phylo")) {
  	stop("'phylogenetic.tree' should be an instance of 'phylo-class' as defined",
  	     " in the ape-package.", call. = FALSE)
  }
  
  X.OTU <- otu_table(X, taxa_are_rows=FALSE)
  X.phyloseq <- phyloseq(X.OTU, phylogenetic.tree)  
  
  dissimilarities <- as.matrix(distance(X.phyloseq, method = method))

  # convert dissimilarities into similarities
  tolerance <- 10^(-10)
  similarities <- -.5* (diag(1, nrow(dissimilarities))- 1 / nrow(dissimilarities)) %*%
    dissimilarities %*% (diag(1, nrow(dissimilarities))- 1 / nrow(dissimilarities))
  similarities <- round(similarities, -log10(tolerance))
  
  return(invisible(similarities))

}


#############################################################################################################
# poisson kernel
#############################################################################################################

null.model <- function(x, normalization = c("deseq", "mle", "quantile")) {
  
  normalization <- match.arg(normalization)
  rowsum <- rowSums(x)
  colsum <- colSums(x)
  mle <- outer(rowsum, colsum, "*") / sum(rowsum)
  
  if(normalization=="mle"){
    return(list(n = mle, sizes = rowSums(x) / sum(x)))
  } else if (normalization=="quantile"){
    # This is quantile normalization idea of Bullard et al 2010
    # quantile-normalize using 75th quantile of observed counts 
    # for each sample, excluding zero-counts
    sample.qts <- apply(x, 1, quantile, .75)
    # Don't wait to standardize by 0... min allowed is 1
    sample.qts <- pmax(sample.qts, 1) 
    sample.qts <- sample.qts / sum(sample.qts)
    fit <- outer(sample.qts, colsum, "*")
    return(list(n = fit, sizes = sample.qts))
  } else if (normalization=="deseq"){
    #Trying to implement idea from Anders and Huber Genome Biology 2010 paper.
    counts <- t(x)
    geomeans <- exp(rowMeans(log(counts)))
    sizes <- apply(counts, 2, function(cnts) median((cnts / geomeans)[geomeans > 0]))
    rawsizestr <- sizes
    sizes <- sizes / sum(sizes)
    fit <- outer(sizes, colsum, "*")
    return(list(n = fit, sizes = sizes, geomeans = geomeans, rawsizestr = rawsizestr))
  }
  
}
goodness.of.fit <- function(x, normalization) {
  ns <- null.model(x, normalization = normalization)$n
  return(sum(((x - ns)^2) / ns, na.rm = TRUE))
}
find.best.transform <- function(x) {
  alphas <- seq(.01, 1, len=50)
  gof <- rep(NA, length(alphas))
  for(alpha in alphas){
    gof[alphas==alpha] <- goodness.of.fit(x^alpha, normalization="mle")
  }
  return(alphas[which.min(abs(gof - (nrow(x) - 1) * (ncol(x) - 1)))])
}

poisson <- function(X, normalization = c("deseq", "mle", "quantile")) {
  
  beta <- 1
  normalization <- match.arg(normalization)
  alpha <- find.best.transform(X)
  X <- X^alpha
  dd <- matrix(0, nrow = nrow(X), ncol = nrow(X))
  
  for(i in 2:nrow(dd)){
    Xi <- X[i,]
    for(j in 1:(i - 1)){
      Xj <- X[j,]
      n <- null.model(X[c(i, j), ], normalization = normalization)$n
      ni <- n[1,]
      nj <- n[2,]
      di <- (Xi + beta) / (ni + beta)
      dj <- (Xj + beta) / (nj + beta)
      dd[i, j] <- sum(ni + nj - ni * di - nj * dj + Xi * log(di) + Xj * log(dj))
    }
  }
  
  dissimilarities <- as.matrix(as.dist(dd+t(dd)))
  
  # convert dissimilarities into similarities
  tolerance <- 10^(-10)
  similarities <- -.5* (diag(1, nrow(dissimilarities)) - 1 / nrow(dissimilarities)) %*%
    dissimilarities %*% (diag(1, nrow(dissimilarities)) - 1 / nrow(dissimilarities))
  similarities <- forceSymmetric(round(similarities, -log10(tolerance)))
  
  return(invisible(as.matrix(similarities)))
  
}

Try the mixKernel package in your browser

Any scripts or data that you put into this service are public.

mixKernel documentation built on May 29, 2024, 7:34 a.m.