How to format numbers in R, specifying the number of significant digits but keep significant zeroes and integer part?

StackOverflow https://stackoverflow.com/questions/21526038

  •  06-10-2022
  •  | 
  •  

Domanda

I've been struggling with formatting numbers in R using what I feel are very sensible rules. What I would want is to specify a number of significant digits (say 3), keep significant zeroes, and also keep all digits before the decimal point, some examples (with 3 significant digits):

1.23456 -> "1.23"
12.3456 -> "12.3"
123.456 -> "123"
1234.56 -> "1235"
12345.6 -> "12346"
1.50000 -> "1.50"
1.49999 -> "1.50"

Is there a function in R that does this kind of formatting? If not, how could it be done?

I feel these are quite sensible formatting rules, yet I have not managed to find a function that formats in this way in R. As far as I googled this is not a duplicate of many similar questions such as this

Edit:

Inspired by the two good answers I put together a function myself that I believe works for all cases:

sign_digits <- function(x,d){
  s <- format(x,digits=d)
  if(grepl("\\.", s) && ! grepl("e", s)) {
    n_sign_digits <- nchar(s) - 
      max( grepl("\\.", s), attr(regexpr("(^[-0.]*)", s), "match.length") )
    n_zeros <- max(0, d - n_sign_digits)
    s <- paste(s, paste(rep("0", n_zeros), collapse=""), sep="")
  }
  s
}
È stato utile?

Soluzione

format(num,3) comes very close.

format(1.23456,digits=3)
# [1] "1.23"
format(12.3456,digits=3)
# [1] "12.3"
format(123.456,digits=3)
# [1] "123"
format(1234.56,digits=3)
# [1] "1235"
format(12345.6,digits=3)
# [1] "12346"
format(1.5000,digits=3)
# [1] "1.5"
format(1.4999,digits=3)
# [1] "1.5"

Your rules are not actually internally consistent. You want 1234.56 to round down to 1234, yet you want 1.4999 to round up to 1.5.

EDIT This appears to deal with the very valid point made by @Henrik.

sigDigits <- function(x,d){
  z <- format(x,digits=d)
  if (!grepl("[.]",z)) return(z)
  require(stringr)
  return(str_pad(z,d+1,"right","0"))
}

z <- c(1.23456, 12.3456, 123.456, 1234.56, 12345.6, 1.5000, 1.4999)
sapply(z,sigDigits,d=3)
# [1] "1.23"  "12.3"  "123"   "1235"  "12346" "1.50"  "1.50" 

Altri suggerimenti

As @jlhoward points out, your rounding rule is not consistent. Hence you should use a conditional statement:

x <- c(1.23456, 12.3456, 123.456, 1234.56, 12345.6, 1.50000, 1.49999) 
ifelse(x >= 100, sprintf("%.0f", x), ifelse(x < 100 & x >= 10, sprintf("%.1f", x), sprintf("%.2f", x)))
# "1.23"  "12.3"  "123"   "1235"  "12346" "1.50"  "1.50"

It's hard to say the intended usage, but it might be better to use consistent rounding. Exponential notation could be an option:

sprintf("%.2e", x)
[1] "1.23e+00" "1.23e+01" "1.23e+02" "1.23e+03" "1.23e+04" "1.50e+00" "1.50e+00"
sig0=\(x,y){
  dig=abs(pmin(0,floor(log10(abs(x)))-y+1))
  dig[is.infinite(dig)]=y-1
  sprintf(paste0("%.",dig,"f"),x)
}

> v=c(1111,111.11,11.1,1.1,1.99,.01,.001,0,-.11,-.9,-.000011)
> paste(sig0(v,2),collapse=" ")
[1] "1111 111 11 1.1 2.0 0.010 0.0010 0.0 -0.11 -0.90 -0.000011"

Or the following is almost the same with the exception that 0 is converted to 0 and not 0.0 (fg is a special version of f where the digits specify significant digits and not digits after the decimal point, and the # flag causes fg to not drop trailing zeroes):

> paste(sub("\\.$","",formatC(v,2,,"fg","#")),collapse=" ")
[1] "1111 111 11 1.1 2.0 0.010 0.0010 0 -0.11 -0.90 -0.000011"
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top