Tracks
/
R
R
/
Temario
/
Funciones
Fu

Funciones en R

1 ejercicio

Acerca de Funciones

Las funciones se presentaron en el concepto Fundamentos, con ejemplos como este:

squareit <- function(x) {
    x * x
}

squareit(3)
#> [1] 9

# shorter form
squareit_short <- function(x) x ^ 2

Si miramos con más atención la definición de squareit, podemos identificar varias partes:

  • La función recibe un argumento formal x.
  • Hay un cuerpo de función, normalmente entre llaves{ }.
  • Una vez creado el objeto de la función, se asigna a una variable squareit.

En R, una función es un objeto de primera clase, igual que los números y los strings. Así, squareit <- function... es una asignación que, desde el punto de vista sintáctico, es igual que x <- 42.

Advanced

Acceder a los componentes de una función es poco común en el uso normal, pero bastante fácil.

class(squareit)
#> [1] "function"
formals(squareit)
#> $x

body(squareit)
#> {
#>     x * x
#> }

Los argumentos que se obtienen con formals() se ven como una lista, con la sintaxis $x. De hecho, el tipo es pairlist, un tipo particular de lista que contiene pares clave-valor.

El body es código ejecutable, con el tipo language (algo que probablemente no necesites tener en cuenta).

Argumentos

R no hace una distinción clara entre argumentos posicionales y argumentos nombrados, a diferencia de otros lenguajes de scripting como Python y Julia.

Las llamadas a funciones pueden pasar valores de forma posicional o por nombre. Esto último es útil para funciones complejas con muchos argumentos, donde es difícil recordar su orden.

f <- function(x, y) x / y

# call positionally
f(4, 2)
#> [1] 2

# call by name
f(y = 2, x = 4)
#> [1] 2

Argumentos opcionales

Los valores predeterminados de los argumentos se pueden especificar en la definición de la función, pero deben ir después de todos los argumentos que no tienen valor predeterminado.

Después podemos elegir si aceptamos el valor predeterminado o lo sobrescribimos.

g <- function(x, y = 2) x / y

# default y value
g(6)
#> [1] 3

# explicit y value
g(6, 3)
#> [1] 2

Argumentos adicionales

Para aceptar una cantidad arbitraria de argumentos adicionales, usa un ... (puntos suspensivos) en la definición. Es posible convertir cualquier valor extra de la llamada a la función en un vector, pero sigue leyendo para conocer otra forma de usar estos «dot args» (llamados «varargs» en varios otros lenguajes).

var_f <- function(x, y, ...) {
  print(c(...))
}

var_f(2, 3, "opt1", "opt2")
#> [1] "opt1" "opt2"

El entorno de la función

Antes dijimos que los argumentos formales y el cuerpo son componentes de una función.

De hecho, hay un tercer componente: el entorno en el que se define la función.

Esto se puede ilustrar con el caso de las funciones anidadas:

outer_func <- function(x) {
  inner_func <- function(y) {
    x * y
  }
  
  inner_func(3)
}

outer_func(5)
#> [1] 15

La llamada a la función le pasa x = 5 a la función externa, y ese valor está disponible dentro de su cuerpo.

La función interna es parte del cuerpo de la función externa y tiene acceso al valor de x. Dicho de otra forma, x = 5 está en el entorno de la función interna.

Técnicamente, esto se conoce como una clausura.

El entorno es especialmente importante con los dot args, ya que cualquier valor que se pase de esta forma se puede reenviar a las llamadas a funciones dentro del cuerpo de la función. La función externa no necesita saber qué significan los dot args, ni le importa.

f_var <- function(x, ...) {
  sum(x, ...)
}

x <- c(1, 2, NA, 6)

# for sum(), na.rm defaults to FALSE
f_var(x)
#> [1] NA

# pass through the na.rm value
f_var(x, na.rm = TRUE)
#> [1] 9

Esta técnica la usan mucho las librerías del Tidyverse, como stringr. Muchas de las funciones de stringr son un envoltorio fácil de usar alrededor de funciones de bajo nivel de stringi y del R base. Los argumentos adicionales que se pasan a las funciones str_*() simplemente se reenvían a esas funciones de más bajo nivel.

Funciones como valores de retorno

Una función externa puede definir una función interna (ya sea con nombre o anónima, como se describe más abajo) y usarla como valor de retorno.

La función devuelta incluirá el entorno en el que se definió.

times_y <- function(x) {
  # anonymous function - see next section
  \(y) x * y
}

f <- times_y(3)
class(f)
#> [1] "function"

f(4)
#> [1] 12

Funciones anónimas

Cuando definimos una función, normalmente vinculamos el objeto de función resultante a una variable:

x <- 2

# function definition captures x from the environment
doubleit_short <- function(y) x * y

Esto hace que sea fácil usar la función más adelante en el script, pero esa vinculación no es necesaria. Una función corta, de un solo uso, puede ser útil en el contexto inmediato. Sin una vinculación de nombre, se le llama función anónima.

El uso de funciones anónimas es tan común que (desde R v4.1.0) existe una sintaxis abreviada para definirlas: reemplaza la palabra function por una barra invertida \.

En la sección anterior, sobre valores de retorno, se usó un ejemplo de esto.

Copia al modificar

R permite asignar valores a elementos individuales de un vector. Si pasamos un vector como argumento de una función y lo modificamos en el cuerpo de la función antes de devolverlo, obtenemos un vector modificado.

¿Pero qué pasó con el vector original?

f <- function(vec) {
  vec[1] <- 42
  vec
}

vals <- c(1, 3, 4)

# f() returns a modified vector
f(vals)
#> [1] 42  3  4  

# the original vector is unchanged.
vals
#> [1] 1 3 4

R es un lenguaje diseñado para la ciencia de datos. Recopilar esos datos puede costar mucho tiempo, esfuerzo y, potencialmente, una cantidad de dinero escalofriante: ¡es importante no corromperlos!

La política general (con algunas excepciones) es copiar al modificar. Si un objeto (como un vector) se modifica de una forma que podría causar problemas más adelante, R devuelve una copia modificada y deja intacto el original.

Copiar estructuras de datos grandes puede ser costoso desde el punto de vista computacional, pero por lo general es el mal menor cuando la alternativa es la corrupción de los datos.

Caution

Ten cuidado con las operaciones que provocan copias repetidas de los mismos datos.

Parafraseando a Hadley Wickham (autor del Tidyverse):

Los bucles no son lentos en sí mismos, pero hacen peligrosamente fácil incluir operaciones lentas dentro del bucle.

La vectorización o las funciones de orden superior pueden ayudarte a protegerte de este tipo de problema de rendimiento.

Editar en GitHub El enlace se abre en una ventana o pestaña nueva

Aprende Funciones