12.2 Programación funcional en R

La programación funcional, en un sentido amplio, es aquella en que determinadas funciones31 admiten otras como argumento. Por ejemplo, la función sapply:

cuadrado.raro <- function(x) if(x < 5) x^2 else -x^2
sapply(1:10, cuadrado.raro)
##  [1]    1    4    9   16  -25  -36  -49  -64  -81 -100

La programación funcional es sumamente poderosa y sugiere permite programar de la siguiente manera:

  • Crear funciones pequeñas y simples que resuelven un problema pequeño y acotado
  • Aplicar esas funciones a grupos homogéneos de valores.

En el ejemplo de más arriba hemos construido una función, cuadrado.raro, y con la función sapply (lapply, como hemos visto previamente, es una alternativa) se las hemos aplicado a una lista de valores homogéneos, los números del 1 al 10.

Hay muchas funciones en R, algunas de las cuales son ya conocidas, que admiten otras como argumento. Algunas de las más corrientes son:

  • sapply y lapply (que son casi la misma)
  • tapply
  • apply y mapply
  • Las funciones ddply, ldply, etc. del paquete plyr

Dos ejemplos de usos muy habituales de estas funciones son

lapply(iris, class)
sapply(iris, length)

que permiten inspeccionar el tipo de columnas de una tabla. Aprovechan, precisamente, que una tabla es una lista de columnas y las recorren una a una.

Generalmente, el código que usa este tipo de funciones es más breve y legible.

Es conveniente recordar aquí que si consultas la ayuda de las fucniones listadas más arriba verás que suelen incluir un argumento especial, ... que permite pasar argumentos adicionales a la función a la que llaman. En la sección en que introdujimos la función tapply discutimos un caso de uso.

12.2.1 Funciones anónimas

Las funciones que hemos usado son de dos tipos: o exiten en R o las hemos definido previamente. Pero en ocasiones es conveniente usar funciones anónimas32 de esta manera:

sapply(1:10, function(x) if(x < 5) x^2 else -x^2)

Conviene particularmente cuando la función solo se usa una única vez. Las funciones anónimas, debidamente usadas, confieren brevedad y expresividad al código.

Crea el vector de nombres de ficheros de data usando dir; luego, aplícale una función que lea las líneas (readLines) y las cuente.

Usa nchar para contar el número de caracteres de esos ficheros.

Haz lo mismo usando la función ldply de plyr.

12.2.2 Map, reduce y más

Existen dos operaciones fundamentales en programación funcional. La primera es map y consiste en aplicar una función a todos los elementos de una lista o vector. Es, de hecho, la operación que hemos realizado más arriba:

sapply(1:10, function(x) if(x < 5) x^2 else -x^2)

Ese código aplica al vector 1:10 la función anónima que se le pasa a sapply como segundo argumento. Aunque en muchos lenguajes de programación existe una función map explícita (con ese nombre), en R hay varias: además de sapply, también están lapply o apply. La vectorización que hemos discutido previamente es un mecanismo implícito para realizar maps; p.e.,

sqrt(1:10)

aplica la función sqrt a cada elemento de su argumento.

La otra gran operación de la programación funcional es reduce. Consiste en aplicar una operacion binaria (p.e., la que suma dos números) a una lista de valores iterativamente. En R se pueden realizar reduces explícitamente:

Reduce(function(a, b) a + b, 1:10)

La función anónima anterior admite dos argumentos, a y b y los suma. Dentro de Reduce, la función anónima suma los dos primeros elementos, al resultado le suma el tercero, al resultado el cuarto, etc., hasta proporcionarnos la suma total. De nuevo, es frecuente poder realizar estas operaciones implícitamente. Por ejemplo, usando sum:

sum(1:10)

Vamos a crear el objeto x <- split(iris, iris$Species), que es una lista de tres tablas. Usa lapply o sapply para examinarlas: dimensión, nombres de columnas, etc.

Usa Reduce con la función rbind para apilar las tres tablas contenidas en x (véase el ejercicio anterior).

Nota: este ejercicio tiene aplicaciones prácticas importantes. Por ejemplo, cuando se leen tablas del mismo formato de ficheros distintos y es necesario juntarlas todas, i.e., apilarlas, en una única tabla final.

Operaciones tales como

sum(sqrt(1:10))

son, por lo tanto, los famosos mapreduces33 popularizados por las herramientas de big data.

Una operación relacionada con map y muy frecuente en R es replicate. Esta función permite llamar repetidamente a una función (o bloque de código) para, muy frecuentemente, realizar simulaciones:

simula <- function(n, lambda = 4, mean = 5){
  n.visitas <- sum(rpois(n, lambda))
  sum(rnorm(n.visitas, mean = mean))
}

res <- replicate(1000, simula(10, lambda = 7))

La diferencia fundamental con map es que no opera sobre un vector: crea uno de la nada.

Otra metaoperación básica en programación funcional es filter. Un filtro permite seleccionar aquellas observaciones (p.e., en una lista) que cumplen cierta condición. En R sabemos implementar esa operación usando los corchetes. Por ejemplo, para seleccionar los múltiplos de tres en un vector, podemos hacer

x <- 1:20
x[x %% 3 == 0]
## [1]  3  6  9 12 15 18

Pero en R también se puede usar34 la función Filter:

Filter(function(i) i %%3 == 0, x)
## [1]  3  6  9 12 15 18

Muy importantes en R debido a lo habitual de operar con tablas son las operaciones basadas en otra operación funcional, el groupby. El groupby permite partir un objeto en trozos de acuerdo con un determinado criterio para operar a continuación sobre los subloques obtenidos. tapply es una función que implementa una versión básica del groupby. Mucho más potente y versátil que ella es la función ddply del paquete plyr. Esta función, como ya sabemos, realiza tres operaciones:

  • Parte una tabla convenientemente (groupby).
  • Aplica (map) una función sobre cada subtabla.
  • Recompone (reduce) una tabla resultante a partir de los trozos devueltos en el paso anterior.

12.2.2.1 Para saber más

La programación funcional proporciona una serie de operaciones genéricas, map, reduce, filter, groupby y algunas otras más que permiten modelar conceptualmente a los programas. Los programadores experimentados identifican frecuentemente un determinado algoritmo como, por ejemplo, un map seguido de un filter y un reduce final. Eso les facilita, por ejemplo, el poliglotismo: al final, desarrollar en cualquier lenguaje se reduce a expresar esas operaciones genéricas en la sintaxis específica.

La programación funcional, además, abre la puerta de muchas aplicaciones big data, donde impera desde sus inicios la programación funcional. El mismo Hadoop se concibió alrededor del concepto del MapReduce y la más popular de las herramientas actuales de big data, Spark, es una extensión del lenguaje funcional Scala.


  1. Se las conoce como funciones de orden superior.

  2. En otros lenguajes de programación se las conoce también como funciones lambda.

  3. Mapreduce es una operación genérica que consiste en un map seguido de un reduce; muchas manipulaciones de datos se reducen en última instancia a un mapreduce o a una concatenación de ellos.

  4. Aunque no se recomienda: el corchete es más sucinto.