Ficheros csv grandes en R

Una advertencia preliminar: esta entrada se ha escrito para describir un problema concreto con R (el uso de ficheros de datos csv especialmente grandes), y por lo tanto está al margen de la serie principal de entradas del blog, que pretenden ir describiendo progresivamente el uso de R.  Si tu interés principal es ese, aprender poco a poco a usar R, puedes saltarte esta entrada y, en todo caso, regresar a ella cuando se te plantee el problema.

Size matters?

Aunque R es el protagonista de este blog, eso no impide que mantengamos un ojo atento a otras plataformas de software estadístico. Por esa razón, para mejorar mi conocimiento   de una de las alternativas más populares, estoy inscrito en el curso Passion Driven Statistics de la plataforma Coursera. Se trata de un curso básico de Estadística, que utiliza -primordialmente- el programa SAS como herramienta (más concretamente, SAS OnDemand for Academics).

Como me  siento mucho más cómodo trabajando con R, antes de abordar una tarea del curso con SAS, a menudo hago primero las cuentas necesarias con R. De esa manera puedo saber fácilmente si los resultados que obtengo son correctos. Esta semana,  trabajando en una de esas tareas del curso, me he topado con el problema de tener que utilizar  en R un fichero de datos (en formato csv) relativamente grande (15.5 Mb aprox.). De hecho, una de las ventajas  que a menudo se citan, cuando se compara SAS con otros programas, es su capacidad para trabajar con grandes ficheros de datos. Y, de hecho, el primer intento de cargar el fichero en R no funcionó correctamente. Afortunadamente, como sucede a menudo con R, la solución estaba a la vuelta de…  unas pocas esquinas. Pero no adelantemos acontecimientos, y vamos paso a paso. 

El fichero csv de marras es una base de datos sobre cráteres (¡más de trescientos mil!) en la superficie del planeta Marte, procedente de la tesis doctoral (University of Colorado) de  Stuart  Robbins (aunque no es relevante para lo que aquí nos ocupa, el fichero se describe con más detalle en este fichero).

Manos a la obra con el primer intento. Descargo el fichero al Escritorio de la máquina en la que voy a hacer las cuentas (s. operativo Windows 7), arranco R y ejecuto este código, que crea un data frame (llamado mars) de R a partir del fichero csv, y a continuación comprueba el número de filas de ese data frame para verificar que hemos leído tantos datos como esperábamos. Se muestra la salida de los comandos:

> setwd("C:/Users/Fernando/Desktop")
> mars=read.table(file="marscrater_pds.csv",sep=",",header=TRUE)
> nrow(mars)
[1] 169587

Ahora es evidente que algo ha ido mal, porque como hemos dicho el fichero contiene datos de más de trescientos mil cráteres. Mi primera impresión, al ver el resultado, fue que el fichero se había truncado en el proceso de lectura. Y lo peor es que no había aparecido ningún mensaje de advertencia…

La librería sqldf

A estas alturas del problema,  si eres, como yo, un poco maniático (¿un poco, en serio?), te habrás lanzado ya sin frenos por la pendiente de los ficheros de gran tamaño en R. La mala noticia es que me lo hubiera podido ahorrar, porque en realidad el problema se debe a que estaba usando la función read.table de forma incorrecta. La buena noticia es que, por el camino, doblando varias de esas esquinas de las que hablaba, he descubierto una librería de R que permite trabajar con ficheros verdaderamente grandes. Y en este caso no me refiero a unos pocos megas, sino a un fichero csv de ¡cerca de un gigabyte de datos!

La fuente de inspiración de lo que sigue es esta discusión de StackOverflow, que se ha consolidado para mi como uno de los oráculos más fiables de Internet. Para empezar bien las cosas vamos a usar el propio R para crear un fichero de datos verdaderamente enorme. He tenido  que modificar un poco el código de StackOverflow, porque estoy usando una modesta versión de 32 bits de R en una modesta máquina Windows 7, en la que R se quedaba sin memoria para ejecutar directamente el código original. Antes de ejecutar este código asegúrate de que tienes espacio en tu máquina para alojar un fichero de aprox. un gigabyte:

bigdf = data.frame(dim=sample(letters, replace=T, 2e7), fact1=rnorm(2e7), fact2=rnorm(2e7, 20, 50))
write.csv(bigdf, 'bigdf.csv', quote = F)

En mi máquina R tarda unos minutos (<10) en completar estos comandos, y el resultado,  en mi escritorio,  es un enorme fichero csv de 20 millones de líneas. Ahora ha llegado el momento de leer este fichero desde R. Porque una cosa es escribirlo, y otra bien distinta es cargarlo en memoria y poder hacer operaciones con él.  La clave, como explicaban en StackOverflow, es instalar la librería sqldf de R (en la séptima sesión con Rcmdr explicamos como instalar librerías adicionales).

Para cargar el  fichero, reiniciamos  R,  instalamos esa librería, y ejecutamos este código:

setwd("C:/Users/Fernando/Desktop")
library(sqldf)
f =file("bigdf.csv")
bigdf = sqldf("select * from f", dbname = tempfile(), file.format =list(header = T, row.names = F))
nrow(bigdf)

En mi máquina ha tardado menos de diez minutos, lo suficiente para estirar las piernas. Y al final la función nrow(bigdf) ha contestado, como se esperaba, 20000000. Y ahí tenemos un hermoso data frame con veinte millones de filas. Es bastante impresionante ejecutar comandos sobre esos datos y ver como R apenas acusa el esfuerzo. Por ejemplo, he probado a hacer  estas cosas:

> table(bigdf$dim)
a b c d e f g h i j k l m n o p q r s t u v w x y z
768523 770070 770127 768994 769183 770936 768517 769162 769874 768586 769646 768160 767658 768757 768536 770234 769834 769125 768700 769659 768125 769524 769147 768973 770505 769445
> mean(bigdf$fact1)
[1] 0.0007351861
> sd(bigdf$fact1)
[1] 0.9999446
> hist(bigdf$fact1)

Y todas se han ejecutado casi instantáneamente. El resultado del histograma es el que se esperaba, claro:HistogramaBigData

pero lo importante es verlo aparecer casi en el acto, teniendo en cuenta que representa veinte millones de observaciones…

De vuelta a Marte

Naturalmente, cuando se usa la librería sqldf, el fichero de datos de los cráteres de Marte se carga sin problemas. Así que “problema resuelto”. ¿O no? Me seguía rondando la cabeza la idea de que, en la presunta lectura truncada con read.table, no había habido ningún mensaje de advertencia (¿he dicho ya lo de que soy un poco maniático?).  Así que un poco más de búsqueda por la red (ver, por ejemplo, esta discusión) me ha servido para descubrir que, en realidad, lo que sucedía es que estaba usando mal la función read.table, que es muy capaz por si misma, de leer ficheros bastante grandes, de varios cientos de miles de filas. La clave, cuando como en mi caso se usa un separador como

sep=","

es incluir la opción quote=”” en la función read.table. De modo que el segundo intento de usar read.table es este (se muestra la salida):

</pre>
> rm(list=ls())
<span style="line-height: 1.5;">> setwd("C:/Users/Fernando/Desktop")</span>
<span style="line-height: 1.5;">> mars=read.table(file="marscrater_pds.csv",sep=",",header=TRUE,quote="")</span>
<span style="line-height: 1.5;">> nrow(mars)</span>
[1] 384343
<span style="line-height: 1.5;">

Y, como puede verse, ahora las cosas funcionan correctamente, La razón por la que es necesario usar quote=”” es bastante cabalística, y tiene que ver con la presencia de un número muy reducido de comillas simples ( es decir, el carácter ‘ ) en fichero csv de cráteres de Marte. Esas comillas afectan a la forma en que R lee el fichero y le confunden sobre donde empiezan y terminan las líneas. Así que, desde el punto de vista de R, la primera lectura del fichero había sido un éxito. Y eso explica la  ausencia de mensajes de error…

Es un comportamiento muy extraño, que nos recuerda la dificultad intrínseca que tienen siempre estas operaciones de lectura y/o conversión de datos entre distintos formatos. De hecho, la motivación para escribir esta entrada es, básicamente, para que me sirva (a mi, o a otros) de referencia en el futuro, porque estoy seguro de que tardaré en tropezarme otra vez con este dichoso problema de las comillas simples. Al menos, por el camino, hemos aprendido a cargar ficheros enormes en R.

Llevo muy poco tiempo trabajando con SAS, como para formarme una opinión definitiva. Pero me alegra, habiendo apostado por R desde hace tiempo, comprobar que los ficheros grandes tampoco suponen un problema. Y en cuanto a otro de los supuestos inconvenientes de R, su pronunciada “curva de aprendizaje inicial”… en fin, ya digo que tengo muy poca experiencia con SAS. Pero estoy empezando a entender porque alguien lo resumió diciendo “SAS=semicolon, always semicolon”. Me da la sensación de que La sintaxis de SAS es, al menos, tan poco intuitiva como pueda serlo la de R.

Muchas gracias por la atención.

Anuncios