#' MetaClustering
#'
#' Cluster data with automatic number of cluster determination for
#' several algorithms
#'
#' @param data Matrix containing the data to cluster
#' @param method Clustering method to use
#' @param max Maximum number of clusters to try out
#' @param seed Seed to pass on to given clustering method
#' @param ... Extra parameters to pass along
#'
#' @return Numeric array indicating cluster for each datapoint
#' @seealso \code{\link{metaClustering_consensus}}
#'
#' @examples
#' # Read from file, build self-organizing map and minimal spanning tree
#' fileName <- system.file("extdata", "68983.fcs", package = "FlowSOM")
#' flowSOM.res <- ReadInput(fileName, compensate = TRUE,transform = TRUE,
#' scale = TRUE)
#' flowSOM.res <- BuildSOM(flowSOM.res,colsToUse = c(9, 12, 14:18))
#' flowSOM.res <- BuildMST(flowSOM.res)
#'
#' # Apply metaclustering
#' metacl <- MetaClustering(flowSOM.res$map$codes,
#' "metaClustering_consensus",
#' max = 10)
#'
#' # Get metaclustering per cell
#' flowSOM.clustering <- metacl[flowSOM.res$map$mapping[, 1]]
#'
#' @export
MetaClustering <- function(data, method, max = 20, seed = NULL, ...){
res <- DetermineNumberOfClusters(data, max, method, seed = seed, ...)
method <- get(method)
method(data, k = res, seed = seed)
}
DetermineNumberOfClusters <- function(data, max, method, plot = FALSE,
smooth = 0.2, seed = NULL, ...){
# Try out a clustering algorithm for several numbers of clusters and
# select optimal
#
# Args:
# data: Matrix containing the data to cluster
# max: Maximum number of clusters to try
# method: Clustering method to use
# plot: Whether to plot the results for different k
# smooth: Smoothing option to find elbow:
# 0: no smoothing, 1: maximal smoothing
# seed: Seed to pass on to given method
#
# Returns:
# Optimal number of clusters
if(method == "metaClustering_consensus"){
results <- consensus(data,max, seed, ...)
res <- rep(0,max)
res[1] <- SSE(data,rep(1,nrow(data)))
for(i in 2:max){
c <- results[[i]]$consensusClass
res[i] <- SSE(data, c)
}
} else {
method <- get(method)
res <- rep(0,max)
for(i in 1:max){
c <- method(data, k = i,...)
res[i] <- SSE(data, c)
}
}
for(i in 2:(max - 1)){
res[i] <- (1 - smooth) * res[i] +
(smooth / 2) * res[i - 1] +
(smooth / 2) * res[i + 1]
}
if(plot) plot(1:max, res, type = "b", xlab = "Number of Clusters",
ylab = "Within groups sum of squares")
findElbow(res)
}
findElbow <- function(data){
n <- length(data)
data <- as.data.frame(cbind(1:n,data))
colnames(data) <- c("X","Y")
min_r <- Inf
optimal <- 1
for(i in 2:(n-1)){
f1 <- stats::lm(Y~X,data[1:(i-1),])
f2 <- stats::lm(Y~X,data[i:n,])
r <- sum(abs(c(f1$residuals,f2$residuals)))
if(r < min_r){
min_r <- r
optimal <-i
}
}
optimal
}
#' MetaClustering
#'
#' Cluster data using hierarchical consensus clustering with k clusters
#'
#' @param data Matrix containing the data to cluster
#' @param k Number of clusters
#' @param seed Seed to pass to consensusClusterPlus
#'
#' @return Numeric array indicating cluster for each datapoint
#' @seealso \code{\link{MetaClustering}}
#' @examples
#' # Read from file, build self-organizing map and minimal spanning tree
#' fileName <- system.file("extdata", "68983.fcs", package = "FlowSOM")
#' flowSOM.res <- ReadInput(fileName, compensate = TRUE,transform = TRUE,
#' scale = TRUE)
#' flowSOM.res <- BuildSOM(flowSOM.res,colsToUse = c(9, 12, 14:18))
#' flowSOM.res <- BuildMST(flowSOM.res)
#'
#' # Apply consensus metaclustering
#' metacl <- metaClustering_consensus(flowSOM.res$map$codes, k = 10)
#'
#' @export
metaClustering_consensus <- function(data, k = 7, seed = NULL){
results <- suppressMessages(ConsensusClusterPlus::ConsensusClusterPlus(
t(data),
maxK = k, reps = 100, pItem = 0.9, pFeature = 1,
title = tempdir(), plot = "pdf", verbose = FALSE,
clusterAlg = "hc", # "hc","km","kmdist","pam"
distance = "euclidean" ,
#"euclidean","pearson","spearman","binary","maximum","canberra","minkowski"
seed = seed
))
results[[k]]$consensusClass
}
consensus <- function(data, max, seed = NULL){
results <- suppressMessages(ConsensusClusterPlus::ConsensusClusterPlus(
t(data),
maxK = max, reps = 100, pItem = 0.9, pFeature = 1,
title = tempdir(), plot = "pdf", verbose = FALSE,
clusterAlg = "hc", # "hc","km","kmdist","pam"
distance = "euclidean",
#"euclidean","pearson","spearman","binary","maximum","canberra","minkowski"
seed = seed
))
}
metaClustering_hclust <- function(data, k = 7, seed = NULL){
set.seed(seed)
d <- stats::dist(data, method = "minkowski")
fit <- stats::hclust(d, method = "ward.D2")
stats::cutree(fit, k = k)
}
metaClustering_kmeans <- function(data, k = 7, seed = NULL){
set.seed(seed)
stats::kmeans(data, centers = k)$cluster
}
metaClustering_som <- function(data, k = 7, seed = NULL){
set.seed(seed)
s <- SOM(data, xdim = k, ydim = 1, rlen = 100)
s$unit.classif
}
SSE <- function(data,clustering){
if(!is(clustering, "numeric"))
clustering <- as.numeric(as.factor(clustering))
c_wss <- 0
for(j in seq_along(clustering)){
if(sum(clustering == j) > 1){
c_wss <- c_wss + (nrow(data[clustering == j, , drop = FALSE]) - 1) *
sum(apply(data[clustering == j, , drop = FALSE], 2, stats::var))
}
}
c_wss
}
#' F measure
#'
#' Compute the F measure between two clustering results
#'
#' @param realClusters Array containing real cluster labels for each sample
#' @param predictedClusters Array containing predicted cluster labels for each
#' sample
#' @param silent Logical, if FALSE (default), print some information about
#' precision and recall
#'
#' @return F measure score
#' @examples
#' # Generate some random data as an example
#' realClusters <- sample(1:5,100,replace = TRUE)
#' predictedClusters <- sample(1:6, 100, replace = TRUE)
#'
#' # Calculate the FMeasure
#' FMeasure(realClusters,predictedClusters)
#' @export
FMeasure <- function(realClusters, predictedClusters,silent = FALSE){
if (sum(predictedClusters) == 0)
return(0);
a <- table(realClusters, predictedClusters);
p <- t(apply(a, 1, function(x) x / colSums(a)))
r <- apply(a, 2, function(x) x / rowSums(a))
if(!silent) message("Precision: ",
sum(apply(p, 1, max) * (rowSums(a) / sum(a))),
"\nRecall: ",
sum(apply(r, 1, max) * (rowSums(a) / sum(a))),
"\n")
f <- 2 * r * p / (r + p)
f[is.na(f)] <- 0
sum(apply(f, 1, max) * (rowSums(a) / sum(a)))
}
#' GetMetaclusterMFIs
#'
#' Compute the median fluorescence intensities for the metaclusters
#'
#' @param fsom Result of calling the FlowSOM function
#' @param colsUsed Logical. Should report only the columns used to
#' build the SOM. Default = FALSE.
#' @param prettyColnames Logical. Should report pretty column names instead
#' of standard column names. Default = FALSE.
#'
#' @return Metacluster MFIs
#' @examples
#' fileName <- system.file("extdata", "68983.fcs", package = "FlowSOM")
#' ff <- flowCore::read.FCS(fileName)
#' ff <- flowCore::compensate(ff, flowCore::keyword(ff)[["SPILL"]])
#' ff <- flowCore::transform(ff,
#' flowCore::transformList(colnames(flowCore::keyword(ff)[["SPILL"]]),
#' flowCore::logicleTransform()))
#' flowSOM.res <- FlowSOM(ff,
#' scale = TRUE,
#' colsToUse = c(9, 12, 14:18),
#' nClus = 10)
#' mfis <- GetMetaclusterMFIs(flowSOM.res)
#' @export
GetMetaclusterMFIs <- function(fsom, colsUsed = FALSE, prettyColnames = FALSE){
fsom <- UpdateFlowSOM(fsom)
MFIs <- fsom$map$metaclusterMFIs
if (is.null(fsom$map$colsUsed)) colsUsed <- FALSE
if (is.null(fsom$prettyColnames)) prettyColnames <- FALSE
if (colsUsed && !prettyColnames){
MFIs <- MFIs[, fsom$map$colsUsed]
} else if (!colsUsed && prettyColnames) {
colnames(MFIs) <- fsom$prettyColnames
} else if (colsUsed && prettyColnames) {
MFIs <- MFIs[, fsom$map$colsUsed]
colnames(MFIs) <- fsom$prettyColnames[fsom$map$colsUsed]
}
return(MFIs)
}
#' Get percentage-positive values for all metaclusters
#'
#' @param fsom FlowSOM object as generated by the FlowSOM function
#' or the BuildSOM function
#' @param cutoffs named numeric vector. Upper bounds of negative
#' population fluorescence-intensity values for each
#' marker / channel.
#' @param colsUsed logical. Should report only the columns used to
#' build the SOM. Default = FALSE.
#' @param prettyColnames logical. Should report pretty column names instead
#' of standard column names. Default = FALSE.
#'
#' @return Matrix with percentages of cells that are positive in selected markers per each metacluster
#'
#' @examples
#' fileName <- system.file("extdata", "68983.fcs", package = "FlowSOM")
#' flowSOM.res <- FlowSOM(fileName, compensate = TRUE, transform = TRUE,
#' scale = TRUE, colsToUse = c(9, 12, 14:18), nClus = 10)
#' perc_pos <- GetMetaclusterPercentagesPositive(flowSOM.res, cutoffs = c('CD4' = 5000))
#' @export
GetMetaclusterPercentagesPositive <- function(fsom, cutoffs, colsUsed = FALSE, prettyColnames = FALSE){
fsom <- UpdateFlowSOM(fsom)
mc_per_cell <- GetMetaclusters(fsom)
metaclusters <- levels(fsom$metaclustering)
if(is.null(fsom$map$colsUsed)) colsUsed <- FALSE
if(is.null(fsom$prettyColnames)) prettyColnames <- FALSE
channels <- GetChannels(fsom, names(cutoffs))
if(colsUsed && !prettyColnames){
channels <- intersect(channels, fsom$map$colsUsed)
}
perc_pos <- matrix(NA,
nrow = length(metaclusters),
ncol = length(cutoffs), dimnames = list(metaclusters,
names(cutoffs)))
i <- 0
for (metacluster in metaclusters){
i <- i + 1
data_per_metacluster <- fsom$data[mc_per_cell == metacluster, channels, drop = FALSE]
if (length(data_per_metacluster) > 0){
data_per_metacluster <- split(data_per_metacluster, col(data_per_metacluster))
names(data_per_metacluster) <- channels
npoints <- length(data_per_metacluster[[1]])
perc_pos_per_channel <- mapply(function(data_per_channel, cutoff) sum(data_per_channel > cutoff) / npoints,
data_per_metacluster,
cutoffs)
perc_pos[i, ] <- perc_pos_per_channel
}
}
if (prettyColnames){
colnames(perc_pos) <- fsom$prettyColnames[channels]
}
return(perc_pos)
}
#' GetMetaclusterCVs
#'
#' Compute the coefficient of variation for the metaclusters
#'
#' @param fsom Result of calling the FlowSOM function
#' @param colsUsed Logical. Should report only the columns used to
#' build the SOM. Default = FALSE.
#' @param prettyColnames Logical. Should report pretty column names instead
#' of standard column names. Default = FALSE.
#'
#' @return Metacluster CVs
#' @examples
#' fileName <- system.file("extdata", "68983.fcs", package = "FlowSOM")
#' ff <- flowCore::read.FCS(fileName)
#' ff <- flowCore::compensate(ff, flowCore::keyword(ff)[["SPILL"]])
#' ff <- flowCore::transform(ff,
#' flowCore::transformList(colnames(flowCore::keyword(ff)[["SPILL"]]),
#' flowCore::logicleTransform()))
#' flowSOM.res <- FlowSOM(ff,
#' scale = TRUE,
#' colsToUse = c(9, 12, 14:18),
#' nClus = 10)
#' cvs <- GetMetaclusterCVs(flowSOM.res)
#' @export
GetMetaclusterCVs <- function(fsom, colsUsed = FALSE, prettyColnames = FALSE){
CVs <- as.data.frame(t(sapply(levels(fsom$metaclustering),
function(i) {
apply(subset(fsom$data,
fsom$metaclustering[
GetClusters(fsom)] == i),
2,
function(y){
if(length(y) > 0 && mean(y) != 0){
stats::sd(y)/mean(y)
} else {
NA
}})
})))
if (is.null(fsom$map$colsUsed)) colsUsed <- FALSE
if (is.null(fsom$prettyColnames)) prettyColnames <- FALSE
if (colsUsed && !prettyColnames){
CVs <- CVs[, fsom$map$colsUsed]
} else if (!colsUsed && prettyColnames) {
colnames(CVs) <- fsom$prettyColnames
} else if (colsUsed && prettyColnames) {
CVs <- CVs[, fsom$map$colsUsed]
colnames(CVs) <- fsom$prettyColnames[fsom$map$colsUsed]
}
return(CVs)
}
#' UpdateMetaclusters
#'
#' Adapt the metacluster levels. Can be used to rename the metaclusters, split
#' or merge existing metaclusters, add a metaclustering and/or reorder the levels
#' of the metaclustering.
#'
#' @param fsom Result of calling the FlowSOM function.
#' @param newLabels Optional. Named vector, with the names the original
#' metacluster names and the values the replacement. Can be
#' used to rename or merge metaclusters.
#' @param clusterAssignment Optional. Either a named vector, with the names
#' the cluster numbers (characters) or a vector of
#' length NClusters(fsom). Can be used to assign
#' clusters to existing or new metaclusters.
#' @param levelOrder Optional. Vector showing the preferred order of the fsom
#' metacluster levels.
#'
#' @return Updated FlowSOM object
#' @examples
#' fileName <- system.file("extdata", "68983.fcs", package = "FlowSOM")
#' ff <- flowCore::read.FCS(fileName)
#' ff <- flowCore::compensate(ff, flowCore::keyword(ff)[["SPILL"]])
#' ff <- flowCore::transform(ff,
#' flowCore::transformList(colnames(flowCore::keyword(ff)[["SPILL"]]),
#' flowCore::logicleTransform()))
#' flowSOM.res <- FlowSOM(ff,
#' scale = TRUE,
#' colsToUse = c(9, 12, 14:18),
#' nClus = 10,
#' seed = 1)
#'
#' PlotStars(flowSOM.res, backgroundValues = flowSOM.res$metaclustering)
#' GetCounts(flowSOM.res)
#'
#' # Merge MC8 and MC9
#' flowSOM.res <- UpdateMetaclusters(flowSOM.res, newLabels = c("8" = "8+9",
#' "9" = "8+9"))
#' PlotStars(flowSOM.res, backgroundValues = flowSOM.res$metaclustering)
#' GetCounts(flowSOM.res)
#'
#' # Split cluster 24 from metacluster 2 and order the metacluster levels
#' flowSOM.res <- UpdateMetaclusters(flowSOM.res,
#' clusterAssignment = c("24" = "debris?"),
#' levelOrder = c("debris?", as.character(c(1:7)),
#' "8+9", "10"))
#' PlotStars(flowSOM.res, backgroundValues = flowSOM.res$metaclustering)
#' PlotNumbers(flowSOM.res, level = "metaclusters")
#'
#' GetCounts(flowSOM.res)
#'
#' @export
UpdateMetaclusters <- function(fsom, newLabels = NULL, clusterAssignment = NULL,
levelOrder = NULL){
if(!is.null(fsom$metaclustering)){
# newLabels to relabel or merge some MCs
if(!is.null(newLabels)){
currentLevels <- levels(fsom$metaclustering)
newLevels <- currentLevels
names(newLevels) <- currentLevels
for(original in names(newLabels)){
newLevels[currentLevels == original] <- newLabels[original]
}
if(any(duplicated(newLevels))){
fsom$metaclustering <- newLevels[as.character(fsom$metaclustering)]
fsom$metaclustering <- factor(fsom$metaclustering,
levels = unique(newLevels))
fsom$map$nMetaclusters <- length(unique(newLevels))
} else {
levels(fsom$metaclustering) <- newLevels
}
}
if(!is.null(clusterAssignment)){
if (!is.null(names(clusterAssignment))){
# named clusterAssignment to reassign some Cs
currentLevels <- fsom$metaclustering
newLevels <- as.character(currentLevels)
names(newLevels) <- as.character(1:length(newLevels))
for(original in names(clusterAssignment)){
newLevels[original] <- clusterAssignment[original]
}
fsom$metaclustering <- factor(x = newLevels)
fsom$map$nMetaclusters <- length(unique(newLevels))
} else if (length(clusterAssignment) == NClusters(fsom)){
# clusterAssignment of length nClusters to reassign Cs
new <- factor(clusterAssignment)
fsom$metaclustering <- new
fsom$map$nMetaclusters <- length(levels(new))
} else {
stop("The clusterAssignment vector should be named, or the the length
should be equal to the number of fsom clusters.")
}
}
} else if (length(clusterAssignment) == NClusters(fsom)){
new <- factor(clusterAssignment)
fsom$metaclustering <- new
fsom$map$nMetaclusters <- length(levels(new))
} else {
stop("This FlowSOM object does not include a metaclustering and the length
of the clusterAssignment is not equal to the number of fsom clusters.")
}
if (!is.null(levelOrder)){
fsom$metaclustering <- factor(fsom$metaclustering,
levels = levelOrder)
}
fsom <- UpdateDerivedValues(fsom)
return(fsom)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.