4

I have a table of values where each cell has a number, a space, and then another number in parentheses. I'm using xtable to render this table in the document. I'd like the numbers to be justified on the left parenthesis (or on the space). I've used the latex dcolumn package to create a command to justify on the left parenthesis. However, that changes other aspects of how the table is formatted and I'd like to prevent that from happening.

I know just enough latex to be dangerous and am not sure of the next step. Below is a reproducible example showing what the table looks like now and explaining how I'd actually like it to look. I'd like to figure out how to get the formatting I want programmatically, within the rmarkdown document, so that I don't have to hack the latex afterward. Also, I'm not wedded to this particular method of justifying the table values, so please feel free to suggest another approach if I'm on the wrong track.

Since this question focuses on using latex in the context of r, knitr and rmarkdown, I thought it would be better to ask it here, but please let me know if I should move it to the Tex Stack Exchange site instead.

header.tex file containing the dcolumn command:

\usepackage{dcolumn}
\newcolumntype{Q}{D{(}{(}{-1}}

rmarkdown document:

---
title: "Test"
date: "July 19, 2016"
output: 
  pdf_document:
    includes:
      in_header: header.tex
    keep_tex: yes
    number_sections: yes
fontsize: 11pt
geometry: margin=1in
graphics: yes
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE, message=FALSE, warning=FALSE, fig.align="center")
```

```{r}
library(xtable)

# Data frame to create table
tab1 = structure(list(Term = structure(1:5, .Label = c("Fall 2007", 
"Spring 2008", "Fall 2008", "Spring 2009", "Fall 2009", "Spring 2010", 
"Fall 2010", "Spring 2011", "Fall 2011", "Spring 2012", "Fall 2012", 
"Spring 2013", "Fall 2013", "Spring 2014", "Fall 2014", "Spring 2015", 
"Fall 2015", "Spring 2016", "Fall 2016"), class = c("ordered", 
"factor")), `BIO 10` = c("89 (2)", "96 (2)", "77 (1)", "103 (3)", 
"81 (1)"), `BIO 20` = c("194 (5)", "175 (3)", "176 (8)", "168 (3)", 
"170 (4)"), `BIO 30` = c("153 (2)", "154 (14)", "188 (7)", "192 (9)", 
"183 (8)"), `BIO 40` = c("284 (23)", "296 (5)", "267 (17)", "296 (16)", 
"279 (7)"), `BIO 50` = c("88 (1)", "107 (5)", "98 (1)", "109 (7)", 
"93 (5)")), .Names = c("Term", "BIO 10", "BIO 20", "BIO 30", 
"BIO 40", "BIO 50"), row.names = c(NA, 5L), class = "data.frame")
```

```{r results="asis"}
print.xtable(
  xtable(tab1, 
         label="tab:tab1",
         caption = "Default Table"), 
  size="small",
  include.rownames=FALSE, comment=FALSE, caption.placement="top"
)
```

```{r results="asis"}
print.xtable(
  xtable(tab1, 
         label="tab:tab2",
         caption = "Columns aligned at left parenthesis",
         align=c("llQQQQQ")), 
  size="small",
  include.rownames=FALSE, comment=FALSE, caption.placement="top"
)
```

Below is the output of the rmarkdown document. Table 1 is the default table created by xtable. Table 2 uses the dcolumn command in the my_header.tex file. In Table 2, the left-hand number in each cell is right-aligned, which is what I want. However, docolumn has changed the formatting in other ways that I don't want:

  1. Column headers should look like the column headers in Table 1, meaning there should be no italics and there should be a space between BIO and the following number.
  2. Column widths should be more like the widths in Table 1.
  3. In the data columns, there should be a space between the first number and the number in parentheses. For example, "89(2)" should be "89 (2)".
  4. If possible, it would be even better to have both numbers separately right-aligned. This means that there could be either one or two spaces between the numbers, depending on whether the number in parentheses has, respectively two digits or one digit.

enter image description here

eipi10
  • 91,525
  • 24
  • 209
  • 285
  • @user20650, just curious why you deleted your answer. It seems to work and it even justifies *both* sets of numbers separately. – eipi10 Jul 20 '16 at 15:40
  • Hi eipi10; I deleted it as Martin had rolled back his answer to his original (at that point), and I didnt think ny answer was sufficiently different to keep it (and I knew you would still be able to see it). ps: taken from another iteration of Martin's answer, you can get nicer spacing using `c("l","l", rep(c("r@{\\hskip 0in \\;}", "r"),5)))` – user20650 Jul 20 '16 at 18:46

2 Answers2

3

I updated my answer in that sense, that you dont need dcolumn anymore. It is a bit of a mix between using R's regex functionalities and adding primitive LaTeX commands such as{\hskip 0.5em}. The thing is, that you can add these primitives in (as far as I know) any LaTeX environment in order to format your paragraphs and such.

So using apply we reformat the content of the table cells depending on whether the number in parenthesis has 1 or 2 digits and then add a proper horizontal spacing.

By using sanitize.text.function = identity inside of print.xtable we make sure that these LaTeX commands do not get deleted when the data.frame is processed by xtable.

---
title: "Test"
output: 
  pdf_document:
    keep_tex: true
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE, message=FALSE, warning=FALSE, fig.align="center")
```

```{r}
library(xtable)
namesVec <- c("Term", "BIO 10", "BIO 20", "BIO 30", 
"BIO 40", "BIO 50")
# Data frame to create table
tab1 = structure(list(Term = structure(1:5, .Label = c("Fall 2007", 
"Spring 2008", "Fall 2008", "Spring 2009", "Fall 2009", "Spring 2010", 
"Fall 2010", "Spring 2011", "Fall 2011", "Spring 2012", "Fall 2012", 
"Spring 2013", "Fall 2013", "Spring 2014", "Fall 2014", "Spring 2015", 
"Fall 2015", "Spring 2016", "Fall 2016"), class = c("ordered", 
"factor")), `BIO 10` = c("89 (2)", "96 (2)", "77 (1)", "103 (3)", 
"81 (1)"), `BIO 20` = c("194 (5)", "175 (3)", "176 (8)", "168 (3)", 
"170 (4)"), `BIO 30` = c("153 (2)", "154 (14)", "188 (7)", "192 (9)", 
"183 (8)"), `BIO 40` = c("284 (23)", "296 (5)", "267 (17)", "296 (16)", 
"279 (7)"), `BIO 50` = c("88 (1)", "107 (5)", "98 (1)", "109 (7)", 
"93 (5)")), .Names = paste("\\textnormal{", namesVec, "}"), row.names = c(NA, 5L), class = "data.frame")

tab1 <-apply(tab1, 2, function(x) { 
  tmp <- nchar(gsub(".*\\( ?([0-9]+).*","\\1", x))
  skip <-ifelse(tmp == 1, "{\\\\hskip 1em}(", "{\\\\hskip 0.5em}(")
  ifelse(tmp == 1, gsub(x, pattern = " \\(", replacement = paste("{\\\\hskip 1em}(")),
                   gsub(x, pattern = " \\(", replacement = paste("{\\\\hskip 0.5em}(")))
})
```


```{r results="asis"}
print.xtable(
  xtable(tab1, 
         label="tab:tab2",
         caption = "Columns aligned at left parenthesis",
         align=c("llrrrrr")), 
  size="small",
  include.rownames=FALSE, comment=FALSE, caption.placement="top"
, sanitize.text.function = identity)
```

enter image description here

Martin Schmelzer
  • 23,283
  • 6
  • 73
  • 98
1

This is a similar approach to Martin;s now edited away answer. Its probably easier (at least for a non-latex speaker like me) to align the numbers, and numbers in parenthesis separately, so split these into separate columns. You can then use \multicolumn to group the columns, and define the headers (see possible to create latex multicolumns in xtable?)

```{r results="asis", echo=FALSE}    

tab1 = structure(list(Term = structure(1:5, .Label = c("Fall 2007", 
"Spring 2008", "Fall 2008", "Spring 2009", "Fall 2009", "Spring 2010", 
"Fall 2010", "Spring 2011", "Fall 2011", "Spring 2012", "Fall 2012", 
"Spring 2013", "Fall 2013", "Spring 2014", "Fall 2014", "Spring 2015", 
"Fall 2015", "Spring 2016", "Fall 2016"), class = c("ordered", 
"factor")), `BIO 10` = c("89 (2)", "96 (2)", "77 (1)", "103 (3)", 
"81 (1)"), `BIO 20` = c("194 (5)", "175 (3)", "176 (8)", "168 (3)", 
"170 (4)"), `BIO 30` = c("153 (2)", "154 (14)", "188 (7)", "192 (9)", 
"183 (8)"), `BIO 40` = c("284 (23)", "296 (5)", "267 (17)", "296 (16)", 
"279 (7)"), `BIO 50` = c("88 (1)", "107 (5)", "98 (1)", "109 (7)", 
"93 (5)")), .Names = c("Term", "BIO 10", "BIO 20", "BIO 30", 
"BIO 40", "BIO 50"), row.names = c(NA, 5L), class = "data.frame")

tab2 <- cbind(tab1[1], do.call(cbind.data.frame, lapply(tab1[-1], function(x) 
  do.call(rbind, strsplit(as.character(x), " ")))))

addtorow <- list(list(0), paste(names(tab1)[1], paste0('& \\multicolumn{2}{l}{', names(tab1)[-1], '}', collapse=''), '\\\\'))

library(xtable)

print.xtable(
  xtable(tab2,
         align=c("l","l", rep(c("r@{\\hskip 0in}", "r"),5))),
  include.rownames=FALSE, ,
  add.to.row=addtorow, include.colnames=FALSE)

```

enter image description here

Community
  • 1
  • 1
user20650
  • 24,654
  • 5
  • 56
  • 91