0

What would be the best way to get a good table (with kable, for example) if I want to have a measurement and its error in the form y +- error or y(error), having the usual rules for error: have 1 significant digit in error, the same number of digits in value, and so on. For instance:

  • 1.124 \pm 0.003
  • 0.30 \pm 0.02

and so on.

Reproducible example

df<-data.frame(
  x=runif(5),
  Delta.x=runif(5)/10,
  y=runif(5),
  Delta.y=runif(5)/7
)
df.print<-with(df, data.frame(
  x=paste0(x, "(", Delta.x, ")"),
  y=paste0(y, "(", Delta.y, ")")
))

kable(df.print)

If I use format(x, digits=3) x, y and their Deltas, I get different "widths", and I would like to get the same number of digits after the decimal point.

Javi_VM
  • 505
  • 2
  • 10
  • You could use `sprintf`: `df.print<-with(df, data.frame( x=paste(sprintf("%.2f", x), "±", sprintf("%.2f", Delta.x)), y=paste(sprintf("%.2f", y), "±", sprintf("%.2f", Delta.y)) ))` – JasonAizkalns Mar 12 '18 at 17:51
  • Would there be a way to choose the number of digits (`"%.2f"`, `"%.3f"`) automatically based on Delta.x or Delta.y values? – Javi_VM Mar 12 '18 at 18:11
  • See [this post](https://stackoverflow.com/q/27767841/2572423) – JasonAizkalns Mar 12 '18 at 20:16

1 Answers1

2

Here's a tidyverse take on your problem, which is arguably very verbose. Some explanations:

1) The first mutate() block rounds both Delta columns to have one significant digit and transforms it to a character; this preserves the length.

2) The second mutate() block rounds the "normal" x and y columns to have the same length as the Delta columns. The - 2L avoids false rounding based on the number before the . and the . itself in the Delta columns.

3) The third mutate() block first takes care of two "unusual" situations: the first if_else() takes care of situations where the rounded y number does not have a . and digits, but the Delta.y value does. The second if_else() takes care of situations where the last digit in the rounding process is a 0, which R drops in rounding. Both measures are repeated for the x column.

4) The fourth mutate() block adds white space at the end of the values in the x and y columns to make sure the error numbers are aligned.

5) The unite() and mutate() commands at the end merge the columns and add parantheses for the second number.

library("tidyverse")
library("knitr")

df %>% 
  mutate(Delta.x = signif(Delta.x, digits = 1L), 
         Delta.x = as.character(Delta.x), 
         Delta.y = signif(Delta.y, digits = 1L), 
         Delta.y = as.character(Delta.y)) %>% 
  mutate(x = round(x, digits = str_count(Delta.x) - 2L), 
         x = as.character(x),
         y = round(y, digits = str_count(Delta.y) - 2L), 
         y = as.character(y)) %>% 
  mutate(y = if_else(condition = str_count(y, "\\.") == 0, 
                     true = str_c(y, str_dup("0", str_count(Delta.y) - str_count(y) - 1L), sep = "."),
                     false = y),
         y = if_else(condition = str_count(Delta.y) - str_count(y) != 0,
                     true = str_c(y, str_dup("0", times = str_count(Delta.y) - str_count(y))),
                     false = y),
         x = if_else(condition = str_count(x, "\\.") == 0, 
                     true = str_c(x, str_dup("0", str_count(Delta.x) - str_count(x) - 1L), sep = "."),
                     false = x),
         x = if_else(condition = str_count(Delta.x) - str_count(x) != 0,
                     true = str_c(x, str_dup("0", times = str_count(Delta.x) - str_count(x))),
                     false = x)) %>% 
  mutate(x = if_else(condition = str_count(x) < max(str_count(x)),
                     true = str_c(x, str_dup(" ", times = max(str_count(x)) - str_count(x))),
                     false = x),
         y = if_else(condition = str_count(y) < max(str_count(y)),
                     true = str_c(y, str_dup(" ", times = max(str_count(y)) - str_count(y))),
                     false = y)) %>%
  unite(x, x, Delta.x, sep = " (") %>% 
  unite(y, y, Delta.y, sep = " (") %>% 
  mutate(x = str_c(x, ")"), 
         y = str_c(y, ")")) %>% 
  kable()


|x           |y             |
|:-----------|:-------------|
|1.0  (0.1)  |0.20  (0.01)  |
|0.12 (0.07) |0.8   (0.1)   |
|0.71 (0.03) |0.18  (0.09)  |
|0.63 (0.02) |0.805 (0.003) |
|0.27 (0.09) |0.106 (0.008) |

Additionally, you can set the global options(scipen = 999) (or any other large number) to avoid scientific representation of numbers, e.g. 2e-5 (which in the case of your kable should look like 0.00002).

EDIT: Updated and clarified some of the commands.

UPDATE (Javi_VM)

I just turned it into a function. You can give either 2 vectors or 1 data.frame with two columns. It still lacks support for scientific notation (1.05 10^9 for example) but it is ok to start.

scinumber <- function(df=NULL, x, Delta.x){
  if (is.null(df)) {
    df <- data.frame(
      x = x,
      Delta.x = Delta.x
    )
  } else {
    colnames(df)[colnames(df)==x] <- "x"
    colnames(df)[colnames(df)==Delta.x] <- "Delta.x"
  }
  require(tidyverse)
  options(scipen = 999)
  output <- 
    df %>% 
    mutate(Delta.x = signif(Delta.x, digits = 1L), 
           Delta.x = as.character(Delta.x)) %>% 
    mutate(x = round(x, digits = str_count(Delta.x) - 2L), 
           x = as.character(x)
    ) %>% 
    mutate(x = if_else(condition = str_count(x, "\\.") == 0, 
                       true = str_c(x, str_dup("0", str_count(Delta.x) - str_count(x) - 1L), sep = "."),
                       false = x),
           x = if_else(condition = str_count(Delta.x) - str_count(x) != 0,
                       true = str_c(x, str_dup("0", times = str_count(Delta.x) - str_count(x))),
                       false = x)) %>% 
    mutate(x = if_else(condition = str_count(x) < max(str_count(x)),
                       true = str_c(x, str_dup(" ", times = max(str_count(x)) - str_count(x))),
                       false = x)) %>%
    unite(x, x, Delta.x, sep = " (") %>% 
    mutate(x = str_c(x, ")"))

  return(output)
}
Javi_VM
  • 505
  • 2
  • 10
Supertasty
  • 296
  • 2
  • 9
  • Thank you. Your answer however has two problems. First I do not understand it (I guess I should take a look to `tidiverse`). Second, I still have the problem getting the number of digits automatically so that the uncertainty always has only one significant digit – Javi_VM Mar 12 '18 at 19:27
  • Sorry I misunderstood your question initially; I've updated my answer accordingly, which should now dynamically produce the output you desire. Let me know if this was what you were looking for! – Supertasty Mar 13 '18 at 09:22
  • Thank you so much for your edit, @Supertasty. One thing still remains, to convert this to a function to use it more easily, but I won't ask that of you, I'll try to do it by myself. Thank you again!! I'll make further tests to check it will work in all cases... – Javi_VM Mar 14 '18 at 10:20