4

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)
                       ))))
}
)
Ben Bolker
  • 211,554
  • 25
  • 370
  • 453
user2862862
  • 111
  • 1
  • 1
  • 10

3 Answers3

7

Just use signif:

y <- c(0.111,1.11,11.1,111.1,1111)
signif(y, pmax(3,trunc(log10(y)+1)))
#[1]    0.111    1.110   11.100  111.000 1111.000

The pmax(...) part accounts for the case in which the number has 4 or more digits before the decimal (log10 is just a way to count those digits).

nicola
  • 24,005
  • 3
  • 35
  • 56
3

Does this work?

y <- c(0.111,1.11,11.1,111.1,1111,11111)
(ry <- gsub("0+$","",round(y*100)/100))
## [1] "0.11"  "1.11"  "11.1"  "111.1" "1111"  "11111"
as.numeric(ry)
## [1]     0.11     1.11    11.10   111.10  1111.00 11111.00

I don't generally like solutions that convert to character, but it seems to do what you want ... (note that this rounds as you want, but the printing of the numeric values re-includes the zero values: cat(ry,"\n") will print the numbers out as-is)

Ben Bolker
  • 211,554
  • 25
  • 370
  • 453
  • Thats a nice solution! Oh, I must have forgotten about the character/numeric issue. And it is for "pretty printing", so character is fine. Thanks! – user2862862 Jan 13 '20 at 07:40
1

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"
Community
  • 1
  • 1
Uwe
  • 41,420
  • 11
  • 90
  • 134
  • I forgot to point out this is for "pretty printing", so character is fine. This one is the only one that does exactly what I was looking for. Could you please explain whats going on? – user2862862 Jan 13 '20 at 07:46
  • 1
    @user2862862, I have added an explanation (and a code variant). Is the explanation helpful, or do you miss a point? – Uwe Jan 13 '20 at 09:53