Skip to content

kable() in LaTeX mode to typeset numeric columns in math mode. - #1850

Merged
yihui merged 9 commits into
yihui:masterfrom
krivit:kable_latex_minus
Sep 10, 2026
Merged

kable() in LaTeX mode to typeset numeric columns in math mode.#1850
yihui merged 9 commits into
yihui:masterfrom
krivit:kable_latex_minus

Conversation

@krivit

@krivit krivit commented May 20, 2020

Copy link
Copy Markdown
Contributor

This is a partial fix for #1709, addressing the LaTeX case by typesetting numeric columns in math mode and wrapping commas (as decimal or thousands separators) in braces ({}) for correct spacing. This is, in my opinion, the cleanest option that does not require additional LaTeX packages.

I've looked into the other output types (pandoc, html, md, etc.), but I am not quite sure what should be their output.

I've updated NEWS.md and DESCRIPTION to save time, and apologise for the presumption of adding myself as a contributor and adding "thanks" to myself. If this PR doesn't merit a line in the DESCRIPTION, I'll be happy to remove it.

@krivit

krivit commented May 20, 2020

Copy link
Copy Markdown
Contributor Author

Looks like the TARGET=integration DISPLAY=:99.0 failed with

The command "sudo add-apt-repository -y "ppa:marutter/rrutter4.0"" failed and exited with 1 during .

I don't think it's anything in the PR.

@krivit
krivit force-pushed the kable_latex_minus branch from f6f96a0 to 139253a Compare June 15, 2020 22:17
@krivit
krivit force-pushed the kable_latex_minus branch from 139253a to 73ebdde Compare June 25, 2020 02:38
@krivit

krivit commented Jun 25, 2020

Copy link
Copy Markdown
Contributor Author

It no looks like the problem is with knitr-examples, which makes sense. I will make a PR for that.

@krivit

krivit commented Jun 25, 2020

Copy link
Copy Markdown
Contributor Author

I've now updated the knitr-examples to match and opened a PR there, though I haven't been able to get the integration tests to work under Travis-CI, since the repository and branch appear to be hardcoded to yihui/knitr-examples. @yihui, am I doing something wrong?

@krivit
krivit force-pushed the kable_latex_minus branch from 73ebdde to 9247b54 Compare August 19, 2020 00:16
@CLAassistant

CLAassistant commented Sep 22, 2020

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@krivit
krivit force-pushed the kable_latex_minus branch from 9247b54 to 9cc3097 Compare October 8, 2020 12:42
@krivit
krivit force-pushed the kable_latex_minus branch from 644e593 to b320135 Compare January 2, 2021 00:41
@krivit
krivit force-pushed the kable_latex_minus branch 2 times, most recently from bc82afc to 5ecdc1a Compare February 4, 2021 23:18
krivit added a commit to krivit/knitr-examples that referenced this pull request Feb 4, 2021
@krivit
krivit force-pushed the kable_latex_minus branch from 5ecdc1a to 6c77ffc Compare February 4, 2021 23:32
krivit added a commit to krivit/knitr-examples that referenced this pull request Feb 4, 2021
@krivit
krivit force-pushed the kable_latex_minus branch from 6c77ffc to 225e901 Compare February 6, 2021 07:29
@krivit
krivit force-pushed the kable_latex_minus branch 2 times, most recently from 88dba01 to aed7640 Compare April 9, 2021 06:26
@krivit
krivit force-pushed the kable_latex_minus branch from aed7640 to 0cba8f2 Compare April 23, 2021 13:30
@krivit
krivit force-pushed the kable_latex_minus branch from 0cba8f2 to f3b6739 Compare August 11, 2021 05:43
@krivit
krivit force-pushed the kable_latex_minus branch 3 times, most recently from ae02f5b to a79c1d9 Compare September 9, 2026 03:13
@yihui

yihui commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Thanks for the PR! Typesetting numbers in math mode for better minus signs is a nice improvement!

My main concern is that this changes the default LaTeX output: every numeric column now emits \(...\) instead of a bare number (e.g. 1.20\(1.20\)). knitr has 13K+ reverse dependencies on CRAN, and many of them snapshot kable() output in their tests or post-process it downstream. A default change this broad is very likely to break some of them, so I'd prefer this be opt-in rather than on-by-default.

Could you gate it behind an argument that defaults to off, following the same pattern as the other kable_latex() arguments (a formal whose default comes from getOption())? For example:

numeric.math = getOption('knitr.table.latex.numeric.math', FALSE)

That way users can flip it per-call via kable(..., numeric.math = TRUE) or globally via options(knitr.table.latex.numeric.math = TRUE), and the default output stays unchanged for existing users. Compare toprule, midrule, etc., which already work this way.

Two smaller things:

  • The new test uses format.arg= — should be format.args=; it only works today via partial argument matching.
  • NA cells get wrapped as \(NA\). Worth skipping the wrap for NA (and any non-numeric-looking token) so text doesn't land in math mode.

Also, the branch currently conflicts with master — only in NEWS.md (the VERSION 1.53 header); R/table.R and the tests merge cleanly. Please merge master and resolve that one header.

…looking minus signs. Commas, if present (as decimal or thousands separator), are wrapped in braces to prevent additional space being added after. Improves yihui#1709.
@krivit

krivit commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for looking into this! Happy to make all these changes. While we are at it, would it make sense to also pretty-print the scientific notation, i.e., replace "2e20" with "2 \\times 10^{20}"?

@krivit

krivit commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Also, Inf and -Inf should be handled as well.

… by numeric.math= argument to kable() or globally by options(knitr.table.numeric.math=). Infinite values and scientific notation are now properly typeset; non-numerical values such as NA and NaN are passed through. Improves yihui#1709.
…de. The test for it now also tests infinite values and scientific notation. Improves yihui#1709.
@krivit

krivit commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

@yihui , I've pushed changes addressing the comments. For consistency with the argument name, I went with option name knitr.table.numeric.math. I ended up creating a helper function transform_num_for_latex() that takes a string containing a typeset number (such as that received by kable_latex()) and re-typesets it for LaTeX using string operations.

In the longer term, a similar function can be made for Markdown and HTML output.

@yihui

yihui commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Two follow-ups on the latest changes:

Reuse format_sci(). We already have format_sci() / format_sci_one() in R/utils.R, used by the inline hooks for all output formats. It handles infinities, the scipen threshold, the 1 \times 10^n -> 10^n simplification, and the same knitr.inline.times option — so transform_num_for_latex() largely duplicates it. The catch is that format_sci() takes numeric input while your helper takes already-formatted strings, so reusing it would mean applying it on the numeric column before stringification rather than after. Bonus: reusing it means eventual HTML/Markdown support falls out of the existing format argument instead of needing a second helper.

Scientific notation. I'd rather keep this PR focused on the math-mode typesetting only — math-mode wrapping, minus signs, comma-bracing, and Inf/-Inf. Rendering 1.23e6 as 1.23 \times 10^6 changes the number's appearance, not just its typesetting, and deserves its own discussion. Could you split the scientific-notation conversion out into a separate PR (reusing format_sci() there)? That keeps this one small and easy to reason about for the revdep impact.

@krivit

krivit commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

Reuse format_sci(). We already have format_sci() / format_sci_one() in R/utils.R, used by the inline hooks for all output formats. It handles infinities, the scipen threshold, the 1 \times 10^n -> 10^n simplification, and the same knitr.inline.times option — so transform_num_for_latex() largely duplicates it. The catch is that format_sci() takes numeric input while your helper takes already-formatted strings, so reusing it would mean applying it on the numeric column before stringification rather than after. Bonus: reusing it means eventual HTML/Markdown support falls out of the existing format argument instead of needing a second helper.

This might require quite a bit of rearchitecting:

  1. The mathification needs to happen after the escaping of special characters, and the escaping needs to happen after the conversion to string, and also the escaping depends on the output format, which means that there isn't really a clean and simple way to separate format-specific parts from format-agnostic.

    Probably the solution with minimal change would be for the escaping code to skip the numeric columns. But then...

  2. format_sci() formats each element in a vector individually, whereas format_args() that's used by kable() formats the whole column jointly. This is important for tables, because we generally want to have consistent number of digits after the decimal point and consistent use of scientific notation within each column:

    x <- c(1.1, 1.11)
    knitr:::format_args(x)
    #> [1] "1.10" "1.11"
    knitr:::format_sci(x)
    #> [1] "1.1"  "1.11"
    
    y <- c(1.1e-10, 1.11)
    knitr:::format_args(y)
    #> [1] "1.10e-10" "1.11e+00"
    knitr:::format_sci(y)
    #> [1] "1.1\\times 10^{-10}" "1.11"

    So, the format_sci() family would need to be rearchitected as well.

Though I am not very happy with the regex approach either, it has the benefit of outsourcing the scientific notation and the trailing zero decisions to format().

Scientific notation. I'd rather keep this PR focused on the math-mode typesetting only — math-mode wrapping, minus signs, comma-bracing, and Inf/-Inf. Rendering 1.23e6 as 1.23 \times 10^6 changes the number's appearance, not just its typesetting, and deserves its own discussion. Could you split the scientific-notation conversion out into a separate PR (reusing format_sci() there)? That keeps this one small and easy to reason about for the revdep impact.

I can split it out, but I would rather keep it in the same patch. If we keep the regex approach, it's just another branch in the ifelse() in the transform_num_for_latex(). If we get format_sci() to work for this case, the patch will produce the same change anyway via format_sci(), unless a separate code-path is created to prevent scientific notation from being typeset in tables.

You are the maintainer, so I defer to you on the revdep issues, but the scientific notation typesetting will only kick in if numeric.math == TRUE anyway, so revdeps shouldn't be affected either way.

- Remove numeric.math from kable() formals; it flows through ... to
  kable_latex() like other format-specific args
- Store is_num as attr(x, 'is_num') on the matrix (same pattern as
  align), so kable_latex() reads it without needing an explicit arg
- Rename transform_num_for_latex() -> latex_num(); rewrite from nested
  ifelse() to flat logical vectors for readability
- Fix 0e+00 rendering as \(0\) instead of \(0\times 10^{0}\)
- Move numeric.math docs from @PARAM to a dedicated @section for
  LaTeX-specific arguments; regenerate kable.Rd
- Add test for zero in a sci-notation column
@yihui

yihui commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Good points and largely agreed. Thanks! I pushed one more fix: 0e+00 (which format() produces for zero in a column that uses scientific notation) now renders as \(0\) instead of \(0\times 10^{0}\).

@yihui
yihui merged commit 1636bce into yihui:master Sep 10, 2026
@krivit
krivit deleted the kable_latex_minus branch September 10, 2026 23:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants