Search code examples
rroundingdigits

Different rounding rule based on number of digits before the decimal


Say we have the following numerical vector:

y=c(0.111,1.11,11.1,111.1)

I would like it to be rounded as follows:

0.11,1.11,11.1,111  

This rule is simple: I want three digits in total! But if the number is large (e.g. 1111 or 11111) then keep all digits, but without any decimals.

Surely, there has to be a simpler solution?:

lapply(y,function(x){
  ifelse(nchar(as.integer(x))<1,round(x,digits=3),
         ifelse(nchar(as.integer(x))==1,round(x,digits=2),
                ifelse(nchar(as.integer(x))==2,round(x,digits=1),
                       ifelse(nchar(as.integer(x))>2,round(x,digits=0)
                       ))))
}
)

Solution

  • It is not clear whether the question is about pretty printing (result is character) or rounding (result is numeric).

    If the question is about conversion to character for pretty printing, the sprintf() function can be used.

    The sprintf() function understands the conversion specifier g where the number of significant digits can be specified. However, it does not cover the whole range of values. Therefore, different conversion specifiers have to be used depending on the size of y:

    y <- c(0.0111, 0.111, 1.11, 11.1, 111.1, 1111, 11111)
    sprintf(c("%3.2f", "%3.3g", "%3.0f")[cut(y, c(0, 1, 1000, Inf))], y)
    
    [1] "0.01"  "0.11"  "1.11"  "11.1"  "111"   "1111"  "11111"
    

    If the question is about rounding numeric values:

    round(y, c(2, 1, 0)[cut(y, c(0, 10, 100, Inf))])
    
    [1]     0.01     0.11     1.11    11.10   111.00  1111.00 11111.00
    

    Explanation

    c("%3.2f", "%3.3g", "%3.0f")[cut(y, c(0, 1, 1000, Inf))]
    

    is a replacement for nested ifelse() in order to pick the right conversion specifier for each y:

    [1] "%3.2f" "%3.2f" "%3.3g" "%3.3g" "%3.3g" "%3.0f" "%3.0f"
    

    cut() converts from numeric to factor. It divides the range of y into intervals and codes the values in y according to which interval they fall. The leftmost interval corresponds to level one, the next leftmost to level two and so on. (from help("cut")).

    Then, the level number is used to subset the vector of conversion specifiers to pick the appropriate specifier according to the value of y.

    While writing the explanation, there is a slightly different approach. Instead of using the level numbers to subset the vector of conversion specifiers, we can use the labels of the levels directly:

    sprintf(as.character(cut(y, c(0, 1, 1000, Inf), labels = c("%3.2f", "%3.3g", "%3.0f"))), y)
    
    [1] "0.01"  "0.11"  "1.11"  "11.1"  "111"   "1111"  "11111"