Funciones

R es un lenguaje que permite crear nuevas funciones. Una función se define con una asignación de la forma

nombre <- function(arg1, arg2, ...) {
    expresión
}

La expresión es una fórmula o grupo de fórmulas que utilizan los argumentos para calcular su valor. El valor de dicha expresión es el valor que proporciona R en su salida y éste puede ser un simple número, un vector, una gráfica, una lista o un mensaje. Recuerda leer estas indicaciones para mejor lectura de las personas con las que trabajarás.

Ejemplo 1. Suma de una progresión aritmética

suma <- function(a1, d, n) {
    an <- a1 + (n - 1) * d
    ((a1 + an) * n)/2
}

Ejemplo 2. Una función con cuatro argumentos

    FuncionPrueba <- function(a,b,c = 4,d = FALSE){x1<-a*z ...}

Observa que los argumentos a y b tienen que darse en el orden debido o, si los nombramos, podemos darlos en cualquier orden:

FuncionPrueba(4, 5)
FuncionPrueba(b = 5, a = 4)

Pero los argumentos con nombre siempre se tienen que dar después de los posicionales

FuncionPrueba(c = 25, 4, 5)  # error!!

Los argumentos c y d tienen valores predefinidos. Podemos especificarlos nosotros o no (i.e., usar los valores predefinidos). Con args(nombre.funcion) podemos mostrar los argumentos de cualquier función. Además la expresión ... permite pasar argumentos a otra función:

FuncionPrueba2 <- function(x, y, label = "la x", ...) {
    plot(x, y, xlab = label, ...)
}

FuncionPrueba2(1:5, 1:5)
FuncionPrueba2(1:5, 1:5, col = "red")

Para realizar funciones de dos variables se puede utilizar el comando outer.

Ejemplo 3. Ejecutando una función de dos variables

f <- function(x, y) {
    cos(y)/(x^2 - 3)
}
z <- outer(x, y, f)

Ejemplo 4. La familia apply

Utilizar la función apply es generalmente mucho más eficiente que un bucle. Además de más claro, más fácil, etc..

ax <- matrix(rnorm(20), ncol = 5)
medias.por.fila <- apply(ax, 1, mean)
por.si.na <- apply(ax, 1, mean, na.rm = TRUE)
MiFuncion <- function(x) { return(2*x - 25)}
MiFuncion.por.fila <- apply(ax, 1, MiFuncion)
mas.simple <- apply(ax, 1, function(x){r, eval = FALSEeturn(2*x -25)})
medias.por.columna <- apply(ax, 2, mean)
sample.rows <- apply(ax, 1, sample)
DosCosas <- function(y){r, eval = FALSEeturn(c(mean(y), var(y)))}
apply(ax, 1, DosCosas)
t(apply(ax, 1, DosCosas))
parameters <- cbind(mean = -5:5, sd = 2:12)
z.data <- matrix(rnorm(1000 * 11), nrow = 11)
data <- (z.data * parameters[, 2]) + parameters[, 1]
apply(data, 1, mean)
apply(data, 1, sd)

Las funciones sapply(X,función) y lapply(X,función) son como apply(x,i,función) pero no hay que especificar el índice i=2; sapply intenta simplificar el resultado a un vector o a una matriz (la “s” es de “simplify”“), pero lapply siempre devuelve una lista. Ambas pueden aplicarse a vectores, listas, arrays.

data(airquality)
sapply(airquality, function(x) sum(is.na(x)))

La función tapply(x,y,función) calcula la función especificada sobre el objeto x según las categorías de y.

x <- c(19, 14, 15, 17, 20, 23, 19, 19, 21, 18)
trat <- c(rep("A", 5), rep("B", 5))
x.media <- tapply(x, trat, mean)
x.media

Las funciones apply, sapply, lapply y tapply son muy útiles porque contribuyen a hacer el código más legible, fácil de entender, y facilitan posteriores modificaciones y aplicaciones. Consejo: cada vez que vayamos a usar un "loop” intentemos sustituirlo por algún miembro de familia apply. Algunas funciones hacen un apply:

x1 <- 1:10
m1 <- matrix(1:20, ncol = 5)
d1 <- as.data.frame(m1)
mean(x1)
mean(d1)
sd(x1)
sd(d1)
median(m1)
median(d1)

Si te ha gustado el uso de la familia apply puede que también te interese echar un vistazo al paquete plyr

Ejemplo 5. Lexical scoping

En la función FuncionPrueba, \( z \) es una variable libre, entonces ¿cómo se especifica su valor? Lexical scoping. Ver documento Frames, environments and scope in R and S-PLUS de J. Fox en http://cran.r-project.org/doc/contrib/Fox-Companion/appendix.html y sección 10.7 en An introduction to R. También ver demo(scoping)

cubo <- function(n) {
    sq <- function() n * n  # aquí n no es un argumento
    n * sq()
}

En esto R (lexical scope) y S-PLUS (static scope) son distintos.

Ejemplo 6. Control de ejecución

Las principales instrucciones para el control de la ejecución son, como en otros lenguajes:

if (cond) expr
if (cond) cons.expr else alt.expr
for (var in seq) expr
while (cond) expr
repeat expr
break
next

La expresión expr (también alt.expr) puede ser una expresión simple o una de las llamadas expresiones compuestas, normalmente del tipo {expr1; expr2}. Uno de los errores más habituales es el olvido de los corchetes {...} alrededor de las instrucciones, i.e. después de if(...) o for(...)

Instrucciones de condicionamiento con if (cond.logica) instrucción else instrucción.alternativa

FuncionPrueba4 <- function(x) {
    if (x > 5) 
        print("x > 5") else {
        y <- runif(1)
        print(paste("y is ", y))
    }
}

Instrucciones de condicionamiento con ifelse es una versión vectorizada (Thomas Unternährer, R-help, 2003-04-17)

OddEven <- function(x) {
    ifelse(x%%2 == 1, "Odd", "Even")
}

mtf <- matrix(c(TRUE, FALSE, TRUE, TRUE), nrow = 2)
ifelse(mtf, 0, 1)

Bucles for (variable.loop in valores) instrucción

for (i in 1:10) cat("el valor de i es", i, "\n")
continue.loop <- TRUE
x <- 0
while (continue.loop) {
    x <- x + 1
    print(x)
    if (x > 10) 
        continue.loop <- FALSE
}

utilizaremos break para salir de un loop.

Cuando algo va mal en la ejecución de una función

Cuando se produce un error, podemos ejecutar traceback() que nos informará de la secuencia de llamadas antes del “crash” de nuestra función. Es útil cuando se producen mensajes de error incomprensibles. Cuando se producen errores o la función da resultados incorrectos o warnings indebidos podemos seguir la ejecución de la función.

Por ejemplo

MiFuncion2 <- function(x, y) {
    z <- rnorm(10) + y2 <- z * y + y3 <- z * y * x + return(y3 + 25)
}
MiFuncion2(runif(3), 1:4)
debug(MiFuncion2)
MiFuncion2(runif(3), 1:4)
undebug(MiFuncion2)
# insertar un browser() y correr de nuevo

Un ejemplo más

d3 <- data.frame(g1 = runif(10), g2 = rnorm(10), id1 = c(rep("a", 3), rep("b", 
    2), rep("c", 2), rep("d", 3)))

MiFuncion3 <- function(x) {
    las.medias <- mean(x[, -3])
    las.vars <- var(x[, -3])
    max.total <- max(x[, -3])
    tabla.clases <- table(x[, 3])
    return(list(row.means = las.medias, row.vars = las.vars, maximum = max.total, 
        factor.classes = tabla.clases))
}
MiFuncion3(d3)

Ejecución no interactiva

Con source abrimos una sesión de R y ejecutamos todo el código dentro un archivo determinado:

source("mi.fichero.con.codigo.R")

También podemos ejecutar el archivo con todo nuestro código usando BATCH: Rcmd BATCH mi.fichero.con.codigo.R

En general source es más útil porque informa inmediatamente de errores en el código. BATCH no informa, pero tiene la ventaja de no requerir una sesión abierta de R para ejecutar el código (esto es my importante si en un futuro próximo vas a utilizar supercomputación en la forma de colas CONDOR o SLURM). Si lo vas a usar sería conveniente echar un vistazo a la ayuda Rcmd BATCH --help

Puede que necesitemos explícitos print statements o hacer source(my.file.R, echo = TRUE). La función sink es el inverso de source (lo manda todo a un fichero). Se pueden crear paquetes, con nuestras funciones, que se comporten igual que los demás paquetes (ver Writing R extensions). Si esta es tu intención te recomendamos utilizar Rstudio, ya que la creación de paquetes y la depuración de los mismos ha mejorado mucho en las últimas versiones.

Recuerda que R puede llamar código compilado en C/C++ y FORTRAN. Ver .C, .Call, .Fortran. Lexical scoping es importante en programación más avanzada. R es un verdadero object-oriented language. Dos implementaciones, las S3 classes y las S4 classes.

(nota: No hemos mencionado el “computing on the language” (ej., do.call, eval, etc.).