Search code examples
rroundingsignificant-digits

Rounding in R without loosing the trailing zero, when it is part of the significant figure


I am looking for rounding up behaviour for numbers like 0.1298 to be displayed as 0.130 while keeping the trailing zero as a significant digit. This also needs to work for tables where the length of the decimals is different. I use signif() to round the data and it works fine except R loses the trailing zero, but it is needed for accuracy. Is there a working function for this?

does anyone have a good idea for me? Thanks a lot already!

I have tried modifying signif


mod_signif <- function(x,digits) {
   sprintf(paste0('%#.',3,'g'), signif(x,digits))}

and this works fine for numbers up to 5 decimal places, after which the scientific writing kicks in.

> mod_signif(0.129554,3)
[1] "0.130"
> mod_signif(0.0129554,3)
[1] "0.0130"
> mod_signif(0.00129554,3)
[1] "0.00130"
> mod_signif(0.000129554,3)
[1] "0.000130"
> mod_signif(0.0000129554,3)
[1] "1.30e-05"

I would expect 0.0000130 in this case

If I try to counteract this with the format() function, I start going in circles because then i am going back to numeric and lose the zero again....


Solution

  • You could use scales::label_number(). Because of the way the labelers within the scales package work, this requires creating a function within the body of another function.

    Also note that you'll need to do some simple arithmetic to convert from arguments that signif() expects to those that label_number() expects.

    library(scales)
    
    label_nicely <- function(number, digits){
      
      ## I stole this from the documentation for base::round
      ## Pretty neat, huh?
      round_to <- digits - ceiling(log10(abs(number)))
      
      value <- round(number, round_to)
      
      ## accuracy is expected to be (e.g.) 0.01 rather than, say, 3
      accuracy_arg <- 1 / 10^round_to
      
      ## Construct a labeller within the function run
      labeller <- label_number(accuracy = accuracy_arg)
      
      label <- labeller(value)
      
        return(label)
    } 
    
    label_nicely(0.129554,3)
    #> [1] "0.130"
    label_nicely(0.0000129554, 3)
    #> [1] "0.0000130"
    

    Created on 2023-09-15 with reprex v2.0.2


    Edit: The description of base::round tell us that signif(n, x) is equivalent to round(n, x - ceiling(log10(abs(n))). Using that trick, we can convert from 'significant digits' to 'actual digits', which we can pass to the accuracy argument of label_number() to get the required behaviour.