Escribir funciones es una de las actividades más importantes en cualquier lenguaje de programación. Su finalidad es poder encapsular fragmentos de código que necesiten ser ejecutados en múltiples ocasiones, con la posibilidad de ejecutarlos en cada ocasión con diferentes parámetro. A su vez, las funciones aumentan la legibilidad de un programa, de modo que es más fácil de entender para uno mismo y para los demás en caso de compartirlo.
Tabla de Contenidos
Funciones
Introducción
Las funciones en R son tratadas como cualquier otro objeto. Para crearlas utilizamos el comando function()
, el cual crear objetos de tipo function, de la siguiente manera:
f <- function(<argumentos>) { ## Código de la función (function body) }
Luego, para llamar a la función simplemente escribimos el nombre de esta:
f <- function() { cat("Hola Mundo") } f() Hola Mundo class(f) "function"
Las funciones poseen 3 partes:
- El cuerpo (body)
- Los argumentos (formals)
- El ambiente (environment)
f <- function(x, y) { x + y } body(f) { x + y } formals(f) $x $y environment(f) <environment: R_GlobalEnv>
Argumentos
Los argumentos de una función son una serie de valores (opcionales) que se pasan a la función, de modo que ciertas variables dentro de estas posean diferentes valores en cada llamada a la función.
area_rectangulo <- function(lado1, lado2) { area <- lado1 * lado2 print(paste("el área es ", area)) }area_rectangulo(2, 3)"el área es 6" formals(area_rectangulo) $lado1 $lado2
Cuando se hace un llamado a una función, el orden en que se pasan los valores de los argumentos corresponde con la ubicación de estos en la delación de la función. Por ejemplo, en el caso anterior, el valor 2 se pasa a la variable lado1, mientras que el valor 3 se pasa a la variable lado2. Si queremos indicar explícitamente que valor asignar a cada argumento debemos indicar el nombre de este al llamar a la función.
area_rectangulo <- function(lado1, lado2) { area <- lado1 * lado2 print(paste("el área es ", area)) }area_rectangulo(lado1 = 2, lado2 = 3)"el área es 6"
Este ultimo método es el más recomendable cuando las funciones tienen un gran numero de argumentos. Algo a notar cuando asignamos valores a los argumentos es que se suele utiliza el operador =
, en vez del <-
. Esto es simplemente una cuestión de estilo, ya que de otro modo seguiría funcionando correctamente la código.
Ahora supongamos que hacemos una llamada a una función que tenga una serie de argumentos, pero uno de estos no lo pasamos. Lo que sucederá es que la función se ejecutará hasta el punto en que debe ser utilizado el argumento faltante, y es en este momento que se genera un error. Este modo de funcionamiento se debe a que R evalúa las instrucciones al momento de ejecutarse (Lazy Evaluation), y no antes. En caso de que la función nunca utilice el argumento faltante, esta terminará su ejecución sin generar error.
f <- function(x, y, z) { print(x+y) print(z) } f(x = 2, y = 0) 2 Error in print(z) : argument "z" is missing, with no default
Para evitar este tipo de comportamientos podemos asignar valores por defecto (default value) a las variables en la declaración de las funciones. Por ejemplo:
f <- function(x = NULL, y = NULL) { if (!is.null(x) & !is.null(y)){ print(x+y) }else{ print('faltan valores') } } f(x = 2, y = 0) 2 f(x = 2) "faltan valores" f(y = 0) "faltan valores"
Para ejemplificar esto en un caso real veamos la función rnorm
. Esta genera números aleatorios a partir de una distribución normal.
str(rnorm)
function (n, mean = 0, sd = 1)
Como vemos, tiene 3 argumento: la cantidad de muestras a generar (n), la media de la distribución (mean) y el desvió estándar (sd). Si indicamos solo un número, este representa la cantidad de muestras a generar, dejando por defecto los otros dos argumentos con valores por defecto (lo que haría que la función genere numero a partir de una distribución normal estándar)
rnorm(3) -0.2262557 0.6465698 2.3460583 rnorm(3, 1) -0.8456187 1.4596911 1.1237811 rnorm(3, 1, 10) 2.331487 -19.407747 -11.676089
Cuando un argumento es pasado por nombre, este es quitado de la lista de orden de asignación de valores al llamar a una función. De este modo se puede hacer una combinación de asignación de argumentos por posición y por nombre.
rnorm(5, sd = 10, 100) # equivalente a: n = 5, mean = 100, sd = 10
106.67484 91.98517 109.96486 113.93423 97.70806
Para indicar los nombre de los argumentos al llamar a una función, R permite que haya coincidencia parcial en estos. De este modo podemos llamar a una funciona e indicar solo una parte del nombre de cada argumento.
rnorm(5, s = 10, m = 100)
101.70763 91.48214 103.48912 107.92323 98.50723
El orden en que se hace la comprobación de argumentos es:
- Coincidencia exacta del nombre del argumento
- Coincidencia parcial del nombre del argumento
- Asignación por posición
En general no es buena practica la utilización de coincidencias parciales, por lo que debemos tratar de evitar su uso.
El argumento …
Hay un argumento que tiene un uso especial en R, denominando … (tres puntos). Este tiene la capacidad de capturar todos los valores pasados a la función que no coinciden con ningún argumento. De este modo, podemos pasar a una función una cantidad no prefijada de valores.
sumar_pares <- function(...)
{
valores <- c(...)
if(!is.numeric(valores)) return('NaN')
contador <- 0
for(n in valores){
if(n%%2 == 0){
contador <- contador + n
}
}
contador
}
sumar_pares(1:10)
30
Como vemos, para trabajar con los valores capturado por … podemos convertirlos a un vector con c(...)
o una lista con list(...)
.
Retorno de valores
Las funciones anteriores solamente realizan una seria de pasos y finalizan sin devolver ningún valor. En muchas ocasiones deseamos que las funciones al finalizar su ejecución devuelvan algún valor. Para esto tenemos dos posibilidades.
La primea es hacer que la ultima linea de código evaluada dentro de una función sea el valor que queremos que sea devuelto.
## Función que cuenta la cantidad de vocales en una cadena que
## se pasa como argumento
contar_vocales <- function(frase)
{
cantidad_vocales <- 0
frase <- tolower(frase)
frase <- strsplit(frase, "")[[1]]
for (i in frase)
{
if (i %in% c("a", "e", "i", "o", "u"))
{
cantidad_vocales <- cantidad_vocales + 1
}
}
cantidad_vocales
}
resultado <- contar_vocales("Hola mundo, nuevamente")
resultado
9
La segunda alternativa es indicarlo explícitamente mediante el comando return()
. En este ultimo caso, cuando se ejecuta esta instrucción dentro de una función, esta finaliza inmediatamente devolviendo el valor indicado. Lo común es reservar esta alternativa para devolver «señales» en caso de que la función tenga inconvenientes. Por ejemplo, en caso de que un argumento no sea consistente con lo esperado podemos devolver algún valor que nos indique de esta situación.
f <- function(<argumentos>) { if (<alguna_condición>) return(<señal>) # Código de la función ... x # objeto que devuelve la función } # Ejemplo calcular_raiz2 <- function(n) { # Verifico que el que número pasado no sea negativo if (n < 0) return("Numero negativo") # En caso de que los argumentos sean consistentes, # continuo con la ejecución de la función. sqrt(n) } calcular_raiz2(2) 1.414214 calcular_raiz2(-2) "Numero negativo"
Reglas de alcance (Scoping Rules)
El ultimo componente de una función es su ambiente (environment). Un ambiente es una colección de pares (símbolo, valor). Por ejemplo, si hacemos x <- 3
, esta sentencia crea el par (x, 3)
en el ambiente donde se ejecutó la instrucción. Si luego queremos recuperar el valor de la variable x
, R busca el valor en el ambiente y encuentra que vale 3. Cada ambiente tiene un ambiente padre, y es posible para un ambiente tener múltiples ambientes hijos. El único ambiente que no tiene padre (raíz) es el empty environment.
De esta forma, el ambiente de una función controla como se encuentra el valor asociado con una variable dentro de ella. Por ejemplo,
f <- function(x) { x + z }
En la mayoría de los lenguajes de programación, esta declaración conduciría a un error debido a que la variable z
no está definida dentro de la función. Pero en R esto es valido debido a que utiliza lo que se conoce como lexical scoping para encontrar el valor asociado. Como z
no está definida dentro de la función (a estas variables se las conoce como free variables), R irá a buscar su valor en el ambiente donde la función fue definida.
f(10) Error in f(10) : object 'z' not found z <- 1 f(10) 11
Básicamente, lo que hace R al necesitar evaluar el valor de z
es ir a buscarlo dentro la función f
. Como no lo encuentra definido, pasa a buscar el valor de la variable en el ambiente padre. En el primer caso, z
sigue sin estar definido, mientras que en el segundo caso sí lo está.
Las reglas de alcance de un lenguaje determinan justamente como un valor es asociado con una variable. Veamos unos ejemplos:
z <- 1 f <- function(x) { z <- 10 2 * z + g(x) } g <- function(x) { x * z }
¿Qué valor devolverá f(5)
? Veamos.
Cuando llamamos a la función f
lo primero que se evalúa es z <- 10.
Esta acción crea un par (z, 10)
en el ambiente de la función f
. A continuacion se evalúa 2*z + g(x)
. En este paso, R busca el valor de la variable z
en el ambiente de la función f
, y encuentra el par (z, 10)
. Luego busca a la función g
, pero no la encuentra definida dentro de la función f
, por lo que pasa a buscarla en el ambiente padre y encuentra:
g <- function(x) { x * z }
Ahora evalúa g(5)
, y se encuentra con x * z
. Acá vemos claramente que x
vale 3, pero ¿qué valor toma la variable z
? Como dijimos, R utiliza lexical scoping. Estos significa que se va a buscar el valor de la variable al ambiente donde esta fue definida (y no donde fue llamada). Por lo tanto, el valor de z
será 1, y por lo tanto:
f(5)
25
Ahora vemos que sucede si la función g
la hubiésemos definido dentro de la función f
:
f <- function(x) { g <- function(x) { x * z } z <- 10 2 * z + g(x) }
Lo único que cambia al momento de evaluar f(5)
es al momento de que R va a busca el valor de z
cuando evalúa g(5)
. Como ahora g
está definida dentro de f
, R va a buscar el valor de z
en su nuevo ambiente padre, que es justamente el ambiente de la función f,
y encuentra que ahora el valor de z
es 10, y por lo tanto:
f(5)
70
Este comportamiento permite realizar operaciones, sin las cuales muchos paquetes no podrían existir. No obstante, hay que ser cuidadoso al trabajar. Un típico problema aparece cuando creamos una función, la cual no devuelve el valor incorrecto. Luego de debuggear el código nos damos cuenta de que en la función está participando una variable que, sin darnos cuenta, no está definida en la función y toma su valor de otro ambiente. El peor caso surge cuando no nos damos cuenta de que el valor que obtenemos es incorrecto, y dado que no aparec ningún error (porque la función toma su valor de otro ambiente) podemos continuar trabajando y llegar a resultado erróneos más adelante.
Comentarios
Hola, soy nueva en R
tengo un dataframe similar al siguiente:
Chr orden
1
1
1
1
2
2
2
Necesitaria una función que, añada el número de registro dentro de cromosoma
Como se haria?
Muchas gracias de antemano
como se puede resolver el siguiente problema.
Escribe una función que dados dos vectores numéricos realice el siguiente cálculo: Contar cuántos números estrictamente positivos aparecerían como resultado de multiplicar (uno por uno) todos los elementos del primer vector por todos los elementos del segundo. Recuerda: Dado un vector x, length(x) nos devuelve el número de elementos de x.
Mi buena explicación. Se complementa muy bien con las demás presentaciones del blog
Muy buen blog, estoy aprendiendo algo de R y lo he encontrado muy ameno y didáctico.
Hola soy nuevo en esto de R. En un ejercicio que me pedían creé esta función:
intercambia <-function(a,b) {
a <- a + b
b <- a – b
a <- a – b
}
a <- 1; b <- 2
intercambia(a,b)
print(a); print(b)
No se produce intercambio de valores. Cambiando las variables para que no entren en conflicto, tampoco.
Al final he añadido a la función un return(c(a,b)) y la llamada retorna el intercambio pero las variables siguen igual.
Supongo que no he entendido nada. He probado con el operador <<- y tampoco. Si me podéis orientar, os lo agradecería.
El ejemplo que comentas de la función intercambia es interesante, pero no está explicado en esta lección. No me extraña que te tenga desconcertado.
Aquí el lío está en utilizar los mismos identificadores para los parámetros de la función y variables externas. Pero no solo eso, existen dos formas de pasar parámetros en los diferentes lenguajes de programación, una por valor y la otra por referencia. En el paso de parámetros por valor, se crea una copia del valor del argumento en el momento de la llamada, puedes jugar y hacer lo que quieras con la copia, intercambiarlas etc pero siempre estarás manejando una copia, no el original. De ahí que dentro de la función los valores se intercambian, pero al salir de la función, las copias se destruyen y a los originales no les ha pasado nada. Desconozco como o si tiene paso por referencia el lenguaje de programación R, en ese caso no se crearía una copia y se pasaría a la función la dirección de las variables a y b, y todo lo que se hiciera dentro de la función en realidad se estaría haciendo a las variables. El caso que cuentas es el ejemplo típico de un paso de parámetros por valor, y si lo entiendes verás que es lógico.
El paso por referencia implica que una función que hace algo o devuelve un valor también puede ocasionar efectos colaterales (modif de variables) y eso se intenta evitar en todo lo posible. La función intercambia con paso de parámetros por valor es imposible, se debería realizar sin pasar ningún parámetro y modificar directamente las variables de afuera. El código de la función es también singular ya que es el ejemplo de intercambiar los valores de unas variables sin el uso de una variable auxiliar (no siempre posible).
se pueden pasar otras funciones como argumento?
Conciso y efectivo
Gracias
Si, muchísimas gracias por tu aporte.. me ayudó mucho.
Saludos.
Buen aporte
Muy buen blog, tiene una explicacion sencilla y didactica