# Function for Generating LaTeX Tables with Decimal Aligned Numbers

January 4, 2013
By

Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

The `xtable` package is tremendously useful for generating LaTeX tables from data frames. It is also pretty easy to customize the output to handle some special cases of LaTeX formatting. The `xtable.decimal` function will create a LaTeX table where numeric columns will be vertically aligned on the decimal point. In addition to specifying the LaTeX alignment code it will also create appropriate column titles so that the column name spans the two resulting columns. In the following example, we create a data frame with five columns, three of which are `numeric` that we want to display with aligned decimal places. We also have a column of type `character` and another of type `integer`.

``````> df <- data.frame(Id=letters[1:10],
Split1=rnorm(10, mean=0, sd=10),
Numbers=1:10,
Split2=rnorm(10, mean=-1, sd=.5),
Split3=rnorm(10, mean=10, sd=.75))
#A whole number because prettyNum will not print anything after the decimal point.
> df[5,'Split1'] = 2
> df
Id      Split1 Numbers     Split2    Split3
1   a -11.4957434       1 -1.0974334 10.895100
2   b  11.5985173       2 -1.6314018  9.539108
3   c   0.1062397       3 -1.1795816 10.788935
4   d   1.1586916       4 -0.4612825 10.462340
5   e   2.0000000       5 -0.5154210  9.279148
6   f  14.1508151       6 -1.1308398 10.001278
7   g  -6.7793246       7  0.3325152 10.026043
8   h   1.4984447       8 -1.3638784 10.373774
9   i   4.1131184       9 -1.2390835 10.996286
10   j   5.4228637      10 -1.8332372  9.881770
> str(df)
'data.frame':	10 obs. of  5 variables:
\$ Id     : Factor w/ 10 levels "a","b","c","d",..: 1 2 3 4 5 6 7 8 9 10
\$ Split1 : num  -0.5072 13.3672 -0.0906 -9.5944 2 ...
\$ Numbers: int  1 2 3 4 5 6 7 8 9 10
\$ Split2 : num  -1.359 -0.722 -1.341 -0.359 -1.022 ...
\$ Split3 : num  9.87 10.29 9.55 8.51 10.36 ...``````

The `xtable.decimal` function (source code below) has five parameters:

• `x` the data frame to convert.
• `cols` the columns to align. This defaults to columns of type `numeric` but can be specified explicitly as a numeric vector specifying the column position within `x`.
• `colAlignment` all non-aligned columns will be aligned left (i.e. `l`) by default. If you wish to align any columns right (`r`) or centered (`c`), then create a named vector where the name corresponds to the column name (as identified by `names(x)`) and the value the new alignment.
• `tocharFun` the function that will be used to convert the column to a character vector. This is `prettyNum` by default.
• `...` other parameters that are passed to `tocharFun`, `xtable`, and `print.xtable`.

Here we will create the LaTeX table for the data frame created above.

`````` xtable.decimal(df, digits=3,
colAlignment=c(Numbers='c'),
caption.placement='bottom',
caption='Test Data Frame')``````

And the resulting table as it appears in the PDF:

Link to `xtable.decimal.r` as a Gist.

``````require(xtable)

#' Prints a LaTeX table with numeric columns aligned on their decimal points.
#'
#' functions in the \code{xtable} package so that numeric columns are aligned
#' on their decimal place.
#'
#' See \url{http://jason.bryer.org/posts/2013-01-04/xtable_with_aligned_decimals.html}
#'
#' @author Jason Bryer
#' @param x a data frame to create a LaTeX table from.
#' @param cols a numeric vector indicating which columns should be aligned on
#'        decimal points. It defaults to all columns of type numeric.
#' @param colAlignment named character vector where each element name corresponds to a
#         column name and the value is the LaTeX alignment (i.e. l, r, or c).
#' @param tocharFun the function used to convert the numeric vecotr to a character
#'        vector. This defaults to \code{\link{prettyNum}}, but other possible
#'        \code{\link{formatC}}, or some other custom function.
#' @param ... other parameters passed to \code{tocharFun}, \code{\link{xtable}},
#' @seealso xtable
#' @export
xtable.decimal <- function(x,
cols=which(lapply(x, class) == 'numeric'),
colAlignment,
tocharFun=prettyNum,
...) {
splitCol <- function(x, ...) {
s <- strsplit(tocharFun(x, ...), split='.', fixed=TRUE)
right <- sapply(s, FUN=function(x) { ifelse(length(x) == 2, x[2], '0') })
left <- sapply(s, FUN=function(x) { x[1] })
data.frame(left=left, right=right, stringsAsFactors=FALSE)
}

cols <- cols[order(cols, decreasing=TRUE)]
colnames <- names(x)
for(i in cols) {
if(i == 1) {
tmp <- cbind(splitCol(x[,1], ...), x[,2:ncol(x)])
names(tmp)[1:2] <- paste(names(tmp)[1], c('left','right'), sep='.')
names(tmp)[3:ncol(x)] <- names(x)[2:ncol(x)]
x <- tmp
} else if(i == ncol(x)) {
tmp <- cbind(x[,1:(ncol(x)-1)], splitCol(x[,ncol(x)], ...))
names(tmp)[1:(ncol(tmp)-2)] <- names(x)[1:(ncol(x)-1)]
names(tmp)[(ncol(tmp)-1):ncol(tmp)] <- paste(names(x)[ncol(x)],
c('left','right'), sep='.')
x <- tmp
} else {
tmp <- cbind(x[,1:(i-1)], splitCol(x[,i], ...), x[,(i+1):ncol(x)])
names(tmp)[1:(i-1)] <- names(x)[1:(i-1)]
names(tmp)[i:(i+1)] <- paste(names(x)[i], c('left','right'), sep='.')
names(tmp)[(i+2):ncol(tmp)] <- names(x)[(i+1):ncol(x)]
x <- tmp
}
}

colnames[cols] <- paste('\\multicolumn{2}{c}{', colnames[cols], '}', sep='')
colnames <- paste(colnames, collapse=' & ')

addtorow\$command <- paste( colnames, ' \\\\ ', sep='')

align <- rep('l', ncol(x))
if(!missing(colAlignment)) {
for(i in seq_along(colAlignment)) {
align[names(x) == names(colAlignment)[i]] <- colAlignment[i]
}
}
align[grep('.left\$', names(x), perl=TRUE)] <- '[email protected]{.}'
align <- c('l', align) #Add an alignment for row names

xtab <- xtable(x, align=align, ...)
}``````

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.