| # File src/library/graphics/R/coplot.R |
| # Part of the R package, https://www.R-project.org |
| # |
| # Copyright (C) 1995-2013 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/ |
| |
| co.intervals <- function (x, number = 6, overlap = 0.5) |
| { |
| x <- sort(x[!is.na(x)]) |
| n <- length(x) |
| ## "from the record" |
| r <- n/(number * (1 - overlap) + overlap) |
| ii <- 0:(number - 1) * (1 - overlap) * r |
| x1 <- x[round(1 + ii)] |
| xr <- x[round(r + ii)] |
| ## Omit any range of values identical with the previous range; |
| ## happens e.g. when `number' is less than the number of distinct x values. |
| keep <- c(TRUE, diff(x1) > 0 | diff(xr) > 0) |
| ## Set eps > 0 to ensure that the endpoints of a range are never |
| ## identical, allowing display of a given.values bar |
| j.gt.0 <- 0 < (jump <- diff(x)) |
| eps <- 0.5 * if(any(j.gt.0)) min(jump[j.gt.0]) else 0 |
| cbind(x1[keep] - eps, xr[keep] + eps) |
| } |
| |
| panel.smooth <- function(x, y, col = par("col"), bg = NA, pch = par("pch"), |
| cex = 1, col.smooth = "red", span = 2/3, iter = 3, ...) |
| { |
| points(x, y, pch=pch, col=col, bg=bg, cex=cex) |
| ok <- is.finite(x) & is.finite(y) |
| if (any(ok)) |
| lines(stats::lowess(x[ok], y[ok], f=span, iter=iter), |
| col = col.smooth, ...) |
| } |
| |
| coplot <- |
| function(formula, data, given.values, panel=points, rows, columns, |
| show.given = TRUE, col = par("fg"), pch=par("pch"), |
| bar.bg = c(num = gray(0.8), fac = gray(0.95)), |
| xlab = c(x.name, paste("Given :", a.name)), |
| ylab = c(y.name, paste("Given :", b.name)), |
| subscripts = FALSE, axlabels = function(f) abbreviate(levels(f)), |
| number = 6, overlap = 0.5, xlim, ylim, ...) |
| { |
| deparen <- function(expr) { |
| while (is.language(expr) && !is.name(expr) && |
| deparse(expr[[1L]])[1L] == "(") |
| expr <- expr[[2L]] |
| expr |
| } |
| bad.formula <- function() stop("invalid conditioning formula") |
| bad.lengths <- function() stop("incompatible variable lengths") |
| getOp <- function(call) deparse(call[[1L]], backtick=FALSE)[[1L]] |
| |
| ## parse and check the formula |
| |
| formula <- deparen(formula) |
| if (!inherits(formula, "formula")) |
| bad.formula() |
| y <- deparen(formula[[2L]]) |
| rhs <- deparen(formula[[3L]]) |
| if (getOp(rhs) != "|") bad.formula() |
| x <- deparen(rhs[[2L]]) |
| rhs <- deparen(rhs[[3L]]) |
| if (is.language(rhs) && !is.name(rhs) && getOp(rhs) %in% c("*", "+")) { |
| have.b <- TRUE |
| a <- deparen(rhs[[2L]]) |
| b <- deparen(rhs[[3L]]) |
| } else { |
| have.b <- FALSE |
| a <- rhs |
| } |
| |
| ## evaluate the formulae components to get the data values |
| |
| if (missing(data)) |
| data <- parent.frame() |
| x.name <- deparse(x) |
| x <- eval(x, data, parent.frame()) |
| nobs <- length(x) |
| y.name <- deparse(y) |
| y <- eval(y, data, parent.frame()) |
| if(length(y) != nobs) bad.lengths() |
| a.name <- deparse(a) |
| a <- eval(a, data, parent.frame()) |
| if(length(a) != nobs) bad.lengths() |
| if(is.character(a)) a <- as.factor(a) |
| a.is.fac <- is.factor(a) |
| if (have.b) { |
| b.name <- deparse(b) |
| b <- eval(b, data, parent.frame()) |
| if(length(b) != nobs) bad.lengths() |
| if(is.character(b)) b <- as.factor(b) |
| b.is.fac <- is.factor(b) |
| missingrows <- which(is.na(x) | is.na(y) | is.na(a) | is.na(b)) |
| } |
| else { |
| missingrows <- which(is.na(x) | is.na(y) | is.na(a)) |
| b <- NULL |
| b.name <- "" # for default ylab |
| } |
| |
| ## generate the given value intervals |
| |
| number <- as.integer(number) |
| if(length(number) == 0L || any(number < 1)) |
| stop("'number' must be integer >= 1") |
| if(any(overlap >= 1)) stop("'overlap' must be < 1 (and typically >= 0).") |
| |
| bad.givens <- function() stop("invalid 'given.values'") |
| if(missing(given.values)) { |
| a.intervals <- |
| if(a.is.fac) { |
| i <- seq_along(a.levels <- levels(a)) |
| a <- as.numeric(a) |
| cbind(i - 0.5, i + 0.5) |
| } else co.intervals(unclass(a), number=number[1L], overlap=overlap[1L]) |
| b.intervals <- |
| if (have.b) { |
| if(b.is.fac) { |
| i <- seq_along(b.levels <- levels(b)) |
| b <- as.numeric(b) |
| cbind(i - 0.5, i + 0.5) |
| } |
| else { |
| if(length(number) == 1L) number <- rep.int(number,2) |
| if(length(overlap) == 1L) overlap <- rep.int(overlap,2) |
| co.intervals(unclass(b), number=number[2L], overlap=overlap[2L]) |
| } |
| } |
| } else { |
| if(!is.list(given.values)) |
| given.values <- list(given.values) |
| if(length(given.values) != (if(have.b) 2L else 1L)) |
| bad.givens() |
| a.intervals <- given.values[[1L]] |
| if(a.is.fac) { |
| a.levels <- levels(a) |
| if (is.character(a.intervals)) |
| a.intervals <- match(a.intervals, a.levels) |
| a.intervals <- cbind(a.intervals - 0.5, a.intervals + 0.5) |
| a <- as.numeric(a) |
| } |
| else if(is.numeric(a)) { |
| if(!is.numeric(a.intervals)) bad.givens() |
| if(!is.matrix(a.intervals) || ncol(a.intervals) != 2) |
| a.intervals <- cbind(a.intervals - 0.5, a.intervals + 0.5) |
| } |
| if(have.b) { |
| b.intervals <- given.values[[2L]] |
| if(b.is.fac) { |
| b.levels <- levels(b) |
| if (is.character(b.intervals)) |
| b.intervals <- match(b.intervals, b.levels) |
| b.intervals <- cbind(b.intervals - 0.5, b.intervals + 0.5) |
| b <- as.numeric(b) |
| } |
| else if(is.numeric(b)) { |
| if(!is.numeric(b.intervals)) bad.givens() |
| if(!is.matrix(b.intervals) || ncol(b.intervals) != 2) |
| b.intervals <- cbind(b.intervals - 0.5, b.intervals + 0.5) |
| } |
| } |
| } |
| if(anyNA(a.intervals) || (have.b && anyNA(b.intervals))) |
| bad.givens() |
| |
| ## compute the page layout |
| |
| if (have.b) { |
| rows <- nrow(b.intervals) |
| columns <- nrow(a.intervals) |
| nplots <- rows * columns |
| if(length(show.given) < 2L) show.given <- rep.int(show.given, 2L) |
| } |
| else { |
| nplots <- nrow(a.intervals) |
| if (missing(rows)) { |
| if (missing(columns)) { ## default |
| rows <- ceiling(round(sqrt(nplots))) |
| columns <- ceiling(nplots/rows) |
| } |
| else rows <- ceiling(nplots/columns) |
| } |
| else if (missing(columns)) |
| columns <- ceiling(nplots/rows) |
| if (rows * columns < nplots) |
| stop("rows * columns too small") |
| } |
| total.columns <- columns |
| total.rows <- rows |
| f.col <- f.row <- 1 |
| if(show.given[1L]) { |
| total.rows <- rows + 1 |
| f.row <- rows/total.rows |
| } |
| if(have.b && show.given[2L]) { |
| total.columns <- columns + 1 |
| f.col <- columns/total.columns |
| } |
| |
| mar <- if(have.b) rep.int(0, 4) else c(0.5, 0, 0.5, 0) |
| oma <- c(5, 6, 5, 4) |
| if(have.b) { oma[2L] <- 5 ; if(!b.is.fac) oma[4L] <- 5 } |
| if(a.is.fac && show.given[1L]) oma[3L] <- oma[3L] - 1 |
| |
| ## Start Plotting only now |
| opar <- par(mfrow = c(total.rows, total.columns), |
| oma = oma, mar = mar, xaxs = "r", yaxs = "r") |
| on.exit(par(opar)) |
| dev.hold(); on.exit(dev.flush(), add = TRUE) |
| plot.new() |
| ## as.numeric() allowing factors for x & y: |
| if(missing(xlim)) |
| xlim <- range(as.numeric(x), finite = TRUE) |
| if(missing(ylim)) |
| ylim <- range(as.numeric(y), finite = TRUE) |
| pch <- rep_len(pch, nobs) |
| col <- rep_len(col, nobs) |
| do.panel <- function(index, subscripts = FALSE, id) { |
| ## Use `global' variables |
| ## rows, columns, total.rows, total.columns, nplots, xlim, ylim |
| Paxis <- function(side, x) { |
| if(nlevels(x)) { |
| lab <- axlabels(x) |
| axis(side, labels = lab, at = seq(lab), xpd = NA) |
| } else |
| Axis(x, side=side, xpd = NA) |
| } |
| istart <- (total.rows - rows) + 1 |
| i <- total.rows - ((index - 1)%/%columns) |
| j <- (index - 1)%%columns + 1 |
| par(mfg = c(i, j, total.rows, total.columns)) |
| plot.new() |
| plot.window(xlim, ylim) |
| if(anyNA(id)) id[is.na(id)] <- FALSE |
| if(any(id)) { |
| grid(lty="solid") |
| if(subscripts) |
| panel(x[id], y[id], subscripts = id, |
| col = col[id], pch=pch[id], ...) |
| else |
| panel(x[id], y[id], col = col[id], pch=pch[id], ...) |
| } |
| if((i == total.rows) && (j%%2 == 0)) |
| Paxis(1, x) |
| else if((i == istart || index + columns > nplots) && (j%%2 == 1)) |
| Paxis(3, x) |
| |
| if((j == 1) && ((total.rows - i)%%2 == 0)) |
| Paxis(2, y) |
| else if((j == columns || index == nplots) && ((total.rows - i)%%2 == 1)) |
| Paxis(4, y) |
| box() |
| }## END function do.panel() |
| |
| if(have.b) { |
| count <- 1 |
| for(i in 1L:rows) { |
| for(j in 1L:columns) { |
| id <- ((a.intervals[j,1] <= a) & (a <= a.intervals[j,2]) & |
| (b.intervals[i,1] <= b) & (b <= b.intervals[i,2])) |
| do.panel(count, subscripts, id) |
| count <- count + 1 |
| } |
| } |
| } else { |
| for (i in 1L:nplots) { |
| id <- ((a.intervals[i,1] <= a) & (a <= a.intervals[i,2])) |
| do.panel(i, subscripts, id) |
| } |
| } |
| mtext(xlab[1L], side = 1, at = 0.5*f.col, outer = TRUE, line = 3.5, |
| xpd = NA, font = par("font.lab"), cex = par("cex.lab")) |
| mtext(ylab[1L], side = 2, at = 0.5*f.row, outer = TRUE, line = 3.5, |
| xpd = NA, font = par("font.lab"), cex = par("cex.lab")) |
| |
| if(length(xlab) == 1L) |
| xlab <- c(xlab, paste("Given :", a.name)) |
| ##mar <- par("mar") |
| if(show.given[1L]) { |
| par(fig = c(0, f.col, f.row, 1), |
| mar = mar + c(3+ !a.is.fac, 0, 0, 0), new=TRUE) |
| plot.new() |
| nint <- nrow(a.intervals) |
| a.range <- range(a.intervals, finite=TRUE) |
| ## 3% correction because axs = "r" extends by 4% : |
| plot.window(a.range + c(.03,-.03)*diff(a.range), 0.5 + c(0, nint)) |
| rect(a.intervals[, 1], 1L:nint - 0.3, |
| a.intervals[, 2], 1L:nint + 0.3, |
| col = bar.bg[if(a.is.fac) "fac" else "num"]) |
| if(a.is.fac) { |
| text(apply(a.intervals, 1L, mean), 1L:nint, a.levels) |
| } |
| else { |
| Axis(a, side = 3, xpd=NA) |
| axis(1, labels=FALSE) |
| } |
| box() |
| mtext(xlab[2L], 3, line = 3 - a.is.fac, at=mean(par("usr")[1L:2]), |
| xpd=NA, font = par("font.lab"), cex = par("cex.lab")) |
| } |
| else { ## i. e. !show.given |
| mtext(xlab[2L], 3, line = 3.25, outer = TRUE, at = 0.5*f.col, |
| xpd = NA, font = par("font.lab"), cex = par("cex.lab")) |
| } |
| if(have.b) { |
| if(length(ylab) == 1L) |
| ylab <- c(ylab, paste("Given :", b.name)) |
| if(show.given[2L]) { |
| par(fig = c(f.col, 1, 0, f.row), |
| mar = mar + c(0, 3+ !b.is.fac, 0, 0), new=TRUE) |
| plot.new() |
| nint <- nrow(b.intervals) |
| b.range <- range(b.intervals, finite=TRUE) |
| ## 3% correction (see above) |
| plot.window(0.5 + c(0, nint), b.range+ c(.03,-.03)*diff(b.range)) |
| rect(1L:nint - 0.3, b.intervals[, 1], |
| 1L:nint + 0.3, b.intervals[, 2], |
| col = bar.bg[if(b.is.fac)"fac" else "num"]) |
| if(b.is.fac) { |
| text(1L:nint, apply(b.intervals, 1L, mean), b.levels, srt = 90) |
| } |
| else { |
| Axis(b, side=4, xpd=NA) |
| axis(2, labels=FALSE) |
| } |
| box() |
| mtext(ylab[2L], 4, line = 3 - b.is.fac, |
| at = mean(par("usr")[3:4]), xpd = NA, |
| font = par("font.lab"), cex = par("cex.lab")) |
| } |
| else { |
| mtext(ylab[2L], 4, line = 3.25, at=0.5*f.row, outer = TRUE, |
| xpd = NA, font = par("font.lab"), cex = par("cex.lab")) |
| } |
| } |
| if (length(missingrows)) { |
| cat("\n", gettextf("Missing rows: %s", |
| paste0(missingrows, collapse = ", ")), "\n") |
| invisible(missingrows) |
| } else invisible() |
| } |