(I want to have a gtable object with some data, a header and a footnote.
Each of the three elements are gtables, which are combinded to one gtable-object using gtable_add_rows first, and then gtable_add_grob to arrange the three gtables into one grob. Unfortunately the headline/footnote is too long (resp. too short)
How can I set the width of headline/ footline ?
How can I assign the text of headline/ footline to left-alignment?
My code example:
require(gtable)
require(grid)
require(gridExtra)
tbl<-matrix(paste(letters[1:6]),nrow=2)
colnames(tbl)<-c(paste0("col",1:3))
rownames(tbl)<-c(paste0("row",1:2))
tbl
tt1 <- ttheme_default(base_size = 10,rowhead=list(fg_params=list(fontface=2,hjust=0, x=0)))
tt2 <- ttheme_default(base_size = 15,fg_params=list(fontface="bold",hjust=1,x=0.9))
tt3 <- ttheme_default(base_size =7, fg_paras=list(fontface="italic",hjust=0,x=0.9))
gtbl <- tableGrob(tbl, theme=tt1)
htxt <- tableGrob("Headline is too long and this is stupid", theme=tt2)
ftxt <- tableGrob("Footnote", theme=tt3)
padding <- unit(1,"line")
table <- gtable_add_rows(gtbl,
heights = grobHeight(htxt) + padding,
pos = 0)
table <- gtable_add_rows(table,
heights = grobHeight(ftxt)+ padding)
table <- gtable_add_grob(table, list(htxt, ftxt),
t=c(1, nrow(table)), l=c(1,1),
r=ncol(table))
dim(table)
grid.newpage()
grid.draw(table)
Thanks for any help!
Volker
I solved my problem.
Only two minor problems are left. See below my commented code.
# load packages ------------------------------------------------------------
require(gtable)
require(grid)
require(gridExtra)
# id makro ----------------------------------------------------------------
# see here: https://stackoverflow.com/questions/43613320/how-to-add-multi-sub-columns-in-gridextratablegrob/43620247#43620247
# Tanks to: baptiste
id_cell <- function(table, row, col, name="colhead-fg"){
l <- table$layout
which(l$t %in% row & l$l %in% col & l$name==name)
}
# code to test it----------------------------------------------------------------
# Some different table themes
tt1 <- ttheme_default(base_size = 10,rowhead=list(fg_params=list(fontface=2,hjust=0, x=0))) # Data
tt2 <- ttheme_default(base_size = 15,fg_params=list(fontface="bold",hjust=1,x=0.9)) # Header
tt3 <- ttheme_default(base_size = 7,fg_params=list(fontface="italic",hjust=0,x=0.9)) # Footnote
# Convert to gtable
# 1. Data
# The data
tbl<-matrix(paste(letters[1:6]),nrow=2)
colnames(tbl)<-c("col1","column2","verywidecolum3")
rownames(tbl)<-c(paste0("row",1:2))
gtbl <- tableGrob(tbl, theme=tt1)
# 2. Headline
# Note: Define the Headline as gtable with as many columns a your Data
# Headline is defined in last colum due to the Makro id_cells.
hl<-"Headline"
hlm<-matrix(c(rep("",(dim(gtbl)[2]-1)),hl),nrow=1)
hlm
htxt <- tableGrob(hlm, theme=tt2)
# 3. Footnote
# Note: Define the Footnote as gtable with as many columns a your Data
# The text of your footnote is defined in last colum due to the Makro id_cells.
ftm<-matrix(c(rep("",(dim(gtbl)[2]-1)),"Footnote"),nrow=1)
ftxt <- tableGrob(ftm, theme=tt3)
# Define your table with Headline, Data and Footnote in one gtable object
tab2<-combine(htxt,gtbl,ftxt,along=2)
# Draw Grid - not formated
grid.newpage()
grid.draw(tab2)
# Formating of Grid Table
# Adjust columnwidths: same widths for each column
tab2$widths <- unit(rep(1/ncol(tab2), ncol(tab2)), "null")
#
rowg<-dim(tab2)[1] # Number of rows of your gtable
colg<-dim(tab2)[2] # Number of columns of your gtable
eleg<-2*rowg*colg # Number of grobs of your gtable
# Note: Grobs in gtables are arranged in rows. Within each row first grob class "text" is repeated for the columns of gtable, then grob class "rect"
# Identify Grob elements which should be formated
# This is Headline and footnote,
forg<-c(1:colg,(eleg-2*colg+1):(eleg-colg));forg
for (i in forg){
if ((class(tab2$grobs[[i]])=="text")[1] == TRUE) {
print(i)
tab2$grobs[[i]]$x<-unit(0,"npc")
tab2$grobs[[i]]$hjust<-0
}
}
# Headline over all cells
idh <- id_cell(tab2, 1, colg,"core-fg")
tab2$layout[idh,"l"] <- tab2$layout[idh,"l"] - (colg-1)
# Footnote over all cells
idf <- id_cell(tab2, rowg, colg,"core-fg")
tab2$layout[idf,"l"] <- tab2$layout[idf,"l"] - (colg-1)
# Draw grid
grid.newpage()
grid.draw(tab2)
My two minor problems:
a) This code
tab2$widths <- unit(rep(1/ncol(tab2), ncol(tab2)), "null")
makes the same widths for each column. But it seems, that it is not the minimal needed columnwidth. In my example it is the minimum needed width of the last column. How to fit the columns with the minimal needed columnwidth, so that each column has the same width?
b) How to change babtistes function id_cell,
# id makro ----------------------------------------------------------------
# see here: https://stackoverflow.com/questions/43613320/how-to-add-multi-sub-columns-in-gridextratablegrob/43620247#43620247
# Tanks to: baptiste
id_cell <- function(table, row, col, name="colhead-fg"){
l <- table$layout
which(l$t %in% row & l$l %in% col & l$name==name)
}
that it is possible to specify first the header/footnote an then the neccessary empty cells?
This is related to this code section:
# 2. Headline
# Note: Define the Headline as gtable with as many columns a your Data
# Headline is defined in last colum due to the Makro id_cells.
hl<-"Headline"
hlm<-matrix(c(rep("",(dim(gtbl)[2]-1)),hl),nrow=1)
htxt <- tableGrob(hlm, theme=tt2)
and
# Headline over all cells
idh <- id_cell(tab2, 1, colg,"core-fg")
tab2$layout[idh,"l"] <- tab2$layout[idh,"l"] - (colg-1)
Related
I have data on students exam results. Each student takes a 100 question exam. Each question has been allocated (unequally) to one of 5 domains and also has a unique learning outcome associated with it. I need to summarise the data in a table (likely spread over multiple pdf pages), identifying for each domain, the learning outcomes the student got correct/incorrect.
For each Domain I have managed to get a table which shows the the learning outcomes the student got correct/incorrect, but I can't figure out how to join the tables together after the loop. I'd really appreciate some help with this...
Please find below a copy of my code:
library(tidyverse)
library(dplyr)
library(gridExtra)
library(grid)
library(gtable)
library(ids)
# Create some random Learning Objectives:
LO <- ids::adjective_animal(2500, 2, style = "sentence")
# Create data:
testdata <- data.frame(cbind(StudentID=rep(1:25,each=100),
QuestionNumber=rep(1:100),
Correct=sample(c(0,1),replace=TRUE,size=2500),
Domain=c("A","A","A","B","C","C","C","D","D","E"),
LearningOutcome=LO))
rm(LO)
# Create lists for Loops:
DomainList <- unique(testdata$Domain)
StudentList <- unique(testdata$StudentID)
Domain.Colours <- list("steelblue3","red2","violetred3","forestgreen","chocolate1")
for (i in 1:length(unique(testdata$StudentID))){
for (j in 1:length(DomainList)){
# Select Domain Specfic Data
TEMP.DatabyDomain <- testdata[testdata$Domain==DomainList[j],]
# Split Learning Outcomes into Correct/Incorrect Lists
TEMP.Correct <- TEMP.DatabyDomain$LearningOutcome[TEMP.DatabyDomain$StudentID == StudentList[i] & TEMP.DatabyDomain$Correct == 1]
TEMP.Incorrect <- TEMP.DatabyDomain$LearningOutcome[TEMP.DatabyDomain$StudentID == StudentList[i] & TEMP.DatabyDomain$Correct == 0]
# Update the lengths to be the same
n <- max(length(TEMP.Correct), length(TEMP.Incorrect))
length(TEMP.Correct) <- n
length(TEMP.Incorrect) <- n
# Combine the data into a new df
FeedbackData <- data.frame(TEMP.Correct, TEMP.Incorrect)
FeedbackData <- sapply(FeedbackData, as.character)
# Replace NAs with ""
FeedbackData[is.na(FeedbackData)] <- " "
# Create column headings
colnames(FeedbackData) <- c(paste("Correctly answered questions\n in domain:",DomainList[j]),paste("Incorrectly answered questions\n in domain:",DomainList[j]))
# Table Settings
tt1 <- ttheme_default(core=list(fg_params=list(fontsize=8)),
rowhead=list(fg_params=list(hjust=0, x=0)),
colhead=list(bg_params=list(fill=paste(Domain.Colours[j])),
fg_params=list(col="white")))
# Table Results
tfeedback <- tableGrob(FeedbackData, theme=tt1, rows = NULL)
}
### Here is where I get stuck ###
# I would like to append the 5 Domain tables together, so that they are one long table with the column headings in-between
tfeedbacktitle <- textGrob(paste("Feedback for Students"),gp=gpar(fontsize=20))
padding <- unit(10,"mm")
table <- gtable_add_rows(
tfeedback,
heights = grobHeight(tfeedbacktitle) + padding,
pos = 0)
table <- gtable_add_grob(
table,
tfeedbacktitle,
1, 1, 1, ncol(table))
# Code below to allow table to cover multiple pages
# Taken from <https://stackoverflow.com/questions/15937131/print-to-pdf-file-using-grid-table-in-r-too-many-rows-to-fit-on-one-page>
fullheight <- convertHeight(sum(table$heights), "cm", valueOnly = TRUE)
margin <- unit(0,"in")
margin_cm <- convertHeight(margin, "cm", valueOnly = TRUE)
a4height <- 29.7 - margin_cm
nrows <- nrow(table)
npages <- ceiling(fullheight / a4height)
heights <- convertHeight(table$heights, "cm", valueOnly = TRUE)
rows <- cut(cumsum(heights), include.lowest = FALSE,
breaks = c(0, cumsum(rep(a4height, npages))))
groups <- split(seq_len(nrows), rows)
gl1 <- lapply(groups, function(id) table[id,])
mypath <- file.path(pathOutput,paste("StudentID_", StudentList[i], ".pdf", sep = ""))
pdf(file=mypath, paper = "a4", width = 0, height = 0)
for(page in seq_len(npages)){
grid.newpage()
grid.rect(width=unit(21,"cm") - margin,
height=unit(29.7,"cm")- margin)
grid.draw(gl1[[page]])
}
dev.off()
}
The link shows a picture of what I hope to achieve: Example Final Result
I know variations on this question have been up several times, but couldn't figure out how to apply those solutions to this particular challenge:
I would like to use ggplot inside a d*ply call to plot the data (data frame dat below) broken up by the v3variable and display a numeric variable v2 for the 3 conditions in v1. I want to have the plots in one page (pdf), so thought I could use dlply to contain resulting plots in a list that then could be fed to the multiplot wrapper function for ggplot2 found in 'Cookbook for R' here
# Multiple plot function
#
# ggplot objects can be passed in ..., or to plotlist (as a list of ggplot objects)
# - cols: Number of columns in layout
# - layout: A matrix specifying the layout. If present, 'cols' is ignored.
#
# If the layout is something like matrix(c(1,2,3,3), nrow=2, byrow=TRUE),
# then plot 1 will go in the upper left, 2 will go in the upper right, and
# 3 will go all the way across the bottom.
#
multiplot <- function(..., plotlist=NULL, file, cols=1, layout=NULL) {
require(grid)
# Make a list from the ... arguments and plotlist
plots <- c(list(...), plotlist)
numPlots = length(plots)
# If layout is NULL, then use 'cols' to determine layout
if (is.null(layout)) {
# Make the panel
# ncol: Number of columns of plots
# nrow: Number of rows needed, calculated from # of cols
layout <- matrix(seq(1, cols * ceiling(numPlots/cols)),
ncol = cols, nrow = ceiling(numPlots/cols))
}
if (numPlots==1) {
print(plots[[1]])
} else {
# Set up the page
grid.newpage()
pushViewport(viewport(layout = grid.layout(nrow(layout), ncol(layout))))
# Make each plot, in the correct location
for (i in 1:numPlots) {
# Get the i,j matrix positions of the regions that contain this subplot
matchidx <- as.data.frame(which(layout == i, arr.ind = TRUE))
print(plots[[i]], vp = viewport(layout.pos.row = matchidx$row,
layout.pos.col = matchidx$col))
}
}
}
Here is a toy data frame:
set.seed(999)
dat <- data.frame(
v1 = rep(c("A","B","C"),25),
v2 = runif(75,-1,2),
v3 = sample(c("hippo", "smoke", "meat"), 75, replace=T))
Here is the best I could come up with - it gives the plots separately but doesnt merge them, and gives a strange output in console. Note that any solution not using multiplot() is just as good for me.
require(dplyr)
require(ggplot2)
p <- dlply(dat, .(v3), function(x){
ggplot(x,aes(v1, v2)) +
geom_point()})
multiplot(plotlist=p, cols=2)
Here's a different way that avoids multiplot() and uses techniques shown here and here:
library(ggplot2)
library(dplyr)
results <- dat %>%
group_by(v3) %>%
do(plot = ggplot(., aes(v1, v2)) + geom_point())
pdf('all.pdf')
invisible(lapply(results$plot, print))
dev.off()
My following code:
library(gplots);
library(RColorBrewer);
dat <- read.table("http://pastebin.com/raw.php?i=wM7WxEvY",sep="\t",na.strings="NA",header=TRUE)
dat <- dat[complete.cases(dat),]
dat.log <- log2(dat);
# Clustering and distance function
hclustfunc <- function(x) hclust(x, method="ward")
distfunc <- function(x) dist(x,method="maximum")
# Here we cluster based on the Celltype (Column)
d <- distfunc(t(dat.log))
fit <- hclustfunc(d)
clusters <- cutree(fit, h=20)
nofclust.height <- length(unique(as.vector(clusters)));
# Colors setting
hmcols <- rev(redblue(256));
selcol <- colorRampPalette(brewer.pal(9,"Set1"))
clustcol.height = selcol(nofclust.height);
pdf(file="temp.pdf",width=30,height=40);
heatmap.2(as.matrix(dat.log),Colv=FALSE,lhei = c(0.25,4),ColSideColors=clustcol.height[clusters],density.info="none",scale="none",margin=c(10,10),col=hmcols,symkey=F,trace="none",dendrogram="none",keysize=1,hclust=hclustfunc,distfun=distfunc);
dev.off();
Generate this figure:
Note that the ColSideColors is too high. How can shorten it?
?heatmap.2: The layout for the RowSideColor and ColSideColor "can be overriden by specifiying appropriate values for lmat, lwid, and lhei. lmat controls the relative position of each element, while lwid controls the column width, and lhei controls the row height. See the help page for layout for details on how to use these arguments."
In your case, you have to change the lhei, try lhei=c(1,15)
I'm trying to create a table using the gridExtra package in R, and I want to have sub column names under a general column name. For example have one large column titled "Urbana-Champaign" that spans over two smaller column names "element" and "number of genes." I have looked everywhere on the gridExtra support site but can't seem to find a way to create overall column names that encompass subcolumns. Does anyone know how?
It's rather easy to get a basic gtable, and add new text to it, but you'd have to add all the formatting and styling of the cells. That's where I always give up -- way too many parameters and options to take care of.
library(gtable)
gtable_add_grobs <- gtable_add_grob #misleading name
d <- head(iris, 3)
extended_matrix <- cbind(c("", rownames(d)), rbind(colnames(d), as.matrix(d)))
all_grobs <- matrix(lapply(extended_matrix, textGrob), ncol=ncol(d) + 1)
row_heights <- function(m){
do.call(unit.c, apply(m, 1, function(l)
max(do.call(unit.c, lapply(l, grobHeight)))))
}
col_widths <- function(m){
do.call(unit.c, apply(m, 2, function(l)
max(do.call(unit.c, lapply(l, grobWidth)))))
}
g <- gtable_matrix("table", grobs=all_grobs,
widths=col_widths(all_grobs) + unit(4,"mm"),
heights=row_heights(all_grobs) + unit(4,"mm"))
g <- gtable_add_rows(g, unit(1, "line"), 0)
g <- gtable_add_grobs(g, list(textGrob("Sepal's main title"),
textGrob("Petal's main title"))
t=1,b=1,l=c(2, 4), r=c(3, 5))
grid.newpage()
grid.draw(g)
I'm trying my first steps purely in grid. As an exercise, I would like to create a pairs plot (similar to pairs()) purely based on grid. The function myplotGrob below should create the grid object (grob; or gTree) and return the object.
I'm not sure
what's the best way to continue. Which units should one use? (tried "null", too)
Is frameGrob meant to set up the layout? (this is what I understood from Paul Murrell's book) How do I have to choose/adjust the viewports such that I get the desired plot (so far, I only see a mess) Is the layout meant to be set up beforehand or is it better to just step-by-step "concatenate" additional panels to get the (4, 4) plot matrix?
require(grid)
require(mvtnorm)
set.seed(271)
X <- rmvnorm(1000, mean=1:4, sigma=diag(4:1)) # goal: draw this in a pairs plot
## auxiliary function
panel <- function(x, y) pointsGrob(x=x, y=y, name="panel", gp=gpar(), vp=NULL)
## creates and returns a gTree (class)
myplotGrob <- function(X, name=NULL, gp=NULL, vp=NULL)
{
## x-axis grob
## y-axis grob
## ...
## set up layout
layout <- grid.layout(4, 4, # (4, 4) matrix
widths=rep(0.25, 4), heights=rep(0.25, 4),
default.units="npc")
## pushViewport(viewport(layout=layout)) # required???
all <- frameGrob(layout=layout) # produces a gTree without children
for(i in 1:4) {
for(j in 1:4) {
## group grobs together
gt <- gTree(X,
children=gList(panel(X[,i], X[,j])),
name=name, gp=gp, vp=vp, cl="myplotGrob")
all <- placeGrob(all, gt, row=i, col=j)
}
}
all
}
## draw the gTree
grid.myplot <- function(...) grid.draw(myplotGrob(...))
## call
grid.myplot(X)
UPDATE
As it was asked for, here is the design/layout of the original problem I have in mind (the above would have only been a minimal/learning example). The units in cm were just for me (they should be 'relative' in the end). Of course, the number of panels may vary. I would like all parts to be grid objects, so that the function which creates the graphic will return an object (without printing/drawing). This way, each part can be modified afterwards. The graphic should display results from an array of dimension 5 (or less): one dimension is displayed in the row panels [row.vars], one in the column panels [col.vars], one on the x axis of each panel [xvar], and each panel can contain 2 different dimensions of the array (differing by color and line type) [I used d and n in the drawing]. If course, if the array is four-dimensional, then row 8 of the above design should be missing. I can construct the layout via grid, but the whole question is how to continue from there. That's what I wanted to express with my "minimal example" above.
I think you can divide the task in two main parts, like the basic examples in grid.panel() and grid.multipanel()
1- build a function that will produce a single panel, returned as a gTree. You need to figure out all the parameters, i.e. limits, axes, colours, shapes, grid, coordinates, ... You might end up rewriting lattice panel functions and axes,
grid.newpage()
grid::grid.panel(vp=viewport(width=0.8, height=0.8))
2- assemble the panels in a layout. This is much easier (and cleaner) with gtable,
library(gtable)
grid.newpage()
lg <- replicate(16, grobTree(rectGrob(), pointsGrob()), simplify=FALSE)
gt <- gtable_matrix("pairs", grobs=matrix(lg, ncol=4),
widths=unit(rep(1, 4), "null"),
heights=unit(rep(1, 4), "null"))
gt <- gtable_add_col_space(gt, width=unit(0.5,"line"))
gt <- gtable_add_row_space(gt, height=unit(0.5,"line"))
gt <- gtable_add_padding(gt, padding=unit(1,"line"))
grid.draw(gt)
If you want to build everything from scratch, here too you'll end up having to reinvent a good portion of gtable, I reckon.
Here's an attempt similar to grid.multipanel() but returning a gTree, and more specific to your pairs plot,
require(grid)
require(mvtnorm)
set.seed(271)
X <- rmvnorm(100, mean=1:4, sigma=diag(4:1)) # goal: draw this in a pairs plot
panelGrob <- function(x=runif(10, -10, 10), y=runif(10, -10, 100), ...,
xlim = range(x), ylim=range(y),
axis.x=TRUE, axis.y=TRUE){
xx <- pretty(x) ; yy <- pretty(y)
xx <- xx[xx <= xlim[2] & xx >= xlim[1]]
yy <- yy[yy <= ylim[2] & yy >= ylim[1]]
r <- rectGrob()
dvp <- dataViewport(xData=xx, yData=yy)
p <- pointsGrob(x, y, pch=".", gp=gpar(col="red"), default.units="native",
vp = dvp)
ax <- if(axis.x) xaxisGrob(at=xx, vp=dvp) else nullGrob()
ay <- if(axis.y) yaxisGrob(at=yy, vp=dvp) else nullGrob()
grobTree(r, ax, ay, p, ...)
}
grid.panel <- function(...)
grid.draw(panelGrob(...))
grid.newpage()
grid.panel(vp=viewport(width=0.8, height=0.8))
pairsGrob <- function(X, ..., name=NULL, gp=NULL, vp=NULL){
N <- NCOL(X)
layout <- grid.layout(N+1, N+1,
widths=unit(c(2, rep(1, N)), c("lines", rep("null", N))),
heights = unit(c(rep(1, N), 2), c(rep("null", N), "lines")))
wrap <- function(ii, jj, ...){
panelGrob(X[,ii], X[,jj], ..., axis.x= ii == N, axis.y = jj == 1,
vp=viewport(layout.pos.row=ii, layout.pos.col=jj+1))
}
rowcol <- expand.grid(ii=seq_len(N), jj=seq_len(N))
gl <- mapply(wrap, ii=rowcol[,"ii"], jj=rowcol[,"jj"], MoreArgs=list(...),
SIMPLIFY=FALSE)
gTree(children=do.call(gList, gl), vp=viewport(layout=layout))
}
grid.pairs <- function(...) grid.draw(pairsGrob(...))
grid.newpage()
grid.pairs(X, xlim=c(-10,10), ylim=c(-10,10))
Many problems are already apparent: i) it's cumbersome to add spacings in the layout, keeping track of the right viewports; ii) most parameters of the panel function are hard-wired (point shape, colour, grid, axis labels, ...), be prepared for an explosion in complexity, as in args(lattice::panel.xyplot); iii) the range of the axes should match across one row / column, which requires some thought about splitting the data properly in groups (facetting in ggplot2 or lattice); iv) the legend is yet another thing to reinvent in grid; v) ...