Raw File
hyperframe.R
#
#  hyperframe.R
#
# $Revision: 1.24 $  $Date: 2007/05/09 12:56:12 $
#

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(sum(unnamed)), sep="")
  nama <- make.names(nama, unique=TRUE)
  
  # 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
  
  is.dfcolumn <- function(x) {
    is.atomic(x) && (is.vector(x) || is.factor(x))
  }
  is.hypercolumn <- function(x) {
    if(!is.list(x))
      return(FALSE)
    if(length(x) <= 1)
      return(TRUE)
    cla <- class(x[[1]])
    all(sapply(x, function(xi,cla) { identical(class(xi), cla) }, cla=cla))
  }
  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(1, nvars)
    heights[columns] <-  unlist(lapply(aarg[columns], length))
    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)
  }

  # 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
  class1 <- function(x) { class(x)[1] }
  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],
                                          function(x) { class1(x[[1]]) }))
  
  # 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)
}

is.hyperframe <- function(x) inherits(x, "hyperframe")

print.hyperframe <- function(x, ...) {
  ux <- unclass(x)
  nvars <- ux$nvars
  ncases <- ux$ncases
  if(nvars * ncases == 0) {
    cat(paste("NULL hyperframe with", ncases,
              ngettext(ncases, "row (=case)", "rows (=cases)"),
              "and", nvars,
              ngettext(nvars, "column (=variable)", "columns (=variables)"),
              "\n"))
  } else {
    cat("Hyperframe:\n")
    print(as.data.frame(x, discard=FALSE), ...)
  }
  return(invisible(NULL))
}

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 <- names(df)
  y$df <- if(length(df) > 0 && !brief) summary(df) else NULL
  y$row.names <- row.names(df)
  class(y) <- c("summary.hyperframe", class(y))
  return(y)
}

print.summary.hyperframe <- function(x, ...) {
  nvars <- x$nvars
  ncases <- x$ncases
  cat(paste(if(nvars * ncases == 0) "NULL" else NULL,
            "hyperframe with", ncases,
            ngettext(ncases, "row (=case)", "rows (=cases)"),
            "and", nvars,
            ngettext(nvars, "column (=variable)", "columns (=variables)"),
            "\n"))
  if(nvars == 0)
    return(invisible(NULL))
  # Variable names and types
  print(x$typeframe)
  # Ordinary data frame columns
  if(!is.null(x$df)) {
    cat("Summary of data frame columns:\n")
    print(x$df, ...)
  }
  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")
  x$vname <- value
  names(x$df) <- value[x$vtype == "dfcolumn"]
  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)
  df <- y$df
  row.names(df) <- value
  y$df <- df
  class(y) <- c("hyperframe", class(y))
  return(y)
}


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 <- paste("(", vclass, ")", sep="")
    if(any(!dfcol)) 
      lx[!dfcol] <- lapply(as.list(vclassstring[!dfcol]),
                           function(x,n) { rep(x,n)}, n=nrows)
    df <- do.call("data.frame", append(lx, list(row.names=row.names)))
  }
  return(df)
}

as.list.hyperframe <- function(x, ...) {
  ux <- unclass(x)
  nama <- ux$vname
  names(nama) <- nama
  out <- lapply(nama, function(nam, x) { x[, nam, drop=TRUE] }, x=x)
  return(out)
}
  
eval.hyper <- function(e, h, simplify=TRUE, ee=NULL) {
  if(!inherits(h, "hyperframe"))
    stop("h must be a hyperframe")
  if(is.null(ee))
    ee <- as.expression(substitute(e))
  out <- list()
  n <- unclass(h)$ncases
  for(i in 1:n) {
    hi <- h[i,, drop=FALSE]
    out[[i]] <- eval(ee, as.list(hi))
  }
  if(simplify &&
     all(unlist(lapply(out, is.vector))) &&
     all(unlist(lapply(out, length)) == 1)) 
    out <- unlist(out)
  out <- hyperframe(out)[drop=TRUE]
  return(out)
}

cbind.hyperframe <- function(...) {
  aarg <- list(...)
  narg <- length(aarg)
  if(narg == 0)
    return(hyperframe())
  namarg <- names(aarg)
  if(is.null(namarg))
    namarg <- rep("", narg)
  ishyper <- unlist(lapply(aarg, inherits, what="hyperframe"))
  columns <- list()
  for(i in 1:narg) {
    if(ishyper[i]){
      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)
  return(result)
}

plot.hyperframe <- function(x, e, ..., main, arrange=TRUE,
                            nrows=NULL, ncols=NULL,
                            parargs=list(mar=c(1,1,3,1) * marsize),
                            marsize=0.1) {
  xname <- deparse(substitute(x))
  main <- if(!missing(main)) main else xname
  ee <- as.expression(substitute(e))
  
  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]
      class(x) <- c("listof", class(x))
      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))
    }
  }

  # No arrangement: just evaluate the plot expression 'nr' times
  if(!arrange) {
    eval.hyper(ee=ee, h=x)
    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(n), rep(0, nblank)),
                byrow=TRUE, ncol=ncols, nrow=nrows)
  heights <- rep(1, nrows)
  if(banner) {
    # Increment existing panel numbers
    # New panel 1 is the banner
    panels <- (mat > 0)
    mat[panels] <- mat[panels] + 1
    mat <- rbind(rep(1,ncols), mat)
    heights <- c(0.1 * (1 + nlines), heights)
  }
  # initialise plot
  layout(mat, heights=heights)
  # plot banner
  if(banner) {
    opa <- par(mar=rep(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
  eval.hyper(ee=ee, h=x)
  # revert
  layout(1)
  par(opa)
  return(invisible(NULL))
}
  
back to top