Better LaTeX Tables with Booktabs

One of the joys of \LaTeX is that it makes it so easy to typeset tables. I’ve created many \LaTeX tables and I’ve always taken care to fine-tune them, most often by inserting commands of the form \rule{0cm}{8pt} to stop superscripts hitting a horizontal rule in the line above.

I’ve been aware for a while of a \LaTeX package called booktabs that claims to produce more beautiful tables that more closely resemble those used in traditional typesetting. I was dissuaded from trying it by three things: the results looked rather spread out, the package discourages the use of vertical rules (which I thought I needed), and I wasn’t sure if publishers such as SIAM would allow it to be used in their journals.

I was encouraged to give booktabs a try while I was preparing the third edition of Handbook of Writing for the Mathematical Sciences. I redid the tables from the previous edition using the package and, after some experimentation, became convinced that booktabs is indeed a great aid to producing better tables.

The best way to illustrate the attractions of booktabs is with a simple example. This table is from a 2017 SIAM J. Sci. Comput. paper of which I am a co-author: table_ex1.jpg

The original table did not have the vertical rules at the sides or the horizontal rule at the bottom, but I have added them since many people include them.

Here is a revised version of the table created with booktabs:

table_ex2.jpg

The second table dispenses with all the vertical rules and adds two partial horizontal rules to demarcate the span of the pairs of columns covered by the two tol values. Notice the thicker rules at the top and bottom and the greater spacing between lines. The booktabs version of the table is more aesthetically pleasing. It is also slightly easier to typeset. The rules at the top, middle, and bottom are typed with \toprule, \midrule, and \botomrule. The subrules are typed with \cmidrule{m-n} to span columns m to n. The usage \cmidrule(lr){m-n} crops (on the left and right) the rules so that adjacent subrules do not merge.

Here is the source code for the two tables:

% First version of table.
\begin{tabular}{|l|c|c|c|c|c|l|}\hline
& \multicolumn{3}{c|}{$\tol=\tols$} & \multicolumn{3}{c|}{$\tol=\told$}\\
           & $mv$  & Rel.~err & Time    & $mv$  & Rel.~err & Time   \\\hline
\trigmv    & 11034 & 1.3e-7 & 3.9 & 15846 & 2.7e-11 & 5.6 \\
\trigexpmv & 21952 & 1.3e-7 & 6.2 & 31516 & 2.7e-11 & 8.8 \\
\trigblock & 15883 & 5.2e-8 & 7.1 & 32023 & 1.1e-11 & 1.4e1\\
\expleja   & 11180 & 8.0e-9 & 4.3 & 17348 & 1.5e-11 & 6.6 \\\hline
\end{tabular}

% Second version of table, with booktabs.
\begin{tabular}{lcccccl}\toprule
& \multicolumn{3}{c}{$\tol=\tols$} & \multicolumn{3}{c}{$\tol=\told$}
\\\cmidrule(lr){2-4}\cmidrule(lr){5-7}
           & $mv$  & Rel.~err & Time    & $mv$  & Rel.~err & Time\\\midrule
\trigmv    & 11034 & 1.3e-7 & 3.9 & 15846 & 2.7e-11 & 5.6 \\
\trigexpmv & 21952 & 1.3e-7 & 6.2 & 31516 & 2.7e-11 & 8.8 \\
\trigblock & 15883 & 5.2e-8 & 7.1 & 32023 & 1.1e-11 & 1.4e1\\
\expleja   & 11180 & 8.0e-9 & 4.3 & 17348 & 1.5e-11 & 6.6 \\\bottomrule
\end{tabular}

Despite my initial skepticism, it does seem possible to manage without vertical rules in tables. Indeed, the Chicago Manual of Style (17th edition, 2017) advises that “Vertical rules should be used sparingly—for example, when a table is doubled up … or as an aid to comprehension in an especially long or complex table”. The extra space that booktabs puts around horizontal rules makes the table nicer to look at and helps to avoid the problem of superscripts hitting the rule above. And, importantly, SIAM are happy to for authors to use booktabs in their papers.

I am now using booktabs in all my \LaTeX code.

For more before-and-after examples of tables, which illustrate table design as well as the use of booktabs, see Handbook of Writing for the Mathematical Sciences (third edition, 2020).

One booktab tip that I needed in producing the Handbook was to reduce the space at the edges of some of the widest tables by shortening the horizontal rules. This can be done by putting @{} at the beginning and end of the column formatting argument. For the example above, this is done with

\begin{tabular}{@{}lcccccl@{}}\toprule

This blog contains various other posts about \LaTeX.

What’s New in MATLAB R2019a and R2019b?

I didn’t have time earlier this year to write about the first MATLAB release of 2019, so in this post I will discuss R2019a and R2019b together. As usual in this series, I concentrate on the features most relevant to my work.

Modified Akima Cubic Hermite Interpolation (R2019b)

makima_example.jpg

A new function makima computes a piecewise cubic interpolant with continuous first-order derivatives. It produces fewer undulations than the existing spline function, with less flattening than pchip. This form of interpolant has been an option in interp1, interp2, interp3, interpn, and griddedInterpolant since R2017b and is now also a standalone function. For more details see Cleve Moler’s blog post about makima, from which the code used to generate the image above is taken.

Linear Assignment Problem and Equilibration (R2019a)

The matchpairs function solves the linear assignment problem, which requires each row of a matrix to be assigned to a column in such a way that the total cost of the assignments (given by the “sum of assigned elements” of the matrix) is minimized (or maximized). This problem can also be described as finding a minimum-weight matching in a weighted bipartite graph. The problem arises in sparse matrix computations.

The equilibrate function take as input a matrix A (dense or sparse) and returns a permutation matrix P and nonsingular diagonal matrices R and C such that R*P*A*C has diagonal entries of magnitude 1 and off-diagonal entries of magnitude at most 1. The outputs P, R, and C are sparse. This matrix equilibration can improve conditioning and can be a useful preprocessing step both in computing incomplete LU preconditioners and in iterative solvers. See, for example, the recent paper Max-Balanced Hungarian Scalings by Hook, Pestana, Tisseur, and Hogg.

The scaling produced by equilibrate is known as Hungarian scaling, and its computation involves solving a linear assignment problem.

Function Argument Validation (R2019b)

A powerful new feature allows a function to check that its input arguments have the desired class, size, and other aspects. Previously, this could be done by calling the function validateattributes with each argument in turn or by using the inputParser object (see Loren Shure’s comparison of the different approaches).

The new feature provides a more structured way of achieving the same effect. It also allows default values for arguments to be specified. Function argument validation contains many aspects and has a long help page. We just illustrate it with a simple example that should be self-explanatory.

function fav_example(a,b,f,t)
  arguments
    a (1,1) double               % Double scalar.
    b {mustBeNumeric,mustBeReal} % Numeric type and real.
    f double {mustBeMember(f,[-1,0,1])} = 0 % -1, 0 (default), or 1.
    t string = "-"               % String, with default value.
  end
  % Body of function.
end

The functions mustBeNumeric and mustBeMember are just two of a long list of validation functions. Here is an example call:

>> a = 0; b = 2; f = 11; fav_example(a,b,f)
Error using fav_example
Invalid input argument at position 3. Value must be a member of ...
this set:
    -1
    0
    1 

One caveat is that a specification within the arguments block

v (1,:) double    

allows not just 1-by-n vectors (for any n) but also n-by-1 vectors, because of the convention that in many situations MATLAB does not distinguish between row and column vectors.

For more extensive examples, see New with R2019b – Function Argument Validation and Spider Plots and More Argument Validation by Jiro Doke at MathWorks Blogs.

Dot Indexing (R2019b)

One can now dot index the result of a function call. For example, consider

>> optimset('Display','iter').Display
ans =
    'iter'

The output of the dot call can even be indexed:

>> optimset('OutputFcn',{@outfun1,@outfun2}).OutputFcn{2}
ans =
  function_handle with value:
    @outfun2

Of course, these are contrived examples, but in real codes such indexing can be useful. This syntax was not allowed in R2019a and earlier.

Hexadecimal and Binary Values (R2019b)

One can now specify hexadecimal and binary values using the prefixes 0x or 0X for hexadecimal (with upper or lower case for A to F) and 0b or 0B for binary. By default the results have integer type.

>> 0xA
ans =
  uint8
   10
>> 0b101
ans =
  uint8
   5

Changes to Function Precedence Order (R2019b)

The Release Notes say “Starting in R2019b, MATLAB changes the rules for name resolution, impacting the precedence order of variables, nested functions, local functions, and external functions. The new rules simplify and standardize name resolution”. A number of examples are given where the meaning of code is different in R2019b, and they could cause an error to be generated for codes that ran without error in earlier versions. As far as I can see, though, these changes are not likely to affect well-written MATLAB code.

Program File Size (R2019b)

Program files larger than “approximately 128 MB” will no longer open or run. This is an interesting change because I cannot imagine writing an M-file of that size. Good programming practice dictates splitting a code into separate functions and not including all of them in one program file. Presumably, some MATLAB users have produced such long files—perhaps automatically generated.

For a modern introduction and reference to MATLAB, see MATLAB Guide (third edition, 2017)

mg3-front-cover.jpg