Sunteți pe pagina 1din 35

## Funciones

# La creación de funciones es la principal utilidad


de un lenguaje programado,
# aún en un lenguaje orientado a la estadística
como R. Anteriormente nos
# ocupamos de distintos tipos de objeto que podemos
encontrar en R. En la pre-
# sente unidad nos dedicaremos a la comprensión de
la estructura y funcionamien-
# to de las funciones, así como a la creación y
corrección de las mismas.

## ¿Para qué hacer funciones?

# El primer motivo que encontramos para escribir


funciones es la practicidad.
# Esto es cierto en situaciones en las que se debe
realizar un procedimiento
# que requiere la ejecución de comandos en un orden
específico, o volver a eje-
# cutar una serie de comandos que nos quedó muy
atrás en el historial y sabemos
# que los vamos a tener que utilizar nuevamente. En
casos como estos, puede
# ahorrarnos tiempo y esfuerzo escribir una función
que contenga estos pasos y
# que podamos utilizar una y otra vez. Un ejemplo
claro de esto es la función
# 'sort', que ejecuta las instrucciones necesarias
para ordenar un vector.

x <- sample(1:15) # Si generamos un vector


muestreando los valores de 1 a 15
sort (x) # la ejecución de 'sort' nos devuelve
este vector ordenado.

# Dentro de esta función están las instrucciones


necesarias para realizar la
# tarea, y que se ejecutarán sin necesidad de
hacerlo paso a paso.
sort

# Más adelante volveremos sobre la estructura de


las funciones, su anatomía,
# para entender qué exactamente es cada uno de sus
componentes.

# El segundo motivo, que se desprende del anterior,


es la generalización.
# Muchas veces vamos a generar una función para
atender a un caso particular,
# a un determinado set de datos o situación. Sin
embargo, en la medida en que
# las funciones que escribimos nos resulten útiles,
es importante poder pasar
# de su aplicabilidad a valores fijos a utilizar
variables que podamos modificar
# en cada caso. Veamos un ejemplo, en la creación
de una función que nos permita
# calcular el área de un triángulo.

# El problema planteado es el área de un triángulo


que presenta 3 cm de base y
# 4 de altura. La forma más sencilla de estimar la
misma es, directamente,

4 * 3 / 2

# Sin embargo, si necesitamos hallar el área de


varios triángulos, podemos crear
# la función 'area', utilizando base y altura como
variables.

area <- function(a, b) {


a * b / 2
}
area (4, 3) # Podemos usarla con las medidas de
nuestro triángulo
area (12, 10) # o de cualquier otro.

# En este caso, es muy sencillo entender que se


pasó del caso particular de un
# par de valores de base y altura a una función que
corre para cualquier par de
# valores. En este sentido, la abstracción es
similar a lo que uno ve en las
# matemáticas, en el sentido de pasar de valores
particulares a símbolos que
# pueden ser sustituidos en una ecuación por
valores conocidos, logrando poder
# aplicar la función a cualquier situación similar.

# Otro ejemplo más ilustrativo puede ser el


siguiente: teniendo un vector `x` de
# 15 elementos, podemos calcular su promedio de la
siguiente manera:

x <- rpois(15, 4)
p <- sum(x) / 15
p

# Este código sólo sirve para `x` u otro vector con


15 elementos. Si queremos
# adaptar este código para calcular el promedio de
cualquier vector, indepen-
# dientemente de su longitud, vamos a tener que
sustituir este valor por la
# longitud de un vector genérico, empleando
`length(x)`.

p <- sum(x) / length(x)

# En este caso, aún sin crear una función, hemos


ganado en abstracción, ya que
# salimos del caso concreto en el que `x` tiene 15
elementos y ahora podemos
# utilizar esta línea de código para hacerlo para
cualquier vector `x` posible.
# Pero aún dependemos de esta línea de comando, que
podemos haber olvidado o
# puede haber quedado muy atrás en el historial y
volver a encontrarla para
# ejecutarla puede ser un derroche de tiempo y
esfuerzo. Sin mencionar que el
# vector tiene necesariamente que llamarse `x` para
que funcione. En este caso
# puede ser más sencillo crear una función que haga
la operación deseada.

f <- function(v) {
p <- sum(v) / length(v)
p
}

f(rpois(19, 23)) # Y sabemos que devuelve un


resultado para cualquier vector

# Si bien puede parecer un derroche crear una


función para una operación tan
# sencilla. Esto es cierto, pero también debe
tenerse en cuenta que se pueden agregar nuevas
# capacidades y detalles a la función, consideremos
la situación en que nuestro vector
# presenta NAs:

x <- rpois(20, 10)


x[c(3, 6, 7)] <- NA
f(x)

# En tal caso, deberemos agregar un comando para


descartar dichos valores en la
# propia función, por ejemplo creando un objeto 'v'
dentro de la misma que sea
# nuestro vector sin los NAs.

f <- function(v) {
v <- v[!is.na(v)]
p <- sum(v) / length(v)
p
}

f(x)

# Como último detalle, no es necesario que las


funciones representen una
# abstracción o generalización de una operación a
partir de un caso concreto.
# Es posible crear funciones sin argumentos, cosas
tan simples como:

g <- function() print("Hola mundo")


g()

# En la próxima lección exploraremos los


componentes que hacen a las funciones,
# en una suerte de lección de anatomía de las
mismas.
#### Escribir funciones en R

# Muchas veces, salvo para tareas sencillas, vamos


a tener que escribir
# las instrucciones a ejecutar en una nueva
función.
# La función que utilizaremos para este fin será
'function', y se utiliza
# de la siguiente manera:

nombre <- function(arg1, arg2, arg3) instrucciones

# Con este comando la función es *definida* (pero


no *ejecutada*).
# La nueva función 'nombre' va a evaluar las
instrucciones en función de
# los argumentos provistos, y devolverá
posteriormente una salida. La
# clase de este nuevo objeto es "function", para
sorpresa de nadie.
# Una vez definida, la función puede ser ejecutada
de la siguiente manera:

nombre(argumentos)

## Argumentos:
# En este caso los argumentos se llaman "arg1",
"arg2" y "arg3". En el
# momento de definirla, al igual que al llamarla,
estos deben estar
# separados por comas.
# Es posible definir funciones sin argumentos,
aunque generalmente esto
# no es de gran utilidad. Por ejemplo:
alf <- function()
print("¡No hay problema!")

alf() # Es una función con una sóla salida posible.

# Nota: para chequear cuales son los argumentos de


cualquier función
# podemos usar la funcion "args":
args(alf)
args(rnorm)
args(sample)

# Si escribimos el nombre de la función a secas


(sin paréntesis), podemos
# ver la definición de la función:
alf
sd
aov
lm

## Instrucciones:
# Las instrucciones son comandos de R que se van a
ejecutar cada vez que
# el usuario llame la función. En el ejemplo de
"alf", el único comando
# indica que hay que imprimir la frase "¡No hay
problema!".

# Si se define una función con argumentos, estos


serán los objetos
# presentes en el momento en que se ejecuten las
instrucciones. Por
# ejemplo:

edad <- function(x) {


texto <- paste("Vengo de Melmac, tengo", x,
"años.\n")
cat(texto)
}

edad(220)
# Vengo de Melmac, tengo 220 años.
edad("cuarenta")
# Vengo de Melmac, tengo cuarenta años.

# Como se puede ver en el ejemplo, la "x" usada por


la función toma el
# valor que el usuario decide darle. Si el usuario
no da valor alguno, R
# va a indicarlo con un mensaje de error:
edad()
# Error in paste("Vengo de Melmac, tengo", x,
"años.\n") :
# argument "x" is missing, with no default
# Muy acertadamente, R nos indica que está faltando
el argumento "x", el
# cual no tiene valor por defecto.

# Salida:
# Toda función devuelve algo, ya lo dijimos, y por
defecto, ese algo es
# el último objeto generado dentro de las
instrucciones. Para el caso de
# la función "alf", esto será la frase "¡No hay
problema!" (es lo que
# devuelve la función print), y para el caso de
"edad", será NULL, que es
# lo que devuelve la función cat. Más abajo se
muestran otras formas de
# definir la salida de una función.

# El lector atento habrá notado que se usaron las


llaves "{" "}" la
# última vez. Si estas no se usan, el R interpreta
que la función se
# termina con el primer comando de las
instrucciones (ver más abajo). La
# utilidad de las llaves consiste en poder usar
varios comandos dentro de
# la función, indicando el comienzo y el fin de los
mismos.

# Un detalle importante es que las instrucciones de


la función se
# ejecutan en un "ambiente paralelo" a nuestra área
de trabajo, por lo que
# es fundamental tener claro qué objetos están
presentes en este
# ambiente. Por ejemplo, en la función "edad" los
objetos "x" y "texto"
# existen dentro del ambiente de la función y toman
valores particulares a
# raíz de las instrucciones de la función y de los
valores ingresados por
# el usuario. En nuestra área de trabajo puede
existir un objeto llamado
# "x", sin embargo esto no va a afectar el
resutlado del comando "edad(4)",
# ya que dentro de la función "x" toma el valor 4.

# Los objetos que existen dentro del ambiente de la


función son:
# 1- los que están definidos como argumentos de la
función (dentro del
# paréntesis). En el ejemplo anterior sería "x".
# 2- los que están definidos dentro del código de
la función, a
# través de las instrucciones. En el caso
anterior, el objeto "texto"
# tiene estas características.
# 3- los que estaban definidos en el área de
trabajo en el momento en que
# fue definida la función, siempre y cuando no
se genere otro objeto
# con el mismo nombre con las instrucciones. En
el ejemplo visto caso no
# hay ningún caso de este tipo de objetos que
usemos, pero al final de
# la lección ponemos una modificación como
ejemplo.
# Este ambiente paralelo sólo existe durante la
ejecución de la
# función, antes y después todos los objetos
(excepto los que cumplen con
# la condición 3) van a estar ausentes de nuestra
área de trabajo.

# Estas reglas no se aplican para objetos de la


clase "function", es
# decir, funciones: las mismas siempre existirán
dentro de nuestra
# función, siempre y cuando hayan sido definidas de
antemano.

# La principal ventaja de las funciones, como ya


hemos planteado
# anteriormente, es juntar una serie de
instrucciones, de la complejidad
# que sea, para poder ejecutarlas en una sola línea
de código. Esto
# significa que si tenemos que repetir varias veces
una serie de
# instrucciones siguiendo una determinada
secuencia, podremos hacerlo
# escribiendo una función, la cual estará
disponible para reutilizar cuando
# la necesitemos, cambiando los argumentos.

# Un ejemplo muy simple:


sinexp <- function(x, a=1, b=0.1)
sin(a * x) * exp(x ^ b)

# Aquí el último objeto generado es el resultado de


la ecuación escrita,
# por lo que esta es la salida de la función.

# Esta función tiene una sóla instrucción, la cual


se pone (generalmente)
# en la segunda línea y con una indentación.
# Tiene 3 argumentos: x, a y b. Los dos últimos
tienen agregada la
# asignación de un valor, a través del "=": esto
indica que "a" y "b"
# tienen valores por defecto 1 y 0.1, que serán los
que R usará en caso de
# que el usuario no ingrese otros.

# Usando la función podemos evaluar la salida para


distintas
# combinaciones de x, a y b:
sinexp(10) # evaluada para x=10
sinexp(1:10) # evaluada para todos los x=1, 2,
3, ..., 10
sinexp(seq(0, 5, 0.05))
# evaluada para una secuencia de valores (0.05 de
distancia)
sinexp(seq(0, 5, 0.05), 2, 0.08) # a=2, b=0.08
curve(sinexp(x, a=2, b=0.08), to=30, n=1e4)
# hace una gráfica con la función

## Ejemplo: "error estándar".

# Un ejemplo "de libro": el cálculo del error


estándar de un promedio, que
# es una función que no existe en el paquete básico
de R.
# La ecuación para hacer eso es sd / (n - 1) ^ (1 /
2), donde 'sd' es el
# desvío estándar y 'n' la cantidad de
observaciones.
# La función la vamos a escribir de la siguiente
manera:

SEM <- function(datos, na.rm = FALSE) {


if (na.rm)
VAR <- datos[!is.na(datos)]
else
VAR <- datos
SD <- sd(VAR)
N <- length(VAR)
SD / sqrt(N - 1)
}

# Pero mejor vamos a explicar bien cada una de sus


partes:

SEM <- function(datos, na.rm = FALSE) {


# Primer línea:
# - Asignamos la función al objeto 'SEM'.
# - También escribimos los argumentos de la misma,
que van a ser un objeto
# 'datos' (el cual debe ser del tipo vector) y un
objeto lógico 'na.rm'
# (para definir si se remueven los NA de
'datos'). Este argumento por
# defecto será FALSE.
# - Ponemos la llave de apertura "{", para indicar
el comienzo de las
# instrucciones
# Los argumentos pueden ser objetos de cualquier
clase y deben ir
# separados por comas.
if (na.rm)
# Aquí van las instrucciones que se ejecutan si
na.rm es TRUE, es decir,
# si decidimos remover de la muestra los NA (es
una sóla instrucción).
VAR <- datos[!is.na(datos)]
# Crea, dentro del espacio de la función el
objeto 'VAR', que no es
# otra cosa que 'datos' con los NA extirpados.
else
VAR <- datos # Instrucciones por defecto
('else', es el alternativo
# al 'if'), donde 'VAR' va a ser
simplemente los datos
# de la entrada.
# Nota: se va a profundizar en el uso de if/else
en otras lecciones del
# curso, bajo el tema "Estructuras de Control".
SD <- sd(VAR) # Calculamos el desvío estándar
de 'VAR'
N <- length(VAR) # 'N' va a ser la cantidad de
observaciones que tenemos
SD / sqrt(N - 1) # Y, finalmente, introducimos la
ecuación de la que
# partimos.
}
# Y cerramos la llave, indicando el fin de las
instrucciones.

# Nota: el último objeto generado en esta función


es el producto de la
# ecuación del error estándar, por lo cual esta es
la salida de esta
# función. Alternativamente, se puede usar el
comando "return(out)", en
# donde "out" es el objeto que queremos que sea la
salida de la función.
# Usando este comando, es posible definir la salida
de la función en
# cualquier parte de las instrucciones y no
necesariamente en la última.
# De hecho, cuando se ejecuta "return" se dejan de
ejecutar instrucciones
# y se devuelve el objeto indicado. Una función
similar es "invisible",
# cuya única diferencia es que no imprime por
defecto el resultado en la
# consola al terminar la ejecución de la función.

# Ahora vamos a probar nuestra función, para lo que


vamos a crear un
# objeto 'x' que consiste en una serie de
observaciones de alguna
# variable aleatoria.

x <- sample(1:100, 25, replace = T)


SEM(datos = x)

# Ahora vamos realizar un cambio en 'x',


introduciendo algunos NA.

x[c(2,5,12,25)] <- NA

# Y volvemos a ejecutar 'SEM' para 'x', lo que da


un resultado "feo":

SEM(datos = x)

# Para arreglar este asunto, vamos a indicarle en


los argumentos que no
# debe tener en cuenta los NA de 'x'.

SEM(datos = x, na.rm = TRUE)

# Y volvemos a tener un resultado.

## Ejemplo: el argumento especial "..."

# Considerando una función simple que grafica una


curva logística,
# incluimos el argumento "..." de esta manera:
logi <- function(ini=-8, fin=8, int=0.05, ...) {
x <- seq(ini, fin, by=int)
y <- exp(x) / (1 + exp(x))
plot(y ~ x, type='l', ...) # acá también se
agrega "..."
return(y)
}
logi()

# La lista de argumentos contiene "...", el cual se


repite dentro del
# llamado a la función plot. Esto permite pasar
argumentos a la función
# plot sin que estén definidos de antemano, usando
el nombre del argumento
# en el momento de llamar a la función logi. Por
ejemplo, los argumentos
# "col", "lwd", "ylab" y "main" se pueden agregar:
x <- logi(-15, 15, col="blue")
x <- logi(-15, 15, col="blue", lwd=4)
x <- logi(-15, 15, col="blue", lwd=4, ylab="Curva
Logística")
x <- logi(-15, 15, col="blue", lwd=4, main="Curva
Logística", ylab='f(x)')

# El mismo principio se puede aplicar a cualquier


función.

# Como hemos visto las distintas funciones permiten


ejecutar una serie de
# instrucciones que requieren ser evaluadas en una
determinada secuencia,
# pero reduciendo considerablemete el espacio
utilizado y perimitiendo
# reutilizar la función, una vez guardada el área
de trabajo o el script
# en que la definimos. Las funciones que creamos no
quedan incorporadas a
# R en sí, sino que las vamos a tener que volver a
cargar para volver a
# utilizarlas. De todos modos simplifican mucho el
trabajo cuando debemos
# realizar tareas que requieren varias
instrucciones, sobre todo si
# tienen una cierta complejidad.

# Una estrategia muy útil es guardar las


definiciones de las funciones
# que hemos creado en scripts. Se pueden agrupar
varias definiciones en un
# mismo archivo, particularmente si son funciones
que están relacionadas
# (pueden depender unas de otras, por ejemplo), de
forma que cada archivo
# es una "biblioteca" de funciones. En el momento
de iniciar la sesión de
# R basta con usar la función "source" para traer
la biblioteca al área de
# trabajo, por ejemplo:
source("/camino/hacia/el/script.R")
# El archivo "estimaPi.R" es un script con 2
funciones incluidas que
# ilustra esta idea.

# Como comentario final, no hay una receta


universal para escribir
# funciones, pero hay algunas recomendaciones que
facilitan mucho la
# tarea:

# 1- Hacer un esquema previo de qué es lo que


queremos de nuestra función
# 2- En este esquema previo armar la secuencia de
comandos en el orden que
# deberían ejecutarse.
# 3- Tratar, en la medida de lo posible, que las
funciones utilizadas sean
# las opciones más 'económicas' en memoria y
procesador para realizar
# la tarea deseada: si armamos un tándem de
funciones muy demandantes
# el resultado va a ser un monstruo devorador de
recursos.
# 4- Intentar probar varias veces con datos
artificiales (y sobretodo con
# errores artificiales) nuestra función para
comprobar que anda bien.
# 5- Armar el script siguiendo las recomendaciones
de estilo, ya que
# además de facilitar la colaboración con los
colegas sirven
# fundamentalmente para identificar fácilmente
los comandos que puedan
# estar funcionando mal.
# 6- Utilizar herramientas de depuración (ver la
lección correspondiente)
# ayuda a descubir los errores de programación.
# 7- La práctica hace al maestro. No frustrarse si
las funciones no nos
# quedan bien de entrada.
##########################
# Apéndice: se muestra un ejemplo de una función
con un objeto presente en el
# ambiente de la misma, a pesar de estar definido
por fuera de las
# instrucciones y los argumentos:
y <- 5
edad <- function(x) {
texto <- paste("Vengo de Melmac, tengo", x, "años
y", y,"meses.\n")
cat(texto)
}

edad(8)
# Vengo de Melmac, tengo 8 años y 5 meses.

# Como se puede ver, el objeto "y" es usado por la


función para completar la
# frase de salida.

Curso de R | Funciones
by Mauricio | Dic 6, 2016 | Curso R
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 [ocultar]
 1 Funciones
o 1.1 Introducción

o 1.2 Argumentos

o 1.3 El argumento …

o 1.4 Retorno de valores

 2 Reglas de alcance (Scoping Rules)

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:


1. Coincidencia exacta del nombre del argumento
2. Coincidencia parcial del nombre del argumento
3. 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.
Algunas estructuras de programación. Creación de funciones en R
Acciones de Documento


Autor: Alberto Muñoz García
Estructuras de programación

R permite crear estructuras repetitivas (loops) y la ejecución condicional de sentencias.


A este fin, los comandos pueden agruparse entre llaves, utilizando la siguiente sintaxis:

{comando1 ; comando2; comando3 ; ....}


El bucle for

Para crear un bucle repetitivo (un bucle for), la sintaxis es la siguiente:

for (i in listadevalores) { secuencia de comandos }

Por ejemplo:

> for(i in 1:10) { print(i)}


[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6
[1] 7
[1] 8
[1] 9
[1] 10

Un ejemplo de dibujo:

> x = seq(-10,10)
> plot(x,x,xlim=c(0,10),ylim=c(0,10))
> for(i in 1:10)
+ abline(h=i,col=i)
> for(i in 1:10)
+ abline(v=i,col=i)

No obstante, los bucles for son lentos en R (y en Splus), y deben ser evitados en la medida de lo
posible.

El bucle while

La sintaxis es como sigue:

while ( condicion logica) { expresiones a ejecutar }

Por ejemplo, si queremos calcular qué número es el mayor cuyo cuadrado


no excede de 1000, podemos hacer:
> cuadrado = 0
> while(cuadrado<=1000)
+{
+ n<-n+1
+ cuadrado<-n^2
+}
> cuadrado
[1] 1024
>n
[1] 32
> 32^2
[1] 1024

¿Qué ha sucedido? El cuadrado de 32 excede 1000. En realidad, cuando n valía 31, su cuadrado
(961)
no excedía 1000, y el while() permitió entrar en el bucle, lo que hizo n=32. El número correcto sería
en este caso n-1 = 31.

Ejecución condicional: if

La sintaxis general es:

if (condicion) comando1 else comando2

Por ejemplo, vamos a crear dos listas; una para guardar los
números pares de 1 a 10, y otra para los impares:

> n = 10 # Se inicializa n
> pares = c() # Se crea un vector vacío
> impares = c() # Idem
> for(i in 1:n){ # Se van a procesar los números de 1 a n
+ if(i%%2==0) pares<-c(pares,i) # Si al dividir por 2 sale 0
+ else impares<-c(impares,i)} # el numero es par, impar en otro caso
> pares
[1] 2 4 6 8 10
> impares
[1] 1 3 5 7 9

Creación de funciones en R
La estructura general de una función en R es la siguiente:

nombre = function(argumento1 , argumento2, .....) comandos

Por ejemplo, podemos definir una función que calcule la desviación típica:

> desv = function(x){sqrt(var(x))} # Definimos la función


> x<-1:10 # Generamos datos
> desv(x) # Utilizamos la función
[1] 3.027650
> sd(x) # La definida en R coincide con la nuestra
[1] 3.027650

Una vez definida una función, se la puede llamar y utilizar como a cualquiera otra función
predefinida en el sistema. Por ejemplo, vamos a utilizar la función apply combinada con
desv para calcular las desviaciones típicas de las columnas de una matriz:

> x = matrix(rnorm(15),nrow=3)
>x
[,1] [,2] [,3] [,4] [,5]
[1,] 0.1578703 1.6712974 -0.5419452 0.03345786 -0.6675674
[2,] 0.3215741 -0.6352143 -1.0222260 0.39006069 0.3609624
[3,] 0.4770036 -0.3508383 -0.5147970 1.36219826 -1.6669992
> apply(x,2,desv)
[1] 0.1595845 1.2576365 0.2854502 0.6877219 1.0140156

Alcance de las variables

Las variables definidas dentro del cuerpo de una función son locales, y desaparecen al terminar
la ejecución de la función. Por ejemplo:

> y = 10 # Definimos la variable y


> cuadrado = function(x){ y <- x^2 ; return(y)} # Definimos otra y local
>x=2 # Asignamos valor a x
> cuadrado(x) # Calculamos el cuadrado de x : Se hace y=4 (localmente)
[1] 4
>y # Sin embargo, y no ha cambiado. La y local desaparece
[1] 10

Parámetros por defecto


Una función puede tener varios argumentos, y podríamos querer omitir especificar
algunos de ellos, asumiendo que la función tomará por defecto unos valores
preespecificados.

Como ejemplo, vamos a redefinir la función desviación típica, de modo que tengamos
la posibilidad de calcular la desviación típica corregida y sin corregir:

> desv = function(x,n=length(x)-1){ sum((x-mean(x))^2)/n} # Definición de


# la función
> x<-1:10 # Generación de un conjunto de datos
> desv(x) # Desviación típica corregida (al no especificar el
# segundo parámetro, se divide por n-1
[1] 9.166667
> desv(x,10) # Desviación típica sin corregir
[1] 8.25

Funciones con un número variable de argumentos

En R es posible definir funciones con un número variable de argumentos. Para ello, la sintaxis es:

f = function(x, ...) { cuerpo de la función }

f = function(...,x) { cuerpo de la función }

En el primer caso, la función podría llamarse sin hacer referencia explícita a x (por ejemplo f(2) ).
En el segundo caso deberíamos especificar f(x=2), dado que el sistema, al encontrar primero los
argumentos variables, no podría saber si nos estamos refiriendo a x o a uno de los argumentos
variables.

Vamos a poner un ejemplo en dos fases. En primer lugar, para entender como funciona al tema,
definiremos una función que simplemente devuelve sus argumentos:

> f = function(...){ L <- list(...) ; return(L)}


> f(1,2,3)
[[1]]
[1] 1
[[2]]
[1] 2
[[3]]
[1] 3
> f(c(1,2),c(3,4,5))
[[1]]
[1] 1 2
[[2]]
[1] 3 4 5

Así pues, es variable el número de argumentos, tanto como el número de elementos de cada uno.

Vamos a aprovechar esta facilidad para definir una función que devuelva algunas medidas resumen
de las distribuciones que se le pasen como argumento. La entrada a la función será una serie de
conjuntos de datos, y la salida la media, varianza, mínimo y máximo de cada uno de los conjuntos.

f = function(...)
{
datos = list(...)
medias = lapply(datos,mean) # lapply aplica una función sobre una lista
varianzas = lapply(datos,var)
maximos = lapply(datos,max)
minimos = lapply(datos,min)

for(i in 1:length(datos))
{
cat("Distribución ",i,": \n") # La función cat es para visualizar cosas
cat("media: ",medias[[i]],"varianza: ",varianzas[[i]],"maximo: ",maximos[[i]],"minimo:
",minimos[[i]],"\n")
cat("------------------------------------------------\n")
}

Veamos un ejemplo sencillo:

> f(c(1,2),c(1,3,5,7),c(-1,2,-5,6,9))

Distribución 1 :
media: 1.5 varianza: 0.5 maximo: 2 minimo: 1
------------------------------------------------
Distribución 2 :
media: 4 varianza: 6.666667 maximo: 7 minimo: 1
------------------------------------------------
Distribución 3 :
media: 2.2 varianza: 30.7 maximo: 9 minimo: -5
------------------------------------------------

O también:

> x = rnorm(100)
> y = runif(50)
> f(x,y)

Distribución 1 :
media: 0.1616148 varianza: 0.87319 maximo: 2.201592 minimo: -2.143932
------------------------------------------------
Distribución 2 :
media: 0.4985783 varianza: 0.08253697 maximo: 0.9881924 minimo: 0.01329678
------------------------------------------------

# calcular el porcentaje de purinas y pirimidinas en una secuencia de ADN


Purinas<-function(x) {
Purinas <- 0 # asignar 0 a Purinas
for (n in x) {
if (n == "A") Purinas <- Purinas + 1 # contar las purinas
if (n == "G") Purinas <- Purinas + 1 # contar las purinas
}
return((Purinas/(length(x)))*100)
}

Pirimidinas<-function(x) {
Pirimidinas <- 0 # asignar el valor de 100 a Pirimidinas
{
Pirimidinas <- 100-Purinas(x)
}
return(Pirimidinas)
}
#> Pirimidinas(c("A","T","T","G","G","G"))
#[1] 33.33333
#> Purinas(c("A","T","T","G","G","G"))
#[1] 66.66667
#> Purinas(c("A"))
#[1] 100
#> Pirimidinas(c("A"))
#[1] 0
------------------------------------------------------------------------------------
Todas las lineas que se encuentran después de un # son comentarios que se agregan al
código de la función. En este caso el primer comentario menciona lo que hacen las
funciones
que se van a escribir.
Primero se define la primer función y se le da el nombre que se desee y que se aplicara
al objeto x (x), para nuestro caso
el nombre es "purinas" y continuación se empieza a escribir el cuerpo de la función y se
escribe después de haber abierto un corchete ({) :

Purinas<-function(x) {

Le asignamos un valor de 0 (para empezar) al porcentaje de purinas en la secuencia, y a


partir
dse este valor empezaremos a hacer el conteo de las purinas para cada base en la
secuencia:

Purinas <- 0 # asignar 0 a Purinas

Ahora le decimos que para cada elemento n del objeto x , en este caso la secuencia de
ADN, se le aplicara
el resto de el código de la función, en pocas palabras, abrimos un loop:

for (n in x) {

A continuación le decimos lo que debe hacer con cada elemento n del objeto x, cada vez
que lo evalué.
Para nuestro caso seria: si (if) el objeto(n) que encuentra en la secuencia(x) es "A" o
"G", entonces le sume
1 a Purinas, que antes habíamos asignado un valor de 0; Que pase al siguiente
elemento(n) de la secuencia(x)
y que vuelva a hacer lo mismo:

if (n == "A") Purinas <- Purinas + 1 # contar las purinas


if (n == "G") Purinas <- Purinas + 1 # contar las purinas

Finalmente le decimos que cierre la parte de operaciones de la función con el corchete


(}) y que lo ue la función nos
debe arrojar (return) es el valor del ((número de las bases que sean Purinas, sobre la
longitud (length) de la secuencia(x), es decir, el
length(x) es el mismo número de elementos(n) de x), multiplicado x 100).
En pocas palabras, le decimos que nos arroje como resultado de la función el porcentaje
de bases purinicas que se encuentran en la secuencia de
ADN:

}
return((Purinas/(length(x)))*100)
}

De este modo, cerramos el cuerpo y terminamos de escribir, nuestra primer función.


Y empezamos a escribir la segunda función, a la que llamaremos "Pirimidinas":

Pirimidinas<-function(x) {

Al igual que como lo hicimos con la primer funciòn, asignamos una variable llamada
Pirimidinas
con el valor inicial de 0:

Pirimidinas <- 0 # asignar el valor de 100 a Pirimidinas

Lo siguiente es algo muy importante en R, y es el hecho de que se puede llamar una


función dentro de otra función,
en nuestro caso llamaremos la función "Purinas" (previamente creada) y a 100 le
restaremos el valor de el resultado de
la función "Purinas" de una secuencia de ADN (x), puesto que el resto de las bases de
las secuencias que no son
Purinas, deben explicitamente ser Pirimidinas y como es un porcentaje, el porcentaje de
Pirimidinas será 100 menos el
porcentaje de Purinas que ya calculamos previamente:

{
Pirimidinas <- 100-Purinas(x)
}

Finalmente lo que hacemos es decirle a R que nos arroje el resultado de la resta anterior,
que es el resultado del porcentaje
de Pirimidinas en la secuencia de ADN:

return(Pirimidinas)
}

Para finalizar lo que se ponen, son ejemplos de casos en los que se prueba o se utiliza la
función, de tal forma que nos aseguremos
de que las dos funciones esta escritas y definidas correctamente, estos ejemplos, se
escriben en forma de comentarios precedidos por
el símbolo de numeral #:

#> Pirimidinas(c("A","T","T","G","G","G"))
#[1] 33.33333
#> Purinas(c("A","T","T","G","G","G"))
#[1] 66.66667
#> Purinas(c("A"))
#[1] 100
#> Pirimidinas(c("A"))
#[1] 0

**Variable Scope
Una variable/objeto que se crea dentro de una función, es llamada una variable local,
puesto que es temporal y solo se utiliza
dentro de la función, mientras se efectúan los cálculos u operaciones, y una vez
obtenido el resultado esta variable es eliminada.
En nuestra función de ejemplo las variables "Purinas" y "Pirimidinas" a las que les
asignamos 0 inicialmente, al igual que la variable
n, son variables locales.

De tal modo que cuando llamemos a "n" por fuera de la función, en la consola de R, nos
dira
que el objeto 'n' no existe y que no lo encuentra.
y si llamo a "Pirimidinas" en la consola de R, el me dirá que Pirimidinas es una función
que llame con ese nombre
, pero no me lo reconoce como una variable por fuera de la función:
------------------------------------------------------------------------------------
> Pirimidinas(c("A","T","T","G","G","G"))
[1] 33.33333
>n
Error: object 'n' not found
> Pirimidinas
function(x) {
Pirimidinas <- 0 # asignar el valor de 100 a Pirimidinas
{
Pirimidinas <- 100-Purinas(x)
}
return(Pirimidinas)
}
> care_perro <- 08071988
------------------------------------------------------------------------------------
De manera que se pueden definir variables locales dentro de funciones, que tengan los
mismos nombres de variables
globales por fuera de la función o inclusive con el mismo nombre de la función, y R no
se confundirá y mantendrá ambas variables
como separadas.
Y finalmente la variable "care_perro" es una variable global que creamos por fuera de
una función y que contiene el número
08071988 .

**Argumentos por defecto


Consideremos la función:

> firulallo <- function(x,y=5,z=F) { ... }

Esta función la llamamos "firulallo" y "no tiene cuerpo", puesto que dejamos abierto o
vacía la definición de la función per se con los corchetes
y los tres puntos, ( { ... } ).
Mientras tanto, lo que si definimos son argumentos que se ejecutaran por defecto,
siempre y cuando el usuario no cambie el argumento o lo re-defina,
es decir, para y definimos un valor de 5 y para z, que es una variable lógica la definimos
como FALSE o F;
de tal forma que si ejecutamos la función sin cambiar los argumentos por defecto, R
utilizará los que definimos al escribir la función.

Para un objeto x, con valor a 100, utilizando los argumentos por defecto:

>firulallo(100)

Para un objeto x, con valor de 100, cambiando los argumentos por defecto:

>firulallo(100, y=60, Z=TRUE)

Para un objeto x, con valor de 126, cambiando solo un argumento por defecto (el otro
argumento, el que no cambiemos, se ejecutara por defecto):

>firulallo(126, y=45)

ESO ES TODO POR AHORA Y ESPERO PRONTO PODER COMPARTIR MÁS


COSAS Y QUE LES HAYA GUSTADO Y LES PAREZCA MUCHO MÁS FÁCIL
AHORA!

S-ar putea să vă placă și