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:
- 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, 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
- 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
- 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')"
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.