Funciones en R

  • Todo lo que sucede en R es una llamada a una función

  • Una función es una sub-rutina 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 es conveniente encapsularlos en un objeto al que se puede llamar fácilmente cuando sea necesario

  • Las funciones cargadas desde paquetes (incluida las de R básico) también pueden modificarse y sobrescribirse


Estructura básica

Todas las funciones se crean mediante la función function () y siguen la misma estructura:

* Modificado de Grolemund 2014

 

Podemos ver la estructura de funciones ya cargadas en nuestro entorno R. Para ver el código, simplemente llame 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: 0x56233eae4e68>
## <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 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 usan funciones primitivas (la mayoría escritas enC). 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 también son funciones:

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

 


Nombrar una función

 

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

  • No utilice nombres de objetos R comunes (por ejemplo, * iris , x *) u objetos que ya estén en el entorno
  • No utilice nombres de funciones de uso frecuente (por ejemplo, mean)
  • No puedo empezar con un número
  • Debería sugerir lo que se supone que debe hacer
  • No muy largo

En caso de que tenga varias funciones con el mismo nombre, se pueden llamar individualmente usando el nombre del paquete (o name space) seguido de :::

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

# no es una desviacion estandar
sd(1:5)
## [1]       1    1024   59049 1048576 9765625
# desviacion estandar
stats::sd(1:5)
## [1] 1.5811
# remover sd
rm(sd)

# desviacion estandar
sd(1:5)
## [1] 1.5811

 

La sintaxis namespace :: function también se puede usar para llamar a funciones de 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 mas útil en una función Xapplys:

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

 

Permitir a los usuarios introducir objetos y/o operadores condicionales en una función. Los argumentos pueden o no tener valores por defecto. Cuando los argumentos tienen valores predeterminados, no es necesario proporcionarlos:

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

f1(1)
## [1] 3

 

Los argumentos pueden ser modificados:

f1(3, 4)

 

Los argumentos sin valor predeterminado deben ser proporcionados:

f1()
## Error in f1(): el argumento "x" está ausente, sin valor por omisión

 

Si todos los argumentos tienen un valor predeterminado, se pueden llamar 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] "2020-02-16 20:16:29.298 CST"

 

Los argumentos se pueden especificar implícitamente por posición o por nombres de argumento incompletos:

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

# opor 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): el argumento 1 concuerda con multiples argumentos formales

 

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 * NAs *:

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

 


Cuerpo

 

El cuerpo de una función puede contener:

  • Comprobación de argumentos

  • Manipulación de datos

  • Definiendo resultados.

 

El cuerpo de la función puede tomar el mismo tipo del código que se usa en las rutinas de R que no son funciones. 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, que define explícitamente la salida de la función. Esto se hace usando la función return ():

# sin retorno  
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 retorno
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 ninguna * declaración de retorno *, la función devolverá el último objeto que se creó en el cuerpo de la función:

# sin retorno  
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

 

Is safer to use 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) {
  
  # vector de 1 elemento
  z1 <- x + y
  
  # vector de 2 elementos
  z2 <- c(x, y/3)
  
  # vector logical
  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 utilizando la 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

 

Ventajas de usar funciones

 

Código mas limpio

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

# primero remove todo
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

 

Fácil de correr y compartir

Las funciones se pueden llamar desde archivos R externos sin que se definan 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) {

    # transponer matriz y calcular exp 2
    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 guardarla en un archivo de R (usando sink()):

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

# remover todos los objetos
rm(list = ls())

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

 

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

# load function
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. Puede ser enviado por correo electrónico o publicado en línea. Incluso se puede cargar desde repositorios en línea:

# remover todos los objetos
rm(list = ls())

# revisar
exists("fnctn_X")
## [1] FALSE
# cargar desde sitio web
source("https://marceloarayasalas.weebly.com/uploads/2/5/5/2/25524573/function_x.r")

# revisar
exists("fnctn_X")
## [1] TRUE

Facilmente aplicadas a nuevos objetos

Esto deberia ser obvio!

Otras recomendaciones

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

 

Ejercicio 1


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


1.2 Añada valores por defecto a cada argumento


1.3 Ejecute la función con uno de los argumentos establecidos en un número negativo. ¿Qué es lo que pasa? ¿Por qué?


1.4 Agregue un argumento lógico para convertir los argumentos de entrada al valor absoluto (utilizando la función abs()). Agregue las modificaciones necesarias para que la función realice los cálculos con y sin valores absolutos

 

Ejercicio 2


2.1 Calcule la desviación estándar de la longitud del pétalo para cada especie en el conjunto de datos “iris”" usando tapply ()


2.2 Cree una función para calcular el error estándar para cada especie (* pista *: SE = SD / raíz cuadrada (n)) y aplíquela como en 2.1 (es decir, SE para cada especie)

 

Ejercicio 3


3.1 Usando el conjunto de datos “iris”, cree una función que cree un diagrama de dispersión de la longitud del sépalo contra la longitud del pétalo


3.2 Genere un gráfico para cada una de las especies en el conjunto de datos usando una función Xapply


3.2 Agregue argumentos para permitir a los usuarios modificar el color, tamaño y tipo de símbolo de los puntos (col, cex y pch)


3.3. Haga que la función agregue el nombre de la especie al título del gráfico


3.4. Agregue el coeficiente de correlación al título (oista: use cor() y paste())


3.5 Modifique la función para que los usuarios puedan definir qué variables se graficarán


3.6 Haga que la función use puntos rojos si la correlación es mayor que 0.5


3.7 Haga que el tamaño de los puntos sea proporcional al coeficiente de correlación

 

Ejercicio 4


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


4.2 Utilícela con “Sepal.Length” y “Sepal.Width” de “iris”.


4.3 ¿Qué argumentos deben proporcionarse?


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


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


4.6 ¿Qué tipo de objeto es devuelto por esta función?


4.7 ¿Cómo se puede obtener el valor de p sin guarda el resultado primero?


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 3.6.1 (2019-07-05)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 18.04.3 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=es_ES.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=es_CR.UTF-8        LC_COLLATE=es_ES.UTF-8    
##  [5] LC_MONETARY=es_CR.UTF-8    LC_MESSAGES=es_ES.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.2.1      RColorBrewer_1.1-2 dplyr_0.8.3        tidyr_1.0.0       
## [5] readxl_1.3.1       kableExtra_1.1.0   knitr_1.26        
## 
## loaded via a namespace (and not attached):
##  [1] Rcpp_1.0.3        pillar_1.4.2      compiler_3.6.1    cellranger_1.1.0 
##  [5] tools_3.6.1       zeallot_0.1.0     digest_0.6.22     gtable_0.3.0     
##  [9] lifecycle_0.1.0   evaluate_0.14     tibble_2.1.3      viridisLite_0.3.0
## [13] pkgconfig_2.0.3   rlang_0.4.1       rstudioapi_0.10   yaml_2.2.0       
## [17] xfun_0.11         withr_2.1.2       httr_1.4.1        stringr_1.4.0    
## [21] xml2_1.2.2        vctrs_0.2.0       hms_0.5.2         grid_3.6.1       
## [25] tidyselect_0.2.5  webshot_0.5.1     glue_1.3.1        R6_2.4.1         
## [29] rmarkdown_1.17    purrr_0.3.3       readr_1.3.1       magrittr_1.5     
## [33] backports_1.1.5   scales_1.0.0      htmltools_0.4.0   assertthat_0.2.1 
## [37] rvest_0.3.5       colorspace_1.4-1  stringi_1.4.3     lazyeval_0.2.2   
## [41] munsell_0.5.0     crayon_1.3.4