## PLOTMDS.R # Class to hold multidimensional scaling output setClass("MDS",representation("list")) setMethod("show","MDS",function(object) { cat("An object of class MDS\n") print(unclass(object)) }) plotMDS <- function(x,...) UseMethod("plotMDS") plotMDS.MDS <- function(x,labels=NULL,pch=NULL,cex=1,dim.plot=NULL,xlab=NULL,ylab=NULL,var.explained=TRUE,...) # Method for MDS objects # Create a new plot using MDS coordinates previously computed. # Gordon Smyth and Yifang Hu # 21 May 2011. Last modified 23 Aug 2025 { # Check labels if(is.null(labels) & is.null(pch)) { labels <- colnames(x$distance.matrix.squared) if(is.null(labels)) labels <- 1:length(x$x) } # Are new dimensions requested? if(is.null(dim.plot)) { dim.plot <- x$dim.plot } else { if(!identical(dim.plot,x$dim.plot)) { x$dim.plot <- dim.plot lambda <- pmax(x$eigen.values,0) i <- dim.plot[1] x$x <- x$eigen.vectors[,i] * sqrt(lambda[i]) if(lambda[i] < 1e-13) warning("dimension ", i, " is degenerate or all zero") i <- dim.plot[2] x$y <- x$eigen.vectors[,i] * sqrt(lambda[i]) if(lambda[i] < 1e-13) warning("dimension ", i, " is degenerate or all zero") } } # Axis labels if(is.null(x$axislabel)) x$axislabel <- "Principal Coordinate" if(is.null(xlab)) xlab <- paste(x$axislabel,dim.plot[1]) if(is.null(ylab)) ylab <- paste(x$axislabel,dim.plot[2]) if(var.explained) { Perc <- round(x$var.explained[dim.plot]*100) xlab <- paste0(xlab," (",Perc[1],"%)") ylab <- paste0(ylab," (",Perc[2],"%)") } # Make the plot if(is.null(labels)){ # Plot symbols instead of text plot(x$x, x$y, pch = pch, xlab = xlab, ylab = ylab, cex = cex, ...) } else { # Plot text. labels <- as.character(labels) # Need to estimate width of labels in plot coordinates. # Estimate will be ok for default plot width, but maybe too small for smaller plots. StringRadius <- 0.01*cex*nchar(labels) left.x <- x$x-StringRadius right.x <- x$x+StringRadius plot(c(left.x, right.x), c(x$y, x$y), type = "n", xlab = xlab, ylab = ylab, ...) text(x$x, x$y, labels = labels, cex = cex, ...) } invisible(x) } plotMDS.default <- function(x,top=500,labels=NULL,pch=NULL,cex=1,dim.plot=c(1,2),gene.selection="pairwise",xlab=NULL,ylab=NULL,plot=TRUE,var.explained=TRUE,...) # Multi-dimensional scaling with top-distance # Di Wu and Gordon Smyth # 19 March 2009. Last modified 13 May 2021. { # Check x x <- as.matrix(x) nsamples <- ncol(x) if(nsamples < 3) stop(paste("Only",nsamples,"columns of data: need at least 3")) cn <- colnames(x) # Remove rows with missing or Inf values bad <- rowSums(is.finite(x)) < nsamples if(any(bad)) x <- x[!bad,,drop=FALSE] nprobes <- nrow(x) # Check top top <- min(top,nprobes) # Check labels and pch if(is.null(pch) & is.null(labels)) { labels <- colnames(x) if(is.null(labels)) labels <- 1:nsamples } if(!is.null(labels)) labels <- as.character(labels) # Check dim.plot dim.plot <- unique(as.integer(dim.plot)) if(length(dim.plot) != 2L) stop("dim.plot must specify two dimensions to plot") # Check dim ndim <- max(dim.plot) if(ndim < 2L) stop("Need at least two dim.plot") if(nsamples < ndim) stop("ndim is greater than number of samples") if(nprobes < ndim) stop("ndim is greater than number of rows of data") # Check gene.selection gene.selection <- match.arg(gene.selection,c("pairwise","common")) # Distance matrix from pairwise leading fold changes dd <- matrix(0,nrow=nsamples,ncol=nsamples,dimnames=list(cn,cn)) if(identical(gene.selection,"pairwise")) { # Distance measure is mean of top squared deviations for each pair of arrays topindex <- nprobes-top+1L for (i in 2L:(nsamples)) for (j in 1L:(i-1L)) dd[i,j] <- mean(sort.int((x[,i]-x[,j])^2,partial=topindex)[topindex:nprobes]) axislabel <- "Leading logFC dim" } else { # Same genes used for all comparisons if(nprobes > top) { s <- rowMeans((x-rowMeans(x))^2) o <- order(s,decreasing=TRUE) x <- x[o[1L:top],,drop=FALSE] } for (i in 2L:(nsamples)) dd[i,1L:(i-1L)] <- colMeans((x[,i]-x[,1:(i-1),drop=FALSE])^2) axislabel <- "Principal Component" } # Multi-dimensional scaling dd <- dd + t(dd) rm <- rowMeans(dd) dd <- dd - rm dd <- t(dd) - (rm - mean(rm)) mds <- eigen(-dd/2, symmetric=TRUE) names(mds) <- c("eigen.values","eigen.vectors") # Make MDS object lambda <- pmax(mds$eigen.values,0) mds$var.explained <- lambda / sum(lambda) mds$dim.plot <- dim.plot mds$distance.matrix.squared <- dd mds$top <- top mds$gene.selection <- gene.selection mds$axislabel <- axislabel mds <- new("MDS",unclass(mds)) # Add coordinates for plot i <- dim.plot[1] mds$x <- mds$eigen.vectors[,i] * sqrt(lambda[i]) if(lambda[i] < 1e-13) warning("dimension ", i, " is degenerate or all zero") i <- dim.plot[2] mds$y <- mds$eigen.vectors[,i] * sqrt(lambda[i]) if(lambda[i] < 1e-13) warning("dimension ", i, " is degenerate or all zero") if(plot) plotMDS(mds,labels=labels,pch=pch,cex=cex,xlab=xlab,ylab=ylab,var.explained=var.explained,...) else mds }