Curso de R | Debugging

Curso de R | Debugging

debugging en r

 

Introducción


Debuguear un código es el proceso de proceso de identificar y corregir errores dentro de él. R tiene varias maneras de indicar cuando algo falla en la ejecución de un fragmento de código, las cuales son importantes conocer para disminuir el tiempo que requiere esta tarea. Cuando ejecutamos cualquier función en R podemos obtener los siguiente resultados:

  • message: es una notificación genérica producida por al función message(). Luego de esta función, el código continua su ejecución.
  • warning: es una indicación que indica que algo anda mal, pero no fatalmente amal. Es producida por la función warning(), y luego de esta el código continua su ejecución.
  • error: es una indicación de que ocurrió un error fatal que detuvo la ejecución del código. Esta es producida por la función stop().
  • condition: es un concepto genérico par indica que algo inesperado ha ocurrido. Estas en general son creadas por los programadores para indicar lo que ellos desean.

Por ejemplo:

log(-1)
[1] NaN
Warning message:
In log(-1) : NaNs produced

a = b
Error: object ‘b’ not found

En el primer caso, como los logaritmos no están definidos para números negativos no obtenemos un resultado concreto. En cambio obtenemos un NaN (Not a Number) junto a un mensaje tipo warning que nos indica a que se debe esto. La razón de que R no detenga la ejecución del código luego de producir este evento es debido a considera que es posible continuar la ejecución asumiendo que el resultado de la operación es un NaN. En cambio, en el segundo caso, al intentar asignar a la variable a el valor de la variable b se produce un error fatal dado que esta última no está definida y no se puede asignar ningun valor a la variable a.

La principal tarea de debuguear cualquier código en R es diagnosticar correctamente cual es el problema en él. Cuando diagnosticamos un problema, es importante conocer primero cual es el resultado esperado de su ejecución. Luego se necesita identificar que ocurrió y como se desvió lo sucedido respecto de lo esperado. Algunos puntos para comenzar a investigar son:

  • Cuales y de que tipo son las entradas del código
  • Que esperamos que suceda cuando ejecutamos el código
  • Que obtenemos finalmente
  • Como difiere lo que obtenemos respecto de lo que esperábamos obtener

Finalmente, antes de pasar a ver las herramientas que nos provee R debemos mencionar que existen muchas técnicas para debuguear programas. Esto ya excede el objetivo de este post.

 

Herramientas para Debugging en R


R nos trae unas cuantas herramientas para ayudarnos en la tarea de debuguear código. Las herramientas principales son:

  • traceback: imprime en pantalla la secuencia de llamadas a funciones (function call stack) luego de que un error ocurre.
  • debug: marca una función en debug mode, el cual permite avanzar en la ejecución de esta por pasos, una linea por vez.
  • browser: suspende la ejecución de una función si es llamada y pone la función en debug mode.
  • trace: permite insertar código de depuración dentro de una función en un lugar especifico.
  • recover: permite modificar le comportamiento de un error, de modo que se pueda mirar el la pila de funciones llamadas.

Todas estas son herramientas interactivas específicamente diseñadas para facilitar la tarea de debuguear. También, junto a estas, es común utilizar la función print() para ir dejando constancia de posición/estado del código en determinadas posiciones que resulten de utilidad conocer.

 

traceback

La función traceback imprime en pantalla la pila de funciones llamadas luego de que un error fatal ocurra. Por ejemplo, si tenemos una función llamada a() la cual llama a otra función b() que a su vez llama a c() y así sucesivamente. Si un error ocurre, puede no quedar necesariamente claro en cual función el error ocurrió. Entonces, mediante traceback podemos explorar en cuantos niveles de profundidad el error ocurrió.

mean(x)
Error in mean(x) : object 'x' not found
traceback()
1: mean(x)

f1 <- function(n) mean(n)
f1(x)
Error in mean(n) : object 'x' not found
traceback()
2: mean(n) at #1
1: f1(x)

lm(y~x)
Error in eval(expr, envir, enclos) : object 'y' not found
traceback()
7: eval(expr, envir, enclos)
6: eval(predvars, data, env)
5: model.frame.default(formula = y ~ x, drop.unused.levels = TRUE)
4: stats::model.frame(formula = y ~ x, drop.unused.levels = TRUE)
3: eval(expr, envir, enclos)
2: eval(mf, parent.frame())
1: lm(y ~ x)

En el primer caso, es claro que el error ocurrió dentro de mean() porque el objeto x no está definido. La función traceback() debe ser llamada inmediatamente luego de que el error ocurra. Una vez que otra función es llamada, se pierde el rastro (traceback) del error.

En los siguientes casos, vemos como mediante traceback exploramos el stack de funciones llamadas. En un caso el error ocurre en el 2do nivel del stack, mientras que en el ultimo caso ocurre ocurre en el 7mo nivel.

Mirar la salida del traceback es util para descubrir donde ocurrió el error pero no es útil para obtener más información de este. Para tal caso debemos usar la función debug().

 

debug

La función debug() inicia un depurador interactivo (conocido como browser en R) parar una función. Con el depurador se puede ir paso a paso a través de la ejecución de una función hasta encontrar donde el error ocurre.

La función debug() toma como su primer argumento la función que queremos analizar. Por ejemplo:

f2 <- function(d)
{
  a = 1
  b = 2
  c = a + b
  mean(d)
}

debug(f2)

Ahora, cada vez que llamamos a f2() se ejecutará el modo interactivo de depuración. Para desactivar este comportamiento necesitamos ejecutar la función undebug().

f2(x)
debugging in: f2(x)
debug at #2: {
a = 1
b = 2
c = a + b
mean(d)
}
Browse[2]>

debug_mode

 

El depurador llama al browser al comienzo de la ejecución de la función. Desde allí se puede avanzar paso a paso en el cuerpo de la función. Para realizar esto hay una pequeña serie de comandos que debemos conocer:

  • n: ejecuta la expresion actual y avanza a la siguiente
  • c: continua la ejecucion de una función y no se detiene hasta que ocurra un error o se termine la ejecución de la función
  • Q: sale del browser

En el caso anterior:

Browse[2]> n
debug at #3: a = 1
Browse[2]> n
debug at #4: b = 2
Browse[2]> c
Error in mean(d) : object 'x' not found

Cuando estamos en el browser podemos ejecutar cualquier otra función de R. En particular, como ls() para ver en el espacio de trabajo en un determinado momento o la función print() para ver el valor de un objeto en algún determinado momento.

Para desactivar este modo debemos podemos usar undebug() para desactivarlos para todas las funciones, o pasar como argumento el nombre de la función para la cual queremos desactivarlo (por ejemplo undebug(f2)).

 

recover

La función recover() puede ser usada para modificar el comportamiento de R cuando un error ocurre. Normalmente, cuando un error ocurre en un función, R imprimirá en pantalla un mensaje de error, saldrá de la función y retornará al espacio de trabajo para esperar nuevos comandos.

Con recover() podemos decirle a R que cuando ocurra un error, este debe detener la ejecución en el punto exacto donde el error ocurrió. Esto nos da la oportunidad de mirar el espacio de trabajo cuando el error ocurrió, lo cual es útil para ver los valores de distintos objetos en el preciso momento de saltar el error.

options(error = recover)
f2(x)
Error in mean(d) : object 'x' not found

Enter a frame number, or 0 to exit

1: f2(x)
2: #6: mean(d)

Selection:

La función recover() imprimirá la pila de funciones cuando ocurra un error. En ese momento podremos elegir saltar a la función de interés e investigar el problema. Cuando elegimos una, nos enviará al browser y desde ahí miraremos.

 

Capturando excepciones


Más allá de los errores inesperados que tenemos que solucionar en forma interactiva cuando programamos, en otras ocasiones los errores con que nos topemos de alguna forma pueden ser «esperables» (también denominados «excepciones»). A estos errores conviene buscar la forma de gestionarlos automáticamente. Por ejemplo, un error esperable podría ser cuando intentamos leer el contenido de una archivo que no existe, pero de todas formas necesitamos que el código se siga ejecutando.

En R tenemos tres herramientas para manejar excepciones:

  • try: permite continuar la ejecución del código si se presenta un error.
  • tryCatch: permite ejecutar un fragmento de código especifico si se presenta un error.
  • withCallingHandlers: es una variante de tryCatch que establece manejadores (los fragmentos de código que se ejecutan cuando se captura una excepción) locales. Estos son raramente utilizados.

 

try

La funcion try permite ejecutar un fragmento de código dentro de ella y en caso de presentarse un error continuar con con la ejecución del programa. Por ejemplo, en el siguiente caso tenemos definida una función que evalúa el logaritmo de la variable de entrada. En caso de que la variable de entrada de la función no sea un numero se produce un error y el código se detiene.

f1 <- function(x) {
  log(x)
  "Hola"
}

f1("test")
Error in log(x) : non-numeric argument to mathematical function

Sin embargo, si deseamos que el resto del código de la función continúe ejecutándose independientemente del resultado de la operación que de error debemos utilizar try.

f2 <- function(x) {
  try(log(x))
  "Hola"
}

f2("test")
Error in log(x) : non-numeric argument to mathematical function
[1] "Hola"

Como vemos, se imprime en pantalla el mensaje de error producido por la funcion dentro de try. Si queremos que estos mensajes no aparezcan debemos indicar la opcion silent = TRUE como argumento de try.

f3 <- function(x) {
  try(log(x), silent = TRUE)
  "Hola"
}

f3("test")
[1] "Hola"

Si queremos pasar más de una operación dentro del try debemos encerrar estas entre llaves.

try({
  a <- 1
  b <- "test"
  a + b
})

También podemos capturar la salida de la función try. En tal caso lo que obtendremos será, en caso de que el bloque try finalice sin errores, el resultado de la ultima expresión evaluada. Si en cambio se presenta un error durante la operación, lo que obtendremos serán los mensajes de error contenidos en un objeto clase «try-error».

success <- try(1 + 2)
success
[1] 3
class(success)
[1] "numeric"

failure <- try("a" + "b")
Error in "a" + "b" : non-numeric argument to binary operator
failure
[1] "Error in \"a\" + \"b\" : non-numeric argument to binary operator\n"
attr(,"class")
[1] "try-error"
attr(,"condition")
<simpleError in "a" + "b": non-numeric argument to binary operator>
class(failure)
[1] "try-error"

Un ejemplo útil de este tipo de la función try es cuando realizamos operaciones sobre una lista de objetos.

elementos <- list(1:5, -2: 3, letters[1:5])
elementos
[[1]]
[1] 1 2 3 4 5

[[2]]
[1] -2 -1 0 1 2 3

[[3]]
[1] "a" "b" "c" "d" "e"

res1 <- lapply(elementos, log)
Error in FUN(X[[i]], ...) : non-numeric argument to mathematical function
In addition: Warning message:
In FUN(X[[i]], ...) : NaNs produced
res1
Error: object 'res1' not found

res2 <- lapply(elementos, function(x) try(log(x)))
Error in log(x) : non-numeric argument to mathematical function
In addition: Warning message:
In log(x) : NaNs produced
res2
[[1]]
[1] 0.0000000 0.6931472 1.0986123 1.3862944 1.6094379

[[2]]
[1] NaN NaN -Inf 0.0000000 0.6931472 1.0986123

[[3]]
[1] "Error in log(x) : non-numeric argument to mathematical function\n"
attr(,"class")
[1] "try-error"
attr(,"condition")
<simpleError in log(x): non-numeric argument to mathematical function>

Dado que no hay una función que identifique la clase try-error automáticamente, podemos definir esta a mano.

is.error <- function(x) inherits(x, «try-error»)

is.error(res2[[1]])
[1] FALSE
is.error(res2[[2]])
[1] FALSE
is.error(res2[[3]])
[1] TRUE

 

 

tryCatch

Con try podíamos evitar que la aparición de un error detenga la ejecución del programa, pero no nos permite en forma simple tomar una acción en particular en consecuencia. Para lograr esto tenemos la función tryCatch, la cual de presentarse un error dentro de ella ejecuta un código para manejar tal error (handler). En realidad, puede manejar tantos errors como warnings o messages. Cuando se presenta uno de estos en el código a continuación se ejecuta la función asociada.

f <- function(code) {
  tryCatch(code,
  error = function(c) "test: error",
  warning = function(c) "test: warning",
  message = function(c) "test: message"
  )
}

f(stop("!"))
[1] "test: error"

f(warning("?!"))
[1] "test: warning"

f(message("?"))
[1] "test: message"

f(1)
[1] 1

tryCatch tiene un ultimo argumento, finally, el cual especifica un bloque de código que se ejecuta al finalizar el código principal independientemente del resultado final de este. Este se lo suele usar para tareas de limpieza como cerrar conexiones a bases de datos, eliminar variables que no se vuelvan a utilizar o eliminar archivos.

 

 

Comentarios

Curso de R | Manipulación de data frames con ‘tidyr’

Curso de R | Manipulación de data frames con ‘tidyr’

Manipulacion de data frames con tidyr

 

 

Introducción


En el post anterior hemos hablado sobre como manipular un data frame mediante al paquete dplyr. Ahora haremos lo propio con el paquete tidyr. No obstante, antes de ponernos a ver las funcionalidades que nos provee este, debemos conocer algunos conceptos sobre la forma en que debe estar organizada la información en un data frame de modo que pueda ser utilizada por diversos paquetes que iremos viendo en próximos post.

Los temas presentados en este post se basan en el trabajo Tidy Data, publicado en the Journal of Statistical Software: http://www.jstatsoft.org/v59/i10/paper.

Entonce, lo primero, instalamos el paquete y lo cargamos.

install.packages("tidyr")
library(tidyr)

 

Qué significa que un data frame esté ordenado


La misma información puede ser representada de muchas maneras, es decir, diversos data frames pueden contener la misma información, cada uno ordenándola de una forma diferente. Para ejemplificar esto, he tomado algo de información demográfica de Argentina desde Wikipedia. Como se observa, tenemos tres data frames que contienen la misma información. Cada uno muestra los mismos valores para cada una de las variables que se representan (país, censo, población urbana, población total), pero cada uno los organiza de manera diferente.

df1
     Pais Censo Población.urbana  Población
Argentina  1980       23.198.068 27.949.480
Argentina  1991       28.832.127 32.615.528
Argentina  2001       32.380.296 36.260.130
Argentina  2010       36.907.728 40.117.096

df2
     Pais Censo   Tipo  Población
Argentina  1980 urbana 23.198.068
Argentina  1980  total 27.949.480
Argentina  1991 urbana 28.832.127
Argentina  1991  total 32.615.528
Argentina  2001 urbana 32.380.296
Argentina  2001  total 36.260.130
Argentina  2010 urbana 36.907.728
Argentina  2010  total 40.117.096

df3
     Pais Censo Tasa.de.población.urbana
Argentina  1980  23.198.068 / 27.949.480
Argentina  1991  28.832.126 / 32.615.528
Argentina  2001  32.380.296 / 36.260.130
Argentina  2010  36.907.728 / 40.117.096

No obstante, no todos los casos presentan la misma facilidad para utilizar dicha información. En particular, el primer data frame se dice que está «ordenado», lo que hace que presente mayor facilidad para trabajar con él mediante diferentes herramientas que iremos viendo.

Hay 3 reglas que se deben cumplir para considerar que un data frame está ordenado:

  1. Cada variable debe tener su propia columna
  2. Cada observación debe tener su propia fila
  3. Cada valor deber tener su propia celda

Estas tres reglas están interrelacionadas debido a que es imposible satisfacer solo dos de las tres. En la siguiente imagen se observan visualmente estas reglas.

tidy data diagram

Las funcionalidades que iremos viendo del paquete tidyr tienen como finalidad hacer transformaciones de un data frame de modo que podemos llevarlo de una a otra estructura. En particular, lo que buscaremos hacer es, cuando nos enfrentemos con información representada como en el caso de df2 o df3, poder transformarla a la forma de df1. Es decir, cada fila es una observación y cada columna una variable.

Ahora bien, ¿porqué estos paquetes que veremos más adelante necesitan que la información esté organizada con este criterio?

Por un lado está el hecho de que tener un único criterio para considerar a la información ordenada «correctamente» hace que el diseño de paquetes y nuevas funcionalidades se simplifique. Esto es debido a que las funciones siempre esperarán a la información de entrada con una estructura conocida, y a su vez entregaran los resultados con la misma estructura. Por otro lado, el hecho de tener que cada columna represente a una variable permite la utilización de funciones vectorizadas como hemos ido viendo a los largo del curso.

 

El paquete tidyr


El paquete tidyr brinda muchas funcionalidades para realizar transformaciones en un data frame con el fin de poder transformarlo a la estructura que deseemos. El primer paso para esto es averiguar en el dataset cuales son las variables y cuales son las observaciones. Una vez que las hemos identificado, nos enfrentamos a tres tipos de problemas:

  • Una variable está dividida en múltiples columnas.
  • Una observación está dispersa en múltiples filas.
  • Múltiples variables están metidas en una única celda.

Usualmente un dataset solo tendrá unos de estos problemas. Para remediar esto deberemos utilizar las funciones que veremos a continuacion.

 

gather

La función gather toma múltiples columnas y las une en pares clave-valor. Esto permite resolver las situaciones en que tenemos columnas que realmente no representan variables, sino valores de una variable.

Supongamos que tenemos el siguiente data frame:

df
   Provincia Censo_1991 Censo_2001 Censo_2010
Buenos Aires   10934727   11460575   13596320
     Cordoba    1208554    1368301    1466823
     Rosario    1118905    1161188    1236089

Las columnas Censo_1991, Censo_2001Censo_2010 representan a cada censo (o el año en que se realizó cada censo), es decir, el valor de una variable nominal que podría llamarse censo, y cada fila representa tres observaciones, y no una sola.

Para ordenar este dataset necesitamos emplear la función gather.

gather(data = df, key = "censo", value = "poblacion", 2:4)
   Provincia      censo poblacion
Buenos Aires Censo_1991  10934727
     Cordoba Censo_1991   1208554
     Rosario Censo_1991   1118905
Buenos Aires Censo_2001  11460575
     Cordoba Censo_2001   1368301
     Rosario Censo_2001   1161188
Buenos Aires Censo_2010  13596320
     Cordoba Censo_2010   1466823
     Rosario Censo_2010   1236089

Lo que hemos hecho es decir a la función que tome el data frame df, y una las columnas 2:4 (las que tienen los valores obtenidos en cada censo), metiendo el resultado en el par clave (censo) – valor (población).

 

gather diagram

 

 

spread

La función spread es usada cuando tenemos una observación dispersa en múltiples filas. En el siguiente ejemplo tenemos un dataset donde una observación es el resultado de un censo, pero cada observación (Censo) está esparcido en dos filas (una fila para la población total, y otro para la población urbana).

df
     Pais Censo   Tipo  Población
Argentina  1980 urbana 23.198.068
Argentina  1980  total 27.949.480
Argentina  1991 urbana 28.832.127
Argentina  1991  total 32.615.528
Argentina  2001 urbana 32.380.296
Argentina  2001  total 36.260.130
Argentina  2010 urbana 36.907.728
Argentina  2010  total 40.117.096

Para llevar este dataset a la forma de una observación por fila debemos usar la función spread.

spread(data = df, key = Tipo, value = Población)
     Pais Censo      total     urbana
Argentina  1980 27.949.480 23.198.068
Argentina  1991 32.615.528 28.832.127
Argentina  2001 36.260.130 32.380.296
Argentina  2010 40.117.096 36.907.728

La columna que contiene los nombre de las variables corresponde al argumento key, mientras que la columna que contiene el valor de la variable es value.

spread diagram

Como se observa, la función spread hace lo opuesto a gather. Estas son funciones complementarias, es decir, si al resultado de aplicar la función spread le aplicamos la función gather llegamos al dataset original. Otra observación interesante es que gather alarga los data frames, mientras que spread los hace más ancho.

 

separate

Ahora supongamos que tenemos el siguiente dataset.

df
     Pais Censo Tasa.de.población.urbana
Argentina  1980  23198068 / 27949480
Argentina  1991  28832126 / 32615528
Argentina  2001  32380296 / 36260130
Argentina  2010  36907728 / 40117096

Lo que observamos es que tenemos una columna (Tasa.de.población.urbana) que contiene más de un valor. Para solucionar esto tenemos que emplear la función separate. Esta función lo que hace es dividir una columnas en múltiples columnas, tomando como separador algún símbolo.

separate(data =  df, 
         col  =  Tasa.de.población.urbana,  
         into =  c("urbana", " total"), 
         sep  =  "/")

     Pais Censo    urbana     total
Argentina  1980 23198068   27949480
Argentina  1991 28832126   32615528
Argentina  2001 32380296   36260130
Argentina  2010 36907728   40117096

Por defecto, la función separete usa como separador cualquier valor no alfa-numerico. En este ejemplo he indicado explícitamente el separador (sep) solo para claridad.

Por otro lado, el tipo de dato que se asigna a las nuevas columnas es el mismo que el original.

str(df)
'data.frame':	4 obs. of  3 variables:
 $ Pais                    : chr  "Argentina" "Argentina" "Argentina" "Argentina"
 $ Censo                   : chr  "1980" "1991" "2001" "2010"
 $ Tasa.de.población.urbana: chr  "23198068 / 27949480" "28832126 / 32615528" "32380296 / 36260130" "36907728 / 40117096"

str(separate(data =  df, 
             col  =  Tasa.de.población.urbana,  
             into =  c("urbana", " total"), 
             sep  =  "/"))
'data.frame':	4 obs. of  4 variables:
 $ Pais  : chr  "Argentina" "Argentina" "Argentina" "Argentina"
 $ Censo : chr  "1980" "1991" "2001" "2010"
 $ urbana: chr  "23198068 " "28832126 " "32380296 " "36907728 "
 $ total:  chr  " 27949480" " 32615528" " 36260130" " 40117096"

Como es de esperar, las columnas resultantes de la división son de tipo character, pero dado su significado lo que deseamos es que sean tratadas como numeric o integer. Para indicar esto podemos hacer la conversion del tipo de dato manualmente, o se puede agregar la opcion convert = TRUE a la función separate.

str(separate(data =  df, 
             col  =  Tasa.de.población.urbana,  
             into =  c("urbana", " total"), 
             sep  =  "/",
             convert = TRUE))
'data.frame':	4 obs. of  4 variables:
 $ Pais  : chr  "Argentina" "Argentina" "Argentina" "Argentina"
 $ Censo : chr  "1980" "1991" "2001" "2010"
 $ urbana: num  23198068 28832126 32380296 36907728
 $  total: int  27949480 32615528 36260130 40117096

 

unite

Así como teníamos a las funciones complementarias gatherspread, tenemos a la función unite que complementa a separate. Esta función lo que hace es tomar múltiples columnas (...) y las colapsa en una única columna (col), separando los elementos mediante sep.

df

ID dia mes anio
 1  25   9 2015
 2  30   7 2015
 3   7   3 2015
 4  15   8 2015

unite(data = df, 
      col = Fecha, 
      sep = "-",
      dia,mes,anio)

ID     Fecha
 1 25-9-2015
 2 30-7-2015
 3  7-3-2015
 4 15-8-2015

 

 

Comentarios
Curso de R | Manipulación de data frames con ‘dplyr’

Curso de R | Manipulación de data frames con ‘dplyr’

Manipulacion de data frames condplyr

 

Introducción


Ya hemos visto que la estructura de datos más importante en R es el data frame. Esta estructura permite representar la información en forma de tabular y trabajar con ella en una forma fácil de comprender. La idea detrás de un data frame es que cada fila represente una observación y cada columna represente una variable (medición, propiedad, característica, etc). El porqué de esto lo veremos en un próximo post. De momento quedémonos con esta idea.

Por ejemplo, en el dataset iris vemos que cada fila representa una  observación sobre una flor, mientras que cada columna representa una variable de la flor observada.

head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

 

Dada la importancia de trabajar con data frames, es importante conocer las herramientas con que contamos para manipularlos. Ya hemos visto en el post sobre subsetting un poco sobre como extraer partes de un data frame o realizar modificaciones utilizando los operadores [ y $. No obstante, otras operaciones más complejas como realizar agrupaciones u ordenar en función de los valores de una columna pueden resultar tareas tediosas. Es debido a esto que fue desarrollado el paquete dplyr, el cual provee funciones que facilitan enormemente las tareas usuales con que nos vamos a topar al momento de trabajar con data frames.

 

El paquete dplyr


El paquete dplyr fue desarrollado por Hadley Wickham (RStudio) como una optimización del paquete plyr. Este paquete no provee ninguna funcionalidad que no pueda ser realizada con las funciones del paquete base, sino que su valor agregado está en la simplicidad que provee para realizar tales operaciones. A su vez, dado que el paquete está escrito en C++, las funciones que provee permiten hacer operaciones más rápido que su equivalente del paquete base.

Las funciones principales que provee el paquete dplyr son:

  • select: devuelve solo las columnas indicadas de un dataframe.
  • filter: permite filtrar filas de una data frame según una expresión lógica.
  • arrange: ordena las filas de un data frame en función de los valores de una o más columnas.
  • rename: permite cambiar el nombre de una columna.
  • mutate: permite agregar una nueva columna o transformar una existente.
  • summarise: permite realizar resúmenes estadísticos de variables en un data frame.

 

Todas estas funciones tiene la particularidad de que su primer argumento es el data frame al que le realizará la operación, mientras que los subsiguiente argumentos describen como realizar tal operación. Finalmente el resultado de todas estas funciones es un nuevo data frame.

Luego, otra función/operador que nos provee este paquete es el pipe (%>%). Este operador es usado para conectar la salida de una función con la entrada de la siguiente. Esto es muy útil dado que, como mencionamos, todas estas funciones tienen como entrada un data frame, y como salida otro data frame por lo que el pipe nos permite encadenarlas, y así mejorar la legibilidad del código. Más adelante lo veremos en acción.

Antes de comenzar a ver las funciones debemos instalar el paquete y cargarlo:

install.packages("dplyr")
...

library(dplyr)
Attaching package: ‘dplyr’

The following objects are masked from ‘package:stats’:

 filter, lag

The following objects are masked from ‘package:base’:

 intersect, setdiff, setequal, union

El mensaje que aparece al cargarse el paquete (The following objects are masked from) nos indica que el paquete cargado, en este caso dplyr, tiene funciones que poseen el mismo nombre que otro paquete cargado previamente. De este modo, cuando llamemos a una función cuyo nombre está presente en más de un paquete, se entenderá que hacemos referencia a la del último paquete cargado. Si queremos utilizar una función que quedó enmascarada debemos indicar el nombre del paquete al que pertenece de la siguiente forma:

paquete::función()

 

Funciones


Para los ejemplos en esta sección utilizaremos los datasets mtcars e iris.

head(mtcars)
                   mpg cyl disp  hp drat    wt  qsec vs am gear carb
Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

 

select

La función select es usada para seleccionar columnas de un data frame que queremos extraer. Por ejemplo, en el caso del dataset mtcars tenemos 11 columnas, pero solo vamos a trabajar con algunas de ellas. Entonces mediante select podemos indicar cuales columnas seleccionar, teniendo la posibilidad de hacerlo tanto mediante sus indices como mediante su nombres.

head( select(mtcars, 1, 3) )
                   mpg disp
Mazda RX4         21.0  160
Mazda RX4 Wag     21.0  160
Datsun 710        22.8  108
Hornet 4 Drive    21.4  258
Hornet Sportabout 18.7  360
Valiant           18.1  225

head( select(mtcars, mpg, disp) )
                   mpg disp
Mazda RX4         21.0  160
Mazda RX4 Wag     21.0  160
Datsun 710        22.8  108
Hornet 4 Drive    21.4  258
Hornet Sportabout 18.7  360
Valiant           18.1  225

En este ejemplo, hemos extraído las columnas mpg (1) y disp (3). Ambas expresiones son equivalentes. La función head simplemente la utilizamos para imprimir en pantalla solo 6 filas del dataset resultante.

Por otra parte, la función select permite utilizar diferentes funciones de ayuda (?select_helpers) para indicar las columnas a extraer:

"-": selecciona todas las columnas menos las indicadas.

head( select(mtcars, -c(mpg, disp)) )
                  cyl  hp drat    wt  qsec vs am gear carb
Mazda RX4           6 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag       6 110 3.90 2.875 17.02  0  1    4    4
Datsun 710          4  93 3.85 2.320 18.61  1  1    4    1
Hornet 4 Drive      6 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout   8 175 3.15 3.440 17.02  0  0    3    2
Valiant             6 105 2.76 3.460 20.22  1  0    3    1

 

":": selecciona un rango de columnas.

head( select(mtcars, 1:3) ) # Tambien podríamos haber usado mpg:disp
                   mpg cyl disp
Mazda RX4         21.0   6  160
Mazda RX4 Wag     21.0   6  160
Datsun 710        22.8   4  108
Hornet 4 Drive    21.4   6  258
Hornet Sportabout 18.7   8  360
Valiant           18.1   6  225

 

starts_with: selecciona todas las columnas que comiencen con el patrón indicado.

head( select(iris, starts_with("Petal")))
  Petal.Length Petal.Width
1          1.4         0.2
2          1.4         0.2
3          1.3         0.2
4          1.5         0.2
5          1.4         0.2
6          1.7         0.4

 

ends_with: selecciona todas las columnas que terminen con el patrón indicado.

head( select(iris, ends_with("Width")))
  Sepal.Width Petal.Width
1         3.5         0.2
2         3.0         0.2
3         3.2         0.2
4         3.1         0.2
5         3.6         0.2
6         3.9         0.4

 

contains: selecciona las columnas que posean el patrón indicado.

head( select(iris, contains("etal")))
  Petal.Length Petal.Width
1          1.4         0.2
2          1.4         0.2
3          1.3         0.2
4          1.5         0.2
5          1.4         0.2
6          1.7         0.4

 

matches: similar a contains, pero permite poner una expresión regular.

head( select(iris, matches(".t.")))
  Sepal.Length Sepal.Width Petal.Length Petal.Width
1          5.1         3.5          1.4         0.2
2          4.9         3.0          1.4         0.2
3          4.7         3.2          1.3         0.2
4          4.6         3.1          1.5         0.2
5          5.0         3.6          1.4         0.2
6          5.4         3.9          1.7         0.4

 

everything: completa con las columnas del data frame no pasadas como argumento. De esta forma se nos simplifican tareas de reordenar las columnas, poniendo primeras las que nos interesen destacar.

head( select(iris, Species, everything()))
  Species Sepal.Length Sepal.Width Petal.Length Petal.Width
1  setosa          5.1         3.5          1.4         0.2
2  setosa          4.9         3.0          1.4         0.2
3  setosa          4.7         3.2          1.3         0.2
4  setosa          4.6         3.1          1.5         0.2
5  setosa          5.0         3.6          1.4         0.2
6  setosa          5.4         3.9          1.7         0.4

 

one_of: selecciona las variables pasadas en un vector.

vars <- c("Petal.Length", "Petal.Width")

head( select(iris, one_of(vars)))
  Petal.Length Petal.Width
1          1.4         0.2
2          1.4         0.2
3          1.3         0.2
4          1.5         0.2
5          1.4         0.2
6          1.7         0.4

 

filter

Así como la función select es utilizada para seleccionar columnas, la función filter hace lo propio con las filas del data frame. Supongamos que queremos extraer los automóviles del dataset mtcars que poseen 4 cilindros y más de 100 hp de potencia. Entonces haríamos:

filter(mtcars, cyl == 4 & hp > 100)
   mpg cyl  disp  hp drat    wt qsec vs am gear carb
1 30.4   4  95.1 113 3.77 1.513 16.9  1  1    5    2
2 21.4   4 121.0 109 4.11 2.780 18.6  1  1    4    2

 

La expresión lógica de la función filter podemos hacer que sea tan compleja como necesitemos. Hay que recordar que las para indicar múltiples condiciones utilizamos & (and), mientras que para indicar que conserve las filas que cumplen al menos un criterio usamos | (or).

 

arrange

La función arrange es usada para reordenar las filas de una data frame según una o más columnas. Por ejemplo, si queremos reordenar mtcars de acorde a la variable mpg haríamos los siguiente:

arrange(mtcars, mpg)

   mpg cyl disp  hp drat    wt  qsec vs am gear carb
1 10.4   8  472 205 2.93 5.250 17.98  0  0    3    4
2 10.4   8  460 215 3.00 5.424 17.82  0  0    3    4
3 13.3   8  350 245 3.73 3.840 15.41  0  0    3    4
4 14.3   8  360 245 3.21 3.570 15.84  0  0    3    4
5 14.7   8  440 230 3.23 5.345 17.42  0  0    3    4
6 15.0   8  301 335 3.54 3.570 14.60  0  1    5    8

...

27 26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2
28 27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1
29 30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
30 30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2
31 32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
32 33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1

 

Vemos que se ha ordenado de menor a mayor. Si queremos ordenar al revés (primero los valores más grandes) tenemos que poner desc(mpg) como argumento. Por otro lado, si queremos emplear múltiples condiciones de ordenamiento debemos poner tales condiciones en orden dentro de los argumentos de filter. Por ejemplo, con arrange(mtcars, hp, desc(mpg)) haríamos que se ordene primero por la columna hp en orden ascendente, y luego por mpg en forma descendente.

 

rename

La función rename permite modificar el nombre de una columna, conservando el resto de columnas.

head( rename(iris, petal_length = Petal.Length) )
  Sepal.Length Sepal.Width petal_length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

 

La función select permite realizar también una modificación en los nombre de las columnas, pero en ese caso solo se conservan las columnas seleccionadas.

head( select(iris, petal_length = Petal.Length) )
  petal_length
1          1.4
2          1.4
3          1.3
4          1.5
5          1.4
6          1.7

 

mutate

La función mutate tiene la finalidad de realizar transformaciones sobre variables (columnas) en un data frame. Supongamos que en mtcars queremos convertir la variable hp (Horse Power) a cv (caballos de vapor). Hasta el momento lo que hubiésemos hecho es:

mtcars$cv <- mtcars$hp * 0.9863

Esta forma es perfectamente valida, pero si estamos usando las funciones del paquete dplyr, lo que aporta más legibilidad al código es seguir usando la misma familia de funciones. Esto lo ampliaremos más adelante cuando veamos el pipe. Entonces:

head( mutate(mtcars, cv = hp * 0.9863) )
   mpg cyl disp  hp drat    wt  qsec vs am gear carb       cv
1 21.0   6  160 110 3.90 2.620 16.46  0  1    4    4 108.4930
2 21.0   6  160 110 3.90 2.875 17.02  0  1    4    4 108.4930
3 22.8   4  108  93 3.85 2.320 18.61  1  1    4    1  91.7259
4 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1 108.4930
5 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2 172.6025
6 18.1   6  225 105 2.76 3.460 20.22  1  0    3    1 103.5615

Otros ejemplo podría ser estandarizar una variable. Para esto tomemos la variable mpg, le restamos su media y la dividimos por su desvío estándar.

head( mutate(mtcars, mpg_est = (mpg - mean(mpg)) / sd(mpg) ))
   mpg cyl disp  hp drat    wt  qsec vs am gear carb    mpg_est
1 21.0   6  160 110 3.90 2.620 16.46  0  1    4    4  0.1508848
2 21.0   6  160 110 3.90 2.875 17.02  0  1    4    4  0.1508848
3 22.8   4  108  93 3.85 2.320 18.61  1  1    4    1  0.4495434
4 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1  0.2172534
5 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2 -0.2307345
6 18.1   6  225 105 2.76 3.460 20.22  1  0    3    1 -0.3302874

 

group_by y summarize

La función group_by es usada para agrupar datos según una variable. Una vez con los datos agrupados podemos summarize para resumir estos utilizando una función de agregación. Para los que tengan conocimiento de SQL, estos conceptos son idénticos a los empleado en ese lenguaje.

Por ejemplo, en el dataset mtcars podríamos querer saber cual es el valor medio de mpg y hp para los vehículos acorde a su cantidad de cilindros. Entonces agrupamos los vehículos por la variable cyl y tomamos la media de mpg y hp.

by_cyl <- group_by(mtcars, cyl)
summarise(by_cyl, mean(mpg), mean(hp))
# A tibble: 3 × 3
    cyl `mean(mpg)` `mean(hp)`
               
1     4    26.66364   82.63636
2     6    19.74286  122.28571
3     8    15.10000  209.21429

Lo que hemos hecho es tomar todos los vehículos con 4 cilindros y calcular la media de mpg y hp para estos. Luego hemos hecho lo propio con los vehículos de 6 y 8 cilindros. Finalmente se plasma en la tabla resultante estos valores.

Algunas de las funciones de agregación que se pueden emplear son:

  • n():     cantidad de elementos
  • sum():   suma
  • mean():  promedio
  • sd():    desvío estándar.

En general se puede utilizar cualquier función que cumpla con que lo datos de entrada sean numericos y como salida se entregue una constante (vector de longitud 1).

 

pipe

El pipe (%>%) es un operador que permite encadenas funciones. Lo que hace es tomar la salida de una función y pasarla como entrada de la siguiente función. Esto ayuda enormemente a mejorar la legibilidad del código.

Por ejemplo, si queremos tomar el promedio de mpg y hp para vehículos de 6 y 8 cilindros y peso (wt) superior a 3K lbs, con lo que vimos hasta acá deberíamos hacer algo así:

mtcars_aux <- select(mtcars, mpg, hp, cyl, wt)
mtcars_aux <- filter(mtcars_aux, cyl != 4 & wt > 3.000)
mtcars_aux <- group_by(mtcars_aux, cyl)
mtcars_aux <- summarize(mtcars_aux, mean(hp), mean(mpg))
mtcars_aux

En cambio, utilizando el pipe podemos encadenar las operaciones:

mtcars %>% 
  select(mpg, hp, cyl, wt) %>% 
  filter(cyl != 4 & wt > 3.000) %>% 
  group_by(cyl) %>% 
  summarize(mean(hp), mean(mpg))

# A tibble: 2 × 3
 cyl `mean(hp)` `mean(mpg)`
 <dbl> <dbl> <dbl>
1 6 115.2500 19.125
2 8 209.2143 15.100

En este caso estamos tomando el dataset y lo pasamos a la función select. Luego de elegir las columnas con que trabajaremos enviamos el dataset resultante a la función filter, y así sucesivamente hasta obtener el resultado final.

 

 

Comentarios

Curso de R | Loop functions

Curso de R | Loop functions

rollercoaster-801833_640

 

Loop Functions


En los últimos dos post hemos visto las estructuras de control y las funciones en R. Comentamos que escribir bucles for y while es útil cuando programamos, pero no tanto cuando trabajamos en modo interactivo (consola). Para solucionar esto existen las loop functions, las cuales son funciones que permite aplicar una función a los elementos de una estructura de datos sin recurrir a bucles..A su vez, estas permiten realizar programación funcional en R y ayudan a mejorar la elegancia de nuestros códigos.

Las loop functions que veremos son:

  • lapply:  itera sobre una lista o vector y aplica una función a cada elemento de esta.
  • sapply:  hace lo mismo que lapply, pero al final simplifica el resultado de ser posible.
  • apply:  aplica una función sobre columnas o filas de un array.
  • tapply: aplica una función sobre un subconjunto de un vector.
  • mapply: aplicar múltiples conjuntos de argumentos a una función.

 

lapply


La función lapply aplica una función sobre los elementos de una lista o vector y retorna una lista con los elementos transformados:

(x1, x2, ..., xn) ⇒ f(x1), f(x1), ..., f(xn))

Veamos un ejemplo:

x <- list(a = floor(runif(5, 1, 10)), 
          b = floor(runif(5, 1, 10)), 
          c = floor(runif(5, 1, 10)))
x
$a
[1] 4 4 5 1 7
$b
[1] 2 9 7 8 8
$c
[1] 8 3 8 3 3

lapply(X = x, FUN = max)
$a
[1] 7
$b
[1] 9
$c
[1] 8

En este caso tenemos una lista compuesta por tres elementos (a, b y c) a los cuales mediante lapply le aplicamos a cada uno la función max (la cual devuelve el elemento de mayor valor de un vector). Observamos que el resultado es una nueva lista, en la que cada elemento resultante tiene el mismo nombre que el elemento original sobre el cual se aplicó la función.

Cuando utilizamos lapply, esta toma los elementos de la lista y los pasa como primer argumento de la función que aplicará. ¿Qué hacemos entonces cuando necesitamos que a la función se le indiquen otros argumentos? Fácil, pasamos los restantes argumentos de la función luego del nombre de esta. Internamente, lapply trabaja con el argumento ... que habíamos visto en el post de funciones, de modo que captura todos los argumentos extra que pasemos pasándolos luego a la función que aplica.

x <- rep(5, 5)
x
[1] 5 5 5 5 5

lapply(x, rnorm, mean = 100, sd = 5)

[[1]]
[1] 101.42165 100.77961 96.19109 95.63834 99.82672

[[2]]
[1] 100.1418 101.9446 103.6468 102.2501 104.1094

[[3]]
[1] 105.19075 95.26370 100.25450 99.84487 108.98500

[[4]]
[1] 99.50580 100.18585 95.31237 101.38840 105.75572

[[5]]
[1] 99.96794 97.41872 109.72289 84.07879 96.06522

En este caso lo que buscamos es crear varias muestras de una distribución normal. Lo primero que hacemos es crear un vector x con la cantidad de muestras que vayamos a generar y el tamaño de cada una. En este caso vamos a generar 5 grupos de muestras de tamaño 5, pero como no queremos que las muestras sea de una normal estándar (mean = 0 y sd = 1), debemos pasar a lapply los parámetros adecuados.

Ahora vemos otro caso.

x <- list(a = floor(runif(5, 1, 10)), 
          b = floor(runif(5, 1, 10)), 
          c = floor(runif(5, 1, 10)))

x
$a
[1] 1 9 2 4 5
$b
[1] 9 8 4 9 7
$c
[1] 6 9 3 6 7

lapply(x, function(z){max(z) - min(z)})
$a
[1] 8
$b
[1] 5
$c
[1] 6

Vemos que hemos utilizado una función que definimos dentro de lapply, pero que no existe fuera de él, ni no posee nombre. A estas funciones se las llama funciones anónimas. El resultado de este ejemplo es el mismo que si hubiéramos definido la función fuera de lapply.

# lapply(x, function(z){max(z) - min(z)})

f <- function(z)
{
  max(z) - min(z)
}
lapply(x, f)

Definir la función o utilizarla en forma anónima depende del contexto, ya que el resultado obtenido es el mismo. En general, si la función es simple y la usaremos únicamente en una ocasión lo más elegante es usarla en forma anónima.

 

sapply


La siguiente función es sapply, la cual trabaja de una manero muy similar a lapply, pero cambia en la forma que presenta el resultado. Mientas que lapply siempre devuelve una lista, sapply trata de simplificar el resultado:

  • Si el resultado es una lista donde cada elemento es de longitud 1, entonces esta se simplifica a un vector.
  • Si el resultado es una lista donde cada elemento es un vector de igual longitud (mayor a 1), entonces esta se simplifica a una array (o matriz)
  • Si lo anterior no se cumple, entonces se devuelve una lista.

Ejemplo:

x <- list(a = floor(runif(5, 1, 10)), 
          b = floor(runif(5, 1, 10)), 
          c = floor(runif(5, 1, 10)))
x
$a
[1] 6 9 5 6 3
$b
[1] 2 4 7 3 1
$c
[1] 5 1 9 5 9

lapply(x, function(z){max(z) - min(z)})
$a
[1] 6
$b
[1] 6
$c
[1] 8

sapply(x, function(z){max(z) - min(z)})
a b c 
6 6 8

 

split


La función split toma un vector y lo divide en grupos determinados por un factor o una lista de factores. Los argumentos principales de esta función son:

  • x: es el vector, lista o data frame a ser divido.
  • f: es el factor (o lista de factores) en los que se dividirá x.

Entonces:

x <- c(rnorm(5, mean = 0), rnorm(5, mean = 5), rnorm(5, mean = -5))
x
[1] -0.7092309 1.6024699 0.2676539 -1.4502213 -0.2673208 4.8952157 4.7401708 5.8802186 6.6116415
[10] 4.0680763 -4.6925130 -6.0959817 -6.7935937 -5.8665151 -4.6716932

f <- gl(n = 3, k = 5)
f
[1] 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3
Levels: 1 2 3

split(x, f)
$`1`
[1] -0.7092309 1.6024699 0.2676539 -1.4502213 -0.2673208

$`2`
[1] 4.895216 4.740171 5.880219 6.611641 4.068076

$`3`
[1] -4.692513 -6.095982 -6.793594 -5.866515 -4.671693

En este ejemplo primero creamos un vector x compuesto de 3 grupos de 5 muestras cada uno. Luego queremos dividir cada grupo de muestras en una lista. Para esto primero creamos f utilizando la función gl (Generate Factor Levels) que nos permite crear una lista de factores (con n indicamos la cantidad de factores, y con k la cantidad de cada uno). Finalmente la función split hace una asociación de valores del vector x al factor que le corresponde (por posición) de f. Los elementos que esten asociados a un mismo factor van a para a un mismo elemento de la lista.

La función split se la suele utilizar en combinación con lapply y sapply. La idea es tomar una estructura de datos y dividirla en subconjuntos definidos por una variable categórica, y luego a cada uno de estos aplicarle una función. Finalmente se agrupan los resultados obtenidos en una nueva estructura de datos. Esta secuencia de operaciones se la suele conocer map-reduce.

Para ejemplificar, continuemos con el caso anterior. Mediante la función split dividimos el vector en grupos, y ahora queremos calcular la media sobre cada uno de estos grupos.

lapply(X = split(x, f), FUN = mean)
$`1`
[1] -0.1113298

$`2`
[1] 5.239065

$`3`
[1] -5.624059

 

Veamos ahora un ejemplo utilizando el dataset mtcars.

head(mtcars)
                   mpg cyl disp  hp drat    wt  qsec vs am gear carb
Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

Queremos ver cuanto es el valor medio de cada variable (columna) según la cantidad de cilindros de los vehículos (cyl). Lo primero es agrupar los modelos de auto según su la cantidad de cilindros mediante split, y luego utilizar lapply para obtener los promedios para cada subconjunto.

split(mtcars, mtcars$cyl)
$`4`
                mpg cyl  disp  hp drat    wt  qsec vs am gear carb
Datsun 710     22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
Merc 240D      24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2
Merc 230       22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2
Fiat 128       32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
Honda Civic    30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
Toyota Corolla 33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1
Toyota Corona  21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1
Fiat X1-9      27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1
Porsche 914-2  26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2
Lotus Europa   30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2
Volvo 142E     21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2

$`6`
                mpg cyl  disp  hp drat    wt  qsec vs am gear carb
Mazda RX4      21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag  21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
Hornet 4 Drive 21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
Valiant        18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1
Merc 280       19.2   6 167.6 123 3.92 3.440 18.30  1  0    4    4
Merc 280C      17.8   6 167.6 123 3.92 3.440 18.90  1  0    4    4
Ferrari Dino   19.7   6 145.0 175 3.62 2.770 15.50  0  1    5    6

$`8`
                     mpg cyl  disp  hp drat    wt  qsec vs am gear carb
Hornet Sportabout   18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
Duster 360          14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4
Merc 450SE          16.4   8 275.8 180 3.07 4.070 17.40  0  0    3    3
Merc 450SL          17.3   8 275.8 180 3.07 3.730 17.60  0  0    3    3
Merc 450SLC         15.2   8 275.8 180 3.07 3.780 18.00  0  0    3    3
Cadillac Fleetwood  10.4   8 472.0 205 2.93 5.250 17.98  0  0    3    4
Lincoln Continental 10.4   8 460.0 215 3.00 5.424 17.82  0  0    3    4
Chrysler Imperial   14.7   8 440.0 230 3.23 5.345 17.42  0  0    3    4
Dodge Challenger    15.5   8 318.0 150 2.76 3.520 16.87  0  0    3    2
AMC Javelin         15.2   8 304.0 150 3.15 3.435 17.30  0  0    3    2
Camaro Z28          13.3   8 350.0 245 3.73 3.840 15.41  0  0    3    4
Pontiac Firebird    19.2   8 400.0 175 3.08 3.845 17.05  0  0    3    2
Ford Pantera L      15.8   8 351.0 264 4.22 3.170 14.50  0  1    5    4
Maserati Bora       15.0   8 301.0 335 3.54 3.570 14.60  0  1    5    8


lapply(split(mtcars, mtcars$cyl), colMeans)
$`4`
        mpg         cyl        disp          hp        drat          wt        qsec          vs          am        gear        carb 
 26.6636364   4.0000000 105.1363636  82.6363636   4.0709091   2.2857273  19.1372727   0.9090909   0.7272727   4.0909091   1.5454545 

$`6`
        mpg         cyl        disp          hp        drat          wt        qsec          vs          am        gear        carb 
 19.7428571   6.0000000 183.3142857 122.2857143   3.5857143   3.1171429  17.9771429   0.5714286   0.4285714   3.8571429   3.4285714 

$`8`
        mpg         cyl        disp          hp        drat          wt        qsec          vs          am        gear        carb 
 15.1000000   8.0000000 353.1000000 209.2142857   3.2292857   3.9992143  16.7721429   0.0000000   0.1428571   3.2857143   3.5000000 


sapply(split(mtcars, mtcars$cyl), colMeans)
               4           6           8
mpg   26.6636364  19.7428571  15.1000000
cyl    4.0000000   6.0000000   8.0000000
disp 105.1363636 183.3142857 353.1000000
hp    82.6363636 122.2857143 209.2142857
drat   4.0709091   3.5857143   3.2292857
wt     2.2857273   3.1171429   3.9992143
qsec  19.1372727  17.9771429  16.7721429
vs     0.9090909   0.5714286   0.0000000
am     0.7272727   0.4285714   0.1428571
gear   4.0909091   3.8571429   3.2857143
carb   1.5454545   3.4285714   3.5000000

 

tapply


La función tapply es usada para aplicar una función sobre un subconjunto de un vector. Esto sería como una combinación de las funciones split y lapply o sapply, pero únicamente para vectores.

Los argumentos de tapply son:

  • x: el vector.
  • INDEX: factor, o lista de factores.
  • FUN: función a ser aplicada.
  • ...: resto de argumentos
  • simplify: si simplificar el resultado.

 

Entonces:

x <- c(rnorm(5, mean = 0), rnorm(5, mean = 5), rnorm(5, mean = -5))
f <- gl(n = 3, k = 5)


tapply(X = x, INDEX = f, FUN = mean, simplify = TRUE)
 1 2 3 
-0.3474965 5.2008844 -4.7861727 

tapply(X = x, INDEX = f, FUN = mean, simplify = FALSE)
$`1`
[1] -0.3474965

$`2`
[1] 5.200884

$`3`
[1] -4.786173

tapply(X = x, INDEX = f, FUN = range, simplify = TRUE)
$`1`
[1] -1.6484844 0.6662139

$`2`
[1] 3.438505 6.561279

$`3`
[1] -5.983519 -3.851376

En caso de que el resultado no pueda ser simplificado, aunque le pongamos simplify = TRUE, el resultado será una lista.

 

apply


La función apply es usada para evaluar una función sobre las filas o columnas de una matriz. Realmente puede ser utilizada para realizar operaciones sobre arrays, pero lo usual es utilizarla para trabajar con matrices.

Los argumentos de la función son:

  • x: array o matriz.
  • MARGIN: vector que indica con que margenes del array nos quedamos (filas o columnas en cada de una matriz).
  • FUN: función a ser aplicada.
  • ...: resto de argumentos.

Entonces:

x <- matrix(rnorm(10), 2, 5)
x
          [,1]       [,2]       [,3]        [,4]      [,5]
[1,] -1.105622 -0.1478308 -0.7974648 -0.91208208 0.1560445
[2,]  0.235895 -0.6406140  1.7697260 -0.02703201 0.3445469
 
apply(x, 1, mean) % sobre las filas
[1] -0.5613911 0.3365044

apply(x, 2, mean) % sobre las columnas
[1] -0.4348637 -0.3942224 0.4861306 -0.4695570 0.2502957

 

Una forma más compacta y elegante de realizar sumas o promedios sobre columnas o filas de una matriz es mediante las funciones:

  • rowSums  = apply(x, 1, sum)
  • rowMeans = apply(x, 1, mean)
  • colSums  = apply(x, 2, sum)
  • colMeans = apply(x, 2, mean)

 

Sin embargo hay operaciones más complicadas para las cuales no hay funciones predefinidas, como calcular el desvío estándar, la mediana o los cuartiles. Para estos casos es que debemos usar apply.

 

mapply


La función mapply es una versión de apply, la cual permite realizar múltiples llamadas una función (en paralelo) sobre un conjunto de argumentos.

Los argumentos de la función son:

  • FUN: función a ser aplicada.
  • ...: resto de argumentos.
  • MoreArgs: lista de otros argumento para pasar a la función FUN.
  • simplify: si simplificar el resultado.

 

Hay que notas que para esta función invertimos el orden que veníamos utilizando en los argumentos. En este caso, lo primero es la función que vamos a llamar, y luego los parámetros con que realizaremos las llamadas.

mapply(FUN = rnorm, 3, c(1,10,100), 1, SIMPLIFY = F)
[[1]]
[1] 0.4558360 1.4742719 0.9190874
[[2]]
[1] 10.776188 9.920658 12.546668
[[3]]
[1] 100.05986 99.71401 100.81646

En este caso estamos pasando a la función rnorm los argumentos (3, 1, 1), (3, 10, 1) y (3, 100, 1) en paralelo. Como indicamos que al final no se simplifique el resultado obtenemos una lista, donde cada elemento de la lista es el resultado de la llamada a la función con cada grupo de paramentos.

 

Vectorización de funciones


La función mapply puede ser usada para realizar llamadas a una función en paralelo. A esto se lo conoce como vectorizar una función, lo cual significa que estamos creando una nueva función que en vez de tomar un solo grupo de parámetros, puede tomar un conjunto de estos. Mediante la función Vectorize podemos realizar lo mismo de una manera más clara.

Por ejemplo:

vrnorm <- Vectorize(FUN = rnorm, 
                    vectorize.args = c('n', 'mean', 'sd'),
                    SIMPLIFY = F)

vrnorm(5, c(1, 10, 100), 0.1)
[[1]]
[1] 0.9788635 0.9264093 0.7952612 0.9666706 1.1013841

[[2]]
[1] 9.979132 10.171233 9.890230 9.872685 10.031089

[[3]]
[1] 99.69378 100.06064 99.97483 100.12716 100.08537

En este caso estamos creando la función vrnorm, la cual es una versión vectorizada de rnorm a la que le podemos pasar los parámetros que indicamos en vectorize.args . De esta forma, hemos creado una función que nos permite de manera compacta crear grupos de muestras aleatorias a partir de una distribución normal con distintos parámetros.

 

 

 

 

 

Comentarios
Curso de R | Funciones

Curso de R | Funciones

Escribir funciones es una de las actividades más importantes en cualquier lenguaje de programación. Su finalidad es poder encapsular fragmentos de código que necesiten ser ejecutados en múltiples ocasiones, con la posibilidad de ejecutarlos en cada ocasión con diferentes parámetro. A su vez, las funciones aumentan la legibilidad de un programa, de modo que es más fácil de entender para uno mismo y para los demás en caso de compartirlo.

 

r_funciones

 

Funciones


Introducción

Las funciones en R son tratadas como cualquier otro objeto. Para crearlas utilizamos el comando function(), el cual crear objetos de tipo function, de la siguiente manera:

f <- function(<argumentos>) 
{
  ## Código de la función (function body)
}

 

Luego, para llamar a la función simplemente escribimos el nombre de esta:

f <- function() 
{
  cat("Hola Mundo")
}

f()
Hola Mundo

class(f) 
"function"

 

Las funciones poseen 3 partes:

  • El cuerpo (body)
  • Los argumentos (formals)
  • El ambiente (environment)

 

f <- function(x, y) { x + y }

body(f)
{
 x + y
}

formals(f)
$x
$y


environment(f)
<environment: R_GlobalEnv>

 

Argumentos

Los argumentos de una función son una serie de valores (opcionales) que se pasan a la función, de modo que ciertas variables dentro de estas posean diferentes valores en cada llamada a la función.

area_rectangulo <- function(lado1, lado2) 
{
  area <- lado1 * lado2
  print(paste("el área es ", area))
}

area_rectangulo(2, 3)
"el área es  6"

formals(area_rectangulo)
$lado1
$lado2

 

Cuando se hace un llamado a una función, el orden en que se pasan los valores de los argumentos corresponde con la ubicación de estos en la delación de la función. Por ejemplo, en el caso anterior, el valor 2 se pasa a la variable lado1, mientras que el valor 3 se pasa a la variable lado2. Si queremos indicar explícitamente que valor asignar a cada argumento debemos indicar el nombre de este al llamar a la función.

area_rectangulo <- function(lado1, lado2) 
{
  area <- lado1 * lado2
  print(paste("el área es ", area))
}
area_rectangulo(lado1 = 2, lado2 = 3)
"el área es  6"

 

Este ultimo método es el más recomendable cuando las funciones tienen un gran numero de argumentos. Algo a notar cuando asignamos valores a los argumentos es que se suele utiliza el operador =, en vez del <-. Esto es simplemente una cuestión de estilo, ya que de otro modo seguiría funcionando correctamente la código.

Ahora supongamos que hacemos una llamada a una función que tenga una serie de argumentos, pero uno de estos no lo pasamos. Lo que sucederá es que la función se ejecutará hasta el punto en que debe ser utilizado el argumento faltante, y es en este momento que se genera un error. Este modo de funcionamiento se debe a que R evalúa las instrucciones al momento de ejecutarse (Lazy Evaluation), y no antes. En caso de que la función nunca utilice el argumento faltante, esta terminará su ejecución sin generar error.

f <- function(x, y, z)
{
 print(x+y)
 print(z)
}

f(x = 2, y = 0)
2
Error in print(z) : argument "z" is missing, with no default

 

Para evitar este tipo de comportamientos podemos asignar valores por defecto (default value) a las variables en la declaración de las funciones. Por ejemplo:

f <- function(x = NULL, y = NULL)
{
  if (!is.null(x) & !is.null(y)){
    print(x+y)
  }else{
    print('faltan valores')
  }
}

f(x = 2, y = 0)
2
f(x = 2)
"faltan valores"
f(y = 0)
"faltan valores"

 

Para ejemplificar esto en un caso real veamos la función rnorm. Esta genera números aleatorios a partir de una distribución normal.

str(rnorm)
function (n, mean = 0, sd = 1)

 

Como vemos, tiene 3 argumento: la cantidad de muestras a generar (n), la media de la distribución (mean) y el desvió estándar (sd). Si indicamos solo un número, este representa la cantidad de muestras a generar, dejando por defecto los otros dos argumentos con valores por defecto (lo que haría que la función genere numero a partir de una distribución normal estándar)

rnorm(3)
-0.2262557 0.6465698 2.3460583
rnorm(3, 1)
-0.8456187 1.4596911 1.1237811
rnorm(3, 1, 10)
2.331487 -19.407747 -11.676089

 

Cuando un argumento es pasado por nombre, este es quitado de la lista de orden de asignación de valores al llamar a una función. De este modo se puede hacer una combinación de asignación de argumentos por posición y por nombre.

rnorm(5, sd = 10, 100) # equivalente a: n = 5, mean = 100, sd = 10
106.67484 91.98517 109.96486 113.93423 97.70806

 

Para indicar los nombre de los argumentos al llamar a una función, R permite que haya coincidencia parcial en estos. De este modo podemos llamar a una funciona e indicar solo una parte del nombre de cada argumento.

rnorm(5, s = 10, m = 100)
101.70763 91.48214 103.48912 107.92323 98.50723 

 

El orden en que se hace la comprobación de argumentos es:

  1. Coincidencia exacta del nombre del argumento
  2. Coincidencia parcial del nombre del argumento
  3. Asignación por posición

En general no es buena practica la utilización de coincidencias parciales, por lo que debemos tratar de evitar su uso.

 

El argumento …

Hay un argumento que tiene un uso especial en R, denominando … (tres puntos). Este tiene la capacidad de capturar todos los valores pasados a la función que no coinciden con ningún argumento. De este modo, podemos pasar a una función una cantidad no prefijada de valores.

sumar_pares <- function(...)
{
  valores <- c(...)
  if(!is.numeric(valores)) return('NaN')
 
  contador <- 0
  for(n in valores){
    if(n%%2 == 0){
      contador <- contador + n
    }
  }
contador
}

sumar_pares(1:10)
30

Como vemos, para trabajar con los valores capturado por … podemos convertirlos a un vector con c(...) o una lista con list(...).

 

Retorno de valores

Las funciones anteriores solamente realizan una seria de pasos y finalizan sin devolver ningún valor. En muchas ocasiones deseamos que las funciones al finalizar su ejecución devuelvan algún valor. Para esto tenemos dos posibilidades.

La primea es hacer que la ultima linea de código evaluada dentro de una función sea el valor que queremos que sea devuelto.

## Función que cuenta la cantidad de vocales en una cadena que
## se pasa como argumento 
contar_vocales <- function(frase) 
{
  cantidad_vocales <- 0 
  frase <- tolower(frase)
  frase <- strsplit(frase, "")[[1]]
 
  for (i in frase)
  {
    if (i %in% c("a", "e", "i", "o", "u"))
    {
      cantidad_vocales <- cantidad_vocales + 1
    } 
  }
  cantidad_vocales 
}

resultado <- contar_vocales("Hola mundo, nuevamente")
resultado
9

 

La segunda alternativa es indicarlo explícitamente mediante el comando return(). En este ultimo caso, cuando se ejecuta esta instrucción dentro de una función, esta finaliza inmediatamente devolviendo el valor indicado. Lo común es reservar esta alternativa para devolver «señales» en caso de que la función tenga inconvenientes. Por ejemplo, en caso de que un argumento no sea consistente con lo esperado podemos devolver algún valor que nos indique de esta situación.

f <- function(<argumentos>) 
{
  if (<alguna_condición>) return(<señal>)
  # Código de la función 
  ...
  x # objeto que devuelve la función 
}

# Ejemplo
calcular_raiz2 <- function(n)
{
  # Verifico que el que número pasado no sea negativo
  if (n < 0) return("Numero negativo")
 
  # En caso de que los argumentos sean consistentes,
  # continuo con la ejecución de la función.
  sqrt(n)
}

calcular_raiz2(2)
1.414214

calcular_raiz2(-2)
"Numero negativo"

 

Reglas de alcance (Scoping Rules)


El ultimo componente de una función es su ambiente (environment). Un ambiente es una colección de pares (símbolo, valor). Por ejemplo, si hacemos x <- 3, esta sentencia crea el par (x, 3) en el ambiente donde se ejecutó la instrucción. Si luego queremos recuperar el valor de la variable x, R busca el valor en el ambiente y encuentra que vale 3. Cada ambiente tiene un ambiente padre, y es posible para un ambiente tener múltiples ambientes hijos. El único ambiente que no tiene padre (raíz) es el empty environment.

De esta forma, el ambiente de una función controla como se encuentra el valor asociado con una variable dentro de ella. Por ejemplo,

f <- function(x) { x + z }

En la mayoría de los lenguajes de programación, esta declaración conduciría a un error debido a que la variable z no está definida dentro de la función. Pero en R esto es valido debido a que utiliza lo que se conoce como lexical scoping para encontrar el valor asociado. Como z no está definida dentro de la función (a estas variables se las conoce como free variables), R irá a buscar su valor en el ambiente donde la función fue definida.

f(10)
Error in f(10) : object 'z' not found

z <- 1
f(10)
11

Básicamente, lo que hace R al necesitar evaluar el valor de z es ir a buscarlo dentro la función f. Como no lo encuentra definido, pasa a buscar el valor de la variable en el ambiente padre. En el primer caso, z sigue sin estar definido, mientras que en el segundo caso sí lo está.

Las reglas de alcance de un lenguaje determinan justamente como un valor es asociado con una variable. Veamos unos ejemplos:

z <- 1

f <- function(x) 
{
  z <- 10
  2 * z + g(x)
}

g <- function(x) 
{
  x * z
}

¿Qué valor devolverá f(5)? Veamos.

Cuando llamamos a la función f lo primero que se evalúa es z <- 10. Esta acción crea un par (z, 10) en el ambiente de la función f. A continuacion se evalúa 2*z + g(x). En este paso, R busca el valor de la variable z en el ambiente de la función f, y encuentra el par (z, 10). Luego busca a la función g, pero no la encuentra definida dentro de la función f, por lo que pasa a buscarla en el ambiente padre y encuentra:

g <- function(x) 
{
  x * z
}

Ahora evalúa g(5), y se encuentra con x * z. Acá vemos claramente que x vale 3, pero ¿qué valor toma la variable z? Como dijimos, R utiliza lexical scoping. Estos significa que se va a buscar el valor de la variable al ambiente donde esta fue definida (y no donde fue llamada). Por lo tanto, el valor de z será 1, y por lo tanto:

f(5)
25

Ahora vemos que sucede si la función g la hubiésemos definido dentro de la función f:

f <- function(x) 
{
  g <- function(x) 
  {
    x * z
  }
  z <- 10
  2 * z + g(x)
}

Lo único que cambia al momento de evaluar f(5) es al momento de que R va a busca el valor de z cuando evalúa g(5). Como ahora g está definida dentro de f, R va a buscar el valor de z en su nuevo ambiente padre, que es justamente el ambiente de la función f, y encuentra que ahora el valor de z es 10, y por lo tanto:

f(5)
70

 

Este comportamiento permite realizar operaciones, sin las cuales muchos paquetes no podrían existir. No obstante, hay que ser cuidadoso al trabajar. Un típico problema aparece cuando creamos una función, la cual no devuelve el valor incorrecto. Luego de debuggear el código nos damos cuenta de que en la función está participando una variable que, sin darnos cuenta, no está definida en la función y toma su valor de otro ambiente. El peor caso surge cuando no nos damos cuenta de que el valor que obtenemos es incorrecto, y dado que no aparec ningún error (porque la función toma su valor de otro ambiente) podemos continuar trabajando y llegar a resultado erróneos más adelante.

 

Comentarios

Curso de R | Estructuras de control

Curso de R | Estructuras de control

Las estructuras de control nos permiten controlar el flujo de ejecución de una secuencia de comandos. De este modo, podemos poner «lógica» en el código de R y lograr así reutilizar fragmentos de código una y otra vez.

Las estructuras de control más utilizadas son:

if / else: permite decidir si ejecutar o no un fragmento de código en función de una condición.
for: ejecuta un bucle una cantidad fija de veces.
while: ejecuta un bucle mientras sea verdadera una condición.
repeat: ejecuta un bucle indefinidamente (la única forma de detener esta estructura es mediante el comando break).
break: detiene la ejecución de un bucle.
next: salta a la siguiente ejecución de un bucle.

La mayoría de estas no son usadas en sesiones interactivas (cuando escribimos código directo en la consola), sino cuando escribimos funciones o expresiones largas. En el próximo post veremos como trabajar con funcionar en R, pero de momento es necesario tomar conocimiento de las estructuras de control, ya que haremos amplio uso de ellas en el futuro.

 

r_estructuras_control

 

IF-ELSE


La combinación IF-ELSE es la más utilizada en R. Esta estructura de control permite actuar en función de una condición. La sintaxis es:

if(<condicion>) {
  ## bloque de código
}

## Continue with rest of code
if(<condicion>) {
  ## bloque de código
} else {
  ## otro bloque de código
}

if(<condition1>) {
  ## bloque de código
} else if(<condicion2>) {
  ## otro bloque de código
} else {
  ## otro bloque de código
}

Ejemplo:

x <- runif(1, 0, 10) ## creamos un nro aleatorio entre 0 y 10
if(x > 5) {
  y <- TRUE
} else {
  y <- FALSE
}

De este modo estamos asignando un valor a una variable en función del valor de otra. Lo que se debe tener en cuenta es que la condición debe retornar un valor TRUE o FALSE.

Otra opción para usar la estructura IF-ELSE en forma más comprimida es mediante al función ifelse:

# ifelse(condicion, TRUE, FALSE)

ifelse( runif(1) > 0.5, "A", "B")

 

SWITCH


El comando SWITCH permite ejecutar un bloque de código distinto en función del valor de una variable.

switch(<EXPR>,
       <valor_1> {# código},
       ...
       <valor_n> {# código})

Ejemplo:

x <- 1:10
type <- 'mean'
switch(type,
         mean = mean(x),
         median = median(x),
         sd = sd(x))
[1] 5.5

 

FOR


Los bucles FOR son el tipo de bucle más utilizado en R. Estos toman una variable a la que se le asignan los elementos de un objeto (en general, vectores o listas) en forma sucesiva a medida que se van recorriendo los ciclos.

for(<variable> in <objeto iterable>) {
  # código
  ...
}

Ejemplo:

for(i in 1:5) {
  print(i)
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5

Tanto con la sentencia IF como con el bucle FOR, en caso de que solo se ejecute una única sentencia, no es estrictamente necesario el uso de las llaves, no obstante es una buena practica utilizarlas incluso en estos casos.

# for(<variable> in <objeto iterable>) <código>
for(i in 1:5) print(i)
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5

Si queremos recorrer los elementos de un objeto podemos realizarlo directamente, o por su índice ayudándonos de la función seq_along(x), la cual genera una secuencia numérica que usaremos para indica los indices de los elementos de los objetos que queremos recorrer:

# Creamos un vector sobre el cual vamos a iterar
x <- c("a", "b", "c", "d")
seq_along(x)
[1] 1 2 3 4

# Podemos iterar accediendo a los elementos por su índice
for(i in seq_along(x)) {
print(x[i])
}
[1] "a"
[1] "b"
[1] "c"
[1] "d"

# o podemos acceder a estos directamente
for(letra in x) {
  print(letra)
}
[1] "a"
[1] "b"
[1] "c"
[1] "d"

En caso en que se quisiera recorrer una estructuras de más de una dimensión, como puede ser una matriz, simplemente anidamos bucles FOR:

x <- matrix(1:6, 2, 3)
x
     [,1] [,2] [,3]
[1,]   1    3    5
[2,]   2    4    6

for(i in seq_len(nrow(x))) {
  for(j in seq_len(ncol(x))) {
    print(x[i, j])
  }
}
[1] 1
[1] 3
[1] 5
[1] 2
[1] 4
[1] 6

 

WHILE


Los bucles WHILE comienzan comprobando una condición. Si esta es verdadera, entonces se entra al cuerpo del bucle. Una vez completada una ejecución de este bloque, se comprueba la condición nuevamente y así sucesivamente hasta que la comprobación de la condición de falso.

while(<condicion>) {
  # código
  ...
}

Ejemplo:

count <- 0
while(count < 10) {
  print(count)
  count <- count + 1
}

[1] 0
...
[1] 9

 

REPEAT


La estructura REPEAT ejecuta un bucle infinitamente. En general no es utilizada para realizar análisis, sino cuando se realiza programación. La única forma de terminar con el bucle es llamando dentro de este a la función break.

repeat(<condicion>) {
  # código
  ...
}

Ejemplo:

count <- 0
repeat {
  print(count)
  count <- count + 1
  if(count == 10) break
}
[1] 0
...
[1] 9

 

NEXT / BREAK


La función next es utilizada para terminar un ciclo del bucle en ejecución y pasar al siguiente. Por ejemplo, si estamos dentro de un bucle FOR lo que sucedería al momento de ejecutar la función next es que se se salta directo al siguiente elemento sobre sobre el que se está iterando.

Ejemplos:

# Imprimimos por pantalla los numero pares entre 1 y 10
for(i in 1:10) {
 if(i %% 2 == 0) next 
 print(i)
}

Por el contrario, la función break es usada para detener un bucle y salir de él inmediatamente.

# Recorremos los elementos de un vector hasta encontrar una condición y salir de él
for(i in mtcars$mpg) {
  print(i)
  if(i < 15) break
}

[1] 21
[1] 21
[1] 22.8
[1] 21.4
[1] 18.7
[1] 18.1
[1] 14.3

 

Comentarios