| # 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, ...) object |
| |
| as.dendrogram.hclust <- function (object, hang = -1, check = TRUE, ...) |
| ## hang = 0.1 is default for plot.hclust |
| { |
| nolabels <- is.null(object$labels) |
| merge <- object$merge |
| if(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 leaves |
| zk <- as.list(-x) |
| attr(zk, "members") <- 2L |
| attr(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") <- 1L |
| attr(zk[[1L]], "height") <- attr(zk[[2L]], "height") <- h0 |
| attr(zk[[1L]], "leaf") <- attr(zk[[2L]], "leaf") <- TRUE |
| } |
| else if (any(neg)) { # one leaf, one node |
| X <- as.character(x) |
| ## Originally had "x <- sort(..) above => leaf always left, x[1L]; |
| ## don't want to assume this |
| isL <- 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") + 1L |
| attr(zk, "midpoint") <- |
| (.memberDend(zk[[1L]]) + attr(z[[X[1 + isL]]], "midpoint"))/2 |
| attr(zk[[2 - isL]], "members") <- 1L |
| attr(zk[[2 - isL]], "height") <- h0 |
| attr(zk[[2 - isL]], "label") <- object$labels[-x[2 - isL]] |
| attr(zk[[2 - isL]], "leaf") <- TRUE |
| z[[X[1 + isL]]] <- NULL |
| } |
| else { # two non-leaf nodes |
| x <- 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"))/2 |
| z[[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 <- 0L |
| repeat { |
| ## 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$node |
| todo <- todo$todo |
| } |
| } |
| count |
| } |
| |
| ## Reversing as.dendrogram.hclust() above (as much as possible) |
| ## is only possible for dendrograms with *binary* splits |
| as.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 - 1L |
| height <- 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 encountered |
| position <- 0L # position within current node |
| stack <- NULL # parents of current node plus saved state |
| leafCount <- 0L # number of leaves seen |
| nodeCount <- 0L # number of nodes seen |
| repeat { |
| ## 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 + 1L |
| myNodeIndex <- nodeCount |
| if (nodeCount != 1L) { |
| myIdx[,nodeCount] <- c(stack$position, stack$myNodeIndex) |
| } |
| height[nodeCount] <- attr(x, "height") |
| } |
| position <- position + 1L |
| child <- x[[1L]] |
| x <- x[-1L] |
| if (is.leaf(child)) { |
| ## Record information about leaf nodes. |
| leafCount <- leafCount + 1L |
| labsu[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 <- child |
| position <- 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$node |
| myNodeIndex <- stack$myNodeIndex |
| stack <- 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 merges |
| k <- seq_len(n.h-1L) |
| merge[t(myIdx[,ii[k]])] <- + k |
| |
| if (getOption("as.hclust.dendr", FALSE)) { # be verbose |
| for(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 order |
| height = height[ii], # Resort into merge order |
| order = 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 mp |
| |
| midcache.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-public |
| setmid <- function(d, type) { |
| depth <- 0L |
| kk <- integer() |
| jj <- integer() |
| dd <- list() |
| repeat { |
| if(!is.leaf(d)) {# no "midpoint" for leaf |
| k <- length(d) |
| if(k < 1) |
| stop("dendrogram node with non-positive #{branches}") |
| depth <- depth + 1L |
| if(verbose) cat(sprintf(" depth(+)=%4d, k=%d\n", depth, k)) |
| kk[depth] <- k |
| if(storage.mode(jj) != storage.mode(kk)) # (long vectors) |
| storage.mode(jj) <- storage.mode(kk) |
| dd[[depth]] <- d |
| d <- 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) break |
| depth <- depth - 1L |
| if(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) / 2 |
| d <- r |
| } |
| if(!depth) break |
| dd[[depth]] <- r |
| d <- r[[jj[depth] <- j + 1L]] |
| } |
| d |
| } |
| setmid(x, type=type) |
| } |
| |
| |
| ### Define a very concise print() method for dendrograms: |
| ## Martin Maechler, 15 May 2002 |
| print.dendrogram <- function(x, digits = getOption("digits"), ...) |
| { |
| cat("'dendrogram' ") |
| if(is.leaf(x)) |
| cat("leaf '", format(attr(x, "label"), digits = digits),"'", sep = "") |
| else |
| cat("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" here |
| lis <- 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 one |
| repeat { |
| ## 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 + 1L |
| todo <- 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 { ## leaf |
| cat("leaf", |
| if(is.character(at$label)) paste("", at$label,"", sep = '"') else |
| format(object, digits = digits.d),"") |
| any.at <- hgt != 0 |
| if(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$object |
| nest.lev <- todo$nest.lev |
| indent.str <- todo$indent.str |
| todo <- 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 hgt |
| mem.x <- .memberDend(x) |
| yTop <- hgt + edge.root |
| if(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. <- tmp |
| tmp <- 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)$x |
| if (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 children |
| plotNode <- |
| function(x1, x2, subtree, type, center, leaflab, dLeaf, |
| nodePar, edgePar, horiz = FALSE) |
| { |
| wholetree <- subtree |
| depth <- 0L |
| llimit <- list() |
| KK <- integer() |
| kk <- integer() |
| |
| repeat { |
| inner <- !is.leaf(subtree) && x1 != x2 |
| yTop <- attr(subtree, "height") |
| bx <- plotNodeLimit(x1, x2, subtree, center) |
| xTop <- bx$x |
| depth <- depth + 1L |
| llimit[[depth]] <- bx$limit |
| |
| ## handle node specific parameters in "nodePar": |
| hasP <- !is.null(nPar <- attr(subtree, "nodePar")) |
| if(!hasP) nPar <- nodePar |
| |
| if(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 par |
| |
| if(!is.null(nPar)) { ## draw this node |
| pch <- 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 leaf |
| if (leaflab == "perpendicular") { # somewhat like plot.hclust |
| if(horiz) { |
| X <- yTop + dLeaf * lab.cex |
| Y <- xTop; srt <- 0; adj <- c(0, 0.5) |
| } |
| else { |
| Y <- yTop - dLeaf * lab.cex |
| X <- 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 recursively |
| yBot <- attr(child, "height") |
| if (getOption("verbose")) cat("ch.", k, "@ h=", yBot, "; ") |
| if (is.null(yBot)) |
| yBot <- 0 |
| xBot <- |
| if (center) mean(bx$limit[k:(k + 1)]) |
| else bx$limit[k] + .midDend(child) |
| |
| hasE <- !is.null(ePar <- attr(child, "edgePar")) |
| if (!hasE) |
| ePar <- edgePar |
| i <- 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 { # rectangle |
| segmentsHV(xTop,yTop, xBot,yTop)# h |
| segmentsHV(xBot,yTop, xBot,yBot)# v |
| } |
| vln <- NULL |
| if (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)/2 |
| vln <- 1.5 * strheight(nodeText, cex = lab.cex)/2 |
| rect(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)/2 |
| else xBot |
| my <- (yTop + yBot + 2 * vln)/2 |
| } |
| else { |
| mx <- if(type == "triangle") (xTop + xBot)/2 else xBot |
| my <- (yTop + yBot)/2 |
| } |
| ## Both for "triangle" and "rectangle" : Diamond + Text |
| |
| p.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 pars |
| p.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)/2 |
| hlm <- strwidth (c(edgeText,"m"), cex = t.cex)/2 |
| hl3 <- 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 child |
| kk[depth] <- 1L |
| x1 <- bx$limit[1L] |
| x2 <- bx$limit[2L] |
| subtree <- subtree[[1L]] |
| } |
| else { |
| repeat { |
| depth <- depth - 1L |
| if (!depth || kk[depth] < KK[depth]) break |
| } |
| if (!depth) break |
| length(kk) <- depth |
| kk[depth] <- k <- kk[depth] + 1L |
| x1 <- llimit[[depth]][k] |
| x2 <- llimit[[depth]][k + 1L] |
| subtree <- wholetree[[kk]] |
| } |
| } ## repeat |
| invisible() |
| } |
| |
| 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 != x2 |
| limit <- c(x1, |
| if(inner) { |
| K <- length(subtree) |
| mTop <- .memberDend(subtree) |
| limit <- integer(K) |
| xx1 <- x1 |
| for(k in 1L:K) { |
| m <- .memberDend(subtree[[k]]) |
| ##if(is.null(m)) m <- 1 |
| xx1 <- xx1 + (if(center) (x2-x1) * m/mTop else m) |
| limit[k] <- xx1 |
| } |
| limit |
| } else ## leaf |
| x2) |
| 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 <- 1 |
| |
| assignNodes <- function(subtree, h) { |
| if(!is.leaf(subtree)) { |
| if(!(K <- length(subtree))) |
| stop("non-leaf subtree of length 0") |
| new.mem <- 0L |
| for(k in 1L:K) { |
| sub <- subtree[[k]] |
| if(attr(sub, "height") <= h) { |
| ## cut it, i.e. save to LOWER[] and make a leaf |
| at <- attributes(sub) |
| at$leaf <- TRUE |
| at$class <- NULL# drop it from leaf |
| at$x.member <- at$members |
| new.mem <- new.mem + (at$members <- 1L) |
| at$label <- paste("Branch", X) |
| subtree[[k]] <- X #paste("Branch", X) |
| attributes(subtree[[k]]) <- at |
| class(sub) <- "dendrogram" |
| LOWER[[X]] <<- sub |
| X <<- 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 ## leaf |
| as.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 <- 0L |
| kk <- 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 + 1L |
| kk[depth] <- k |
| if(storage.mode(jj) != storage.mode(kk)) |
| storage.mode(jj) <- storage.mode(kk) |
| ## insert/compute 'wts' recursively down the branches: |
| xx[[depth]] <- x |
| x <- x[[jj[depth] <- 1L]] |
| next |
| } |
| while(depth) { |
| b <- x |
| x <- xx[[depth]] |
| j <- jj[depth] |
| x[[j]] <- b |
| if(j < kk[depth]) break |
| depth <- depth - 1L |
| vals <- vapply(x, attr, numeric(1L), which="value") |
| iOrd <- sort.list(vals) |
| attr(x, "value") <- agglo.FUN(vals[iOrd]) |
| x[] <- x[iOrd] |
| } |
| if(!depth) break |
| xx[[depth]] <- x |
| x <- 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) ## recurse |
| r[[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 here |
| attr(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 i |
| add <- 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 form |
| msg <- 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.max |
| else 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") <- height |
| class(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:43 |
| FUN <- 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()s |
| if(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 means |
| if(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 indices |
| |
| if(doRdend) { |
| if(inherits(Rowv, "dendrogram")) |
| ddr <- Rowv |
| else { |
| 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:nr |
| |
| if(doCdend) { |
| if(inherits(Colv, "dendrogram")) |
| ddc <- Colv |
| else 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 x |
| x <- 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 layout |
| lmat <- 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 layout |
| if(!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 layout |
| if(!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)] <- 0 |
| if(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 bars |
| if(!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 carpet |
| par(mar = c(margins[1L], 0, 0, margins[2L])) |
| if(!symm || scale != "none") |
| x <- t(x) |
| if(revC) { # x columns reversed |
| iy <- nr:1 |
| if(doRdend) ddr <- rev(ddr) |
| x <- x[,iy] |
| } else iy <- 1L:nr |
| |
| image(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() |
| |
| ## title |
| if(!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 )) |
| } |