Funciones en R


  • Todo lo que sucede es una llamada de función


  • Una función es una subrutina que tiene como objetivo realizar una tarea específica


  • La mayoría de las manipulaciones y cálculos de objetos en R se realizan a través de una función


  • Permite a los usuarios juntar bloques de código que se usan con frecuencia y, por lo tanto, resulta conveniente encapsular en un objeto que se puede llamar fácilmente cuando sea necesario


  • Las funciones cargadas desde los paquetes (incluido R básico) también se pueden modificar y sobrescribir


 


Estructura básica

Todas las funciones son creadas iguales … mediante la función function() y siguen la misma estructura:

R

* Modificado de Grolemund 2014

 

Podemos observar la estructura de funciones ya cargadas en nuestro entorno de R. Para ver el código simplemente corra al nombre de la función en R (sin el paréntesis). Por ejemplo, el código de la función sd() se puede mostrar de la siguiente manera:

sd
## function (x, na.rm = FALSE) 
## sqrt(var(if (is.vector(x) || is.factor(x)) x else as.double(x), 
##     na.rm = na.rm))
## <bytecode: 0x5616965e2a70>
## <environment: namespace:stats>

 

En Rstudio, el código también se puede mostrar usando F2 cuando el cursor está en el nombre de la función.

 

Además, podemos diseccionar las funciones en sus elementos básicos:

# cuerpo 
body(sd)
## sqrt(var(if (is.vector(x) || is.factor(x)) x else as.double(x), 
##     na.rm = na.rm))
# argumentos
formals(sd)
## $x
## 
## 
## $na.rm
## [1] FALSE
# ambiente
environment(sd)
## <environment: namespace:stats>

 

Algunas funciones R utilizan funciones primitivas (principalmente escritas en C). En estos casos, el código no se muestra:

sum
## function (..., na.rm = FALSE)  .Primitive("sum")
body(sum)
## NULL
formals(sum)
## NULL
environment(sum)
## NULL

 

Las funciones son en sí mismas un tipo específico de objeto:

class(sd)
## [1] "function"

 

Los operadores son funciones:

1 + 1
## [1] 2
'+'(1, 1)
## [1] 2
2 * 3
## [1] 6
'*'(2, 3)
## [1] 6

 


Nombre

R  

Los nombres de las funciones tienen pocas restricciones. Siguen las mismas reglas que otros objetos en R. Algunas recomendaciones/reglas:

  • No utilice nombres de objetos comunes R (por ejemplo, iris, x) u objetos que ya están en el entorno

  • No use nombres de funciones de uso frecuente (por ejemplo, mean)

  • No puede comenzar con un número

  • Debería sugerir lo que hace

  • No debe ser muy largo

En caso de que tenga varias funciones con el mismo nombre, puede llamarlas individualmente utilizando el nombre del paquete (o namespace) seguido de :::

# crear funcion sd
sd <- function(x) x^10

# no calcula desv. st.
sd(1:5)
## [1]       1    1024   59049 1048576 9765625
# sd
stats::sd(1:5)
## [1] 1.5811
# remover nueva sd
rm(sd)

# usar de nuevo
sd(1:5)
## [1] 1.5811

 

La sintaxis namespace::function también se puede usar para llamar a funciones desde paquetes que se han instalado pero que no están cargados en el entorno actual.

 

Las funciones pueden ser anónimas:

(function(x) x^10)(1:5)
## [1]       1    1024   59049 1048576 9765625

 

Esto es más útil cuando se usan las funciones Xapply:

l <- list(1:5, 1:4, 1:3)

lapply(l,function(x) x^10)
## [[1]]
## [1]       1    1024   59049 1048576 9765625
## 
## [[2]]
## [1]       1    1024   59049 1048576
## 
## [[3]]
## [1]     1  1024 59049

Argumentos

R  

Permiten a los usuarios ingresar objetos en la función. Los argumentos pueden o no tener valores predeterminados. Cuando los argumentos tienen valores predeterminados, no es necesario proporcionarlos:

f1 <- function(x, y = 2) x + y

f1(1)
## [1] 3

 

Por supuesto, se pueden modificar:

f1(3, 4)

 

Los argumentos sin valor predeterminado deben ser proporcionados:

f1()
## Error in f1(): argumento "x" ausente, sem padrão

 

Si todos los argumentos tienen un valor predeterminado, se puede invocar la función sin proporcionar ningún argumento:

f1 <- function(x = -2, y = 2) x + y

f1()
## [1] 0

 

Ese es el caso de dev.off() y Sys.time():

Sys.time()
## [1] "2021-05-06 17:01:40.525 CST"

 

Los argumentos pueden especificarse implícitamente por posición o por nombres incompletos:

f2 <- function(a1, b1, b2) {
  list(a1 = a1, b1 = b1, b2 = b2)
}

# por posicion
str(f2(1, 2, 3))
## List of 3
##  $ a1: num 1
##  $ b1: num 2
##  $ b2: num 3
# por posicion y nombre
str(f2(a = 1, 2, 3))
## List of 3
##  $ a1: num 1
##  $ b1: num 2
##  $ b2: num 3
str(f2(1, a= 2, 3))
## List of 3
##  $ a1: num 2
##  $ b1: num 1
##  $ b2: num 3
str(f2(1,  2, a= 3))
## List of 3
##  $ a1: num 3
##  $ b1: num 1
##  $ b2: num 2
# por posicion y nombre parcial
str(f2(1, a = 2, b1 =3))
## List of 3
##  $ a1: num 2
##  $ b1: num 3
##  $ b2: num 1

 

Sin embargo, esto no funciona si los nombres son ambiguos:

f2(b= 1,  2, a = 3)
## Error in f2(b = 1, 2, a = 3): argumento 1 corresponde a múltiplos argumentos formais

 

Es más seguro (y, por lo tanto, una mejor práctica) usar los nombres completos de los argumentos.

 

Las funciones también pueden tomar argumentos lógicos. Estos son útiles para modificar el comportamiento de la función para que coincida con diferentes escenarios. Por ejemplo, mean() permite a los usuarios ignorar los NA:

v1 <- c(1, 2, 3, NA)

# sin ignorar NAs
mean(v1, na.rm = FALSE)
## [1] NA
# ignorando NAs
mean(v1, na.rm = TRUE)
## [1] 2

 

Los argumentos lógicos utilizan constructo de control if(). Este permite modificar el comportamiento interno de la función de acuerdo a un argumento lógico. Por ejemplo, la siguiente función repite un valor numérico un numero de veces pero permite ignorar o no los “NAs”:

# crear funcion
rep2 <- function(x, n, na.rm = FALSE) {
  
  if (na.rm) x <- x[!is.na(x)]
  
  rep(x, n)
}

# usarla sin NA
rep2(x = c(2, 4), n = 3)
## [1] 2 4 2 4 2 4
# con NA
rep2(x = c(2, 4, NA), n = 3)
## [1]  2  4 NA  2  4 NA  2  4 NA
# con NA ignorandolos
rep2(x = c(2, 4, NA), n = 3, na.rm = TRUE)
## [1] 2 4 2 4 2 4

 

Ejercicio 1


1.1 ¿Qué hace la función cor.test()?


1.2 Úsela con “Sepal.Length” y “Sepal.Width” de los datos de ejemplo iris (use data(iris))


1.3 ¿Qué argumentos deben proporcionarse?


1.4 ¿Qué hace el argumento alternative? Use diferentes valores para este argumento y compare los resultados


1.5 ¿Cómo se puede calcular la correlación de Spearman?


1.6 ¿Qué tipo de objeto devuelve esta función?


1.7 ¿Cómo puede obtener el valor de p directamente de el resultado de la función (sin guardar el resultado como un objeto)?


Cuerpo

R  

El cuerpo de una función puede contener:

  • Comprobación de argumentos

  • Manipulación de datos

  • Definición de resultados

 

El cuerpo de la función puede tomar el mismo tipo de código utilizado en cualquier rutina de R. Sin embargo, los objetos creados no estarán disponibles en el entorno.

Cuando se realizan varios cálculos, debemos incluir una declaración de retorno (return statement), que define explícitamente la salida de la función. Esto se hace usando la función return():

# sin "return statement"  
f1 <- function(x, y) {
  
  z1 <- 2*x + y
  z2 <- x + 2*y
  z3 <- 2*x + 2*y
  z4 <- x/y
  }

f1(5, 3)

f1_out <- f1(5, 3)

f1_out
## [1] 1.6667
# con "return statement"  
f2 <- function(x, y) {
  
  z1 <- 2*x + y
  z2 <- x + 2*y
  z3 <- 2*x + 2*y
  z4 <- x/y
  
  return(c(z1, z2, z3, z4))
  
}

f2(5, 3)
## [1] 13.0000 11.0000 16.0000  1.6667
f2_out <- f2(5, 3)

f2_out
## [1] 13.0000 11.0000 16.0000  1.6667

 

Por lo tanto, cuando no se proporciona una declaración de retorno, la función devolverá el último objeto que se creó en el cuerpo de la función:

# sin "return statement"  
f3 <- function(x, y) {
  
  z1 <- 2*x + y
  z2 <- x + 2*y
  z3 <- 2*x + 2*y
  z4 <- x/y
  
  c(z1, z2, z3, z4)
  
}

f3(5, 3)
## [1] 13.0000 11.0000 16.0000  1.6667
f3_out <- f3(5, 3)

f3_out
## [1] 13.0000 11.0000 16.0000  1.6667

 

Es más seguro usar return().

 

Cuando una función realiza varias tareas, podemos usar una lista para juntar los diferentes objetos. Esto es particularmente útil cuando se generan objetos de diferentes clases (por ejemplo, vectores y listas):

f4 <- function(x, y) {
  
  # 1 elemento 
  z1 <- x + y
  
  # 2 elementos
  z2 <- c(x, y/3)
  
  # vector logico 
  z3 <- z2 < 10
  
  
  l <- list(z1, z2, z3)
  
  return(l)
}

f4(10, 5)
## [[1]]
## [1] 15
## 
## [[2]]
## [1] 10.0000  1.6667
## 
## [[3]]
## [1] FALSE  TRUE

 

Podemos acceder a elementos específicos de la salida de una función mediante indexación:

# elemento 1
f4(10, 5)[[1]]
## [1] 15
# elemento 2
f4(10, 5)[[2]]
## [1] 10.0000  1.6667
# elemento 3
f4(10, 5)[[3]]
## [1] FALSE  TRUE

 

Por supuesto, también podemos guardar el resultado como un objeto y acceder a cada elemento mediante la indexación:

# elemento 1
out <- f4(10, 5)

# elemento 1
out[[1]]
## [1] 15
# elemento 2
out[[2]]
## [1] 10.0000  1.6667

 

Ejercicio 2


2.1 Cree una función que tome 3 argumentos numéricos, multiplíquelos y luego calcule el logaritmo natural del resultado (función log ())


2.2 Agregue valores predeterminados a cada argumento


2.3 Ejecute la función con uno de los argumentos con un número negativo. ¿Qué pasa? ¿Por qué?


2.4 Agregue un argumento lógico que permita a los usuarios (si lo desean) convertir los argumentos de entrada a su valor absoluto (usando la función abs()). Agregue las modificaciones necesarias para que la función haga los cálculos con y sin valores absolutos.

Ventajas de usar funciones

 

Código mas limpio

Los objetos creados dentro del cuerpo no están disponibles en el entorno actual:

# primero remover todo los objetos
rm(list = ls())

f5 <- function(x) {
    sqr <- x * x
    lg_sqr <- log(x)
        return(lg_sqr)
}

f5(7)
## [1] 1.9459
exists("sqr")
## [1] FALSE
exists("lg_sqr")
## [1] FALSE

 

x <- 7
sqr <- x * x
lg_sqr <- log(x)
      

exists("sqr")
## [1] TRUE
exists("lg_sqr")
## [1] TRUE

 

Facil de correr y compartir

Se pueden invocar funciones desde archivos de R externos sin estar definidas en el código actual con la función source(). En este ejemplo creamos la función fnctn_X:

fnctn_X <- function(sq_mt, vctr) {

    # trasponer matriz y calcular est dev
    stp1 <- t(sq_mt)
    stp2 <- vctr * vctr

    # guardar en lista
    rslt <- list(stp1, stp2)
    return(rslt)
}

fnctn_X(sq_mt = cbind(c(1, 2), c(3, 4)), vctr = c(2, 3))
## [[1]]
##      [,1] [,2]
## [1,]    1    2
## [2,]    3    4
## 
## [[2]]
## [1] 4 9

 

… y guárdelo como un archivo R (usandosink()):

sink(file = "function_X.R")
cat("fnctn_X <-")
fnctn_X
sink()

# remover objetos
rm(list = ls())

ls()
## [1] "f5"      "fnctn_X" "lg_sqr"  "sqr"     "x"

 

Ahora la función se carga usando source():

# cargar funcion
source("function_X.R")

# run it
fnctn_X(sq_mt = cbind(c(1, 2), c(3, 4)), vctr = c(2, 3))
## [[1]]
##      [,1] [,2]
## [1,]    1    2
## [2,]    3    4
## 
## [[2]]
## [1] 4 9

 

Además, este código se puede compartir fácilmente. Se puede enviar por correo electrónico o publicar en línea. Incluso se puede cargar desde repositorios en línea:

# remover objetos
rm(list = ls())

# revisar si existe en ambiente actual
exists("fnctn_X")
## [1] FALSE
# cargar desde el internet
source("https://marceloarayasalas.weebly.com/uploads/2/5/5/2/25524573/function_x.r")

# revisar si existe en ambiente actual
exists("fnctn_X")
## [1] TRUE

Se aplica fácilmente a nuevos objetos

Esto ya deberia ser obvio a este punto.

Otros consejos

  • Construir funciones cortas:
    • Fácil de leer
    • Fácil de arreglar y actualizar
    • Si es demasiado largo, probablemente debería dividirse en varias funciones
    • Genera modularidad
  • Añadir comentarios a todo el código  
  • Agregar descripciones a cada uno de los argumentos que toma  
  • Función de prueba con diferentes valores/escenarios

 

Ejercicio 3


3.1 Cree una funcion que calcule el promedio, la desviación estandar y el error estandar de un vector numérico. Estos valores deben ser devueltos como una lista con nombres.


3.2 Permita a los usuarios ignorar los “NAs” (similar al argumento “na.rm” en mean(), pista: añada un argumento lógico)


3.3 Haga que la función además produzca un histograma del vector númerico proporcionado por el usuario


3.4 Haga que la función escoja un color al azar para las barras del histograma cada vez que se corre (pista: sample(vector.de.colores))


3.5 Añada un argumento a la función que permita el usuario calcular la sumatoria (sum()) junto con el resto de las descriptores estadísticos


3.6 Agregue el promedio y la desviación estandar al título del histograma (pista: use paste())


3.7 Modifique la función para que tambien cree una linea vertical indicando el promedio del vector numérico proporcionado por el usuario


3.8 Modifique la función para que añada un polígono transparente sobre el area que cubre el promedio +/- la desviación estandar

 


Referencias

  • Wickham, Hadley, and Garrett Grolemund. 2016. R for data science: import, tidy, transform, visualize, and model data. website

Información de la sesión

## R version 4.0.5 (2021-03-31)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 20.04.2 LTS
## 
## Matrix products: default
## BLAS:   /usr/lib/x86_64-linux-gnu/atlas/libblas.so.3.10.3
## LAPACK: /usr/lib/x86_64-linux-gnu/atlas/liblapack.so.3.10.3
## 
## locale:
##  [1] LC_CTYPE=pt_BR.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=es_CR.UTF-8        LC_COLLATE=pt_BR.UTF-8    
##  [5] LC_MONETARY=es_CR.UTF-8    LC_MESSAGES=pt_BR.UTF-8   
##  [7] LC_PAPER=es_CR.UTF-8       LC_NAME=C                 
##  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
## [11] LC_MEASUREMENT=es_CR.UTF-8 LC_IDENTIFICATION=C       
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] ggplot2_3.3.3      RColorBrewer_1.1-2 kableExtra_1.3.4   knitr_1.33        
## 
## loaded via a namespace (and not attached):
##  [1] bslib_0.2.4       compiler_4.0.5    pillar_1.6.0      jquerylib_0.1.4  
##  [5] tools_4.0.5       digest_0.6.27     jsonlite_1.7.2    evaluate_0.14    
##  [9] lifecycle_1.0.0   tibble_3.1.1      gtable_0.3.0      viridisLite_0.4.0
## [13] pkgconfig_2.0.3   rlang_0.4.11      rstudioapi_0.13   yaml_2.2.1       
## [17] xfun_0.22         withr_2.4.2       dplyr_1.0.5       httr_1.4.2       
## [21] stringr_1.4.0     xml2_1.3.2        generics_0.1.0    vctrs_0.3.8      
## [25] sass_0.3.1        systemfonts_1.0.1 tidyselect_1.1.1  webshot_0.5.2    
## [29] grid_4.0.5        svglite_2.0.0     glue_1.4.2        R6_2.5.0         
## [33] fansi_0.4.2       rmarkdown_2.7     purrr_0.3.4       magrittr_2.0.1   
## [37] scales_1.1.1      htmltools_0.5.1.1 ellipsis_0.3.2    rvest_1.0.0      
## [41] colorspace_2.0-0  utf8_1.2.1        stringi_1.5.3     munsell_0.5.0    
## [45] crayon_1.4.1