una - Eficiencia de operaciones en estructuras de datos R
funciones con vectores en r (3)
Data IO fue una de las características que analicé antes de comprometerme con el aprendizaje de R. Para bien o para mal, aquí están mis observaciones y soluciones / paliativos sobre estos temas:
1. Que R no maneja big data (> 2 GB?) Para mí esto es un nombre inapropiado. De forma predeterminada, las funciones comunes de entrada de datos cargan sus datos en la RAM. No es para ser simplista, pero para mí, esta es una característica que no es un error: cada vez que mis datos caben en la memoria RAM disponible, ahí es donde la quiero. Del mismo modo, una de las características más populares de SQLite es la opción en memoria: el usuario tiene la opción fácil de cargar todo el dB en la RAM. Si sus datos no caben en la memoria, R hace que sea sorprendentemente fácil de persistir, a través de las conexiones a los sistemas RDBMS comunes (RODBC, RSQLite, RMySQL, etc.), a través de opciones sencillas como el paquete filehash, y a través de sistemas que utilizan tecnología / prácticas actuales (por ejemplo, puedo recomendar ff ). En otras palabras, los desarrolladores de R han elegido un valor predeterminado sensible (y probablemente óptimo), del cual es muy fácil optar por no participar.
2. El rendimiento de read.table (read.csv, read.delim, et al.), Los medios más comunes para obtener datos en R, se puede mejorar 5x (y muchas veces más en mi experiencia) simplemente al optar por no participar. Algunos de los argumentos por defecto de read.table: los que tienen el mayor efecto en el rendimiento se mencionan en la Ayuda de R (? read.table). Brevemente, los desarrolladores de R nos dicen que si proporciona valores para los parámetros ''colClasses'', ''nrows'', ''sep'' y ''comment.char'' (en particular, pase '''' si sabe que su archivo comienza con encabezados o datos en la línea 1), verá un aumento significativo del rendimiento. He encontrado que eso es cierto.
Aquí están los fragmentos que uso para esos parámetros:
Para obtener el número de filas en su archivo de datos (proporcione este fragmento de código como un argumento al parámetro, ''nrows'', en su llamada a read.table):
as.numeric((gsub("[^0-9]+", "", system(paste("wc -l ", file_name, sep=""), intern=T))))
Para obtener las clases de cada columna:
function(fname){sapply(read.table(fname, header=T, nrows=5), class)}
Nota: No puede pasar este fragmento de código como un argumento, debe llamarlo primero, luego pasar el valor devuelto; en otras palabras, llamar a la función, vincular el valor devuelto a una variable y luego pasar el variable como el valor para el parámetro ''colClasses'' en su llamada a read.table:
3. Utilizando Scan . Con solo un poco más de molestia, puede hacerlo mejor que eso (optimizando ''read.table'') utilizando ''scan'' en lugar de ''read.table'' (''read.table'' en realidad es solo una envoltura alrededor de ''scan''). Una vez más, esto es muy fácil de hacer. Utilizo ''scan'' para ingresar cada columna individualmente y luego compilo mi data.frame dentro de R, es decir, df = data.frame (cbind (col1, col2, ....)).
4. Use los Contenedores de R para la persistencia en lugar de los formatos de archivo normales (por ejemplo, ''txt'', ''csv''). El archivo de datos nativos de R ''.RData'' es un formato binario que es un poco más pequeño que un archivo de datos txt comprimido (''.gz''). Los creas usando guardar (,). Lo vuelve a cargar en el espacio de nombres R con load (). La diferencia en los tiempos de carga en comparación con ''read.table'' es dramática. Por ejemplo, con un archivo de 25 MB (tamaño sin comprimir)
system.time(read.table("tdata01.txt.gz", sep=","))
=> user system elapsed
6.173 0.245 **6.450**
system.time(load("tdata01.RData"))
=> user system elapsed
0.912 0.006 **0.912**
5. Prestar atención a los tipos de datos a menudo puede aumentar el rendimiento y reducir la huella de memoria. Este punto es probablemente más útil para obtener datos de R. El punto clave que se debe tener en cuenta aquí es que, de forma predeterminada, los números en las expresiones R se interpretan como punto flotante de doble precisión, por ejemplo,> typeof (5) devuelve "doble". " Compare el tamaño del objeto de una matriz de tamaño razonable de cada uno y puede ver el significado (use object.size ()). Así que coaccionar a entero cuando puedas.
Finalmente, la familia de funciones ''aplicar'' (entre otras) no son "bucles ocultos" o envoltorios de bucles. Son bucles implementados en C - gran diferencia en cuanto a rendimiento. [edit: AWB ha señalado correctamente que mientras ''sapply'', ''tapply'' y ''mapply'' están implementados en C, ''apply'' es simplemente una función de envoltorio.
Me pregunto si existe alguna documentación sobre la eficiencia de las operaciones en R
, específicamente aquellas relacionadas con la manipulación de datos.
Por ejemplo:
- Imagino que es eficiente agregar columnas a un marco de datos, porque supongo que solo estás agregando un elemento a una lista vinculada.
- Me imagino que agregar filas es más lento porque los vectores se mantienen en matrices en el
C level
y tienes que asignar una nueva matriz de longitudn+1
y copiar todos los elementos.
Es probable que los desarrolladores no quieran vincularse a una implementación en particular, pero sería bueno tener algo más sólido que conjeturas para continuar.
Además, sé que la sugerencia principal de rendimiento de R
es utilizar operaciones vectorizadas siempre que sea posible en lugar de loops
.
- ¿Qué pasa con los diversos sabores de
apply
? - son esos solo
hidden loops
? - ¿Qué pasa con las
matrices
frente adata frames
?
Estas cosas aparecen en las listas, en particular en r-devel. Un nugget bastante bien establecido es que, por ejemplo matrix
operaciones matrix
tienden a ser más rápidas que data.frame
operaciones data.frame
. Luego hay paquetes complementarios que funcionan bien: el paquete data.table de Matt es bastante rápido y Jeff ha conseguido que la indexación xts sea rápida.
Pero "todo depende", por lo que generalmente es mejor recomendar un perfil en su código particular . R
tiene un montón de soporte de perfiles, por lo que debe utilizarlo. Los tutoriales de Introducción a HPC con R tienen varios ejemplos de perfiles.
Intentaré volver y dar más detalles. Si tiene alguna pregunta sobre la eficiencia de una operación sobre otra, debería hacer un mejor perfil de su propio código (como sugiere Dirk). La función system.time()
es la forma más fácil de hacer esto, aunque hay muchas más utilidades avanzadas (por ejemplo, Rprof, como se documenta here ).
Una respuesta rápida para la segunda parte de su pregunta:
¿Qué pasa con los diversos sabores de aplicar? ¿Son esos bucles sólo ocultos?
En su mayor parte sí, las funciones de aplicación son solo bucles y pueden ser más lentas que for
declaraciones. Su principal beneficio es el código más claro. La principal excepción que he encontrado es el lapply
que puede ser más rápido porque está codificado en C directamente.
¿Y qué pasa con las matrices frente a los marcos de datos?
Las matrices son más eficientes que los marcos de datos porque requieren menos memoria para el almacenamiento. Esto se debe a que los marcos de datos requieren datos de atributos adicionales. De R Introducción :
Un marco de datos puede, para muchos propósitos, considerarse como una matriz con columnas posiblemente de modos y atributos diferentes.