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:
and so on.
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.
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.
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)
}