R/plotMDS.R
330266f3
 ##  PLOTMDS.R
 
28557c53
 #	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")
 
1e27129c
 plotMDS.MDS <- function(x,labels=NULL,pch=NULL,cex=1,dim.plot=NULL,xlab=NULL,ylab=NULL,var.explained=TRUE,...)
28557c53
 #	Method for MDS objects
1e27129c
 #	Create a new plot using MDS coordinates previously computed.
987de974
 #	Gordon Smyth and Yifang Hu
28b157e9
 #	21 May 2011.  Last modified 23 Aug 2025
28557c53
 {
987de974
 #	Check labels
d3439c3f
 	if(is.null(labels) & is.null(pch)) {
1e27129c
 		labels <- colnames(x$distance.matrix.squared)
987de974
 		if(is.null(labels)) labels <- 1:length(x$x)
 	}
 
28557c53
 #	Are new dimensions requested?
4effd768
 	if(is.null(dim.plot)) {
 		dim.plot <- x$dim.plot
 	} else {
1e27129c
 		if(!identical(dim.plot,x$dim.plot)) {
 			x$dim.plot <- dim.plot
 			lambda <- pmax(x$eigen.values,0)
 			i <- dim.plot[1]
5be4d38b
 			x$x <- x$eigen.vectors[,i] * sqrt(lambda[i])
1e27129c
 			if(lambda[i] < 1e-13) warning("dimension ", i, " is degenerate or all zero")
 			i <- dim.plot[2]
5be4d38b
 			x$y <- x$eigen.vectors[,i] * sqrt(lambda[i])
1e27129c
 			if(lambda[i] < 1e-13) warning("dimension ", i, " is degenerate or all zero")
4effd768
 		}
28557c53
 	}
6c28eb0a
 
4effd768
 #	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])
1e27129c
 	if(var.explained) {
 		Perc <- round(x$var.explained[dim.plot]*100)
 		xlab <- paste0(xlab," (",Perc[1],"%)")
 		ylab <- paste0(ylab," (",Perc[2],"%)")
 	}
4effd768
 
987de974
 #	Make the plot
 	if(is.null(labels)){
 #		Plot symbols instead of text
44c0c299
 		plot(x$x, x$y, pch = pch, xlab = xlab, ylab = ylab, cex = cex, ...)
987de974
 	} else {
4effd768
 #		Plot text.
987de974
 		labels <- as.character(labels)
4effd768
 #		Need to estimate width of labels in plot coordinates.
 #		Estimate will be ok for default plot width, but maybe too small for smaller plots.
987de974
 		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, ...)
44c0c299
 		text(x$x, x$y, labels = labels, cex = cex, ...)
987de974
 	}
6c28eb0a
 
d3439c3f
 	invisible(x)
28557c53
 }
 
4e95d10d
 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,...)
330266f3
 #	Multi-dimensional scaling with top-distance
 #	Di Wu and Gordon Smyth
4e95d10d
 #	19 March 2009.  Last modified 13 May 2021.
330266f3
 {
880a8c3f
 #	Check x
330266f3
 	x <- as.matrix(x)
880a8c3f
 	nsamples <- ncol(x)
c1b517da
 	if(nsamples < 3) stop(paste("Only",nsamples,"columns of data: need at least 3"))
880a8c3f
 	cn <- colnames(x)
8d5d23a8
 #	Remove rows with missing or Inf values
880a8c3f
 	bad <- rowSums(is.finite(x)) < nsamples
 	if(any(bad)) x <- x[!bad,,drop=FALSE]
 	nprobes <- nrow(x)
8d5d23a8
 
880a8c3f
 #	Check top
 	top <- min(top,nprobes)
330266f3
 
987de974
 #	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)
880a8c3f
 
c1b517da
 #	Check dim.plot
 	dim.plot <- unique(as.integer(dim.plot))
 	if(length(dim.plot) != 2L) stop("dim.plot must specify two dimensions to plot")
 
880a8c3f
 #	Check dim
1e27129c
 	ndim <- max(dim.plot)
c1b517da
 	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")
330266f3
 
880a8c3f
 #	Check gene.selection
8d5d23a8
 	gene.selection <- match.arg(gene.selection,c("pairwise","common"))
 
880a8c3f
 #	Distance matrix from pairwise leading fold changes
28557c53
 	dd <- matrix(0,nrow=nsamples,ncol=nsamples,dimnames=list(cn,cn))
28b157e9
 	if(identical(gene.selection,"pairwise")) {
8d5d23a8
 #		Distance measure is mean of top squared deviations for each pair of arrays
c1b517da
 		topindex <- nprobes-top+1L
 		for (i in 2L:(nsamples))
 		for (j in 1L:(i-1L))
28b157e9
 			dd[i,j] <- mean(sort.int((x[,i]-x[,j])^2,partial=topindex)[topindex:nprobes])
4effd768
 		axislabel <- "Leading logFC dim"
8d5d23a8
 	} else {
 #		Same genes used for all comparisons
c1b517da
 		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))
28b157e9
 			dd[i,1L:(i-1L)] <- colMeans((x[,i]-x[,1:(i-1),drop=FALSE])^2)
4effd768
 		axislabel <- "Principal Component"
8d5d23a8
 	}
330266f3
 
880a8c3f
 #	Multi-dimensional scaling
1e27129c
 	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)
28b157e9
 	mds$dim.plot <- dim.plot
 	mds$distance.matrix.squared <- dd
 	mds$top <- top
 	mds$gene.selection <- gene.selection
4effd768
 	mds$axislabel <- axislabel
1e27129c
 	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")
 
d3439c3f
 	if(plot)
4e95d10d
 		plotMDS(mds,labels=labels,pch=pch,cex=cex,xlab=xlab,ylab=ylab,var.explained=var.explained,...)
d3439c3f
 	else
 		mds
330266f3
 }