Mostrando entradas con la etiqueta microbenchmark. Mostrar todas las entradas
Mostrando entradas con la etiqueta microbenchmark. Mostrar todas las entradas

2015-10-26

Una manera más eficiente de comparar números que con ifelse en R

Title

Problema

Tenemos el siguiente data frame.

       x  y
[1,]  -1 99
[2,]   5  4
[3,]  10 -2
[4,] 600  0
[5,] -16  1
[6,]   0 55

Y deseamos obtener un vector fruto de la siguiente comparación de x e y:

1. El menor valor en y si tanto x e y son positivos.
2. Cualquier valor positivo de y si x es negativo.
3. O dejar el valor de x si no se cumplen las dos condiciones anteriores.

Datos

dat <- structure(list(x = c(-1L, 5L, 10L, 600L, -16L, 0L), y = c(99L, 
4L, -2L, 0L, 1L, 55L)), .Names = c("x", "y"), class = "data.frame", row.names = c("[1,]", 
"[2,]", "[3,]", "[4,]", "[5,]", "[6,]"))

Soluciones

 # Extraemos los vectores
x <- dat[, 1]
y <- dat[, 2]
  • ifelse
  • El problema de esta opción es que no es muy eficiente computacionalmente.

    ifelse(y >= 0, ifelse(x < 0, y, ifelse(x > y, y, x)), x)
    
     [1] 99  4 10  0  1  0

  • Indexando
  • Esta opción es entre 5 y 6 veces más rápida.

    xz[(x < y & x >= 0)| y < 0] <- x[(x < y & x >= 0)| y < 0];z
    
     [1] 99  4 10  0  1  0
  • Sin indexar
  • Aún más rápida que la anterior.

    x * ((x < y & x >= 0) | y < 0) + y * ((x > y & y >= 0) | x < 0) 
    
     [1] 99  4 10  0  1  0

    Comparativa

    Empleamos el paquete microbenchmark para comparar el tiempo de ejecución de las 3 opciones.

    microbenchmark(
      if_else = ifelse(y >= 0, ifelse(x < 0, y, ifelse(x > y, y, x)), x),
      indexar= z[(x < y & x >= 0)| y < 0] <- x[(x < y & x >= 0)| y < 0],  
      sinindexar = x *((x < y & x >= 0)| y < 0)+ y * ((x > y & y >= 0)| x < 0),
        )
    
    Unit: microseconds
           expr    min      lq     mean median     uq     max neval cld
        if_else 43.023 47.1785 53.29921 53.289 53.290 107.067   100  b
        indexar  8.800  9.2900 10.71218  9.778 10.267  53.290   100  a 
     sinindexar  7.333  7.8230  8.89833  8.311  8.800  52.800   100  a 
    
    La opción ifelse es la menos eficiente. La opción indexar mejora sustancialmente la velocidad de ejecución. Finalmente, la opción sinindexar es algo más rápida aún que indexar. Existen opciones más rápidas incluso, que puedes ver en el enlace del apartado referencias. Sin embargo, quizá la ganancia en velocidad de ejecución no compensa la pérdida de legibilidad.

    Entradas relacionadas

    Referencias

    2015-09-07

    Comparar el tiempo de ejecución de expresiones en R con microbenchmark

    Title

    Problema

    Queremos evaluar el tiempo de ejecución de varias expresiones en R. Partimos del ejemplo de la entrada anterior, en la que seleccionábamos un porcentajes de filas de un data frame mediante diferentes métodos.

    Utilizamos el conjunto de datos ToothGrowth, que cuenta con 60 filas. Lo partiremos en dos, la primera con un 70% de las filas (42) y la segunda con un 30% (18 filas). Aunque en este ejemplo no es necesario, empleamos la función round, para que en el caso de que por ejemplo el número de filas sea 6,6 devuelva 7 filas en lugar de 6.

    Opciones

  • head y tail
  • head(ToothGrowth, round(nrow(ToothGrowth)*0.7))
    tail(ToothGrowth, round(nrow(ToothGrowth)*0.3))
    
  • Filtrando con índices
  • porcentaje = round(0.7*nrow(ToothGrowth))
    ToothGrowth[1:porcentaje,]
    ToothGrowth[-(1:porcentaje),]
    
  • Usando dplyr
  • porcentaje = round(0.7*nrow(ToothGrowth))
    library(dplyr)
    slice(ToothGrowth, 1:porcentaje)
    slice(ToothGrowth, -(1:porcentaje))

    Medición

    library(microbenchmark)
    library(ggplot2)
    porcentaje <-  round(0.7*nrow(ToothGrowth))
    rdo <- microbenchmark(
      opcion1 = head(ToothGrowth, round(nrow(ToothGrowth) * 0.7)),
      opcion2 = ToothGrowth[1:porcentaje,],
      opcion3 = slice(ToothGrowth, 1:porcentaje))
    
    Cargamos microbenchmark para la medición y ggplot2 para la representación gráfica. Separamos cada expresión que deseamos evaluar por una coma. Por defecto microbenchmark ejecuta cada expresión 100 veces (neval en el resultado). Para modificar el número de veces incluimos el argumento times.

    Resultado

    rdo
    
    Unit: microseconds
        expr     min       lq      mean    median       uq      max neval
     opcion1 134.933  147.156  203.3685  179.6675  225.867 1017.378   100
     opcion2  98.756  105.112  138.9135  116.3565  161.334  418.490   100
     opcion3 978.756 1026.912 1096.2901 1056.2450 1087.289 3765.912   100
    
    Los resultados incluyen 8 columnas. El mínimo (min), cuartil inferior (lq), media (mean) ,mediana (median), cuartil superior (uq), y máximo (max). Nos centraremos en la mediana, y en los cuartiles inferior y superior (lq y uq) para hacernos una idea de la variabilidad. En nuestro ejemplo vemos que la mejor opción en todos los parámetros y con menor variabilidad es la opción 2. También debemos prestar atención a la unidad de medida, antes de las columnas con los resultados, en nuestro ejemplo microsegundos.

    Gráfico

    Generamos un gráfico de tipo violín con los resultados de la medición.

    autoplot(rdo)
    

    Entradas relacionadas

    Referencias

    Nube de datos