blob: 854c06b8118a630026e32ccfbecd82e83747e356 [file] [log] [blame]
# 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()
}