R/clustering.R
233354bd
 #' spectralClustering
846c3061
 #'
233354bd
 #' A function to perform spectral clustering
846c3061
 #'
89fa3af2
 #' @param affinity An affinity matrix
 #' @param K number of clusters
 #' @param type type
 #' @param fast fast
 #' @param maxdim maxdim
 #' @param delta delta
 #' @param t t
 #' @param neigen neigen
846c3061
 #'
233354bd
 #' @return A list indicates the spectral clustering results
 #'
c3e74dcc
 #' @examples
 #'
 #' data("sce_control_subset", package = "CiteFuse")
7d0ee522
 #' sce_control_subset <- CiteFuse(sce_control_subset)
c3e74dcc
 #' SNF_W_clust <- spectralClustering(S4Vectors::metadata(sce_control_subset)[["SNF_W"]],
 #' K = 5)
 #'
89fa3af2
 #' @importFrom igraph arpack
 #' @importFrom methods as
233354bd
 #'
 #'
89fa3af2
 #' @export
 
300a2111
 spectralClustering <- function(affinity, K = 20, type = 4,
33544f55
                                fast = TRUE,
300a2111
                                maxdim = 50, delta = 1e-5,
                                t = 0, neigen = NULL)
89fa3af2
 {
846c3061
 
89fa3af2
   d <- rowSums(affinity)
   d[d == 0] <- .Machine$double.eps
   D <- diag(d)
   L <- affinity
846c3061
 
 
89fa3af2
   neff <- K + 1
846c3061
 
89fa3af2
   if (type == 1) {
     NL <- L
   }
   else if (type == 2) {
     Di <- diag(1/d)
     NL <- Di %*% L
   }
   else if (type == 3) {
     Di <- diag(1/sqrt(d))
     NL <- Di %*% L %*% Di
846c3061
 
89fa3af2
   } else if (type == 4) {
     v <- sqrt(d)
300a2111
     NL <- L/(v %*% t(v))
89fa3af2
   }
846c3061
 
300a2111
   if (!fast) {
89fa3af2
     eig <- eigen(NL)
   }else {
     f = function(x, A = NULL){ # matrix multiplication for ARPACK
       as.matrix(A %*% x)
     }
846c3061
 
89fa3af2
     n <- nrow(affinity)
846c3061
 
89fa3af2
     NL <- ifelse(NL > delta, NL, 0)
     NL <- methods::as(NL, "dgCMatrix")
846c3061
 
 
89fa3af2
     eig <- igraph::arpack(f, extra = NL, sym = TRUE,
33544f55
                           options = list(which = 'LA', nev = neff,
                                          n = n, ncv = max(min(c(n,4*neff)))))
846c3061
 
89fa3af2
   }
846c3061
 
89fa3af2
   psi = eig$vectors / (eig$vectors[,1] %*% matrix(1, 1, neff))#right ev
   eigenvals <- eig$values
846c3061
 
89fa3af2
   cat('Computing Spectral Clustering \n')
846c3061
 
33544f55
   res <- sort(abs(eigenvals), index.return = TRUE, decreasing = TRUE)
233354bd
   U <- eig$vectors[, res$ix[seq_len(K)]]
89fa3af2
   normalize <- function(x) x/sqrt(sum(x^2))
846c3061
 
89fa3af2
   if (type == 3 | type == 4) {
     U <- t(apply(U, 1, normalize))
   }
   # This part is equal to performing kmeans
   # labels <- kmeans(U, centers = K, nstart = 1000)$cluster
   eigDiscrete <- .discretisation(U)
   eigDiscrete <- eigDiscrete$discrete
   labels <- apply(eigDiscrete, 1, which.max)
846c3061
 
89fa3af2
   cat('Computing Diffusion Coordinates\n')
   if (t <= 0) {# use multi-scale geometry
     lambda = eigenvals[-1]/(1 - eigenvals[-1])
     lambda = rep(1,n) %*% t(lambda)
300a2111
     if (is.null(neigen)) {#use no. of dimensions corresponding to 95% dropoff
89fa3af2
       lam = lambda[1,]/lambda[1,1]
       # neigen = min(which(lam < .05)) # default number of eigenvalues
       neigen = min(neigen, maxdim, K)
233354bd
       eigenvals = eigenvals[seq_len((neigen + 1))]
89fa3af2
       cat('Used default value:',neigen,'dimensions\n')
     }
233354bd
     X = psi[,2:(neigen + 1)]*lambda[, seq_len(neigen)] #diffusion coords. X
89fa3af2
   }
   else{# use fixed scale t
     lambda = eigenvals[-1]^t
     lambda = rep(1, n) %*% t(lambda)
846c3061
 
89fa3af2
     if (is.null(neigen)) {#use no. of dimensions corresponding to 95% dropoff
       lam = lambda[1, ]/lambda[1, 1]
       neigen = min(which(lam < .05)) # default number of eigenvalues
       neigen = min(neigen, maxdim)
233354bd
       eigenvals = eigenvals[seq_len(neigen + 1)]
89fa3af2
       cat('Used default value:', neigen, 'dimensions\n')
     }
846c3061
 
233354bd
     X = psi[, 2:(neigen + 1)] * lambda[, seq_len(neigen)] #diffusion coords. X
89fa3af2
   }
846c3061
 
   return(list(labels = labels,
               eigen_values = eig$values,
89fa3af2
               eigen_vectors = eig$vectors,
               X = X))
 }
 
 
 
 
 .discretisationEigenVectorData <- function(eigenVector) {
846c3061
 
89fa3af2
   Y = matrix(0,nrow(eigenVector),ncol(eigenVector))
   maxi <- function(x) {
     i = which(x == max(x))
     return(i[1])
   }
   j = apply(eigenVector,1,maxi)
233354bd
   Y[cbind(seq_len(nrow(eigenVector)), j)] = 1
846c3061
 
89fa3af2
   return(Y)
846c3061
 
89fa3af2
 }
 
 
 .discretisation <- function(eigenVectors) {
846c3061
 
89fa3af2
   normalize <- function(x) x / sqrt(sum(x^2))
   eigenVectors = t(apply(eigenVectors,1,normalize))
846c3061
 
89fa3af2
   n = nrow(eigenVectors)
   k = ncol(eigenVectors)
846c3061
 
89fa3af2
   R = matrix(0,k,k)
   R[,1] = t(eigenVectors[round(n/2),])
846c3061
 
89fa3af2
   mini <- function(x) {
     i = which(x == min(x))
     return(i[1])
   }
846c3061
 
233354bd
   c = matrix(0, n, 1)
   for (j in seq(2, k)) {
300a2111
     c = c + abs(eigenVectors %*% matrix(R[,j - 1], k, 1))
89fa3af2
     i = mini(c)
     R[,j] = t(eigenVectors[i,])
   }
846c3061
 
89fa3af2
   lastObjectiveValue = 0
233354bd
   for (i in seq_len(1000)) {
89fa3af2
     eigenDiscrete = .discretisationEigenVectorData(eigenVectors %*% R)
846c3061
 
89fa3af2
     svde = svd(t(eigenDiscrete) %*% eigenVectors)
     U = svde[['u']]
     V = svde[['v']]
     S = svde[['d']]
846c3061
 
300a2111
     NcutValue = 2 * (n - sum(S))
     if (abs(NcutValue - lastObjectiveValue) < .Machine$double.eps)
89fa3af2
       break
846c3061
 
89fa3af2
     lastObjectiveValue = NcutValue
     R = V %*% t(U)
846c3061
 
89fa3af2
   }
846c3061
 
233354bd
   return(list(discrete = eigenDiscrete, continuous = eigenVectors))
89fa3af2
 }
 
 
 
233354bd
 #' reducedDimSNF
846c3061
 #'
233354bd
 #' A function to reduce the dimension of the similarity matrix
846c3061
 #'
89fa3af2
 #' @param sce A singlecellexperiment object
c3e74dcc
 #' @param metadata indicates the meta data name of
 #' affinity matrix to virsualise
 #' @param method the method of visualisation, which can be UMAP,
 #' tSNE and diffusion map
89fa3af2
 #' @param dimNames indicates the name of the reduced dimension results.
846c3061
 #'
89fa3af2
 #' @param ... other parameters for tsne(), umap()
233354bd
 #'
 #' @return A SingleCellExperiment object
 #'
c3e74dcc
 #' @examples
 #' data("sce_control_subset", package = "CiteFuse")
7d0ee522
 #' sce_control_subset <- CiteFuse(sce_control_subset)
c3e74dcc
 #' sce_control_subset <- reducedDimSNF(sce_control_subset,
 #' method = "tSNE",
 #' dimNames = "tSNE_joint")
 #'
89fa3af2
 #' @importFrom uwot umap
 #' @importFrom Rtsne Rtsne
 #' @importFrom SingleCellExperiment reducedDim
 #' @importFrom S4Vectors metadata
846c3061
 #' @importFrom stats as.dist
89fa3af2
 #' @export
 
846c3061
 reducedDimSNF <- function(sce,
                           metadata = "SNF_W",
                           method = "UMAP",
                           dimNames = NULL,
                           ...) {
 
89fa3af2
   method <- match.arg(method, c("UMAP", "tSNE"), several.ok = FALSE)
846c3061
 
89fa3af2
   if (!metadata %in% names(S4Vectors::metadata(sce))) {
     stop("sce does not contain metadata")
   }
846c3061
 
89fa3af2
   W <- S4Vectors::metadata(sce)[[metadata]]
846c3061
 
 
 
89fa3af2
   if (is.null(dimNames)) {
     dimNames <- paste(method, metadata, sep = "_")
   }
846c3061
 
 
89fa3af2
   if ("UMAP" %in% method) {
846c3061
 
89fa3af2
     dimred <- uwot::umap(as.dist(0.5 - W), ...)
     colnames(dimred) <- paste("UMAP", seq_len(ncol(dimred)), sep = " ")
846c3061
 
89fa3af2
     SingleCellExperiment::reducedDim(sce, dimNames) <- dimred
   }
846c3061
 
89fa3af2
   if ("tSNE" %in% method) {
26fa6b89
 
     if (nrow(W) - 1 < 3 * 30) {
       stop("Please set a smaller perplexity number for Rtsne()")
     }
 
89fa3af2
     dimred <- Rtsne::Rtsne(as.dist(0.5 - W), is_distance = TRUE, ...)$Y
     colnames(dimred) <- paste("tSNE", seq_len(ncol(dimred)), sep = " ")
846c3061
 
89fa3af2
     SingleCellExperiment::reducedDim(sce, dimNames) <- dimred
   }
846c3061
 
 
 
89fa3af2
   return(sce)
846c3061
 
89fa3af2
 
 }
 
 
 
 
 
 
233354bd
 #' visualiseDim
 #'
89fa3af2
 #' A function to visualise the reduced dimension
846c3061
 #'
89fa3af2
 #' @param sce A singlecellexperiment object
 #' @param dimNames indicates the name of the reduced dimension results.
a320ea6f
 #' @param colour_by A character indicates how the cells coloured by.
 #' The information either stored in colData, assay, or altExp.
 #' @param shape_by A character indicates how the cells shaped by.
 #' The information either stored in colData, assay, or altExp.
 #' @param data_from A character indicates where the colour by data stored
 #' @param assay_name A character indicates the assay name of the expression
 #' @param altExp_name A character indicates the name of alternative expression
 #' @param altExp_assay_name A character indicates the assay name of alternative expression
c3e74dcc
 #' @param dim a vector of numeric with length of 2 indicates
 #' which component is being plot
846c3061
 #'
233354bd
 #' @return A ggplot of the reduced dimension visualisation
 #'
c3e74dcc
 #' @examples
 #' data("sce_control_subset", package = "CiteFuse")
7d0ee522
 #' sce_control_subset <- CiteFuse(sce_control_subset)
 #' sce_control_subset <- reducedDimSNF(sce_control_subset,
 #' method = "tSNE",
 #' dimNames = "tSNE_joint")
c3e74dcc
 #' visualiseDim(sce_control_subset, dimNames = "tSNE_joint",
 #' colour_by = "SNF_W_clust")
 #'
89fa3af2
 #' @importFrom SingleCellExperiment reducedDimNames
 #' @importFrom SummarizedExperiment colData
 #' @importFrom S4Vectors metadata
 #' @import ggplot2
846c3061
 #'
89fa3af2
 #' @export
 
846c3061
 visualiseDim <- function(sce,
89fa3af2
                          dimNames = NULL,
                          colour_by = NULL,
                          shape_by = NULL,
a320ea6f
                          data_from = c("colData", "assay", "altExp"),
                          assay_name = NULL,
                          altExp_name = NULL,
                          altExp_assay_name = NULL,
233354bd
                          dim = seq_len(2)){
846c3061
 
89fa3af2
 
   if (!dimNames %in% SingleCellExperiment::reducedDimNames(sce)) {
     stop("sce does not contain dimNames")
   }
846c3061
 
a320ea6f
   data_from <- match.arg(data_from,
                          c("colData", "assay", "altExp"),
                          several.ok = TRUE)
846c3061
 
 
 
a320ea6f
   cts <- FALSE
846c3061
 
a320ea6f
   if (is.null(colour_by)) {
846c3061
 
a320ea6f
     colour_by <- NULL
846c3061
 
2a9ffb3f
   }else if ("character" %in% is(colour_by) & length(colour_by) == 1) {
846c3061
 
a320ea6f
     df_colour_by <- .get_color_by(sce, colour_by, data_from,
                                assay_name, altExp_name,
                                altExp_assay_name)
     colour_by <- df_colour_by$colour_by_info
     cts <- df_colour_by$cts
846c3061
 
89fa3af2
   } else if (length(colour_by) != ncol(sce)) {
846c3061
 
33544f55
     stop("colour_by needs to be a character or a vector with
          length equal to the number of cells.")
846c3061
 
89fa3af2
   } else{
846c3061
 
89fa3af2
     colour_by <- colour_by
846c3061
 
89fa3af2
   }
846c3061
 
 
89fa3af2
   if (is.null(shape_by)) {
846c3061
 
89fa3af2
     shape_by <- NULL
846c3061
 
2a9ffb3f
   }else if ("character" %in% is(shape_by) & length(shape_by) == 1) {
846c3061
 
300a2111
     if (!shape_by %in% names(colData(sce))) {
846c3061
 
89fa3af2
       stop("There is no colData with name shape_by")
846c3061
 
89fa3af2
     } else {
846c3061
 
89fa3af2
       shape_by <- as.factor(SummarizedExperiment::colData(sce)[, shape_by])
846c3061
 
89fa3af2
     }
846c3061
 
89fa3af2
   } else if (length(shape_by) != ncol(sce)) {
846c3061
 
33544f55
     stop("shape_by needs to be a character or a vector with
          length equal to the number of cells.")
846c3061
 
89fa3af2
   } else{
846c3061
 
89fa3af2
     shape_by <- shape_by
846c3061
 
89fa3af2
   }
846c3061
 
 
89fa3af2
   dimred <- reducedDim(sce, dimNames)
846c3061
 
89fa3af2
   if (is.null(colnames(dimred))) {
     colnames(dimred) <- paste(dimNames, seq_len(ncol(dimred)), sep = "_")
   }
846c3061
 
89fa3af2
   dimred <- data.frame(dimred)
846c3061
 
a320ea6f
   if (cts) {
     g_color <- scale_color_viridis_c()
   } else {
     g_color <- scale_color_manual(values = cite_colorPal(length(unique(colour_by))))
   }
 
33544f55
   ggplot2::ggplot(dimred, aes(x = dimred[, dim[1]],
                               y = dimred[, dim[2]],
                               col = colour_by,
                               shape = shape_by)) +
89fa3af2
     geom_point() +
a320ea6f
     g_color +
89fa3af2
     scale_shape_manual(values = cite_shapePal(length(unique(shape_by)))) +
     theme_bw() +
     theme(aspect.ratio = 1) +
     xlab(colnames(dimred)[dim[1]]) +
     ylab(colnames(dimred)[dim[2]])
 
846c3061
 
 
 
 
89fa3af2
 }
 
 
a320ea6f
 .get_color_by <- function(sce,
                           colour_by,
                           data_from = c("colData", "assay", "altExp"),
                           assay_name = NULL,
                           altExp_name = NULL,
                           altExp_assay_name = NULL) {
 
 
 
   data_from <- match.arg(data_from,
                          c("colData", "assay", "altExp"),
                          several.ok = TRUE)
 
   colour_by_info <- NULL
   cts <- TRUE
 
   if ("colData" %in% data_from & is.null(colour_by_info)) {
     if (colour_by %in% names(colData(sce))) {
       colour_by_info <- SummarizedExperiment::colData(sce)[, colour_by]
       cts <- FALSE
     }
   }
 
   if ("assay" %in% data_from & is.null(colour_by_info)) {
     if (colour_by %in% rownames(sce)) {
 
 
       if (is.null(assay_name)) {
         if ("logcounts" %in% SummarizedExperiment::assayNames(sce)) {
           assay_name <- "logcounts"
         } else {
           assay_name <- SummarizedExperiment::assayNames(sce)[1]
         }
       } else {
         if (!assay_name %in% SummarizedExperiment::assayNames(sce)) {
           stop("There is no assay_name in assayNames of the sce")
         }
       }
       colour_by_info <- SummarizedExperiment::assay(sce, assay_name)[colour_by, ]
 
     }
   }
 
   if ("altExp" %in% data_from &
       !is.null(SingleCellExperiment::altExpNames(sce))  &
       is.null(colour_by_info)) {
     if (is.null(altExp_name)) {
       if ("ADT" %in% SingleCellExperiment::altExpNames(sce)) {
         altExp_name <- "ADT"
       } else {
         altExp_name <-  SingleCellExperiment::altExpNames(sce)[1]
       }
     }
     alt_se <- SingleCellExperiment::altExp(sce, altExp_name)
 
     if (colour_by %in% rownames(alt_se)) {
 
       if (is.null(altExp_assay_name)) {
         if ("logcounts" %in% SummarizedExperiment::assayNames(alt_se)) {
           altExp_assay_name <- "logcounts"
         } else {
           altExp_assay_name <- SummarizedExperiment::assayNames(alt_se)[1]
         }
       } else {
         if (!altExp_assay_name %in% SummarizedExperiment::assayNames(alt_se)) {
           stop("There is no altExp_assay_name in assayNames of the altExp")
         }
       }
       colour_by_info <- SummarizedExperiment::assay(alt_se,
                                                     altExp_assay_name)[colour_by, ]
     }
   }
 
   if (is.null(colour_by_info)) {
     warning("Can not find the required colour_by info,
             please check input data_from, assay_name, altExp_name, altExp_assay_name")
   }
   return(list(colour_by_info = colour_by_info, cts = cts))
 }
 
 
89fa3af2
 
 
 .normalize <- function(X) {
   row.sum.mdiag <- rowSums(X) - diag(X)
   row.sum.mdiag[row.sum.mdiag == 0] <- 1
   X <- X/(2 * (row.sum.mdiag))
   diag(X) <- 0.5
   return(X)
 }
 
233354bd
 #' igraphClustering
846c3061
 #'
233354bd
 #' A function to perform igraph clustering
846c3061
 #'
89fa3af2
 #' @param sce A singlecellexperiment object
c3e74dcc
 #' @param metadata indicates the meta data name of affinity matrix
 #' to virsualise
 #' @param method A character indicates the method for finding communities f
 #' rom igraph. Default is louvain clustering.
300a2111
 #' @param ... Other inputs for the igraph functions
846c3061
 #'
233354bd
 #' @return A vector indicates the membership (clustering) results
 #'
c3e74dcc
 #' @examples
 #'
 #' data("sce_control_subset", package = "CiteFuse")
7d0ee522
 #' sce_control_subset <- CiteFuse(sce_control_subset)
c3e74dcc
 #' SNF_W_louvain <- igraphClustering(sce_control_subset,
 #' method = "louvain")
 #'
89fa3af2
 #' @importFrom S4Vectors metadata
 #' @importFrom dbscan sNN
 #' @importFrom igraph graph_from_adjacency_matrix cluster_louvain
300a2111
 #' cluster_walktrap cluster_spinglass cluster_optimal
a320ea6f
 #' cluster_edge_betweenness cluster_fast_greedy cluster_label_prop
 #' cluster_leading_eigen
89fa3af2
 #' @importFrom stats median as.dist
 #' @export
 
300a2111
 igraphClustering <- function(sce,
                              metadata = "SNF_W",
a320ea6f
                              method = c("louvain", "walktrap", "spinglass",
                                         "optimal", "leading_eigen",
                                         "label_prop", "fast_greedy",
                                         "edge_betweenness"),
300a2111
                              ...) {
 
   method <- match.arg(method, c("louvain", "walktrap", "spinglass", "optimal",
33544f55
                                 "leading_eigen", "label_prop", "fast_greedy",
                                 "edge_betweenness"))
846c3061
 
   normalized.mat <- S4Vectors::metadata(sce)[[metadata]]
89fa3af2
   diag(normalized.mat) <- stats::median(as.vector(normalized.mat))
   normalized.mat <- .normalize(normalized.mat)
   normalized.mat <- normalized.mat + t(normalized.mat)
846c3061
 
   binary.mat <- dbscan::sNN(stats::as.dist(0.5 - normalized.mat), k = 20)
 
233354bd
   binary.mat <- sapply(seq_len(nrow(normalized.mat)), function(x) {
89fa3af2
     tmp <- rep(0, ncol(normalized.mat))
     tmp[binary.mat$id[x,]] <- 1
     tmp
   })
846c3061
 
89fa3af2
   rownames(binary.mat) <- colnames(binary.mat)
   dim(binary.mat)
846c3061
 
 
89fa3af2
   g <- igraph::graph_from_adjacency_matrix(binary.mat, mode = "undirected")
846c3061
 
300a2111
   if (method == "louvain") {
     X <- igraph::cluster_louvain(g, ...)
   }
 
   if (method == "walktrap") {
     X <- igraph::cluster_walktrap(g, ...)
   }
 
   if (method == "spinglass") {
     X <- igraph::cluster_spinglass(g, ...)
   }
 
   if (method == "optimal") {
     X <- igraph::cluster_optimal(g, ...)
   }
 
   if (method == "leading_eigen") {
     X <- igraph::cluster_leading_eigen(g, ...)
   }
 
   if (method == "label_prop") {
     X <- igraph::cluster_label_prop(g, ...)
   }
 
   if (method == "fast_greedy") {
     X <- igraph::cluster_fast_greedy(g, ...)
   }
 
   if (method == "edge_betweenness") {
     X <- igraph::cluster_edge_betweenness(g, ...)
   }
846c3061
 
89fa3af2
   clustres <- X$membership
300a2111
 
89fa3af2
   return(clustres)
846c3061
 
89fa3af2
 }
 
300a2111
 
 
 
233354bd
 
 
 #' visualiseKNN
846c3061
 #'
233354bd
 #' A function to perform louvain clustering
846c3061
 #'
89fa3af2
 #' @param sce A singlecellexperiment object
 #' @param colour_by the name of coldata that is used to colour the node
 #' @param metadata indicates the meta data name of affinity matrix to virsualise
846c3061
 #'
233354bd
 #' @return A igraph plot
 #'
c3e74dcc
 #' @examples
 #' data("sce_control_subset", package = "CiteFuse")
7d0ee522
 #' sce_control_subset <- CiteFuse(sce_control_subset)
 #' SNF_W_louvain <- igraphClustering(sce_control_subset,
 #' method = "louvain")
c3e74dcc
 #' visualiseKNN(sce_control_subset, colour_by = "SNF_W_louvain")
 #'
89fa3af2
 #' @importFrom S4Vectors metadata
 #' @importFrom dbscan sNN
 #' @importFrom igraph graph_from_adjacency_matrix
 #' @importFrom SummarizedExperiment colData
846c3061
 #' @importFrom graphics plot legend
89fa3af2
 #' @export
 
 visualiseKNN <- function(sce,
                          colour_by = NULL,
                          metadata = "SNF_W") {
846c3061
 
 
 
   normalized.mat <- S4Vectors::metadata(sce)[[metadata]]
89fa3af2
   diag(normalized.mat) <- stats::median(as.vector(normalized.mat))
   normalized.mat <- .normalize(normalized.mat)
   normalized.mat <- normalized.mat + t(normalized.mat)
846c3061
 
   binary.mat <- dbscan::sNN(as.dist(0.5 - normalized.mat), k = 20)
 
233354bd
   binary.mat <- sapply(seq_len(nrow(normalized.mat)), function(x) {
89fa3af2
     tmp <- rep(0, ncol(normalized.mat))
     tmp[binary.mat$id[x,]] <- 1
     tmp
   })
846c3061
 
89fa3af2
   rownames(binary.mat) <- colnames(binary.mat)
   dim(binary.mat)
846c3061
 
 
89fa3af2
   g <- igraph::graph_from_adjacency_matrix(binary.mat, mode = "undirected")
846c3061
 
89fa3af2
   # cat('no. of clusters')
   # print(nlevels(as.factor(X$membership)))
846c3061
   #
89fa3af2
   if (is.null(colour_by)) {
     colour_by <- as.factor(rep(1, ncol(sce)))
   } else {
     colour_by <- as.factor(SummarizedExperiment::colData(sce)[, colour_by])
   }
846c3061
 
   graphics::plot(g, vertex.label = NA,
                  edge.arrow.size = 0.000001,
                  layout = igraph::layout.fruchterman.reingold,
                  vertex.color = cite_colorPal(nlevels(colour_by))[as.numeric(colour_by)],
                  vertex.size = 4)
 
   graphics::legend('bottomright',
                    legend = levels(colour_by),
                    col = cite_colorPal(nlevels(colour_by)),
                    pch = 16,
                    ncol = ceiling(nlevels(colour_by)/10),
                    cex = 0.5)
89fa3af2
 
 }