Rev 72315 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
# File src/library/stats/R/dendrogram.R# Part of the R package, https://www.R-project.org## Copyright (C) 1995-2017 The R Core Team## 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.## A copy of the GNU General Public License is available at# https://www.R-project.org/Licenses/as.dendrogram <- function(object, ...) UseMethod("as.dendrogram")as.dendrogram.dendrogram <- function(object, ...) objectas.dendrogram.hclust <- function (object, hang = -1, check = TRUE, ...)## hang = 0.1 is default for plot.hclust{nolabels <- is.null(object$labels)merge <- object$mergeif(check && !isTRUE(msg <- .validity.hclust(object, merge, order=nolabels)))stop(msg)if(nolabels)object$labels <- seq_along(object$order)z <- list()nMerge <- length(oHgt <- object$height)hMax <- oHgt[nMerge]for (k in 1L:nMerge) {x <- merge[k, ]# no sort() anymore!if (any(neg <- x < 0))h0 <- if (hang < 0) 0 else max(0, oHgt[k] - hang * hMax)if (all(neg)) { # two leaveszk <- as.list(-x)attr(zk, "members") <- 2Lattr(zk, "midpoint") <- 0.5 # mean( c(0,1) )objlabels <- object$labels[-x]attr(zk[[1L]], "label") <- objlabels[1L]attr(zk[[2L]], "label") <- objlabels[2L]attr(zk[[1L]], "members") <- attr(zk[[2L]], "members") <- 1Lattr(zk[[1L]], "height") <- attr(zk[[2L]], "height") <- h0attr(zk[[1L]], "leaf") <- attr(zk[[2L]], "leaf") <- TRUE}else if (any(neg)) { # one leaf, one nodeX <- as.character(x)## Originally had "x <- sort(..) above => leaf always left, x[1L];## don't want to assume thisisL <- x[1L] < 0 ## is leaf left?zk <-if(isL) list(-x[1L], z[[X[2L]]])else list(z[[X[1L]]], -x[2L])attr(zk, "members") <- attr(z[[X[1 + isL]]], "members") + 1Lattr(zk, "midpoint") <-(.memberDend(zk[[1L]]) + attr(z[[X[1 + isL]]], "midpoint"))/2attr(zk[[2 - isL]], "members") <- 1Lattr(zk[[2 - isL]], "height") <- h0attr(zk[[2 - isL]], "label") <- object$labels[-x[2 - isL]]attr(zk[[2 - isL]], "leaf") <- TRUEz[[X[1 + isL]]] <- NULL}else { # two non-leaf nodesx <- as.character(x)## "merge" the two ('earlier') branches:zk <- list(z[[x[1L]]], z[[x[2L]]])attr(zk, "members") <- attr(z[[x[1L]]], "members") +attr(z[[x[2L]]], "members")attr(zk, "midpoint") <- (attr(z[[x[1L]]], "members") +attr(z[[x[1L]]], "midpoint") +attr(z[[x[2L]]], "midpoint"))/2z[[x[1L]]] <- z[[x[2L]]] <- NULL}attr(zk, "height") <- oHgt[k]z[[as.character(k)]] <- zk}structure(z[[as.character(k)]], class = "dendrogram")}## Count the number of leaves in a dendrogram.nleaves <- function (node) {if (is.leaf(node))return(1L)todo <- NULL # Non-leaf nodes to traverse after this one.count <- 0Lrepeat {## For each child: count iff a leaf, add to todo list otherwise.while (length(node)) {child <- node[[1L]]node <- node[-1L]if (is.leaf(child)) {count <- count + 1L} else {todo <- list(node=child, todo=todo)}}## Advance to next node, terminating when no nodes left to count.if (is.null(todo)) {break} else {node <- todo$nodetodo <- todo$todo}}count}## Reversing as.dendrogram.hclust() above (as much as possible)## is only possible for dendrograms with *binary* splitsas.hclust.dendrogram <- function(x, ...){stopifnot(is.list(x), length(x) == 2L)n <- nleaves(x)stopifnot(n == attr(x, "members"))## Ord and labels for each leaf node (in preorder).ord <- integer(n)labsu <- character(n)## Height and (parent,index) for each internal node (in preorder).n.h <- n - 1Lheight <- numeric(n.h)myIdx <- matrix(NA_integer_, 2L, n.h)## Record merges initially in preorder traversal## We will resort into merge order at end.merge <- matrix(NA_integer_, 2L, n.h)## Starting at root, traverse dendrogram recording## information above about leaves and nodes encounteredposition <- 0L # position within current nodestack <- NULL # parents of current node plus saved stateleafCount <- 0L # number of leaves seennodeCount <- 0L # number of nodes seenrepeat {## Pre-order traversal of the current node.## Will descend into non-leaf children pushing parents onto stack.while (length(x)) {## Record height and index list on first visit to each internal node.if (position == 0L) {nodeCount <- nodeCount + 1LmyNodeIndex <- nodeCountif (nodeCount != 1L) {myIdx[,nodeCount] <- c(stack$position, stack$myNodeIndex)}height[nodeCount] <- attr(x, "height")}position <- position + 1Lchild <- x[[1L]]x <- x[-1L]if (is.leaf(child)) {## Record information about leaf nodes.leafCount <- leafCount + 1Llabsu[leafCount] <- attr(child,'label')ord[leafCount] <- as.integer(child)merge[position,myNodeIndex] <- - ord[leafCount]} else {stopifnot (length(child)==2L)## Descend into non-leaf nodes, saving state on stack.stack <- list(node=x, position=position,myNodeIndex=myNodeIndex, stack=stack)x <- childposition <- 0L}}## All children of current node have been traversed.## Terminate if current node was the root node.if (is.null(stack)) {break}## Otherwise, pop parent node and state.position <- stack$position # Restore position in parent node.x <- stack$nodemyNodeIndex <- stack$myNodeIndexstack <- stack$stack}iOrd <- sort.list(ord)if(!identical(ord[iOrd], seq_len(n)))stop(gettextf("dendrogram entries must be 1,2,..,%d (in any order), to be coercible to \"hclust\"",n), domain=NA)## ties: break ties "compatibly" with above preorder traversal -- relies on stable sort here:ii <- sort.list(height, decreasing=TRUE)[n.h:1L]stopifnot(ii[n.h] == 1L)## Record internal mergesk <- seq_len(n.h-1L)merge[t(myIdx[,ii[k]])] <- + kif (getOption("as.hclust.dendr", FALSE)) { # be verbosefor(k in seq_len(n.h)) {cat(sprintf("ii[k=%2d]=%2d ", k, ii[k]))cat("-> s=merge[[,ii[k]]]=")str(merge[,ii[k]])}}structure(list(merge = t(merge[,ii]), # Resort into merge orderheight = height[ii], # Resort into merge orderorder = ord,labels = labsu[iOrd],call = match.call(),method = NA_character_,dist.method = NA_character_),class = "hclust")}### MM: 'FIXME' (2002-05-14):### =====## We currently (mis)use a node's "members" attribute for two things:## 1) #{sub nodes}## 2) information about horizontal layout of the given node## Because of that, cut.dend..() cannot correctly set "members" as it should!## ==> start using "x.member" and the following function :.memberDend <- function(x) {r <- attr(x,"x.member")if(is.null(r)) {r <- attr(x,"members")if(is.null(r)) r <- 1L}r}.midDend <- function(x)if(is.null(mp <- attr(x, "midpoint"))) 0 else mpmidcache.dendrogram <- function (x, type = "hclust", quiet=FALSE){## Recompute "midpoint" attributes of a dendrogram, e.g. after reorder().type <- match.arg(type) ## currently only "hclust"stopifnot( inherits(x, "dendrogram") )verbose <- getOption("verbose", 0) >= 2 # non-publicsetmid <- function(d, type) {depth <- 0Lkk <- integer()jj <- integer()dd <- list()repeat {if(!is.leaf(d)) {# no "midpoint" for leafk <- length(d)if(k < 1)stop("dendrogram node with non-positive #{branches}")depth <- depth + 1Lif(verbose) cat(sprintf(" depth(+)=%4d, k=%d\n", depth, k))kk[depth] <- kif(storage.mode(jj) != storage.mode(kk)) # (long vectors)storage.mode(jj) <- storage.mode(kk)dd[[depth]] <- dd <- d[[jj[depth] <- 1L]]next}while(depth) {k <- kk[depth]j <- jj[depth]r <- dd[[depth]] # incl. attributes!r[[j]] <- unclass(d)if(j < k) breakdepth <- depth - 1Lif(verbose) cat(sprintf(" depth(-)=%4d, k=%d\n", depth, k))midS <- sum(vapply(r, .midDend, 0))if(!quiet && type == "hclust" && k != 2)warning("midcache() of non-binary dendrograms only partly implemented")## compatible to as.dendrogram.hclust() {MM: doubtful if k > 2}attr(r, "midpoint") <- (.memberDend(r[[1L]]) + midS) / 2d <- r}if(!depth) breakdd[[depth]] <- rd <- r[[jj[depth] <- j + 1L]]}d}setmid(x, type=type)}### Define a very concise print() method for dendrograms:## Martin Maechler, 15 May 2002print.dendrogram <- function(x, digits = getOption("digits"), ...){cat("'dendrogram' ")if(is.leaf(x))cat("leaf '", format(attr(x, "label"), digits = digits),"'", sep = "")elsecat("with", length(x), "branches and",attr(x,"members"), "members total")cat(", at height", format(attr(x,"height"), digits = digits), "\n")invisible(x)}str.dendrogram <-function (object, max.level = NA, digits.d = 3L, give.attr = FALSE,wid = getOption("width"), nest.lev = 0L, indent.str = "",last.str = getOption("str.dendrogram.last"), stem = "--", ...){## TO DO: when object is part of a larger structure which is str()ed## with default max.level= NA, it should not be str()ed to all levels,## but only to e.g. level 2## Implement via smarter default for 'max.level' (?)pasteLis <- function(lis, dropNam, sep = " = ") {## drop uninteresting "attributes" herelis <- lis[!(names(lis) %in% dropNam)]fl <- sapply(lis, format, digits = digits.d)paste(paste(names(fl), fl, sep = sep), collapse = ", ")}todo <- NULL # Nodes to process after this onerepeat {## when indent.str ends in a blank, i.e. "last" (see below)istr <- sub(" $", last.str, indent.str)cat(istr, stem, sep = "")at <- attributes(object)memb <- at[["members"]]hgt <- at[["height"]]if(!is.leaf(object)) {le <- length(object)if(give.attr) {if(nzchar(at <- pasteLis(at, c("class", "height", "members"))))at <- paste(",", at)}cat("[dendrogram w/ ", le, " branches and ", memb, " members at h = ",format(hgt, digits = digits.d), if(give.attr) at, "]",if(!is.na(max.level) && nest.lev == max.level)" ..", "\n", sep = "")if (is.na(max.level) || nest.lev < max.level) {## Push children onto todo list in reverse order.## Assumes at least one child.nest.lev <- nest.lev + 1Ltodo <- list(object=object[[le]], nest.lev = nest.lev,indent.str = paste(indent.str, " "), todo = todo)indent.str <- paste (indent.str, " |")while ((le <- le - 1L) > 0L) {todo <- list(object=object[[le]], nest.lev = nest.lev,indent.str = indent.str, todo = todo)}}} else { ## leafcat("leaf",if(is.character(at$label)) paste("", at$label,"", sep = '"') elseformat(object, digits = digits.d),"")any.at <- hgt != 0if(any.at) cat("(h=",format(hgt, digits = digits.d))if(memb != 1) #MM: when can this happen?cat(if(any.at)", " else {any.at <- TRUE; "("}, "memb= ", memb, sep = "")at <- pasteLis(at, c("class", "height", "members", "leaf", "label"))if(any.at || nzchar(at)) cat(if(!any.at)"(", at, ")")cat("\n")}## Advance to next node, if any.if (is.null(todo)) {break} else {object <- todo$objectnest.lev <- todo$nest.levindent.str <- todo$indent.strtodo <- todo$todo}}invisible()}## The ``generic'' method for "[[" (analogous to e.g., "[[.POSIXct"):## --> subbranches (including leafs!) are dendrograms as well!`[[.dendrogram` <- function(x, ..., drop = TRUE) {if(!is.null(r <- NextMethod("[[")))structure(r, class = "dendrogram")}nobs.dendrogram <- function(object, ...) attr(object, "members")## FIXME: need larger par("mar")[1L] or [4L] for longish labels !## {probably don't change, just print a warning ..}plot.dendrogram <-function (x, type = c("rectangle", "triangle"), center = FALSE,edge.root = is.leaf(x) || !is.null(attr(x, "edgetext")),nodePar = NULL, edgePar = list(),leaflab = c("perpendicular", "textlike", "none"), dLeaf = NULL,xlab = "", ylab = "", xaxt="n", yaxt="s",horiz = FALSE, frame.plot = FALSE, xlim, ylim, ...){type <- match.arg(type)leaflab <- match.arg(leaflab)hgt <- attr(x, "height")if (edge.root && is.logical(edge.root))edge.root <- 0.0625 * if(is.leaf(x)) 1 else hgtmem.x <- .memberDend(x)yTop <- hgt + edge.rootif(center) { x1 <- 0.5 ; x2 <- mem.x + 0.5 }else { x1 <- 1 ; x2 <- mem.x }xl. <- c(x1 - 1/2, x2 + 1/2)yl. <- c(0, yTop)if (horiz) {## swap and reverse direction on `x':tmp <- xl.; xl. <- rev(yl.); yl. <- tmptmp <- xaxt; xaxt <- yaxt; yaxt <- tmp}if(missing(xlim) || is.null(xlim)) xlim <- xl.if(missing(ylim) || is.null(ylim)) ylim <- yl.dev.hold(); on.exit(dev.flush())plot(0, xlim = xlim, ylim = ylim, type = "n", xlab = xlab, ylab = ylab,xaxt = xaxt, yaxt = yaxt, frame.plot = frame.plot, ...)if(is.null(dLeaf))dLeaf <- .75*(if(horiz) strwidth("w") else strheight("x"))if (edge.root) {### FIXME: the first edge + edgetext is drawn here, all others in plotNode()### ----- maybe use trick with adding a single parent node to the top ?x0 <- plotNodeLimit(x1, x2, x, center)$xif (horiz)segments(hgt, x0, yTop, x0)else segments(x0, hgt, x0, yTop)if (!is.null(et <- attr(x, "edgetext"))) {my <- mean(hgt, yTop)if (horiz)text(my, x0, et)else text(x0, my, et)}}plotNode(x1, x2, x, type = type, center = center, leaflab = leaflab,dLeaf = dLeaf, nodePar = nodePar, edgePar = edgePar, horiz = horiz)}### the work horse: plot node (if pch) and lines to all childrenplotNode <-function(x1, x2, subtree, type, center, leaflab, dLeaf,nodePar, edgePar, horiz = FALSE){wholetree <- subtreedepth <- 0Lllimit <- list()KK <- integer()kk <- integer()repeat {inner <- !is.leaf(subtree) && x1 != x2yTop <- attr(subtree, "height")bx <- plotNodeLimit(x1, x2, subtree, center)xTop <- bx$xdepth <- depth + 1Lllimit[[depth]] <- bx$limit## handle node specific parameters in "nodePar":hasP <- !is.null(nPar <- attr(subtree, "nodePar"))if(!hasP) nPar <- nodeParif(getOption("verbose")) {cat(if(inner)"inner node" else "leaf", ":")if(!is.null(nPar)) { cat(" with node pars\n"); str(nPar) }cat(if(inner )paste(" height", formatC(yTop),"; "),"(x1,x2)= (", formatC(x1, width = 4), ",", formatC(x2, width = 4), ")","--> xTop=", formatC(xTop, width = 8), "\n", sep = "")}Xtract <- function(nam, L, default, indx)rep(if(nam %in% names(L)) L[[nam]] else default,length.out = indx)[indx]asTxt <- function(x) # to allow 'plotmath' labels:if(is.character(x) || is.expression(x) || is.null(x)) x else as.character(x)i <- if(inner || hasP) 1 else 2 # only 1 node specific parif(!is.null(nPar)) { ## draw this nodepch <- Xtract("pch", nPar, default = 1L:2, i)cex <- Xtract("cex", nPar, default = c(1,1), i)col <- Xtract("col", nPar, default = par("col"), i)bg <- Xtract("bg", nPar, default = par("bg"), i)points(if (horiz) cbind(yTop, xTop) else cbind(xTop, yTop),pch = pch, bg = bg, col = col, cex = cex)}if(leaflab == "textlike")p.col <- Xtract("p.col", nPar, default = "white", i)lab.col <- Xtract("lab.col", nPar, default = par("col"), i)lab.cex <- Xtract("lab.cex", nPar, default = c(1,1), i)lab.font <- Xtract("lab.font", nPar, default = par("font"), i)lab.xpd <- Xtract("xpd", nPar, default = c(TRUE, TRUE), i)if (is.leaf(subtree)) {## label leafif (leaflab == "perpendicular") { # somewhat like plot.hclustif(horiz) {X <- yTop + dLeaf * lab.cexY <- xTop; srt <- 0; adj <- c(0, 0.5)}else {Y <- yTop - dLeaf * lab.cexX <- xTop; srt <- 90; adj <- 1}nodeText <- asTxt(attr(subtree,"label"))text(X, Y, nodeText, xpd = lab.xpd, srt = srt, adj = adj,cex = lab.cex, col = lab.col, font = lab.font)}}else if (inner) {segmentsHV <- function(x0, y0, x1, y1) {if (horiz)segments(y0, x0, y1, x1, col = col, lty = lty, lwd = lwd)else segments(x0, y0, x1, y1, col = col, lty = lty, lwd = lwd)}for (k in seq_along(subtree)) {child <- subtree[[k]]## draw lines to the children and draw them recursivelyyBot <- attr(child, "height")if (getOption("verbose")) cat("ch.", k, "@ h=", yBot, "; ")if (is.null(yBot))yBot <- 0xBot <-if (center) mean(bx$limit[k:(k + 1)])else bx$limit[k] + .midDend(child)hasE <- !is.null(ePar <- attr(child, "edgePar"))if (!hasE)ePar <- edgePari <- if (!is.leaf(child) || hasE) 1 else 2## define line attributes for segmentsHV():col <- Xtract("col", ePar, default = par("col"), i)lty <- Xtract("lty", ePar, default = par("lty"), i)lwd <- Xtract("lwd", ePar, default = par("lwd"), i)if (type == "triangle") {segmentsHV(xTop, yTop, xBot, yBot)}else { # rectanglesegmentsHV(xTop,yTop, xBot,yTop)# hsegmentsHV(xBot,yTop, xBot,yBot)# v}vln <- NULLif (is.leaf(child) && leaflab == "textlike") {nodeText <- asTxt(attr(child,"label"))if(getOption("verbose"))cat('-- with "label"',format(nodeText))hln <- 0.6 * strwidth(nodeText, cex = lab.cex)/2vln <- 1.5 * strheight(nodeText, cex = lab.cex)/2rect(xBot - hln, yBot,xBot + hln, yBot + 2 * vln, col = p.col)text(xBot, yBot + vln, nodeText, xpd = lab.xpd,cex = lab.cex, col = lab.col, font = lab.font)}if (!is.null(attr(child, "edgetext"))) {edgeText <- asTxt(attr(child, "edgetext"))if(getOption("verbose"))cat('-- with "edgetext"',format(edgeText))if (!is.null(vln)) {mx <-if(type == "triangle")(xTop+ xBot+ ((xTop - xBot)/(yTop - yBot)) * vln)/2else xBotmy <- (yTop + yBot + 2 * vln)/2}else {mx <- if(type == "triangle") (xTop + xBot)/2 else xBotmy <- (yTop + yBot)/2}## Both for "triangle" and "rectangle" : Diamond + Textp.col <- Xtract("p.col", ePar, default = "white", i)p.border <- Xtract("p.border", ePar, default = par("fg"), i)## edge label pars: defaults from the segments parsp.lwd <- Xtract("p.lwd", ePar, default = lwd, i)p.lty <- Xtract("p.lty", ePar, default = lty, i)t.col <- Xtract("t.col", ePar, default = col, i)t.cex <- Xtract("t.cex", ePar, default = 1, i)t.font <- Xtract("t.font", ePar, default = par("font"), i)vlm <- strheight(c(edgeText,"h"), cex = t.cex)/2hlm <- strwidth (c(edgeText,"m"), cex = t.cex)/2hl3 <- c(hlm[1L], hlm[1L] + hlm[2L], hlm[1L])if(horiz) {polygon(my+ c(-hl3, hl3), mx + sum(vlm)*c(-1L:1L, 1L:-1L),col = p.col, border = p.border,lty = p.lty, lwd = p.lwd)text(my, mx, edgeText, cex = t.cex, col = t.col,font = t.font)} else {polygon(mx+ c(-hl3, hl3), my + sum(vlm)*c(-1L:1L, 1L:-1L),col = p.col, border = p.border,lty = p.lty, lwd = p.lwd)text(mx, my, edgeText, cex = t.cex, col = t.col,font = t.font)}}}}if (inner && length(subtree)) {KK[depth] <- length(subtree)if (storage.mode(kk) != storage.mode(KK))storage.mode(kk) <- storage.mode(KK)## go to first childkk[depth] <- 1Lx1 <- bx$limit[1L]x2 <- bx$limit[2L]subtree <- subtree[[1L]]}else {repeat {depth <- depth - 1Lif (!depth || kk[depth] < KK[depth]) break}if (!depth) breaklength(kk) <- depthkk[depth] <- k <- kk[depth] + 1Lx1 <- llimit[[depth]][k]x2 <- llimit[[depth]][k + 1L]subtree <- wholetree[[kk]]}} ## repeatinvisible()}plotNodeLimit <- function(x1, x2, subtree, center){## get the left borders limit[k] of all children k=1..K, and## the handle point `x' for the edge connecting to the parent.inner <- !is.leaf(subtree) && x1 != x2limit <- c(x1,if(inner) {K <- length(subtree)mTop <- .memberDend(subtree)limit <- integer(K)xx1 <- x1for(k in 1L:K) {m <- .memberDend(subtree[[k]])##if(is.null(m)) m <- 1xx1 <- xx1 + (if(center) (x2-x1) * m/mTop else m)limit[k] <- xx1}limit} else ## leafx2)mid <- attr(subtree, "midpoint")center <- center || (inner && !is.numeric(mid))x <- if(center) mean(c(x1,x2)) else x1 + (if(inner) mid else 0)list(x = x, limit = limit)}cut.dendrogram <- function(x, h, ...){LOWER <- list()X <- 1assignNodes <- function(subtree, h) {if(!is.leaf(subtree)) {if(!(K <- length(subtree)))stop("non-leaf subtree of length 0")new.mem <- 0Lfor(k in 1L:K) {sub <- subtree[[k]]if(attr(sub, "height") <= h) {## cut it, i.e. save to LOWER[] and make a leafat <- attributes(sub)at$leaf <- TRUEat$class <- NULL# drop it from leafat$x.member <- at$membersnew.mem <- new.mem + (at$members <- 1L)at$label <- paste("Branch", X)subtree[[k]] <- X #paste("Branch", X)attributes(subtree[[k]]) <- atclass(sub) <- "dendrogram"LOWER[[X]] <<- subX <<- X+1}else { ## don't cut up here, possibly its children:subtree[[k]] <- assignNodes(sub, h)new.mem <- new.mem + attr(subtree[[k]], "members")}}## re-count members:attr(subtree,"x.member") <- attr(subtree,"members")attr(subtree,"members") <- new.mem}subtree}# assignNodes()list(upper = assignNodes(x, h), lower = LOWER)}## cut.dendrogram()is.leaf <- function(object) (is.logical(L <- attr(object, "leaf"))) && L## *Not* a method (yet):order.dendrogram <- function(x) {if( !inherits(x, "dendrogram") )stop("'order.dendrogram' requires a dendrogram")if(is.list(x))unlist(x)else ## leafas.vector(x)}##RG's first version -- for posterity# order.dendrogram <- function(x) {# if( !inherits(x, "dendrogram") )# stop("order.dendrogram requires a dendrogram")# ord <- function(x) {# if( is.leaf(x) ) return(x[1L])# return(c(ord(x[[1L]]), ord(x[[2L]])))# }# return(ord(x))# }reorder <- function(x, ...) UseMethod("reorder")reorder.dendrogram <- function(x, wts, agglo.FUN = sum, ...){if( !inherits(x, "dendrogram") )stop("'reorder.dendrogram' requires a dendrogram")agglo.FUN <- match.fun(agglo.FUN)oV <- function(x, wts) {depth <- 0Lkk <- jj <- integer()xx <- list()repeat {if(is.leaf(x))attr(x, "value") <- wts[x[1L]]else {k <- length(x)if(k == 0L) stop("invalid (length 0) node in dendrogram")depth <- depth + 1Lkk[depth] <- kif(storage.mode(jj) != storage.mode(kk))storage.mode(jj) <- storage.mode(kk)## insert/compute 'wts' recursively down the branches:xx[[depth]] <- xx <- x[[jj[depth] <- 1L]]next}while(depth) {b <- xx <- xx[[depth]]j <- jj[depth]x[[j]] <- bif(j < kk[depth]) breakdepth <- depth - 1Lvals <- vapply(x, attr, numeric(1L), which="value")iOrd <- sort.list(vals)attr(x, "value") <- agglo.FUN(vals[iOrd])x[] <- x[iOrd]}if(!depth) breakxx[[depth]] <- xx <- x[[jj[depth] <- j + 1L]]}x}midcache.dendrogram( oV(x, wts) )}rev.dendrogram <- function(x) {if(is.leaf(x))return(x)k <- length(x)if(k < 1)stop("dendrogram non-leaf node with non-positive #{branches}")r <- x # incl. attributes!for(j in 1L:k) ## recurser[[j]] <- rev(x[[k+1-j]])midcache.dendrogram( r )}labels.dendrogram <- function(object, ...) {if(is.list(object))rapply(object, function(n) attr(n,"label"))else # can "end" in a leaf hereattr(object, "label")}merge.dendrogram <- function(x, y, ..., height,adjust = c("auto", "add.max", "none")){stopifnot(inherits(x,"dendrogram"), inherits(y,"dendrogram"))if((adjust <- match.arg(adjust)) == "auto")adjust <-## dendrograms as from hclust(), have entries {1,2,..,n}; "cheap" check:if(min(unlist(x)) == 1 && min(unlist(y)) == 1)"add.max"else # for now, can imagine more:"none"if(adjust == "add.max") {add.ifleaf <- function(i, add) if(is.leaf(i)) i + add else iadd <- max(unlist(x))y <- dendrapply(y, add.ifleaf, add=add)}r <- list(x,y)if(length(xtr <- list(...))) {if(!all(is.d <- vapply(xtr, inherits, NA, what="dendrogram"))) {xpr <- substitute(c(...))nms <- sapply(xpr[-1][!is.d], deparse, nlines = 1L)## do not simplify: xgettext needs this formmsg <- ngettext(length(nms),"extra argument %s is not of class \"%s\"","extra arguments %s are not of class \"%s\"s")stop(sprintf(msg, paste(nms, collapse=", "), "dendrogram"),domain = NA)}if(adjust == "add.max") {add <- max(add, unlist(y))for(i in seq_along(xtr)) {if(i > 1L) add <- max(add, unlist(xtr[i-1L]))xtr[[i]] <- dendrapply(xtr[[i]], add.ifleaf, add=add)}}r <- c(r, xtr)}attr(r, "members") <- sum(vapply(r, attr, 0L, which="members"))h.max <- max(vapply(r, attr, 0., which="height"))if(missing(height) || is.null(height))height <- 1.1 * h.maxelse if(height < h.max) {msg <- gettextf("'height' must be at least %g, the maximal height of its components", h.max)stop(msg, domain = NA)}attr(r, "height") <- heightclass(r) <- "dendrogram"midcache.dendrogram(r, quiet=TRUE)}dendrapply <- function(X, FUN, ...){## Purpose: "dendrogram" recursive apply {to each node}## ----------------------------------------------------------------------## Author: Martin Maechler, Date: 26 Jun 2004, 22:43FUN <- match.fun(FUN)if( !inherits(X, "dendrogram") ) stop("'X' is not a dendrogram")## Node apply recursively:Napply <- function(d) {r <- FUN(d, ...)if(!is.leaf(d)) {if(!is.list(r)) r <- as.list(r) # fixing unsafe FUN()sif(length(r) < (n <- length(d))) r[seq_len(n)] <- vector("list", n)## and overwrite recursively, possibly keeping "attr"r[] <- lapply(d, Napply)}r}Napply(X)}## original Andy Liaw; modified RG, MM :heatmap <-function (x, Rowv=NULL, Colv=if(symm)"Rowv" else NULL,distfun = dist, hclustfun = hclust,reorderfun = function(d,w) reorder(d,w),add.expr, symm = FALSE, revC = identical(Colv, "Rowv"),scale = c("row", "column", "none"), na.rm=TRUE,margins = c(5, 5), ColSideColors, RowSideColors,cexRow = 0.2 + 1/log10(nr), cexCol = 0.2 + 1/log10(nc),labRow = NULL, labCol = NULL, main = NULL, xlab = NULL, ylab = NULL,keep.dendro = FALSE,verbose = getOption("verbose"), ...){scale <- if(symm && missing(scale)) "none" else match.arg(scale)if(length(di <- dim(x)) != 2 || !is.numeric(x))stop("'x' must be a numeric matrix")nr <- di[1L]nc <- di[2L]if(nr <= 1 || nc <= 1)stop("'x' must have at least 2 rows and 2 columns")if(!is.numeric(margins) || length(margins) != 2L)stop("'margins' must be a numeric vector of length 2")doRdend <- !identical(Rowv,NA)doCdend <- !identical(Colv,NA)if(!doRdend && identical(Colv, "Rowv")) doCdend <- FALSE## by default order by row/col meansif(is.null(Rowv)) Rowv <- rowMeans(x, na.rm = na.rm)if(is.null(Colv)) Colv <- colMeans(x, na.rm = na.rm)## get the dendrograms and reordering indicesif(doRdend) {if(inherits(Rowv, "dendrogram"))ddr <- Rowvelse {hcr <- hclustfun(distfun(x))ddr <- as.dendrogram(hcr)if(!is.logical(Rowv) || Rowv)ddr <- reorderfun(ddr, Rowv)}if(nr != length(rowInd <- order.dendrogram(ddr)))stop("row dendrogram ordering gave index of wrong length")}else rowInd <- 1L:nrif(doCdend) {if(inherits(Colv, "dendrogram"))ddc <- Colvelse if(identical(Colv, "Rowv")) {if(nr != nc)stop('Colv = "Rowv" but nrow(x) != ncol(x)')ddc <- ddr}else {hcc <- hclustfun(distfun(if(symm)x else t(x)))ddc <- as.dendrogram(hcc)if(!is.logical(Colv) || Colv)ddc <- reorderfun(ddc, Colv)}if(nc != length(colInd <- order.dendrogram(ddc)))stop("column dendrogram ordering gave index of wrong length")}else colInd <- 1L:nc## reorder xx <- x[rowInd, colInd]labRow <-if(is.null(labRow))if(is.null(rownames(x))) (1L:nr)[rowInd] else rownames(x)else labRow[rowInd]labCol <-if(is.null(labCol))if(is.null(colnames(x))) (1L:nc)[colInd] else colnames(x)else labCol[colInd]if(scale == "row") {x <- sweep(x, 1L, rowMeans(x, na.rm = na.rm), check.margin=FALSE)sx <- apply(x, 1L, sd, na.rm = na.rm)x <- sweep(x, 1L, sx, "/", check.margin=FALSE)}else if(scale == "column") {x <- sweep(x, 2L, colMeans(x, na.rm = na.rm), check.margin=FALSE)sx <- apply(x, 2L, sd, na.rm = na.rm)x <- sweep(x, 2L, sx, "/", check.margin=FALSE)}## Calculate the plot layoutlmat <- rbind(c(NA, 3), 2:1)lwid <- c(if(doRdend) 1 else 0.05, 4)lhei <- c((if(doCdend) 1 else 0.05) + if(!is.null(main)) 0.2 else 0, 4)if(!missing(ColSideColors)) { ## add middle row to layoutif(!is.character(ColSideColors) || length(ColSideColors) != nc)stop("'ColSideColors' must be a character vector of length ncol(x)")lmat <- rbind(lmat[1,]+1, c(NA,1), lmat[2,]+1)lhei <- c(lhei[1L], 0.2, lhei[2L])}if(!missing(RowSideColors)) { ## add middle column to layoutif(!is.character(RowSideColors) || length(RowSideColors) != nr)stop("'RowSideColors' must be a character vector of length nrow(x)")lmat <- cbind(lmat[,1]+1, c(rep(NA, nrow(lmat)-1), 1), lmat[,2]+1)lwid <- c(lwid[1L], 0.2, lwid[2L])}lmat[is.na(lmat)] <- 0if(verbose) {cat("layout: widths = ", lwid, ", heights = ", lhei,"; lmat=\n")print(lmat)}## Graphics `output' -----------------------dev.hold(); on.exit(dev.flush())op <- par(no.readonly = TRUE)on.exit(par(op), add = TRUE)layout(lmat, widths = lwid, heights = lhei, respect = TRUE)## draw the side barsif(!missing(RowSideColors)) {par(mar = c(margins[1L],0, 0,0.5))image(rbind(if(revC) nr:1L else 1L:nr), col = RowSideColors[rowInd], axes = FALSE)}if(!missing(ColSideColors)) {par(mar = c(0.5,0, 0,margins[2L]))image(cbind(1L:nc), col = ColSideColors[colInd], axes = FALSE)}## draw the main carpetpar(mar = c(margins[1L], 0, 0, margins[2L]))if(!symm || scale != "none")x <- t(x)if(revC) { # x columns reversediy <- nr:1if(doRdend) ddr <- rev(ddr)x <- x[,iy]} else iy <- 1L:nrimage(1L:nc, 1L:nr, x, xlim = 0.5+ c(0, nc), ylim = 0.5+ c(0, nr),axes = FALSE, xlab = "", ylab = "", ...)axis(1, 1L:nc, labels = labCol, las = 2, line = -0.5, tick = 0,cex.axis = cexCol)if(!is.null(xlab)) mtext(xlab, side = 1, line = margins[1L] - 1.25)axis(4, iy, labels = labRow, las = 2, line = -0.5, tick = 0,cex.axis = cexRow)if(!is.null(ylab)) mtext(ylab, side = 4, line = margins[2L] - 1.25)if (!missing(add.expr))eval.parent(substitute(add.expr))## the two dendrograms :par(mar = c(margins[1L], 0, 0, 0))if(doRdend)plot(ddr, horiz = TRUE, axes = FALSE, yaxs = "i", leaflab = "none")else frame()par(mar = c(0, 0, if(!is.null(main)) 1 else 0, margins[2L]))if(doCdend)plot(ddc, axes = FALSE, xaxs = "i", leaflab = "none")else if(!is.null(main)) frame()## titleif(!is.null(main)) {par(xpd = NA)# {we have room on the left}title(main, cex.main = 1.5*op[["cex.main"]])}invisible(list(rowInd = rowInd, colInd = colInd,Rowv = if(keep.dendro && doRdend) ddr,Colv = if(keep.dendro && doCdend) ddc ))}