127

I am writing a function to plot data. I would like to specify a nice round number for the y-axis max that is greater than the max of the dataset.

Specifically, I would like a function foo that performs the following:

foo(4) == 5
foo(6.1) == 10 #maybe 7 would be better
foo(30.1) == 40
foo(100.1) == 110 

I have gotten as far as

foo <- function(x) ceiling(max(x)/10)*10

for rounding to the nearest 10, but this does not work for arbitrary rounding intervals.

Is there a better way to do this in R?

theforestecologist
  • 4,667
  • 5
  • 54
  • 91
Abe
  • 12,956
  • 12
  • 51
  • 72
  • The R default behavior when plotting is to set the plot limits ~4% beyond the range of the data in each direction. If this isn't satisfying to you maybe just write something that goes out by a higher or lower %? – joran Jun 23 '11 at 22:39
  • @joran thanks for the info, but I want multiple plots that all have the same axis limits and ticks and I am not sure how this helps. – Abe Jun 23 '11 at 22:43
  • 1
    Well, I'm sort of groping in the dark here, since I don't know all the background. Your foo will round up to the nearest X if you just add another parameter X and replace both 10's with X. Or you could use faceting. – joran Jun 23 '11 at 22:50
  • 16
    Are you looking for `?pretty` ? – hadley Jun 24 '11 at 00:24
  • Why is `foo(4)==5` and not `10`? – James Jun 24 '11 at 13:04
  • @james because I would not want to make xlim = c(0,10) if max(x) = 4. – Abe Jun 24 '11 at 15:16
  • @hadley yes, I was apparently looking for `pretty` and if you post that as an answer, I will accept it. Your `plyr::round_any` also provides a very nice implementation of the idea in my original post – Abe Jun 24 '11 at 15:24
  • @Abe: I might be wrong, but most of the answers do not add an extra value to rounded input numbers - see your example: `foo(4) == 5`. Please clarify if this is really needed, as I made up my answer concentrating on this requirement. – daroczig Jun 24 '11 at 18:58
  • @daroczig your answer works well and it nicely solves the central algebraic question that I thought I was facing. Still, from Tommy's answer `roundUpNice(x=4, nice = c(1,5,10)) == 5` or from Ramnath's answer `round_any(4,5)` also provide the desired functionality of `foo(4)==5`. I think that the use of the `nice` argument in the `roundUpNice` function makes Tommy's answer the best; I am sorry if my question was poorly stated. – Abe Jun 27 '11 at 15:47

13 Answers13

156

The plyr library has a function round_any that is pretty generic to do all kinds of rounding. For example

library(plyr)
round_any(132.1, 10)               # returns 130
round_any(132.1, 10, f = ceiling)  # returns 140
round_any(132.1, 5, f = ceiling)   # returns 135
Ramnath
  • 54,439
  • 16
  • 125
  • 152
  • 4
    For a `dplyr` replacement, see: https://stackoverflow.com/a/46489816/435093 – slhck Mar 25 '19 at 14:34
  • Is there an equivalent function that uses standard rounding, rather than R's "round to the nearest even number" approach? I know ```round2``` in the ```webexercises``` library works as an alternative to base R ```round```, but this doesn't let you round to the nearest 'any number' – Dee G May 26 '23 at 10:04
77

If you just want to round up to the nearest power of 10, then just define:

roundUp <- function(x) 10^ceiling(log10(x))

This actually also works when x is a vector:

> roundUp(c(0.0023, 3.99, 10, 1003))
[1] 1e-02 1e+01 1e+01 1e+04

..but if you want to round to a "nice" number, you first need to define what a "nice" number is. The following lets us define "nice" as a vector with nice base values from 1 to 10. The default is set to the even numbers plus 5.

roundUpNice <- function(x, nice=c(1,2,4,5,6,8,10)) {
    if(length(x) != 1) stop("'x' must be of length 1")
    10^floor(log10(x)) * nice[[which(x <= 10^floor(log10(x)) * nice)[[1]]]]
}

The above doesn't work when x is a vector - too late in the evening right now :)

> roundUpNice(0.0322)
[1] 0.04
> roundUpNice(3.22)
[1] 4
> roundUpNice(32.2)
[1] 40
> roundUpNice(42.2)
[1] 50
> roundUpNice(422.2)
[1] 500

[[EDIT]]

If the question is how to round to a specified nearest value (like 10 or 100), then James answer seems most appropriate. My version lets you take any value and automatically round it to a reasonably "nice" value. Some other good choices of the "nice" vector above are: 1:10, c(1,5,10), seq(1, 10, 0.1)

If you have a range of values in your plot, for example [3996.225, 40001.893] then the automatic way should take into account both the size of the range and the magnitude of the numbers. And as noted by Hadley, the pretty() function might be what you want.

theforestecologist
  • 4,667
  • 5
  • 54
  • 91
Tommy
  • 39,997
  • 12
  • 90
  • 85
  • 1
    `Vectorize(roundUpNice)` is quite fast =) +1 Anyway. – mbq Jun 24 '11 at 09:40
  • I was wondering if there is a way to edit the roundUp function to half the end result if the original value is lower? for example a number between 101-500 would roundup to 500 and numbers 501-999 would roundUp to 1000? instead of all rounding to 1000? – helen.h Mar 11 '16 at 10:25
  • Just change the nice vector: `roundUpNice(501, nice=c(5, 10)) # 1000` – Tommy Jul 07 '16 at 21:16
  • 1
    Just a warning since you work with logarithms in your function, I would had 2 cases, one where `x < 0` and apply `- x` in the log before putting the `-` back. I would also add an exception for the situation where `x = 0` – Yohan Obadia Sep 01 '16 at 16:29
  • The pretty function worked really well for my needs (to set a pretty limit on a graph's X axis) – Joon Apr 12 '20 at 14:01
  • Great approach. I'd suppress the `length` condition and wrap it up with `sapply` so you can actually pass a vector: `roundUpNice <- function(x, nice = c(1,2,4,5,6,8,10)) { sapply(x, function (x) 10^floor(log10(x)) * nice[[which(x <= 10^floor(log10(x)) * nice)[[1]]]]) }` – Bernardo Aug 14 '20 at 15:25
  • Nice work, Tommy (pardon the pun). I adapted your function to this form which some may find slightly easier. ```purrr::keep(10^floor(log10(x)) * nice, ~ . >= x)[1]``` – James N Jul 07 '23 at 00:55
58

The round function in R assigns special meaning to the digits parameter if it is negative.

round(x, digits = 0)

Rounding to a negative number of digits means rounding to a power of ten, so for example round(x, digits = -2) rounds to the nearest hundred.

This means a function like the following gets pretty close to what you are asking for.

foo <- function(x)
{
    round(x+5,-1)
}

The output looks like the following

foo(4)
[1] 10
foo(6.1)
[1] 10
foo(30.1)
[1] 40
foo(100.1)
[1] 110
Community
  • 1
  • 1
Zero
  • 1,147
  • 1
  • 9
  • 13
  • 2
    Awesome! Your answer should be marked as the right one! – Alessandro Jacopson May 30 '17 at 07:15
  • +1 . However, @Alessandro the functions in my [answer](https://stackoverflow.com/a/32508105/4581200) are much more versatile: you can round *ANY* number up *OR* down to *ANY* interval. – theforestecologist Jan 20 '19 at 20:56
  • @theforestecologist thank you for the hint! As a personal taste, I usually prefer a language built-in solution rather than a custom one. – Alessandro Jacopson Jan 21 '19 at 07:22
  • obviously the big issue here is that 110 and 120 both = 120, 130 and 140 = 140, etc etc... which will be unbalanced and I don't think anyone is wanting multiples of 10 to themselves be rounded... – M.L. May 07 '23 at 17:26
48

If you add a negative number to the digits argument of round(), R will round it to the multiples of 10, 100 etc.

round(9, digits = -1) 
[1] 10    
round(89, digits = -1) 
[1] 90
round(89, digits = -2) 
[1] 100
benson23
  • 16,369
  • 9
  • 19
  • 38
34

How about:

roundUp <- function(x,to=10)
{
  to*(x%/%to + as.logical(x%%to))
}

Which gives:

> roundUp(c(4,6.1,30.1,100.1))
[1]  10  10  40 110
> roundUp(4,5)
[1] 5
> roundUp(12,7)
[1] 14
James
  • 65,548
  • 14
  • 155
  • 193
  • 1
    @daroczig The question is a little confusing, I wrote this focusing on the "arbitrary X" requirement, but clearly all the expected values could not be produce by "a single round up to the nearest X" solution. It seems that the OP wants to produce values for an axis, so `pretty` is probably the best option. – James Jun 25 '11 at 15:53
  • 1
    Obviously late to this party, but wouldn't `to * ceiling(x / to)` be cleaner? – jared Oct 04 '19 at 15:04
29

Round ANY number Up/Down to ANY interval

You can easily round numbers to a specific interval using the modulo operator %%.

The function:

round.choose <- function(x, roundTo, dir = 1) {
  if(dir == 1) {  ##ROUND UP
    x + (roundTo - x %% roundTo)
  } else {
    if(dir == 0) {  ##ROUND DOWN
      x - (x %% roundTo)
    }
  }
}

Examples:

> round.choose(17,5,1)   #round 17 UP to the next 5th
[1] 20
> round.choose(17,5,0)   #round 17 DOWN to the next 5th
[1] 15
> round.choose(17,2,1)   #round 17 UP to the next even number
[1] 18
> round.choose(17,2,0)   #round 17 DOWN to the next even number
[1] 16

How it works:

The modulo operator %% determines the remainder of dividing the first number by the 2nd. Adding or subtracting this interval to your number of interest can essentially 'round' the number to an interval of your choosing.

> 7 + (5 - 7 %% 5)       #round UP to the nearest 5
[1] 10
> 7 + (10 - 7 %% 10)     #round UP to the nearest 10
[1] 10
> 7 + (2 - 7 %% 2)       #round UP to the nearest even number
[1] 8
> 7 + (100 - 7 %% 100)   #round UP to the nearest 100
[1] 100
> 7 + (4 - 7 %% 4)       #round UP to the nearest interval of 4
[1] 8
> 7 + (4.5 - 7 %% 4.5)   #round UP to the nearest interval of 4.5
[1] 9

> 7 - (7 %% 5)           #round DOWN to the nearest 5
[1] 5
> 7 - (7 %% 10)          #round DOWN to the nearest 10
[1] 0
> 7 - (7 %% 2)           #round DOWN to the nearest even number
[1] 6

Update:

The convenient 2-argument version:

rounder <- function(x,y) {
  if(y >= 0) { x + (y - x %% y)}
  else { x - (x %% abs(y))}
}

Positive y values roundUp, while negative y values roundDown:

 # rounder(7, -4.5) = 4.5, while rounder(7, 4.5) = 9.

Or....

Function that automatically rounds UP or DOWN based on standard rounding rules:

Round <- function(x,y) {
  if((y - x %% y) <= x %% y) { x + (y - x %% y)}
  else { x - (x %% y)}
}

Automatically rounds up if the x value is > halfway between subsequent instances of the rounding value y:

# Round(1.3,1) = 1 while Round(1.6,1) = 2
# Round(1.024,0.05) = 1 while Round(1.03,0.05) = 1.05
theforestecologist
  • 4,667
  • 5
  • 54
  • 91
  • I was asked how to convert `Round` to VBA in Excel: `Function ROUND(x,y) 'Function that automatically rounds UP or DOWN based on standard rounding rules. 'Automatically rounds up if the "x" value is > halfway between subsequent instances of the rounding value "y": If (y - (Evaluate("Mod(" & x & "," & y & ")"))) <= (Evaluate("Mod(" & x & "," & y & ")")) Then Ans = x + (y - (Evaluate("Mod(" & x & "," & y & ")"))) Else Ans = x - (Evaluate("Mod(" & x & "," & y & ")")) End If ROUND = Ans End Function` – theforestecologist Dec 06 '18 at 23:42
  • Great answer here. I combined the functions here together and came up with: `round_well <- function(x, roundTo, dir = NULL) { if(is.null(dir)){ if((roundTo - x %% roundTo) <= x %% roundTo) { x + (roundTo - x %% roundTo)} else { x - (x %% roundTo)}} else if(dir == "Up") { ##ROUND UP x + (roundTo - x %% roundTo) } else if (dir == "Down") { ##ROUND DOWN x - (x %% roundTo) } else {print("Options are Up, Down, and blank.")} }` Defaults to normal rounding rules to whatever number to pick, but you can pass "Up" or "Down" to specify direction – Pake Mar 31 '21 at 13:38
10

Regarding the rounding up to the multiplicity of an arbitrary number, e.g. 10, here is a simple alternative to James's answer.

It works for any real number being rounded up (from) and any real positive number rounded up to (to):

> RoundUp <- function(from,to) ceiling(from/to)*to

Example:

> RoundUp(-11,10)
[1] -10
> RoundUp(-0.1,10)
[1] 0
> RoundUp(0,10)
[1] 0
> RoundUp(8.9,10)
[1] 10
> RoundUp(135,10)
[1] 140

> RoundUp(from=c(1.3,2.4,5.6),to=1.1)  
[1] 2.2 3.3 6.6
hanna
  • 627
  • 9
  • 15
5

If you always want to round a number up to the nearest X, you can use the ceiling function:

#Round 354 up to the nearest 100:
> X=100
> ceiling(354/X)*X
[1] 400

#Round 47 up to the nearest 30:
> Y=30
> ceiling(47/Y)*Y
[1] 60

Similarly, if you always want to round down, use the floor function. If you want to simply round up or down to the nearest Z, use round instead.

> Z=5
> round(367.8/Z)*Z
[1] 370
> round(367.2/Z)*Z
[1] 365
Steve Walsh
  • 127
  • 1
  • 7
2

I think your code just works great with a small modification:

foo <- function(x, round=10) ceiling(max(x+10^-9)/round + 1/round)*round

And your examples run:

> foo(4, round=1) == 5
[1] TRUE
> foo(6.1) == 10            #maybe 7 would be better
[1] TRUE
> foo(6.1, round=1) == 7    # you got 7
[1] TRUE
> foo(30.1) == 40
[1] TRUE
> foo(100.1) == 110
[1] TRUE
> # ALL in one:
> foo(c(4, 6.1, 30.1, 100))
[1] 110
> foo(c(4, 6.1, 30.1, 100), round=10)
[1] 110
> foo(c(4, 6.1, 30.1, 100), round=2.3)
[1] 101.2

I altered your function in two way:

  • added second argument (for your specified X )
  • added a small value (=1e-09, feel free to modify!) to the max(x) if you want a bigger number
daroczig
  • 28,004
  • 7
  • 90
  • 124
2

This rounds x up to the nearest integer multiple of y when y is positive and down when y is negative:

rom=\(x,y)x+(y-x%%y)%%y
rom(8.69,.1) # 8.7
rom(8.69,-.1) # 8.6
rom(8.69,.25) # 8.75
rom(8.69,-.25) # 8.5
rom(-8.69,.25) # -8.5

This always rounds to the nearest multiple like round_any in plyr (https://github.com/hadley/plyr/blob/34188a04f0e33c4115304cbcf40e5b1c7b85fedf/R/round-any.r):

rnm=\(x,y)round(x/y)*y
rnm(8.69,.25) # 8.75
plyr::round_any(8.69,.25) # 8.75

round_any can also be given ceiling as the third argument to always round up or floor to always round down:

plyr::round_any(8.51,.25,ceiling) # 8.75
plyr::round_any(8.69,.25,floor) # 8.5
nisetama
  • 7,764
  • 1
  • 34
  • 21
1

You will find an upgraded version of Tommy's answer that takes into account several cases:

  • Choosing between lower or higher bound
  • Taking into account negative and zero values
  • two different nice scale in case you want the function to round differently small and big numbers. Example: 4 would be rounded at 0 while 400 would be rounded at 400.

Below the code :

round.up.nice <- function(x, lower_bound = TRUE, nice_small=c(0,5,10), nice_big=c(1,2,3,4,5,6,7,8,9,10)) {
  if (abs(x) > 100) {
    nice = nice_big
  } else {
    nice = nice_small
  }
  if (lower_bound == TRUE) {
    if (x > 0) {
      return(10^floor(log10(x)) * nice[[max(which(x >= 10^floor(log10(x)) * nice))[[1]]]])
    } else if (x < 0) {
      return(- 10^floor(log10(-x)) * nice[[min(which(-x <= 10^floor(log10(-x)) * nice))[[1]]]])
    } else {
      return(0)
    }
  } else {
    if (x > 0) {
      return(10^floor(log10(x)) * nice[[min(which(x <= 10^floor(log10(x)) * nice))[[1]]]])
    } else if (x < 0) {
      return(- 10^floor(log10(-x)) * nice[[max(which(-x >= 10^floor(log10(-x)) * nice))[[1]]]])
    } else {
      return(0)
    }
  }
}
Community
  • 1
  • 1
Yohan Obadia
  • 2,552
  • 2
  • 24
  • 31
  • The default output of this is a round down: `> round.up.nice(.01) [1] 0 > round.up.nice(4.5) [1] 0 > round.up.nice(56) [1] 50` – jessi Feb 19 '17 at 23:38
  • I think part of the problem is that `nice_big` and `nice_small` are defined backwards, (if we flip them in the function, `round.up.nice(4.5)` becomes `4`) but it still rounds down. – jessi Feb 19 '17 at 23:48
1

I tried this without using any external library or cryptic features and it works!

Hope it helps someone.

ceil <- function(val, multiple){
  div = val/multiple
  int_div = as.integer(div)
  return (int_div * multiple + ceiling(div - int_div) * multiple)
}

> ceil(2.1, 2.2)
[1] 2.2
> ceil(3, 2.2)
[1] 4.4
> ceil(5, 10)
[1] 10
> ceil(0, 10)
[1] 0
Rahul Chawla
  • 1,048
  • 10
  • 15
0

Might be missing something but is it not as easy as:

some_number = 789
1000 * round(some_number/1000, 0)

to produce something rounded to 1000s?

cs0815
  • 16,751
  • 45
  • 136
  • 299