Round up from .5
Solution 1
This is not my own function, and unfortunately, I can't find where I got it at the moment (originally found as an anonymous comment at the Statistically Significant blog), but it should help with what you need.
round2 = function(x, n) {
posneg = sign(x)
z = abs(x)*10^n
z = z + 0.5 + sqrt(.Machine$double.eps)
z = trunc(z)
z = z/10^n
z*posneg
}
x
is the object you want to round, and n
is the number of digits you are rounding to.
An Example
x = c(1.85, 1.54, 1.65, 1.85, 1.84)
round(x, 1)
# [1] 1.8 1.5 1.6 1.8 1.8
round2(x, 1)
# [1] 1.9 1.5 1.7 1.9 1.8
(Thanks @Gregor for the addition of + sqrt(.Machine$double.eps)
.)
Solution 2
If you want something that behaves exactly like round
except for those xxx.5 values, try this:
x <- seq(0, 1, 0.1)
x
# [1] 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
floor(0.5 + x)
# [1] 0 0 0 0 0 1 1 1 1 1 1
Solution 3
As @CarlWitthoft said in the comments, this is the IEC 60559 standard as mentioned in ?round
:
Note that for rounding off a 5, the IEC 60559 standard is expected to be used, ‘go to the even digit’. Therefore round(0.5) is 0 and round(-1.5) is -2. However, this is dependent on OS services and on representation error (since e.g. 0.15 is not represented exactly, the rounding rule applies to the represented number and not to the printed number, and so round(0.15, 1) could be either 0.1 or 0.2).
An additional explanation by Greg Snow:
The logic behind the round to even rule is that we are trying to represent an underlying continuous value and if x comes from a truly continuous distribution, then the probability that x==2.5 is 0 and the 2.5 was probably already rounded once from any values between 2.45 and 2.54999999999999..., if we use the round up on 0.5 rule that we learned in grade school, then the double rounding means that values between 2.45 and 2.50 will all round to 3 (having been rounded first to 2.5). This will tend to bias estimates upwards. To remove the bias we need to either go back to before the rounding to 2.5 (which is often impossible to impractical), or just round up half the time and round down half the time (or better would be to round proportional to how likely we are to see values below or above 2.5 rounded to 2.5, but that will be close to 50/50 for most underlying distributions). The stochastic approach would be to have the round function randomly choose which way to round, but deterministic types are not comforatable with that, so "round to even" was chosen (round to odd should work about the same) as a consistent rule that rounds up and down about 50/50.
If you are dealing with data where 2.5 is likely to represent an exact value (money for example), then you may do better by multiplying all values by 10 or 100 and working in integers, then converting back only for the final printing. Note that 2.50000001 rounds to 3, so if you keep more digits of accuracy until the final printing, then rounding will go in the expected direction, or you can add 0.000000001 (or other small number) to your values just before rounding, but that can bias your estimates upwards.
Solution 4
This appears to work:
rnd <- function(x) trunc(x+sign(x)*0.5)
Ananda Mahto's response seems to do this and more - I am not sure what the extra code in his response is accounting for; or, in other words, I can't figure out how to break the rnd() function defined above.
Example:
seq(-2, 2, by=0.5)
# [1] -2.0 -1.5 -1.0 -0.5 0.0 0.5 1.0 1.5 2.0
round(x)
# [1] -2 -2 -1 0 0 0 1 2 2
rnd(x)
# [1] -2 -2 -1 -1 0 1 1 2 2
Solution 5
Depending on how comfortable you are with jiggling your data, this works:
round(x+10*.Machine$double.eps)
# [1] 1 2 3 4 5 6 7 8 9 10
Related videos on Youtube
jakob-r
Updated on July 05, 2022Comments
-
jakob-r almost 2 years
Yes I know why we always round to the nearest even number if we are in the exact middle (i.e. 2.5 becomes 2) of two numbers. But when I want to evaluate data for some people they don't want this behaviour. What is the simplest method to get this:
x <- seq(0.5,9.5,by=1) round(x)
to be 1,2,3,...,10 and not 0,2,2,4,4,...,10.
Edit: To clearify: 1.4999 should be 1 after rounding. (I thought this would be obvious)
-
Michael Allen over 11 yearsAm I right in thinking you want values <= 0.4 to round to 0 and values >= 0.5 to round to 1?
-
flodel over 11 years@CarlWitthoft, are they really? Can you elaborate? That
round
mapsn + .5
ton
seems arbitrary to me. -
jakob-r over 11 yearsIt is easy to simulate. Based on the sequence
x
from above trymean(x); mean(round(x)); mean(floor(0.5 + x))
. Of course this does not proof anything as this could be only a special case. But look at this this way: If we round every x.5 up of course our rounded data than is biased. If we round down every second x.5 we counter this effect. That's why we round to the next even number. -
James over 11 years@flodel Comapre
sum(seq(0.5,1e3,by=0.5))
with the sums of each of the rounded versions of the sequences -
Carl Witthoft over 11 yearsNot to mention that "rounding to the even digit" is the IEC 60559 standard as mentioned in
?round
.
-
-
jakob-r over 11 yearsSo simple that I am a little suspicous.
-
James over 11 years@jakobr It's relies on you wanting to only round to whole numbers, rather than any other power of 10
-
flodel over 11 years+1 - Very nice, I only realize now that your answer is a generalization of mine. Maybe you should have used
n=0
in your example, even set it as default in your function. Also note that it handles negative numbers differently:round2(-0.5, 0)
gives-1
while my method will return0
. -
A5C1D2H2I1M1N2O1R2T1 over 11 years@flodel, Unfortunately, I can't take credit for the function. I honestly didn't know there were so many different ways of rounding until I had encountered R's round-to-even behavior and did some reading up on the topic.
-
A5C1D2H2I1M1N2O1R2T1 over 10 yearsDid you post your entire function? The
rdn
function you posted only takes one argument (the input vector) but you show it being used with two arguments. The function I've posted tries to address rounding to other place values as well. For instance, compare the difference betweenround(x, 2)
andround2(x, 2)
whenx = c(1.855, 1.545, 1.655, 1.855, 1.845)
. -
Dason almost 10 yearsYou could also use sign(x)*floor(abs(x)+ 0.5) if you want to do rounding "away" from 0 and it's possible you have negative values.
-
Megatron about 8 yearsThis solution works for me:
x <- seq(-5, 5, by=0.5)
and thentrunc(x+sign(x)*0.5)
. Elegant! -
krevelen about 7 yearsJust to provide the single-line version for rounding
x
away from zero withn
digits:round2 = function(x, n=0) {scale<-10^n; sign(x)*trunc(abs(x)*scale+0.5)/scale}
. Surely there's a native/built-in function for this in R? -
krevelen about 7 yearsAnd now I see the elegant solution below, which would yield:
round2 = function(x, n=0) {scale<-10^n; trunc(x*scale+sign(x)*0.5)/scale}
-
Dogan Askan over 6 yearsWhy does
floor(0.2850*100+0.5)
return 28, whereasfloor(0.3850*100+0.5)
returns 39, andfloor(0.4850*100+0.5)
returns 49 -
flodel over 6 years@DoganAskan, it is due to floating point errors. See Circle 1 of burns-stat.com/pages/Tutor/R_inferno.pdf. I highly recommend reading the whole pdf if you have time.
-
MS Berends over 5 yearsOr as a oneliner:
round2 <- function(x, n) (trunc((abs(x) * 10 ^ n) + 0.5) / 10 ^ n) * sign(x)
-
Miff over 4 yearsif you add a factor of $sign(x)$ to the small number it replicates Excel's behaviour of rounding away from zero for negative numbers
-
Gregor Thomas over 4 yearsWhat's the point of the
abs(x)
in the calculation? Doesn't seem like the amount added should depend on the magnitude ofx
. I could see adding a factor of 10 or 100 times thedouble.eps
if you want a small cushion---it just doesn't make sense to me that the cushion should depend on the magnitude ofx
-
mpschramm over 4 yearsI don't have a good answer for that, possibly a platform and representation issue (I did this on Windows):
x = 2.5 round(x + sign(x) * .Machine$double.eps, 0)
results in2
. Whereas,round(x + abs(x) * sign(x) * .Machine$double.eps, 0)
results in3
. Adding a small fixed amount instead of something that scales with x fails as x increases. -
Gregor Thomas over 4 yearsYou've demonstrated that the fixed amount needs to be bigger than just
.Machine$double.eps
, but you haven't shown that it needs to scale withx
. I think you just need to make your fixed amount slightly bigger. Multiply by a factor of 10 as I suggest in my comment, and as is done in MichaelChirico's answer. Making it scale withx
means that, asx
gets larger, values more and more below 0.5 will round up. -
mpschramm over 4 yearsI see your logic and recognize this approach will cause issues if x is quite large. I might be failing to see how to functionalize this approach. For example:
round(10000.5 + 10 *.Machine$double.eps, 0)
returns 10000 instead of 10001. -
Gregor Thomas over 4 yearsHmmm, interesting. So maybe there is something to do with the magnitude of
x
... at 10000 I had to go up to10000 * .Machine$double.eps
---i.e.,abs(x)
. So, you've convinced me! -
Gregor Thomas almost 4 yearsThere are numerical precision issues, e.g.,
round2(2436.845, 2)
returns2436.84
. Changingz + 0.5
toz + 0.5 + sqrt(.Machine$double.eps)
seems to work for me. -
Gregor Thomas almost 4 yearsChanging
z + 0.5
toz + 0.5 + sqrt(.Machine$double.eps)
might work as well, and be a little more elegant. -
bryn over 3 yearsThe janitor package also adds this functionality, so it might be an easy way to get the behaviour you want without writing it yourself. cran.r-project.org/web/packages/janitor/vignettes/…
-
jgarces over 2 yearsVery useful. And what about if there're more that one decimal and you wanna round all of them? For example,
round2(1.48, 0)
gives me1
and not2
. I solved troughround2(round(1.48), 0)
but maybe another option in the function would be super nice.