#
# hyperframe.R
#
# $Revision: 1.72 $ $Date: 2020/01/21 04:15:26 $
#
hyperframe <- local({
hyperframe <- function(...,
row.names=NULL, check.rows=FALSE, check.names=TRUE,
stringsAsFactors=default.stringsAsFactors()) {
aarg <- list(...)
nama <- names(aarg)
## number of columns (= variables)
nvars <- length(aarg)
if(nvars == 0) {
## zero columns - return
result <- list(nvars=0,
ncases=0,
vname=character(0),
vtype=factor(,
levels=c("dfcolumn","hypercolumn","hyperatom")),
vclass=character(0),
df=data.frame(),
hyperatoms=list(),
hypercolumns=list())
class(result) <- c("hyperframe", class(result))
return(result)
}
## check column names
if(is.null(nama))
nama <- paste("V", 1:nvars, sep="")
else if(any(unnamed <- (nama == "")))
nama[unnamed] <- paste("V", seq_len(sum(unnamed)), sep="")
nama <- make.names(nama, unique=TRUE)
names(aarg) <- nama
## Each argument must be either
## - a vector suitable as a column in a data frame
## - a list of objects of the same class
## - a single object of some class
dfcolumns <- sapply(aarg, is.dfcolumn)
hypercolumns <- sapply(aarg, is.hypercolumn)
hyperatoms <- !(dfcolumns | hypercolumns)
## Determine number of rows (= cases)
columns <- dfcolumns | hypercolumns
if(!any(columns)) {
ncases <- 1
} else {
heights <- rep.int(1, nvars)
heights[columns] <- lengths(aarg[columns])
u <- unique(heights)
if(length(u) > 1) {
u <- u[u != 1]
if(length(u) > 1)
stop(paste("Column lengths are inconsistent:",
paste(u, collapse=",")))
}
ncases <- u
if(ncases > 1 && all(heights[dfcolumns] == 1)) {
## force the data frame to have 'ncases' rows
aarg[dfcolumns] <- lapply(aarg[dfcolumns], rep, ncases)
heights[dfcolumns] <- ncases
}
if(any(stubs <- hypercolumns & (heights != ncases))) {
## hypercolumns of height 1 should be hyperatoms
aarg[stubs] <- lapply(aarg[stubs], "[[", i=1L)
hypercolumns[stubs] <- FALSE
hyperatoms[stubs] <- TRUE
}
}
## Collect the data frame columns into a data frame
if(!any(dfcolumns))
df <- as.data.frame(matrix(, ncases, 0), row.names=row.names)
else {
df <- do.call(data.frame,
append(aarg[dfcolumns],
list(row.names=row.names,
check.rows=check.rows,
check.names=check.names,
stringsAsFactors=stringsAsFactors)))
names(df) <- nama[dfcolumns]
}
## Storage type of each variable
vtype <- character(nvars)
vtype[dfcolumns] <- "dfcolumn"
vtype[hypercolumns] <- "hypercolumn"
vtype[hyperatoms] <- "hyperatom"
vtype=factor(vtype, levels=c("dfcolumn","hypercolumn","hyperatom"))
## Class of each variable
vclass <- character(nvars)
if(any(dfcolumns))
vclass[dfcolumns] <- unlist(lapply(as.list(df), class1))
if(any(hyperatoms))
vclass[hyperatoms] <- unlist(lapply(aarg[hyperatoms], class1))
if(any(hypercolumns))
vclass[hypercolumns] <- unlist(lapply(aarg[hypercolumns], class1of1))
## Put the result together
result <- list(nvars=nvars,
ncases=ncases,
vname=nama,
vtype=vtype,
vclass=vclass,
df=df,
hyperatoms=aarg[hyperatoms],
hypercolumns=aarg[hypercolumns])
class(result) <- c("hyperframe", class(result))
return(result)
}
dateclasses <-
is.dfcolumn <- function(x) {
is.atomic(x) &&
(is.vector(x) ||
is.factor(x) ||
inherits(x, c("POSIXlt", "POSIXct", "Date")))
}
is.hypercolumn <- function(x) {
if(!is.list(x))
return(FALSE)
if(inherits(x, c("listof", "anylist")))
return(TRUE)
if(length(x) <= 1)
return(TRUE)
cla <- lapply(x, class)
return(length(unique(cla)) == 1)
}
class1 <- function(x) { class(x)[1L] }
class1of1 <- function(x) { class(x[[1L]])[1L] }
hyperframe
})
is.hyperframe <- function(x) inherits(x, "hyperframe")
print.hyperframe <- function(x, ...) {
ux <- unclass(x)
nvars <- ux$nvars
ncases <- ux$ncases
if(nvars * ncases == 0) {
splat("NULL hyperframe with", ncases,
ngettext(ncases, "row (=case)", "rows (=cases)"),
"and", nvars,
ngettext(nvars, "column (=variable)", "columns (=variables)"))
} else {
if(waxlyrical('gory')) cat("Hyperframe:\n")
print(as.data.frame(x, discard=FALSE), ...)
}
return(invisible(NULL))
}
dim.hyperframe <- function(x) {
with(unclass(x), c(ncases, nvars))
}
summary.hyperframe <- function(object, ..., brief=FALSE) {
x <- unclass(object)
y <- list(
nvars = x$nvars,
ncases = x$ncases,
dim = c(x$ncases, x$nvars),
typeframe = data.frame(VariableName=x$vname, Class=x$vclass),
storage = x$vtype,
col.names = x$vname)
classes <- x$vclass
names(classes) <- x$vname
y$classes <- classes
# Ordinary data frame columns
df <- x$df
y$dfnames <- colnames(df)
y$df <- if(length(df) > 0 && !brief) summary(df) else NULL
y$row.names <- row.names(df)
# insert into full array
if(!brief && x$nvars > 0) {
isobject <- (x$vtype != "dfcolumn")
nobj <- sum(isobject)
if(nobj == 0) {
allcols <- y$df
} else {
nas <- rep(list(NA_character_), nobj)
names(nas) <- x$vname[isobject]
allcols <- do.call(cbind, append(list(y$df), nas))
acnames <- c(colnames(df), names(nas))
allcols <- allcols[ , match(x$vname, acnames), drop=FALSE]
}
pclass <- padtowidth(paren(classes), colnames(allcols), justify="right")
allcols <- as.table(rbind(class=pclass, as.table(allcols)))
row.names(allcols) <- rep("", nrow(allcols))
y$allcols <- allcols
}
class(y) <- c("summary.hyperframe", class(y))
return(y)
}
print.summary.hyperframe <- function(x, ...) {
nvars <- x$nvars
ncases <- x$ncases
splat(if(nvars * ncases == 0) "NULL hyperframe" else "hyperframe",
"with", ncases,
ngettext(ncases, "row", "rows"),
"and", nvars,
ngettext(nvars, "column", "columns"))
if(nvars == 0)
return(invisible(NULL))
print(if(any(x$storage == "dfcolumn")) x$allcols else noquote(x$classes))
return(invisible(NULL))
}
names.hyperframe <- function(x) { unclass(x)$vname }
"names<-.hyperframe" <- function(x, value) {
x <- unclass(x)
stopifnot(is.character(value))
value <- make.names(value)
if(length(value) != x$nvars)
stop("Incorrect length for vector of names")
vtype <- x$vtype
names(x$df) <- value[vtype == "dfcolumn"]
names(x$hyperatoms) <- value[vtype == "hyperatom"]
names(x$hypercolumns) <- value[vtype == "hypercolumn"]
x$vname <- value
class(x) <- c("hyperframe", class(x))
return(x)
}
row.names.hyperframe <- function(x) {
return(row.names(unclass(x)$df))
}
"row.names<-.hyperframe" <- function(x, value) {
y <- unclass(x)
row.names(y$df) <- value
class(y) <- c("hyperframe", class(y))
return(y)
}
dimnames.hyperframe <- function(x) {
ux <- unclass(x)
return(list(row.names(ux$df), ux$vname))
}
"dimnames<-.hyperframe" <- function(x, value) {
if(!is.list(value) || length(value) != 2 || !all(sapply(value, is.character)))
stop("Invalid 'dimnames' for a hyperframe", call.=FALSE)
rn <- value[[1L]]
cn <- value[[2L]]
d <- dim(x)
if(length(rn) != d[1L])
stop(paste("Row names have wrong length:",
length(rn), "should be", d[1L]),
call.=FALSE)
if(length(cn) != d[2L])
stop(paste("Column names have wrong length:",
length(cn), "should be", d[2L]),
call.=FALSE)
y <- unclass(x)
row.names(y$df) <- value[[1L]]
y$vname <- value[[2]]
class(y) <- c("hyperframe", class(y))
return(y)
}
## conversion to hyperframe
as.hyperframe <- function(x, ...) {
UseMethod("as.hyperframe")
}
as.hyperframe.hyperframe <- function(x, ...) {
return(x)
}
as.hyperframe.data.frame <- function(x, ..., stringsAsFactors=FALSE) {
xlist <- if(missing(x)) NULL else as.list(x)
do.call(hyperframe,
resolve.defaults(
xlist,
list(...),
list(row.names=rownames(x),
stringsAsFactors=stringsAsFactors),
.StripNull=TRUE))
}
as.hyperframe.anylist <-
as.hyperframe.listof <- function(x, ...) {
if(!missing(x)) {
xname <- sensiblevarname(short.deparse(substitute(x)), "x")
xlist <- list(x)
names(xlist) <- xname
} else xlist <- NULL
do.call(hyperframe,
resolve.defaults(
xlist,
list(...),
list(row.names=rownames(x)),
.StripNull=TRUE))
}
as.hyperframe.default <- function(x, ...) {
as.hyperframe(as.data.frame(x, ...))
}
#### conversion to other types
as.data.frame.hyperframe <- function(x, row.names = NULL,
optional = FALSE, ...,
discard=TRUE, warn=TRUE) {
ux <- unclass(x)
if(is.null(row.names))
row.names <- row.names(ux$df)
vtype <- ux$vtype
vclass <- ux$vclass
dfcol <- (vtype == "dfcolumn")
if(discard) {
nhyper <- sum(!dfcol)
if(nhyper > 0 && warn)
warning(paste(nhyper,
ngettext(nhyper, "variable", "variables"),
"discarded in conversion to data frame"))
df <- as.data.frame(ux$df, row.names=row.names, optional=optional, ...)
} else {
lx <- as.list(x)
nrows <- ux$ncases
vclassstring <- paren(vclass)
if(any(!dfcol))
lx[!dfcol] <- lapply(as.list(vclassstring[!dfcol]),
rep.int, times=nrows)
df <- do.call(data.frame, append(lx, list(row.names=row.names)))
colnames(df) <- ux$vname
}
return(df)
}
as.list.hyperframe <- function(x, ...) {
ux <- unclass(x)
out <- vector(mode="list", length=ux$nvars)
vtype <- ux$vtype
df <- ux$df
if(any(dfcol <- (vtype == "dfcolumn")))
out[dfcol] <- as.list(df)
if(any(hypcol <- (vtype == "hypercolumn"))) {
hc <- lapply(ux$hypercolumns, as.solist, demote=TRUE)
out[hypcol] <- hc
}
if(any(hatom <- (vtype == "hyperatom"))) {
ha <- ux$hyperatoms
names(ha) <- NULL
hacol <- lapply(ha, list)
hacol <- lapply(hacol, rep.int, times=ux$ncases)
hacol <- lapply(hacol, as.solist, demote=TRUE)
out[hatom] <- hacol
}
out <- lapply(out, "names<-", value=row.names(df))
names(out) <- names(x)
return(out)
}
# evaluation
# eval.hyper <- function(e, h, simplify=TRUE, ee=NULL) {
# .Deprecated("with.hyperframe", package="spatstat")
# if(is.null(ee))
# ee <- as.expression(substitute(e))
# with.hyperframe(h, simplify=simplify, ee=ee)
# }
with.hyperframe <- function(data, expr, ..., simplify=TRUE, ee=NULL,
enclos=NULL) {
if(!inherits(data, "hyperframe"))
stop("data must be a hyperframe")
if(is.null(ee))
ee <- as.expression(substitute(expr))
if(is.null(enclos))
enclos <- parent.frame()
n <- nrow(data)
out <- vector(mode="list", length=n)
datalist <- as.list(data)
for(i in 1:n) {
rowi <- lapply(datalist, "[[", i=i) # ensures the result is always a list
outi <- eval(ee, rowi, enclos)
if(!is.null(outi))
out[[i]] <- outi
}
names(out) <- row.names(data)
if(simplify && all(unlist(lapply(out, is.vector)))) {
# if all results are atomic vectors of equal length,
# return a matrix or vector.
lenfs <- lengths(out)
if(all(unlist(lapply(out, is.atomic))) &&
length(unique(lenfs)) == 1) {
out <- t(as.matrix(as.data.frame(out)))
row.names(out) <- row.names(data)
out <- out[,,drop=TRUE]
return(out)
}
}
out <- hyperframe(result=out, row.names=row.names(data))$result
return(out)
}
cbind.hyperframe <- function(...) {
aarg <- list(...)
narg <- length(aarg)
if(narg == 0)
return(hyperframe())
namarg <- names(aarg)
if(is.null(namarg))
namarg <- rep.int("", narg)
ishyper <- unlist(lapply(aarg, inherits, what="hyperframe"))
isdf <- unlist(lapply(aarg, inherits, what="data.frame"))
columns <- list()
for(i in 1:narg) {
if(ishyper[i] || isdf[i]){
if(ncol(aarg[[i]]) > 0) {
newcolumns <- as.list(aarg[[i]])
if(namarg[i] != "")
names(newcolumns) <- paste(namarg[i], ".", names(newcolumns), sep="")
columns <- append(columns, newcolumns)
}
} else {
nextcolumn <- list(aarg[[i]])
names(nextcolumn) <- namarg[i]
columns <- append(columns, nextcolumn)
}
}
result <- do.call(hyperframe, columns)
## tack on row names
rona <- lapply(aarg, row.names)
good <- (lengths(rona) == nrow(result))
if(any(good)) {
rona <- rona[[min(which(good))]]
row.names(result) <- make.names(rona, unique=TRUE)
}
return(result)
}
rbind.hyperframe <- function(...) {
argh <- list(...)
if(length(argh) == 0)
return(NULL)
# convert them all to hyperframes
argh <- lapply(argh, as.hyperframe)
#
nargh <- length(argh)
if(nargh == 1)
return(argh[[1L]])
# check for compatibility of dimensions & names
dfs <- lapply(argh, as.data.frame, discard=FALSE)
dfall <- do.call(rbind, dfs)
# check that data frame columns also match
dfs0 <- lapply(argh, as.data.frame, discard=TRUE, warn=FALSE)
df0all <- do.call(rbind, dfs0)
# assemble data
rslt <- list()
nam <- names(dfall)
nam0 <- names(df0all)
for(k in seq_along(nam)) {
nama <- nam[k]
if(nama %in% nam0) {
# data frame column: already made
rslt[[k]] <- dfall[,k]
} else {
# hypercolumns or hyperatoms: extract them
hdata <- lapply(argh, "[", j=nama, drop=FALSE)
hdata <- lapply(lapply(hdata, as.list), getElement, name=nama)
# append them
hh <- hdata[[1L]]
for(j in 2:nargh) {
hh <- append(hh, hdata[[j]])
}
rslt[[k]] <- hh
}
}
## collect the row names
rona <- sapply(dfs, row.names)
rona <- make.names(rona, unique=TRUE)
## make hyperframe
names(rslt) <- nam
out <- do.call(hyperframe, append(rslt,
list(stringsAsFactors=FALSE,
row.names=rona)))
return(out)
}
plot.hyperframe <-
function(x, e, ..., main, arrange=TRUE,
nrows=NULL, ncols=NULL,
parargs=list(mar=mar * marsize),
marsize=1, mar=c(1,1,3,1)) {
xname <- short.deparse(substitute(x))
main <- if(!missing(main)) main else xname
mar <- rep(mar, 4)[1:4]
if(missing(e)) {
# default: plot first column that contains objects
ok <- (summary(x)$storage %in% c("hypercolumn", "hyperatom"))
if(any(ok)) {
j <- min(which(ok))
x <- x[,j, drop=TRUE, strip=FALSE]
x <- as.solist(x, demote=TRUE)
plot(x, ..., main=main, arrange=arrange, nrows=nrows, ncols=ncols)
return(invisible(NULL))
} else {
# hyperframe does not contain any objects
# invoke plot.data.frame
x <- as.data.frame(x)
plot(x, ..., main=main)
return(invisible(NULL))
}
}
if(!is.language(e))
stop(paste("Argument e should be a call or an expression;",
"use quote(...) or expression(...)"))
ee <- as.expression(e)
if(!arrange) {
# No arrangement specified: just evaluate the plot expression 'nr' times
with(x, ee=ee)
return(invisible(NULL))
}
# Arrangement
# Decide whether to plot a main header
banner <- (sum(nchar(as.character(main))) > 0)
if(length(main) > 1)
main <- paste(main, collapse="\n")
nlines <- if(!is.character(main)) 1 else length(unlist(strsplit(main, "\n")))
# determine arrangement of plots
# arrange like mfrow(nrows, ncols) plus a banner at the top
n <- summary(x)$ncases
if(is.null(nrows) && is.null(ncols)) {
nrows <- as.integer(floor(sqrt(n)))
ncols <- as.integer(ceiling(n/nrows))
} else if(!is.null(nrows) && is.null(ncols))
ncols <- as.integer(ceiling(n/nrows))
else if(is.null(nrows) && !is.null(ncols))
nrows <- as.integer(ceiling(n/ncols))
else stopifnot(nrows * ncols >= length(x))
nblank <- ncols * nrows - n
# declare layout
mat <- matrix(c(seq_len(n), numeric(nblank)),
byrow=TRUE, ncol=ncols, nrow=nrows)
heights <- rep.int(1, nrows)
if(banner) {
# Increment existing panel numbers
# New panel 1 is the banner
panels <- (mat > 0)
mat[panels] <- mat[panels] + 1L
mat <- rbind(rep.int(1,ncols), mat)
heights <- c(0.1 * (1 + nlines), heights)
}
# initialise plot
layout(mat, heights=heights)
# plot banner
if(banner) {
opa <- par(mar=rep.int(0,4), xpd=TRUE)
plot(numeric(0),numeric(0),type="n",ann=FALSE,axes=FALSE,
xlim=c(-1,1),ylim=c(-1,1))
cex <- resolve.defaults(list(...), list(cex.title=2))$cex.title
text(0,0,main, cex=cex)
}
# plot panels
npa <- do.call(par, parargs)
if(!banner) opa <- npa
with(x, ee=ee)
# revert
layout(1)
par(opa)
return(invisible(NULL))
}
str.hyperframe <- function(object, ...) {
d <- dim(object)
x <- unclass(object)
argh <- resolve.defaults(list(...), list(nest.lev=0, indent.str=" .."))
cat(paste("'hyperframe':\t",
d[1L], ngettext(d[1L], "row", "rows"),
"and",
d[2L], ngettext(d[2L], "column", "columns"),
"\n"))
nr <- d[1L]
nc <- d[2L]
if(nc > 0) {
vname <- x$vname
vclass <- x$vclass
vtype <- as.character(x$vtype)
indentstring <- with(argh, paste(rep.int(indent.str, nest.lev), collapse=""))
for(j in 1:nc) {
tag <- paste("$", vname[j])
switch(vtype[j],
dfcolumn={
desc <- vclass[j]
if(nr > 0) {
vals <- object[1:min(nr,3),j,drop=TRUE]
vals <- paste(paste(format(vals), collapse=" "), "...")
} else vals <- ""
},
hypercolumn=,
hyperatom={
desc <- "objects of class"
vals <- vclass[j]
})
cat(paste(paste(indentstring, tag, sep=""),
":", desc, vals, "\n"))
}
}
return(invisible(NULL))
}
subset.hyperframe <- function(x, subset, select, ...) {
stopifnot(is.hyperframe(x))
r <- if(missing(subset)) {
rep_len(TRUE, nrow(x))
} else {
r <- eval(substitute(
with(x, e, enclos=parent.frame()),
list(e=substitute(subset))))
if (!is.logical(r))
stop("'subset' must be logical")
r & !is.na(r)
}
vars <- if(missing(select)) {
TRUE
} else {
nl <- as.list(seq_len(ncol(x)))
names(nl) <- names(x)
eval(substitute(select), nl, parent.frame())
}
nama <- names(x)
names(nama) <- nama
vars <- nama[vars]
z <- x[i=r, j=vars, ...]
return(z)
}
head.hyperframe <- function (x, n = 6L, ...) {
stopifnot(length(n) == 1L)
n <- if(n < 0L) max(nrow(x) + n, 0L) else min(n, nrow(x))
x[seq_len(n), , drop = FALSE]
}
tail.hyperframe <- function(x, n = 6L, ...) {
stopifnot(length(n) == 1L)
nrx <- nrow(x)
n <- if(n < 0L) max(nrx + n, 0L) else min(n, nrx)
sel <- seq.int(to = nrx, length.out = n)
x[sel, , drop = FALSE]
}
edit.hyperframe <- function(name, ...) {
x <- name
isdf <- unclass(x)$vtype == "dfcolumn"
if(!any(isdf)) {
warning("No columns of editable data", call.=FALSE)
return(x)
}
y <- x[,isdf]
ynew <- edit(as.data.frame(y), ...)
xnew <- x
for(na in names(ynew)) xnew[,na] <- ynew[,na]
losenames <- setdiff(names(y), names(ynew))
for(na in losenames) xnew[,na] <- NULL
return(xnew)
}