comment 1

De los comandos a las funciones: Tres razones para programar funciones propias en R

As soon as you notice that the changes you are making are at all substantial or start to see recurring patterns in them, consider turning the patterns into an R function.

J.M Chambers [1, p. 40]

Siempre me ha llamado la atención lo que nos cuesta (a muchos) utilizar el entorno R aprovechando una de sus mayores ventajas: programando funciones creadas por nosotros mismos, o funciones propias. Imagino, queridos lectores, que muchos de vosotros compartiréis conmigo la experiencia de haber empezado analizando datos con un software estadístico rígido como el SPSS. Al pasar a R, a menudo solemos reproducir la forma de trabajar que traemos con nosotros mismos, y no solemos pasar más allá de utilizar funciones ya programadas en paquetes que vamos descargando. Al menos de entrada, no se nos ocurre que si añadiéramos nuestra propia funcionalidad a los programas de R, podríamos diseñar arquitecturas mucho más eficientes y con mayor alcance para nuestros análisis de datos.

Al analizar datos estadísticos, la mayor parte del tiempo trabajamos por iteración: introducimos una orden, obtenemos un primer resultado que se parece a lo que estamos buscando, modificamos ligeramente la orden anterior (léase: copy-paste de código + insertar algún cambio), obtenemos otro resultado, volvemos a modificar código… Pues bien: si en este proceso nos encontramos reutilizando partes significativas de nuestro código, es señal de que necesitamos programar una función propia, ¡aunque seamos unos absolutos principiantes!

Programar una función en R consiste básicamente en empaquetar el código que estamos reutilizando con ligeras modificaciones, lo cual no es en absoluto difícil, y tiene unas ventajas enormes en cuanto a la eficiencia (uso del tiempo) y la garantía en los cálculos (minimizar la probabilidad de cometer errores). John M. Chambers nos indica tres razones tajantes para adoptar esta forma de trabajo en su libro Software for Data Analysis. Las traduzco y adapto ligeramente aquí, y seguidamente las ilustraré con ayuda de un ejemplo del mismo libro.

1Nos obliga a reflexionar acerca de los patrones que estamos repitiendo, y que hemos decidido empaquetar en forma de función para su reutilización. Esta forma de trabajar nos hará apreciar más posibilidades de análisis que de otra manera serían difíciles de ver.

2Ahorraremos significativamente a la hora de teclear código. En lugar de repetir un patrón recurrente el cual, generalmente, estará compuesto por unas cuantas líneas, bastará con teclear una simple llamada a nuestra función.

3Los cálculos que se realizan al llamar a una función son locales. Esto es importante porque asegura que no se producirán, generalmente, efectos indeseados (i.e. cálculos incorrectos por el hecho de usar objetos que tenemos almacenados previamente en el entorno).

Para entender cada uno de los tres motivos, vayamos ahora con el ejemplo, tomado del mismo libro (páginas 40-43). Se trata de generar modelos de regresión lineales multivariantes a partir del conjunto de datos clouds, con 24 observaciones y 7 variables, de las cuales la última, rainfall (cantidad de lluvia en cientos de millones de metros cúbicos) se explica en función de las restantes 6 variables. Imaginad que hemos ajustado un modelo dado, y lo hemos guardado en el objeto model:

data(clouds)
formula <- rainfall ~ seeding *  (sne + cloudcover + prewetness + echomotion) + time
model <- lm(formula, data = clouds)

Ahora queremos eliminar todos los términos en los que interviene la variable sne, y guardar el resultado en un segundo objeto, model2. Adicionalmente, nos interesa observar cómo cambian los residuos al pasar de un modelo a otro, así que los representamos gráficamente usando plot:

model2 <- update(model, ~.-sne - seeding:sne)
plot(resid(model), resid(model2))
abline(0,1) # para dibujar una recta x=y

Estamos satisfechos con lo que hemos hecho, pero ¿qué pasa si quisiéramos hacer lo mismo, pero con otro término, por ejemplo cloudcover? ¿O con todos los términos posibles: cloudcover, prewetness, y echomotion? Repetir lo anterior para cada caso parece algo excesivo: necesitamos una función (con esto, el motivo 2). Fijémonos en nuestro patrón recurrente (updateplotabline), será el cuerpo de la función:

updatePlotResid <- function(dropTerm){  # 1 argumento: dropTerm, término a eliminar
  model2 <- update(model, dropTerm)        # |---> aquí comienza el patrón recurrente
  plot(resid(model), resid(model2))
  abline(0,1)			           #.....|---> y aquí termina
  return(model2)		           # y nos devuelve el nuevo modelo.
}

Para usar la función, bastará con dar un valor a sus argumentos; como antes, guardamos el resultado en un objeto model2.1. Usando identical comprobaremos que el resultado es exactamente el mismo:

model2.1 <- updatePlotResid(dropTerm=~.-sne - seeding:sne)
identical(model2, model2.1) #TRUE

En este punto, una advertencia: en la función que acabamos de crear, updatePlotResid, model no es un argumento sino una variable libre: al llamar a updatePlotResid, solamente necesitamos informar del valor del término dropTerm. R tendrá que encargarse de buscar los valores que le faltan (en este caso, el de model), empezando por el entorno donde la función fue definida. Si, por descuido, tuviéramos un valor incorrecto almacenado en model, los resultados serían incorrectos; aún más: ¡probablemente no nos enteraríamos! Pero esto tiene solución: podemos introducir model como un argumento más de la función, y mantener igual todo lo demás (con esto, el motivo 3):

updatePlotResid <- function(model, dropTerm){  # 2 argumentos: modelo+término
  model2 <- update(model, dropTerm)         # ---> Todo lo demás igual.
  plot(resid(model), resid(model2))
  abline(0,1)
  return(model2)
}

Al llamar a la función, garantizaremos que model contiene el valor correcto, volviéndolo a definir al pasar su valor:

model2.2 <- updatePlotResid(model=rainfall ~ seeding * (sne + cloudcover + prewetness + echomotion) + time, dropTerm=~.-sne - seeding:sne )

(Vosotros mismos podéis comprobar, usando identical, que model2.2 coincide con model2 y con model2.1).

Finalmente, vamos a emplear la función para lo que queríamos: probar todas las combinaciones de forma segura y rápida. De camino aprovechamos para dar un nombre informativo a cada objeto model, y así organizar nuestros análisis (y así estaremos generando una arquitectura… con esto, el motivo 1, en mi opinión, el más importante de los tres):

modelSne <- updatePlotResid(model=model, dropTerm=~.-sne-seeding:sne )
modelCover <- updatePlotResid(model=model, dropTerm=~.-cloudcover-seeding:cloudcover )
modelEcho <- updatePlotResid(model=model, dropTerm=~.-echomotion-seeding:echomotion )

Referencias

[1] John M.Chambers, Software for Data Analysis. Programming with R. Springer.

[2] El conjunto de datos clouds se encuentra en el paquete HSAUR que acompaña el libro Handbook of statistical analysis using R. El capítulo que hacer referencia a este ejemplo está disponible aquí.

Anuncios
Filed under: R

About the Author

Escrito por

Nací en el año 1980 en Donostia, hija única de una familia entusiasta de las Letras. A pesar de ello a los dieciocho años tomé la rara decisión de estudiar Matemáticas. Obtuve la Licenciatura en 2003, y comencé mi andadura profesional en Tekniker, un centro tecnológico afincado en Eibar dedicado al mundo de la automoción. Desde el inicio me encontré analizando datos estadísticos que nunca se ajustaban a los ejemplos de los libros de texto. Tras un breve paso por la Universidad de Deusto, el destino me llevó a un Banco donde trabajé con modelos de riesgo de crédito (de esos que saben si uno va a pagar incluso antes de que se le ocurra pedir la hipoteca). Debido a la crisis salí catapultada de aquel puesto, circunstancia que aproveché para cuidar plenamente de mi hijo recién nacido, Miguel. A los pocos meses de nacer el segundo, Ion, corrí a incorporarme a un proyecto sobre enlace de encuestas en el Instituto Vasco de Estadística - Eustat. Ahora sigo trabajando con datos que no se ajustan a los libros de texto, pero ya casi no me importa, y soy más feliz pensando que a lo mejor se trata de hacer algo que no está escrito.

1 Comment so far

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

w

Conectando a %s