【问题标题】:R: remove repeating row entries in gridExtra tableR:删除 gridExtra 表中的重复行条目
【发布时间】:2013-09-30 06:18:09
【问题描述】:

问题:

我使用gridExtra 包创建了一个表:

require("gridExtra")

# Prepare data frame
col1 = c(rep("A", 3), rep("B", 2), rep("C", 5))
col2 = c(rep("1", 4), rep("2", 3), rep("3", 3))
col3 = c(1:10)
df = data.frame(col1, col2, col3)

# Create table
grid.arrange(tableGrob(df, show.rownames=F))

输出:

问题:

我想摆脱重复的行条目并实现看起来像这样的跨越条目(此图像是用 Photoshop 制作的模型):

任何想法如何在 R 中以编程方式实现这一目标?

【问题讨论】:

  • 好奇心:你想在什么应用中使用它?
  • @Ferdinand.kraft:该表是作为 knitr 代码块的一部分生成的。
  • 我不认为这就是@Ferdinand.kraft 的意思。我认为问题更多的是“你为什么要这样做?”
  • @AnandaMahto:Knitr 代码块是 LaTeX 报告的一部分。报告包含表格。为了更轻松的阅读体验,我更喜欢跳过重复的行条目。我知道还有其他用于 LaTeX 输出的表格相关包,但在这种情况下我更喜欢 extraGrid 的表格功能。希望这能澄清“为什么”?
  • 我会为此使用 gtable。 This gist 有几个例子被注释掉了。

标签: r gridextra


【解决方案1】:

我会使用 gtable,并利用其更灵活的框架,

require(gtable)
require(plyr)

## build a rectGrob with parameters
cellRect <- function(fill=NA) 
  rectGrob(gp=gpar(fill=fill, col=NA))

cellText <- function(label, colour="black", 
                     hjust=c("left", "center", "right"), ...) {
  hjust <- match.arg(hjust)
  x <- switch(hjust,
              "left" = 0,
              "center"=0.5,
              "right"=1)
  textGrob(label, x=x, hjust=x, gp=gpar(col=colour, ...))
}


rowMax_units <- function(m){
  do.call(unit.c, apply(m, 1, function(l)
    max(do.call(unit.c, lapply(l, grobHeight)))))
}

colMax_units <- function(m){
  do.call(unit.c, apply(m, 2, function(l)
    max(do.call(unit.c, lapply(l, grobWidth)))))
}

findHeights <- function(l)
  do.call(unit.c, lapply(l,grobHeight))
findWidths <- function(l)
  do.call(unit.c, lapply(l,grobWidth))

## NAs are used to indicate grobs that span multiple cells
gtable_colheader <- function(header, n = NULL, 
                             padding=unit(rep(5,5),"mm"), ...){

  type <- 2L
  if(is.null(n)) n <- max(apply(header, type, length))

  start <- alply(header, type, function(s) which(!is.na(s), TRUE))
  end <- llply(start, function(s) c(s[-1], n+1) - 1 )

  fixed <- rep(seq_along(start), sapply(start, length)) # t,b for rows, l,r for cols

  label <- header[!is.na(header)]

  d <- data.frame(label =  label,
                  start=unlist(start), end=unlist(end), fixed, fixed,
                  stringsAsFactors=FALSE)

  names(d) <- c("label","t","b","l","r")

  ## make grobs
  d$grobs <- lapply(d$label, cellText, hjust="center")
  d$widths <- lapply(d$grobs, grobWidth)
  d$heights <- lapply(d$grobs, grobHeight)

  widths <- dlply(d, names(d)[4], # t if type==1, l if type==2
                  function(d) width=do.call(unit.c, d$widths))
  heights <- dlply(d, names(d)[4],
                   function(d) heights=do.call(unit.c, d$heights))

  ## extract widths and heights relevant to the layout
  attr(d, "widths") <- do.call(unit.c, lapply(widths, max))
  attr(d, "heights") <- heights[[which(sapply(heights, length) == n)]]

  ## create gtable
  g <- gtable()
  g <- gtable_add_cols(g, attr(d,"widths") + padding[1])
  g <- gtable_add_rows(g, attr(d,"heights")+ padding[2])

  ## vertical/horizontal separators
  sgh <- segmentsGrob(x0 = unit(0, "npc"), y0 = unit(0, "npc"),
                      x1 = unit(1, "npc"), y1 = unit(0, "npc"),
                      gp=gpar(lwd=2, col="white"))
  sgv <- segmentsGrob(x0 = unit(1, "npc"), y0 = unit(0, "npc"),
                      x1 = unit(1, "npc"), y1 = unit(1, "npc"),
                      gp=gpar(lwd=2, col="white"))
  d2 <- subset(d, b < n)
  g <- with(d2, gtable_add_grob(g, replicate(length(d2$grobs), sgh, simplify=FALSE),
                                t, l, b, r, z=1, name="seph"))
  g <- gtable_add_grob(g, replicate(ncol(g)-1, sgv, simplify=FALSE),
                       t=1, b=nrow(g),l=seq.int(ncol(g)-1), z=1, name="sepv")
  g <- with(d, gtable_add_grob(g, grobs, t, l, b, r, z=0, name="text"))
  g <- gtable_add_grob(g, rectGrob(gp=gpar(fill="grey90", col="white")), t=1, l=1, 
                       b=nrow(g), r=ncol(g), z=-Inf, name="rect")
  g
}

v <- cbind(c("A", NA, NA, "B", NA, "C", NA, NA, NA, NA),
           c(1, NA, NA, NA, 2, NA, NA, 3, NA, NA),
           seq(1,10))
g2 <- gtable_colheader(v)
header <- paste0("col #",1:3)
head <- lapply(header, textGrob, gp=gpar(fontface="bold"))
w <- do.call(unit.c, lapply(header, stringWidth)) + unit(5, "mm")
h <- max(do.call(unit.c, lapply(head, grobHeight))) + unit(5, "mm")
hg <- gtable_matrix("header",  widths=w, heights=h,
                      grobs=matrix(head, nrow=1))

grid.newpage()
grid.draw(gtable:::rbind_gtable(hg, g2, size="first"))

【讨论】:

  • 哇,这是一个完美的解决方案。最后一步对我开放:如何将我的数据框df 转换为您用于答案的对象v。您能否修改您的答案,使其适用于我的数据框df
  • 我想不出一个聪明的把戏,所以你可能想问一个单独的问题
  • @user2030503 我会发布代码来做这件事,但你最好给 baptiste 打勾,否则所有过去的程序员都会困扰你,并在你的大脑中输入额外的击键。跨度>
  • 是的,表格布局相关的答案是基于 baptiste 的代码,而数据相关的代码来自 DWin - 非常感谢!
  • attr(d, "heights") attr(d, "heights")
【解决方案2】:
 require(grid)
 require(gridExtra)
   Loading required package: gridExtra

 df = data.frame(col1, col2, col3, stringsAsFactors=FALSE)
df2 <- df
df2[] <- lapply(df2, function(col) col <- ifelse( !duplicated(col, fromLast=TRUE), col, ""))
df2
#---------------
   col1 col2 col3
1               1
2               2
3     A         3
4          1    4
5     B         5
6               6
7          2    7
8               8
9               9
10    C    3   10
#-------------
 grid.arrange(tableGrob(df2, show.rownames=F))  # works

复制和分配到df2[] 的两步过程保留了数据帧结构。重复参数 fromLast 将“命中”更改为系列中的最后一个而不是第一个。

根据明确要求,这里是计算第一列位置的代码:

> tapply(df[[1]], df[[1]], FUN=function(x) mean(seq_along(x)))
  A   B   C 
2.0 1.5 3.0 

以下是根据您的数据创建 v-matrix 的代码:

v <- as.matrix( as.data.frame( lapply(df,function(col) 
             ifelse(!duplicated(col), as.character(col), NA)))  )
v
      col1 col2 col3
 [1,]    1    1    1
 [2,]   NA   NA    2
 [3,]   NA   NA    3
 [4,]    2   NA    4
 [5,]   NA    2    5
 [6,]    3   NA    6
 [7,]   NA   NA    7
 [8,]   NA    3    8
 [9,]   NA   NA    9
[10,]   NA   NA   10
  g2 <- gtable_colheader(v)
 header <- colnames(v)
 head <- lapply(header, textGrob, gp=gpar(fontface="bold"))
 w <- do.call(unit.c, lapply(header, stringWidth)) + unit(5, "mm")
 h <- max(do.call(unit.c, lapply(head, grobHeight))) + unit(5, "mm")
 hg <- gtable_matrix("header",  widths=w, heights=h,
                       grobs=matrix(head, nrow=1))

 grid.newpage()
 grid.draw(gtable:::rbind_gtable(hg, g2, size="first"))

【讨论】:

  • 谢谢。您的代码有 2 个问题。第一个它覆盖 col1 中的字母以显示为数字,第二个跨越后的条目出现在底部。我更喜欢中间(我的问题)或顶部。
  • 哎呀!再次被f-ing因素咬伤。只需在数据帧调用中使用 stringsAsFactors=FALSE 即可。 (或使用as.character(col)),但是,我不知道你指的是什么“中间”。
  • 对不起,我的解释有点草率: 示例:“C”不应出现在第 10 行组合单元格的底部,而应出现在中间。查看我的模型图片 - 它出现在第 8 行,而不是第 10 行。
  • 您可以提供用于构建该表的代码。我不打算追求这个,因为它似乎需要你已经完成的一堆“网格”黑客攻击。
  • 表格布局相关的答案是基于 baptiste 的代码,而数据相关的代码来自 DWin - 非常感谢!
猜你喜欢
  • 1970-01-01
  • 2013-03-09
  • 2012-04-10
  • 1970-01-01
  • 2017-04-08
  • 1970-01-01
  • 1970-01-01
  • 2015-11-24
相关资源
最近更新 更多