lunes, 1 de febrero de 2010

Software estadístico- R - Trabajar con grandes conjuntos de datos. (II)

En la anterior entrada veíamos cómo importar un fichero de 4,7 Gb y un buen número de variables a un archivo legible en R. Se hacía a través de varios pasos y el uso de dos librerías adicionales , filehash y colbycol

Ahora vamos a importar el mismo fichero a través de la librería ff . Podéis ver detalles de sus instrucciones aquí.

La librería ff proporciona un medio de almacenamiento plano y de acceso rápido, proporcionando vistas físicas y virtuales de los datos. Uno de los objetos disponibles en esta librería se llama ffdf y es un análogo al data frame de R. A uno de estos objetos vamos a importar nuestro fichero a través de la función read.table.ffdf, construida para ser muy similar al read.table y almacenar ficheros con varias columnas (variables) en formato data frame, entre otras aplicaciones.

Una instrucción correcta para importar nuestros datos será por ejemplo:

>base_fichero<-read.table.ffdf("/home/datos/fichero1.txt",sep='|',fill=TRUE,x=NULL,first.rows=1e5,next.rows=8e5)

No comentaré opciones tradicionales de R(aquí las podemos meter cai todas sin problemas) , pero sí las nuevas:

-x=NULL se pone para indicar que nuestros datos no serán anexados a nada. Si tuviéramos otro ffdf y quisiéramos anexar, lo indicaríamos aquí.

-first.rows=1e5: La función read.table.ffdf lee "a trozos". Y el primer "trozo" a leer es importante porque desde aquí se toman datos relativos a factores, tipos de datos , valores etc. Por defecto, lee 1000 casos. En nuestro caso es mejor poner más para el correcto cálculo de niveles en los factores. Decir que esto se podrá recalcular posteriormente.

-next.rows=8e5: Después de la primera lectura , la función leerá y escribirá porciones del fichero original para irlos almacenando en los ficheros físicos que maneja. En esta opción se especifica el número de líneas a ller en cada pasada. Si se dispone de cierta capacidad de RAM (en mi caso unos 3Gb libres) , conviene dar manga ancha a este parámetro, puesto que redundará en mayor rapidez al disminuir el trabajo I/O.

Hay bastantes más opciones y quizá más eficientes. Pero deciros que con esto , soy capaz de leer todo el fichero original en un tiempo bastante razonable, y de una sola tacada. Es por tanto éste un método mejor que la combinación de filehash+ colbycol.

Para el uso de los datos veremos la manera de tomar los datos de una variable, denominada campo1 (ocupa la primera columna del fichero) de nuestro ffdf base_fichero:

>vector_campo1<- base_fichero[,1]
>vector_campo1<- base_fichero[,"campo1"]

Estas dos instrucciones realizan la misma labor, asignar a vector_campo1 los valores de la citada variable. El acceso a los datos de un ffdf se realiza con notación "matricial" [filas,columnas], por índice o por nombre. Si deseamos omitir filas o columnas , como en el ejemplo , simplemente se omite.

Otra posible acción es la siguiente:

>ajuste<-lm(base_fichero[,"campo1"]~ base_fichero[,"campo2"]

Y.... ahora igual que la otra vez, el sistema se queda sin memoria. Ya trabajamos en modo R clásico, poniendo los procesos en RAM . En las siguientes entradas referentes a esta serie veremos métodos para hacer análisis lo más acertados posible teniendo en cuenta esta adversidad.

Salimos..

lunes, 25 de enero de 2010

Software estadístico- R - Trabajar con grandes conjuntos de datos. (I)

En mi interés por trabajar en entornos fuera de los comercialmente afianzados, y disponibles sólo en grandes empresas, debido fundamentalmente a su coste, llegue a conocer R.

R es un entorno de análisis estadístico donde, uno, aparte de su estructura digamos, común, puede encontrar multitud de paquetes detinados a conexión con sistemas de base de datos, tratamiento de datos y todo tipo de procedimientos estadísticos y matemáticos. Básicamente nos movemos en una línea de comandos desde donde damos instrucciones ordenadas de qué queremos hacer, modo consola de SO. Dispone también de librerías como RCommander para trabajar en un entorno gráfico que ataja algunos de esos comandos.

Al poco tiempo de empezar a documentarme sobre el entorno pude ver cómo era un problema recurrente y ya extendido en el tiempo la dificultad de trabajar con grandes volúmenes de datos. ¿Por qué? Muy sencillo. R trabaja con los datos cargados en memoria. Por lo que el conjunto de datos con el que trabajamos debe ser como mucho inferior a la memoria que tengamos libre. Esta es una limitación muy seria en mi opinión si se quiere tratar de tomar R como un entorno serio, puesto que cada vez tenemos que enfrentarnos a colecciones de datos mayores y más complejas, cuando no llegar directamente a trabajar sobre textos, etc

En este proceso de búsqueda acabé en esta magnífica página/proyecto al que deseo francamente larga vida: Análisis y decisión.

Aquí pude encontrar este artículo tres fracasos y medio con R donde se dan detalles del problema. A partir de aquí me decidí a tratar de alguna manera de abordar esto, o directamente no seguir interesándome mucho por un entorno que no ofrece mucha posibilidad de superar este inconveniente.

Una vez vistas algunas vías de salida, me decidí a investigar un poco en serio dos paquetes diseñados para alojar datos en disco, en vez de en RAM y por tanto son escalables; Los paquetes ff y filehash. Empezaremos por este último. Podéis obtener información (no muy extensa, pero suficiente) aquí. Básicamente y sobre el papel esta librería ofrece la posibilidad de trabajar directamente con bases de datos o ficheros alojados en el disco duro, sin necesidad de alojar todo en memoria.

La diversidad de documentación o ejemplos de uso variados de este paquete es más bien escasa, por lo que me lancé a la aventura para ir aprendiendo sobre la marcha. La idea es cargar un fichero de más de 40 variables con casi 10.000.000 de registros, en toal 4.7 GB. La RAM de mi equipo es de 4GB.
Filehash crea bbdd orientadas a clave, y tiene pocas pero simples funciones posibles. Una de ellas carga data frames en ficheros en disco duro.

La instrucción es directamente
> dumpDF(read.table("c:\fichero.txt",header=TRUE,sep="|";fill=TRUE),"basedatos")

Esto lee los datos de un fichero y los carga directamente en una bbdd filehash de nombre "basedatos". Aunque en algunos textos alumbran que esto se hace directamente a disco, no es cierto. Es proceso lee el fichero normalmente (a memoria) y por tanto falla.

Segunda opción: Partir nuestro fichero en pongamos 5 ó 6 ficheros de la misma estructura con menos datos. La idea sería cargar un fichero , liberar memoria, y anexar los siguientes, repitiendo el proceso. Intento inútil porque no fui capaz(no sé si se podrá hacer) de anexar nada a la base de datos. No funcionan las cosas que podrían hacerse con un dataframe, porque no lo es y la función dbInsert de filehash parece que sólo carga claves (columnas o variables, vamos). Tampoco sirve cargar tal cual, porque sustituye lo anterior.

Tercera opción: Dado que parece que se pueden insertar vectores completos, pues la respuesta está en el ar´ticulo que puse arriba . Utilizo el paquete colbycol , me desagrega el fichero original en vectores individuales. Ahora sí, tiene sentido untilizar la función dbInsert

-primero leemos el fichero con colbycol
> data<-cbc.read.table("f:/fichero.txt",header=TRUE,sep="|",fill=TRUE)
-creamos la bbdd filehash
> dbCreate("vacia")
> bvacia<-dbInit("vacia")
-ahora probamos a insertar vectores extraidos por el colbycol, de diversos modos

> dbInsert(bvacia,"CAMPO1",CAMPO1)
> dbInsert(bvacia,colnames(data)[1],cbc.get.col(data, 1 ))
> dbInsert(bvacia,"CAMPO1",cbc.get.col( data, 1 ))



Estas tres instrucciones hacen lo mismo, insertan el vector correspodiente al primer campo del fichero desmenuzado por colbycol.
En principio hice las pruebas con 6 ó 7 campos por separado y lo cierto es que fue bien en todos menos en uno de ellos, sospecho que por algún tema de memoria, ya que tenía en sistema muy cargado (no había limpiado nada y había hecho muchas pruebas de lecturas etc.) y el vector era un campo de texto (factor).

En este punto, la táctica será utilizar el número de campos del fichero como variable contador para extraer el nombre e información de los vectores y cargar todo de un tirón:
>NUMBER=ncol(data)
>for (i in 1:NUMBER){CONTENT= cbc.get.col(data, i )
+NAME=colnames(data)[i]
+dbInsert(bvacia,NAME,CONTENT)
+rm(NAME)
+rm(CONTENT) }

Esto, aunque es lento funciona. Os preguntaréis quizá por qué asigno las columnas a variables intemedias. El caso es que hacerlo directamente me resultó más lento, no sé si por que lo es, o porque en ese momento el sistema estaba más cargado. He de decir que estas pruebas no están hechas en un entorno profesional, sino en un PC casero con aplicaciones p2p, firefox, antivirus y sobre windows. Tengo pendiente testar esto en Ubuntu server , sólo dedicado a estos menesteres.

Bien, eso de arriba crea una base de datos en disco que siempre podremos recuperar mediante la asignación a un objeto:

>bvacia<- dbInit("vacia")


Para trabajar con esta bbdd , se utiliza la función with, por ejemplo, nada más tener los datos, probé:

>media<-with(bvacia,mean(campo3)) , con éxito, pero al hacer:
>with(bvacia,lm(campo3~campo8))

volvemos al principio, aunque con lo aprendido por el camino. Aquí, R comienza a actuar normalmente y traslada los datos a RAM, llegando al error "Reached total allocation of xxxxx" . Bueno al menos siempre podremos tomar muestras y analizar..

El próximo paso del que les daré cuenta será testar la librería ff , también existente con el propósito de alojar datos en disco, si bien esta promete más rapidez e incluso integración con métodos estadísticos de R, como biglm.

Salimos hasta la próxima entrada.