3.1 Funciones

En R todas las operaciones 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
`+`(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

Escribimos una función para calcular un promedio ponderado:

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

Notemos que esta función recibe hasta dos argumentos:

  1. x: el vector a partir del cual calcularemos el promedio y

  2. wt: un vector de ponderadores para cada componente del vector x.

Notemos además que al segundo argumento le asignamos un valor predeterminado, esto implica que si no especificamos los ponderadores la función usará el valor predeterminado y promediara con mismo peso a todas las componentes.

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

Veamos como escribir una función que reciba un vector y devuelva el mismo vector centrado en cero.

  • Comenzamos escribiendo el código para un caso particular, por ejemplo, reescalando el vector \((0, 5, 10)\).
vec <- c(0, 5, 10)
vec - mean(vec)
#> [1] -5  0  5

Una vez que lo probamos lo convertimos en función:

center_vector <- function(vec) {
  vec - mean(vec)
}
center_vector(c(0, 5, 10))
#> [1] -5  0  5

Ejercicio

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.  

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, esto implica que en R los valores de los símbolos se basan en como se anidan las funciones cuando fueron creadas y no en como son llamadas.

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)
}
# Posición
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')"

R tiene 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

Como alternativa surge el paquete conflicted que alerta cuando hay conflictos y tiene funciones para especificar a que paquete se desea dar preferencia en una sesión de R.