4.1 Funciones

“To understand computations in R, two slogans are helpful:
* Everything that exists is an object.
* Everything that happens is a function call.”
— John Chambers

Todas las operaciones en R son producto de la llamada a una función, esto incluye operaciones como +, operadores que controlan flujo como for, if y while, e incluso operadores para obtener subconjuntos como [ ] y $.

a <- 3
b <- 4
`+`(a, b)
#> [1] 7

for (i in 1:2) print(i)
#> [1] 1
#> [1] 2
`for`(i, 1:2, print(i))
#> [1] 1
#> [1] 2

Para escribir código eficiente y fácil de leer es importante saber escribir funciones, se dice que si hiciste copy-paste de una sección de tu código 3 o más veces es momento de escribir una función.

Escribimos una función para calcular un promedio ponderado:

wtd_mean <- function(x, wt = rep(1, length(x))) {
  sum(x * wt) / sum(wt)
}

Y se usa:

wtd_mean(1:10)
#> [1] 5.5
wtd_mean(1:10, 10:1)
#> [1] 4

Escribe una función que reciba un vector y devuelva el mismo vector reescalado al rango 0 a 1. * Comienza escribiendo el código para un caso particular, por ejemplo, empieza reescalando el vector . Tip: la función range() devuelve el rango de un vector.
* Aplica tu función a las columnas a a d del data.frame df df <- data.frame(ind = 1:10, a = rnorm(10), b = rnorm(10), c = rnorm(10), d = rnorm(10)).

Estructura de una función

Las funciones de R tienen tres partes:

  1. El cuerpo: el código dentro de la función
body(wtd_mean)
#> {
#>     sum(x * wt)/sum(wt)
#> }
  1. Los formales: la lista de argumentos que controlan como puedes llamar a la función,
formals(wtd_mean)
#> $x
#> 
#> 
#> $wt
#> rep(1, length(x))
  1. El ambiente: el mapeo de la ubicación de las variables de la función.
library(ggplot2)
environment(wtd_mean)
#> <environment: R_GlobalEnv>
environment(ggplot)
#> <environment: namespace:ggplot2>

Veamos mas ejemplos, ¿qué regresan las siguientes funciones?

# 1
x <- 5
f <- function(){
  y <- 10
  c(x = x, y = y) 
}
rm(x, f)

# 2
x <- 5
g <- function(){
  x <- 20
  y <- 10
  c(x = x, y = y)
}
rm(x, g)

# 3
x <- 5
h <- function(){
  y <- 10
  i <- function(){
    z <- 20
    c(x = x, y = y, z = z)
  }
  i() 
}

# 4 ¿qué ocurre si la corremos por segunda vez?
j <- function(){
  if (!exists("a")){
    a <- 5
  } else{
    a <- a + 1 
}
  print(a) 
}
x <- 0
y <- 10

# 5 ¿qué regresa k()? ¿y k()()?
k <- function(){
  x <- 1
  function(){
    y <- 2
    x + y 
  }
}

Las reglas de búsqueda determinan como se busca el valor de una variable libre en una función. A nivel lenguaje R usa lexical scoping, una alternativa es dynamic scoping. En R (lexical scoping) los valores de los símbolos se basan en como se anidan las funciones cuando fueron creadas y no en como son llamadas. Esto es, en R no importa como son las llamadas a una función para saber como se va a buscar el valor de una variable.

f <- function(x) {
  x + y
}
f(2)
#> Error in f(2): object 'y' not found

Si creamos el objeto y.

y <- 1
f(2)
#> [1] 3

Como consecuencia de las reglas de búsqueda de R, todos los objetos deben ser guardados en memoria y, si uno no es cuidadoso se pueden cometer errores fácilmente.

y <- 100
f(2)
#> [1] 102

Observaciones del uso de funciones

  1. Cuando llamamos a una función podemos especificar los argumentos en base a posición, nombre completo o nombre parcial:
f <- function(abcdef, bcde1, bcde2) {
  c(a = abcdef, b1 = bcde1, b2 = bcde2)
}

f(1, 2, 3)
#>  a b1 b2 
#>  1  2  3
f(2, 3, abcdef = 1)
#>  a b1 b2 
#>  1  2  3
# Podemos abreviar el nombre de los argumentos
f(2, 3, a = 1)
#>  a b1 b2 
#>  1  2  3
# Siempre y cuando la abreviación no sea ambigua
f(1, 3, b = 1)
#> Error in f(1, 3, b = 1): argument 3 matches multiple formal arguments
  1. Los argumentos de las funciones en R se evalúan conforme se necesitan (lazy evaluation),
f <- function(a, b){
  a ^ 2
}
f(2)
#> [1] 4

La función anterior nunca utiliza el argumento b, de tal manera que f(2) no produce ningún error.

  1. Funciones con el mismo nombre en distintos paquetes:

La función filter() (incluida en R base) aplica un filtro lineal a una serie de tiempo de una variable.

x <- 1:100
filter(x, rep(1, 3))
#> Time Series:
#> Start = 1 
#> End = 100 
#> Frequency = 1 
#>   [1]  NA   6   9  12  15  18  21  24  27  30  33  36  39  42  45  48  51
#>  [18]  54  57  60  63  66  69  72  75  78  81  84  87  90  93  96  99 102
#>  [35] 105 108 111 114 117 120 123 126 129 132 135 138 141 144 147 150 153
#>  [52] 156 159 162 165 168 171 174 177 180 183 186 189 192 195 198 201 204
#>  [69] 207 210 213 216 219 222 225 228 231 234 237 240 243 246 249 252 255
#>  [86] 258 261 264 267 270 273 276 279 282 285 288 291 294 297  NA

Ahora cargamos 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
filter(x, rep(1, 3))
#> Error in UseMethod("filter_"): no applicable method for 'filter_' applied to an object of class "c('integer', 'numeric')"

El problema es un conflicto en la función a llamar, nosotros requerimos usar filter de stats y no la función filter de dplyr. R utiliza por default la función que pertenece al último paquete que se cargó.

search()
#>  [1] ".GlobalEnv"        "package:dplyr"     "package:ggplot2"  
#>  [4] "package:stats"     "package:graphics"  "package:grDevices"
#>  [7] "package:utils"     "package:datasets"  "package:methods"  
#> [10] "Autoloads"         "package:base"

Una opción es especificar el paquete en la llamada de la función:

stats::filter(x, rep(1, 3))
#> Time Series:
#> Start = 1 
#> End = 100 
#> Frequency = 1 
#>   [1]  NA   6   9  12  15  18  21  24  27  30  33  36  39  42  45  48  51
#>  [18]  54  57  60  63  66  69  72  75  78  81  84  87  90  93  96  99 102
#>  [35] 105 108 111 114 117 120 123 126 129 132 135 138 141 144 147 150 153
#>  [52] 156 159 162 165 168 171 174 177 180 183 186 189 192 195 198 201 204
#>  [69] 207 210 213 216 219 222 225 228 231 234 237 240 243 246 249 252 255
#>  [86] 258 261 264 267 270 273 276 279 282 285 288 291 294 297  NA

Una alternativa es el paquete conflicted que alerta cuando hay conflictos y tiene funciones para especificar a que paquete se desea dar preferencia por default en una sesión.