blob: 593fe126f2ae9bbf7809d8b1449c772858527e1e [file] [log] [blame]
# File src/library/stats/R/addmargins.R
# Part of the R package, https://www.R-project.org
#
# Copyright (C) 2004-2015 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/
addmargins <-
function(A, margin = seq_along(dim(A)), FUN = sum, quiet = FALSE)
{
## The workhorse for this margin-expansion is the function
## expand.one, which is defined and called at the bottom.
##
## All this initial stuff is just to check consistency of
## specifications, and form maximally sensible margin names
##
## BxC, August 2003
## Sept 2003: Single margins caused crash. Fixed.
## Duncan Murdoch, Feb 2004: Machinery to derive functionnames
## from unnamed lists
##-------------------------------------------------------------
if(is.null(dim(A))) stop("'A' must be an array or table")
## How many dimensions of A, and how many sides do we touch?
n.sid <- length(margin)
## Check if FUN was specified
##
miss.FUN <- missing(FUN)
## Check if FUN has the same length as margin, and if not, stop or
## expand a single function specification to a list of the same
## length as the margins vector.
if (length(FUN) == 1 && !is.list(FUN)) {
fname <- if (!miss.FUN) deparse(substitute(FUN)) else "Sum"
FUN <- setNames(list(FUN), fname)
}
if (!miss.FUN) {
## Recursive function to add names to unnamed list components
add.names <- function(thelist)
{
n <- names(thelist)
if (is.null(n)) n <- rep("", length(thelist))
for (i in seq_along(thelist)[-1L]) {
if (!is.call(thelist[[i]])) {
if (n[i] == "") n[i] <- as.character(thelist[[i]])
} else if (as.character(thelist[[i]][[1L]]) == "list")
thelist[[i]] <- add.names(thelist[[i]])
}
names(thelist) <- n
thelist
}
## this only makes sense if we were given an expression for FUN
## which we can deparse.
if(mode(substitute(FUN)) == "call")
FUN <- eval(add.names(substitute(FUN)))
if (is.null(names(FUN))) names(FUN) <- rep("", length(FUN))
}
## At this point FUN is a list with names wherever
## we could figure them out, empty strings otherwise
if(length(FUN) != n.sid) {
if(length(FUN) == 1L)
FUN <- rep(FUN, n.sid)
else
stop(gettextf(
"length of FUN, %d,\n does not match the length of the margins, %d",
length(FUN), n.sid), domain = NA)
}
## If FUN is not given the default sum is put in the margin
## otherwise make a list to fill with names
##
fnames <- vector("list", n.sid)
## Use the names from FUN and also possibly the names from
## sublists of FUN. Replace blanks with constructed names
for(i in seq_along(FUN)) {
fnames[[i]] <- names(FUN)[i]
if (is.list(FUN[[i]])) {
topname <- fnames[[i]]
fnames[[i]] <- names(FUN[[i]])
blank <- fnames[[i]] == ""
fnames[[i]][blank] <- seq_along(blank)[blank]
if (topname == "") {
fnames[[i]][blank] <-
paste0("Margin ", margin[i], ".", fnames[[i]][blank])
} else {
fnames[[i]] <- paste0(topname, ".", fnames[[i]])
}
} else if (fnames[[i]] == "")
fnames[[i]] <- paste("Margin", margin[i])
}
## So finally we have the relevant form of FUN and fnames to pass
## on to expand.one which expands over one factor at a time.
expand.one <- function(A, margin, FUN, fnames)
{
## Function to expand a table with a set of margins over the
## side <margin>, i.e. by a set of marginal tables classified by
## all factors except <margin>.
##
## BxC, August 2003
## Make sure that FUN is a list
if(!inherits(FUN, "list")) FUN <- list(FUN)
## Useful constants
d <- dim(A)
n.dim <- length(d) # number of dimensions in the table
n.mar <- length(FUN) # number of margins to be added
## Define the dimensions of the new table with the margins
newdim <- d
newdim[margin] <- newdim[margin] + n.mar
if(is.null(dnA <- dimnames(A))) dnA <- vector("list", n.dim)
dnA[[margin]] <-
c(if(is.null(dnA[[margin]])) rep("", d[[margin]]) else dnA[[margin]],
fnames)
## Number of elements in the expanded array
n.new <- prod(newdim)
## The positions in the vector-version of the new table
## where the original table values goes, as a logical vector
skip <- prod(d[1L:margin])
runl <- skip / d[margin]
apos <- rep(c(rep_len(TRUE, skip), rep_len(FALSE, n.mar*runl)),
n.new/(skip+n.mar*runl))
## Define a vector to hold all the values of the new table
values <- double(length(apos))
## First fill in the body of the table
values[apos] <- as.vector(A)
## Then sucessively compute and fill in the required margins
for(i in 1L:n.mar) {
mtab <- if(n.dim > 1)
apply(A, (1L:n.dim)[-margin], FUN[[i]])
else
FUN[[i]](A)
## Vector the same length as the number of margins
select <- rep_len(FALSE, n.mar)
## The position of the current margin
select[i] <- TRUE
## Expand that to a vector the same length as the entire new matrix
mpos <- rep(c(rep_len(FALSE, skip), rep(select, each=runl)),
prod(dim(A))/skip)
## Fill the marginal table in there
values[mpos] <- as.vector(mtab)
}
## the new table with contents and margins
array(values, dim=newdim, dimnames=dnA)
}
## Once defined, we can use the expand.one function repeatedly
new.A <- A
for(i in 1L:n.sid)
new.A <- expand.one(A = new.A, margin = margin[i], FUN = FUN[[i]],
fnames = fnames[[i]])
if(inherits(A, "table")) # result shall be table, too
class(new.A) <- c("table", class(new.A))
## Done! Now print it.
##
if(!quiet && !miss.FUN && n.sid > 1) {
cat("Margins computed over dimensions\nin the following order:\n")
## FIXME: what is paste(i) supposed to do?
for(i in seq_len(n.sid))
cat(paste(i), ": ", names(dimnames(A))[margin[i]], "\n", sep = "")
}
new.A
}