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:
x
: el vector a partir del cual calcularemos el promedio ywt
: un vector de ponderadores para cada componente del vectorx
.
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:
- El cuerpo: el código dentro de la función
body(wtd_mean)
#> {
#> sum(x * wt)/sum(wt)
#> }
- Los formales: la lista de argumentos que controlan como puedes llamar a la función,
formals(wtd_mean)
#> $x
#>
#>
#> $wt
#> rep(1, length(x))
- 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
- 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
- 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.
- 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.