¿Cómo redondear a la decena más cercana (o 100 o X)?

93

Estoy escribiendo una función para trazar datos. Me gustaría especificar un buen número redondo para el eje y maxque sea mayor que el máximo del conjunto de datos.

Específicamente, me gustaría una función fooque realice lo siguiente:

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

He llegado tan lejos como

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

para redondear a la decena más cercana, pero esto no funciona para intervalos de redondeo arbitrarios.

¿Hay una mejor manera de hacer esto en R?

Abe
fuente
El comportamiento predeterminado de R al trazar es establecer los límites de la trama ~ 4% más allá del rango de los datos en cada dirección. Si esto no le satisface, tal vez escriba algo que salga en un% más alto o más bajo.
joran
@joran gracias por la información, pero quiero múltiples parcelas que tengan los mismos límites de eje y ticks y no estoy seguro de cómo esto ayuda.
Abe
1
Bueno, estoy buscando a tientas en la oscuridad, ya que no conozco todos los antecedentes. Tu foo redondeará a la X más cercana si simplemente agregas otro parámetro X y reemplazas ambos decenas con X. O podrías usar facetas.
joran
15
¿Está buscando ?pretty?
hadley
¿Por qué es foo(4)==5y no 10?
James

Respuestas:

64

Si solo desea redondear a la potencia más cercana de 10, simplemente defina:

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

En realidad, esto también funciona cuando x es un vector:

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

... pero si desea redondear a un número "agradable", primero debe definir qué es un número "agradable". Lo siguiente nos permite definir "agradable" como un vector con buenos valores base de 1 a 10. El valor predeterminado se establece en los números pares más 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]]]]
}

Lo anterior no funciona cuando x es un vector, ya es demasiado tarde en la noche :)

> 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

[[EDITAR]]

Si la pregunta es cómo redondear a un valor específico más cercano (como 10 o 100), entonces la respuesta de James parece la más apropiada. Mi versión le permite tomar cualquier valor y redondearlo automáticamente a un valor razonablemente "agradable". Algunas otras buenas opciones del vector "agradable" anterior son:1:10, c(1,5,10), seq(1, 10, 0.1)

Si tiene un rango de valores en su gráfico, por ejemplo [3996.225, 40001.893], la forma automática debe tener en cuenta tanto el tamaño del rango como la magnitud de los números. Y como señaló Hadley , la pretty()función podría ser la que desee.

Tommy
fuente
1
Vectorize(roundUpNice)es bastante rápido =) +1 de todos modos.
mbq
Me preguntaba si hay una manera de editar la función roundUp a la mitad del resultado final si el valor original es menor. por ejemplo, un número entre 101-500 se redondearía a 500 y los números 501-999 se redondearían a 1000? en lugar de redondear todo a 1000?
helen.h
Simplemente cambie el vector agradable:roundUpNice(501, nice=c(5, 10)) # 1000
Tommy
1
Solo una advertencia, ya que trabaja con logaritmos en su función, tendría 2 casos, uno donde x < 0y lo aplicaría - xen el registro antes de poner el -reverso. También agregaría una excepción para la situación en la quex = 0
Yohan Obadia
La función bonita funcionó muy bien para mis necesidades (para establecer un límite bonito en el eje X de un gráfico)
Joon
132

La plyrbiblioteca tiene una función round_anyque es bastante genérica para hacer todo tipo de redondeo. Por ejemplo

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
fuente
Para obtener un dplyrreemplazo, consulte: stackoverflow.com/a/46489816/435093
slhck
46

La función de redondeo en R asigna un significado especial al parámetro de dígitos si es negativo.

redondo (x, dígitos = 0)

Redondear a un número negativo de dígitos significa redondear a una potencia de diez, por lo que, por ejemplo, round (x, dígitos = -2) redondea a la centena más cercana.

Esto significa que una función como la siguiente se acerca bastante a lo que está pidiendo.

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

La salida se parece a la siguiente

foo(4)
[1] 10
foo(6.1)
[1] 10
foo(30.1)
[1] 40
foo(100.1)
[1] 110
Cero
fuente
2
¡Increíble! ¡Su respuesta debe estar marcada como la correcta!
Alessandro Jacopson
+1. Sin embargo, @Alessandro, las funciones en mi respuesta son mucho más versátiles: puede redondear CUALQUIER número hacia arriba O hacia abajo a CUALQUIER intervalo.
theforestecologist
@theforestecologist ¡gracias por la pista! Como gusto personal, por lo general prefiero una solución de idiomas integrada en lugar de una personalizada.
Alessandro Jacopson
27

Qué tal si:

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

Lo que da:

> 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
fuente
1
@daroczig La pregunta es un poco confusa, escribí esto concentrándome en el requisito de "X arbitrario", pero claramente todos los valores esperados no podrían producirse mediante "una sola ronda hasta la solución X más cercana". Parece que el OP quiere producir valores para un eje, por prettylo que probablemente sea la mejor opción.
James
1
Obviamente tarde para esta fiesta, pero ¿no to * ceiling(x / to)estaría más limpio?
jared
25

Si agrega un número negativo al argumento de dígitos de round (), R lo redondeará a los múltiplos de 10, 100, etc.

    round(9, digits = -1) 
    [1] 10    
    round(89, digits = -1) 
    [1] 90
    round(89, digits = -2) 
    [1] 100
Anastasia Pupynina
fuente
17

Redondea CUALQUIER número hacia arriba o hacia abajo a CUALQUIER intervalo

Puede redondear números fácilmente a un intervalo específico utilizando el operador de módulo %% .

La función:

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)
    }
  }
}

Ejemplos:

> 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

Cómo funciona:

El operador de módulo %%determina el resto de dividir el primer número por el segundo. Sumar o restar este intervalo a su número de interés esencialmente puede "redondear" el número a un intervalo de su elección.

> 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

Actualizar:

La conveniente versión de 2 argumentos:

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

yValores positivos roundUp, mientras que yvalores negativos roundDown:

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

O....

Función que redondea automáticamente hacia ARRIBA o ABAJO según las reglas de redondeo estándar:

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

Redondea automáticamente hacia arriba si el xvalor se encuentra a >medio camino entre instancias posteriores del valor de redondeo 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
el forestecólogo
fuente
Me preguntaron cómo convertir Rounda VBA en 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
@Abe No estaba seguro de si alguna vez dices mi respuesta o no, ya que publiqué 4 años después de que me preguntaste, pero creo que mi respuesta puede ser bastante elegante y muy VERSÁTIL. ¡Espero eso ayude!
theforestecologist
7

Con respecto al redondeo a la multiplicidad de un número arbitrario , por ejemplo, 10, aquí hay una alternativa simple a la respuesta de James.

Funciona para cualquier número real redondeado hacia arriba ( from) y cualquier número real positivo redondeado hacia arriba ( to):

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

Ejemplo:

> 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
fuente
2

Creo que tu código funciona muy bien con una pequeña modificación:

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

Y tus ejemplos corren:

> 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

Alteré tu función de dos maneras:

  • agregado segundo argumento (para su X especificada )
  • agregó un pequeño valor (¡ =1e-09siéntase libre de modificar!) al max(x)si desea un número mayor
daroczig
fuente
1

Si siempre desea redondear un número hasta el X más cercana, puede utilizar la ceilingfunción:

#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

De manera similar, si siempre desea redondear hacia abajo , use la floorfunción. Si desea simplemente redondear hacia arriba o hacia abajo a la Z más cercana, utilice rounden su lugar.

> Z=5
> round(367.8/Z)*Z
[1] 370
> round(367.2/Z)*Z
[1] 365
Steve Walsh
fuente
0

Encontrará una versión mejorada de la respuesta de Tommy que tiene en cuenta varios casos:

  • Elegir entre límite inferior o superior
  • Teniendo en cuenta los valores negativos y cero
  • dos bonitas escalas diferentes en caso de que desee que la función redondee números grandes y pequeños de forma diferente. Ejemplo: 4 se redondearía a 0 mientras que 400 se redondearía a 400.

Debajo del código:

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)
    }
  }
}
Yohan Obadia
fuente
La salida predeterminada de esto es un redondeo hacia abajo:> round.up.nice(.01) [1] 0 > round.up.nice(4.5) [1] 0 > round.up.nice(56) [1] 50
jessi
Creo que parte del problema es que nice_bigy nice_smallestán definidos al revés, (si los volteamos en la función, se round.up.nice(4.5)convierte 4) pero aún así se redondea hacia abajo.
jessi
0

Intenté esto sin usar ninguna biblioteca externa o características crípticas y ¡funciona!

Espero que ayude a alguien.

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
fuente