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.
suma <- function(a1, d, n) {
an <- a1 + (n - 1) * d
((a1 + an) * n)/2
}
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
.
f <- function(x, y) {
cos(y)/(x^2 - 3)
}
z <- outer(x, y, f)
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
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.
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 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.
browser
interrumpe la ejecución a partir de ese punto y permite seguir la ejecución o examinar el entorno; con n paso a paso, si otra tecla sigue la ejecución normal. Q para salir.
debug
es como poner un broswer
al principio de la función y se ejecuta la función paso a paso. Se sale con Q.
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)
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.).