lunes, 21 de diciembre de 2009

Aplicar una función a un vector o una lista


Ciertamente R está preparado para operar todos los elementos de un vector de forma casi "natural". La mayoría de funciones son vectoriales. No ocurre lo mismo con una lista. Aunque muchas funciones devuelven una lista, que permite mucha flexibilidad, no se puede aplicar una función cualquiera directamente a una lista. Para ello disponemos de las funciones lapply y sapply.
Las dos funciones admiten, básicamente, dos argumentos: el primero es el objeto vector o lista sobre el que se aplica la función expresada en el segundo argumento. La diferencia entre ambas es que mientras lapply devuelve una (l)ista, sapply siempre tratará de (s)implificar el resultado en un vector, si es posible.

> mi.lista <- list(a=1:10, b=letters[1:3], cc=c(TRUE,FALSE,TRUE,FALSE))

> length(mi.lista)
[1] 3

> lapply(mi.lista, length)
$a
[1] 10

$b
[1] 3

$cc
[1] 4

> sapply(mi.lista, length)
a b cc
10 3 4

Observamos que mientras la función length aplicada a la lista nos devuelve el número de elementos de la lista (3), aplicada mediante un lapply o sapply nos da la longitud de sus elementos.

Cuando el objeto del primer argumento no es un vector o una lista, se forzará automáticamente que sea una lista mediante la función as.list. De este modo podemos aplicar estas funciones a un data.frame que no es estrictamente una lista, pero su conversión es sencilla.

> mi.df <- data.frame(a=1:10, b=letters[1:10], ca=runif(10), cb=rnorm(10))
> class(mi.df)
[1] "data.frame"

> sapply(mi.df,class)
a b ca cb
"integer" "factor" "numeric" "numeric"

> mi.df.num <-
mi.df[ ,sapply(mi.df,class)=="numeric"]

> sapply(mi.df.num, mean)
ca cb
0.59009349 0.02423536


Cuando la función a aplicar (segundo argumento) necesita fijar sus propios argumentos, éstos se pueden incluir en las funciones lapply o sapply.

> sapply(mi.df.num, mean, trim = 0.05)

Por último, como estas funciones sirven para repetir el mismo cálculo sobre los elementos de un vector o lista, podemos pensar en utilizarlas siempre que podamos en lugar de un bucle (loop).
Además, tenemos una función replicate que permite simulaciones como la siguiente:

hist(replicate(100,mean(rexp(10))))

viernes, 11 de diciembre de 2009

Fórmulas matemáticas en un gráfico


Algunas de las funciones que permiten introducir texto en un gráfico son text, mtext, axis, title,...
Si en el argumento que explicita el texto de alguna de estas funciones se escribe una expresión, R la interpretará como una expresión matemática y le dará formato al estilo TeX. Dichas expresiones son el resultado de una función expression().
En una expresión de R ciertos nombres se interpretan como símbolos matemáticos, por ejemplo, alpha será la letra griega, sum será el símbolo de sumatorio, etc.
Para ver las diversas posibilidades, se puede consultar la ayuda help(plotmath) o la demostración demo(plotmath).

El gráfico inicial se consigue con el siguiente código:

curve(dnorm(x),-3,3,axes=F)
box()
axis(2)
axis(1,at=0,labels=c(expression(mu)))
title("Densidad normal")
text(2, 0.35, expression(paste(f(x)==frac(1, sigma*sqrt(2*pi)), " ",
plain(e)^{frac(-(x-mu)^2, 2*sigma^2)})),
cex = 1.2)

En ciertas situaciones, como por ejemplo para definir una función, se necesita combinar texto con valores y variables. En ese caso no es posible utilizar la función expression() ya que las variables se tratarían literalmente como texto. Para solucionar este caso se utiliza la función substitute().
Ejemplo:

mifunc <- function(media) {
text(2, 3, substitute(paste("El valor de ", bar(x), " es ", media)))
}

Este tipo de fórmulas matemáticas se puede reproducir en cualquiera de los dispositivos gráficos de pantalla como X11, Windows y Quartz y, gracias a la información de las fuentes Adabe Tipo 1 estándar que tiene R, también en PostScript y PDF.

martes, 8 de diciembre de 2009

Clases S3 y S4


El sistema de clases de los objetos en R proporciona alguno de los mecanismos de la programación orientada a objetos como el despacho del método (method dispatch) y la herencia.
El método es la implementación de un algoritmo que representa una operación o función que un objeto realiza. El conjunto de los métodos de un objeto determinan el comportamiento del objeto. En R, el despacho del método consiste en examinar la clase de los argumentos de una función para decidir (despachar) la versión adecuada para los objetos de esa clase. No todas las funciones de R tienen despacho del método. Las que sí lo tienen se llaman funciones genéricas.
La herencia permite a los programadores crear nuevas clases, similares a otras ya existentes. Únicamente deberán proporcionar métodos adecuados para las nuevas clases o mantener los heredados. Un objeto de R que hereda las propiedades de un objeto ya definido, tiene como atributo de clase un vector que contiene la clase de ese objeto (en primer lugar), junto con las clases del objeto del que hereda.

El primer mecanismo de la programación orientada a objetos en R es el conjunto de clases S3 o del viejo estilo (old-style), donde el despacho del método se produce a través de las funciones genéricas del siguiente modo:

Supongamos que tenemos un objeto con el nombre de cdr de la clase Cuadrado, el cual es una subclase de Rectangulo que a su vez es una subclase de Forma. El mecanismo de despacho del método consiste en S3 y la función UseMethod. Utilizando S3 definimos un método Area para la clase Rectangulo como
  Area.Rectangulo <- function(objeto) { attr(objeto, "ladoA") * attr(objeto, "ladoB"); } 
dado que un objeto Rectangulo tiene dos atributos ladoA y ladoB (ahora se puede acceder directamente a los argumentos con el operador @, es decir, objeto@ladoA y objeto@ladoB, respectivamente). Entonces definimos la función genérica Area así:
  Area <- function(objeto, ...) UseMethod("Area"); 
Cuando esta función se aplica en el objeto con Area(cdr), UseMethod despachará el método basado en la clase del primer argumento, es decir, objeto. Como cdr es de la clase Cuadrado, primero buscará un método llamado Area.Cuadrado. Si no existe, como en este caso, probará con Area.Rectangulo y así sucesivamente. Si no existiera ningún método específico para cualquiera de las clases del objeto, entonces se recurrirá al método Area.default que siempre debemos tener.

Observemos que las funciones genéricas S3 se pueden reconocer por la utilización de UseMethod en su código. Esto es importante, ya que las páginas de ayuda para una combinación de método y objeto depende de su nombre completo del tipo "function.class". Por ejemplo, la página de ayuda de la función summary no explica absolutamente nada sobre su actuación cuando le pasamos un objeto de la clase factor. Será mejor buscar la ayuda de la función summary.factor, aunque para que la función actúe sólo hay que escribir summary(objeto).

Cuando se crea una clase, asociadas a ella, deberemos crear un conjunto de funciones para extraer datos o información sobre los objetos de esa clase. Dada la convención explicada sobre los nombres de las funciones en las clases S3, podemos utilizar la función apropos para hallar los métodos disponibles para una clase:

> apropos('.*\\.factor$')
[1] "all.equal.factor" "as.character.factor" "as.data.frame.factor"
[4] "as.Date.factor" "as.factor" "as.list.factor"
[7] "as.POSIXlt.factor" "as.vector.factor" "codes.factor"
[10] "[<-.factor" "[.factor" "[[.factor"
[13] "format.factor" "is.factor" "is.na<-.factor"
[16] "length<-.factor" "levels<-.factor" "Math.factor"
[19] "Ops.factor" "print.factor" "rep.factor"
[22] "summary.factor" "Summary.factor" "xtfrm.factor"

Así descubriremos que existen algunas funciones específicas para la clase factor.

Ahora bien, como el despachado del método que proporcionan las clases S3 está limitado al primer argumento de la función, y como las convenciones sobre los nombres que hemos explicado pueden crear alguna confusión, se decidió crear un nuevo sistema de clases S4 o de nuevo estilo (new-style). Éste es ahora el sistema preferido en el desarrollo de R.
En las clases S4, las funciones genéricas se reconocen por la llamada a una función standardGeneric en su definición.
Las funciones necesarias para trabajar con las clases S4 se hallan en el paquete methods. Por ejemplo, para saber si un objeto utiliza el nuevo estilo podemos hacer:


> temp <- c(20,15,15,20,20,30,25,25,30,15,25,30,30)
> temp <- factor(temp)
> isS4(temp)
[1] FALSE

Para saber los métodos asociados a una clase S4 como los objetos mle, resultado de la función de estimación de máxima verosimilitud, hacemos:

> library(methods)
> showMethods(class='mle')

Descubriremos que se puede calcular la matriz de varianzas-covarianzas de un objeto mle con la función vcov.

Aunque no hay una función genérica print para las clases S4, la función show permite ver el contenido de un objeto.
Por otra parte, los elementos que componen un objeto S4 se guardan en los llamados slots. Para ver los tipos de slots en un objeto, podemos utilizar la función showClass.

> library(stats4)
> showClass("mle")
Class “mle” [package "stats4"]

Slots:

Name: call coef fullcoef vcov min details minuslogl
Class: language numeric numeric matrix numeric list function

Name: method
Class: character

Para acceder directamente a un slot de un objeto, utilizaremos el operador @ del mismo modo que el operador $ para una lista. La función slot también obtiene el mismo resultado.

> slot(objeto.lme,"vcov")

lunes, 30 de noviembre de 2009

Primeras jornadas de R

Cuando me disponía a redactar un resumen de las Primeras jornadas de R celebradas en la Universidad de Murcia los pasados días 26 y 27 de noviembre, he descubierto con agrado que se me han adelantado.
En el blog Análisis y decisión podéis leer un amplio resumen de Carlos Gil Bellosta.
En fin, un ejemplo de trabajo colaborativo, él lo escribe y yo lo cito. ¡Ejem! Ya sé que no es eso.
Simplemente debo añadir que corroboro todas las buenas impresiones que nos llevamos las personas que asistimos del Departamento de Estadística de la Universidad de Barcelona: Miquel Calvo, Esteban Vegas y yo mismo. Además de conocernos y hablar de R y del software libre en general, también han quedado algunos compromisos de colaboraciones que poco a poco seguro que iremos concretando.
Un abrazo a todos y todas los que estuvisteis allí.

sábado, 28 de noviembre de 2009

Factores numéricos

Población africana con SIDA

Frecuentemente es necesario convertir una variable numérica en factor ya que algunas funciones de R así lo esperan. Sin embargo, si disponemos únicamente de un factor no podremos calcular algunos estadísticos u otras operaciones numéricas, aunque los niveles (o sus etiquetas) sean aparentemente numéricos.


> temp <- c(20,15,15,20,20,30,25,25,30,15,25,30,30)
> temp <- factor(temp)
> temp
[1] 20 15 15 20 20 30 25 25 30 15 25 30 30
Levels: 15 20 25 30
> mean(temp)
[1] NA
Warning message:
In mean.default(temp) : argument is not numeric or logical: returning NA


Si no disponemos de los datos numéricos originales y deseamos convertir el factor a datos numéricos podemos probar así:

> temp.n <- as.numeric(temp)
> temp.n
[1] 2 1 1 2 2 4 3 3 4 1 3 4 4

Pero el resultado son los valores enteros en los que se codifica internamente el factor.
Mejor si primero convertimos el factor a caracteres con las etiquetas de los niveles y luego esos mismos a valores numéricos:

> temp.n <- as.numeric(as.character(temp))
> temp.n
[1] 20 15 15 20 20 30 25 25 30 15 25 30 30


Por otra parte, para crear un factor a partir de una variable continua se utiliza la función cut.
Los siguientes datos corresponden a la tasa de mortalidad del SIDA por cada mil habitantes en los países africanos en el año 2007:

> tasa <- c(7.21,1.15,10.49,2.86,2.37,3.79,2.49,4.88,0.81,4.70,2.10,1.97,0.49,0.65,
0.89,8.96,1.30,5.84,2.53,1.29,0.65,8.76,0.62,1.38,0.80,1.70,0.47,2.46)

> tasa.f <- cut(tasa,breaks=seq(0,12,2))

> table(tasa.f)
tasa.f
(0,2] (2,4] (4,6] (6,8] (8,10] (10,12]
14 7 3 1 2 1

> class(tasa.f)
[1] "factor"

domingo, 22 de noviembre de 2009

Valores perdidos

En R, para representar un valor perdido (missing value) o que falta se utiliza el valor NA (not available) sin comillas. Se puede asignar este valor a una variable de la forma

> x <- c(3,2,NA,6)

pero no se puede consultar así

> x[3] == NA
[1] NA

para ello tenemos la función is.na():

> is.na(x[3])
[1] TRUE


Por otra parte, al realizar un determinado cálculo, como dividir por cero o la raíz de un negativo, el resultado puede ser Inf o NaN (not a number). La función is.nan() permite dirimir si se trata de uno de estos valores.

Cuando se importan datos de algún tipo de archivo, los valores perdidos pueden ser un problema si no se tratan de forma adecuada. Una posibilidad es utilizar el argumento na.strings= de la función read.table, al que le podemos pasar un vector con todos los valores del tipo carácter que R debe considerar perdidos y dar valor NA. En todo caso vale la pena revisar el resultado de la importación. Si los datos provienen de una hoja de cálculo, siempre podemos substituir los valores perdidos por NA, antes de la exportación del archivo al formato CSV.

Trabajar con valores perdidos

Algunas funciones de R disponen de argumentos para trabajar con datos que contienen valores perdidos. Por ejemplo, la mayoría de funciones estadísticas del tipo mean, var, sum, min, max, etc.
tienen un argumento na.rm= que se puede pasar a TRUE para que suprima los valores perdidos del cálculo. Para otras funciones que no tengan este parámetro podemos eliminar dichos valores creando un nuevo vector:


> x[!is.na(x)]

[1] 3 2 6


También algunas funciones de modelización estadística, como lm, glm, etc., disponen de un argumento na.action= al que le podemos pasar la acción a realizar para los valores perdidos. La más común es na.action=na.omit con lo que prescindirá de dichos valores en su procedimiento.
Otra opción es seleccionar del vector, matriz o data.frame(s) los individuos o filas que no tienen valores perdidos mediante la función complete.cases que devuelve un vector lógico:

> complete.cases(x)
[1] TRUE TRUE FALSE TRUE


Finalmente, señalar que en la conversión de un vector a factor, los valores perdidos no se consideran.

> y <- factor(x)
> y
[1] 3 2 6
Levels: 2 3 6


Si deseamos formar un nivel con los valores perdidos haremos

> y <- factor(x, exclude=NULL)
> y
[1] 3 2 NA 6
Levels: 2 3 6 NA

domingo, 8 de noviembre de 2009

De R a ODF: el paquete odfWeave

En el artículo LaTeX y R ya comenté las ventajas de combinar texto y fórmulas junto con instrucciones de R para obtener un informe dinámico que contendrá el texto, las fórmulas y los resultados numéricos y gráficos generados por R. Lamentablemente esto supone saber LaTeX (y R), y utilizar LaTeX no es sencillo. Sin embargo, la potencia de la función Sweave en R se ha extendido a otros formatos más amigables. Primero fué el paquete R2HTML para el formato html y ahora el paquete odfWeave para el formato ODF u OpenDocument.
El paquete odfWeave proporciona las funciones y el entorno para escribir informes automáticos en un formato libre y gratuito (como R), que se puede modificar a posteriori de forma muy sencilla y que permite su exportación a otros formatos como html o PDF.
La idea consiste en escribir un archivo ODF con un programa editor como OpenOffice con comentarios o explicaciones y código R para generar resultados y gráficos. La función odfWeave procesa ese archivo y nos entrega el documento ODF con el mismo texto y los resultados numéricos o gráficos insertados. Ese documento se puede modificar añadiendo otros elementos: vínculos, fotografías, encabezados y piés de página, etc. y finalmente imprimir o exportar.
Por ahora sólo funciona con documentos de texto, pero más adelante se puede ampliar a presentaciones y hojas de cálculo.

Veamos un ejemplo.

El documento de nombre ejemplo.odt contiene el siguiente texto:


Ejemplo

En este documento vamos a probar el paquete odfWeave.

Primero cargamos los datos de forma oculta para el documento final.

<<>>=

# Los datos son

coches <- mtcars[ ,1:6]

@

Los datos a estudiar contienen \Sexpr{dim(coches)[2]} variables medidas sobre \Sexpr{dim(coches)[1]} coches.

Podemos insertar una tabla resumen de los datos:

<<>>=

medias <- apply(coches,2,mean)

odfTable(medias, horizontal = TRUE)

@

También podemos añadir algún bonito gráfico:

<<>>=

pairs(coches)

@

Y así seguiría nuestro trabajo.





Si aplicamos el código:

library(odfWeave)
odfWeave(ejemplo.odt,resultado.odt)


obtenemos el documento resultado.odt


Ejemplo

En este documento vamos a probar el paquete odfWeave.

Primero cargamos los datos de forma oculta para el documento final.

Los datos a estudiar contienen 6 variables medidas sobre 32 coches.

Podemos insertar una tabla resumen de los datos:

mpg

cyl

disp

hp

drat

wt

20.091

6.188

230.722

146.688

3.597

3.217

También podemos añadir algún bonito gráfico:

Y así seguiría nuestro trabajo.




El formato de las imágenes y su tamaño se controlan con las funciones getImageDefs y setImageDefs.
Del mismo modo, el formato del texto o de las tablas se controla con las funciones de definición de estilo getStyleDefs y setStyleDefs, y las de asignación getStyle y setStyle para determinar algún elemento.

La clase odfTable se utiliza para convertir vectores, matrices y data frames a tablas ODF. La función odfCat sirve para escribir texto directamente en el formato ODF. También hay funciones para crear listas y para insertar imágenes externas.

Una vez tenemos el documento resultado, podemos utilizar OpenOffice para convertir manualmente este documento a otros formatos. Para ello se usa el menú Guardar como... o Exportar... del menú Archivo. En algunos sistemas operativos también disponemos de programas conversores desde la consola.

Para utilizar el paquete odfWeave es necesario que nuestro ordenador disponga de un compresor/descompresor de archivos ZIP. También deberemos tener en cuenta que los gráficos se graban en formato PNG, por si nuestro sistema no tiene el dispositivo adecuado.