El análisis y la manipulación de grandes volúmenes de datos son tareas fundamentales en diversos campos como la estadística, la ciencia de datos, la inteligencia artificial y la investigación en general. En este contexto, el lenguaje de programación R ofrece múltiples librerías diseñadas para mejorar la eficiencia en la lectura, escritura y procesamiento de conjuntos de datos masivos, optimizando el rendimiento computacional y reduciendo el tiempo de ejecución de las operaciones.
El presente documento tiene como objetivo comparar el rendimiento de distintas librerías disponibles en R para el manejo de datos de gran escala. Se evaluará el tiempo de ejecución de tareas fundamentales como la lectura, escritura y conversión de archivos, considerando diferentes formatos y estructuras de datos. Las librerías analizadas incluyen:
fwrite()
y fread()
.Para llevar a cabo la comparación, se genera un conjunto de datos simulado con 100 millones de observaciones, conteniendo variables de distintos tipos (numéricas, enteras, categóricas y lógicas). Posteriormente, se mide el tiempo de ejecución de cada librería en distintas operaciones clave, proporcionando una evaluación objetiva del desempeño de cada una.
Los resultados obtenidos permitirán identificar las opciones más eficientes para distintos escenarios de análisis de datos, ayudando a los usuarios de R a seleccionar la herramienta más adecuada según sus necesidades específicas.
El estudio se desarrolló en tres fases:
rnorm()
para simular datos
numéricos y sample()
para variables categóricas.system.time()
.ggplot2
.data.frame
en operaciones sobre grandes
volúmenes de datos.tidyverse
,
diseñado por Hadley Wickham, proporciona una sintaxis clara y eficiente
para manipulación de datos.# configurar la ruta para el trabajo
setwd("G:/Mi unidad/CursoBigData")
# Lista de paquetes necesarios
paquetes <- c("data.table", "arrow", "fst", "ff", "bigmemory", "sparklyr", "dplyr")
# Verificar e instalar los paquetes que falten
paquetes_faltantes <- paquetes[!(paquetes %in% installed.packages()[,"Package"])]
if(length(paquetes_faltantes) > 0){
install.packages(paquetes_faltantes, dependencies = TRUE)
}
library(sparklyr)
##
## Attaching package: 'sparklyr'
## The following object is masked from 'package:stats':
##
## filter
# Instalar Spark en la carpeta local predeterminada
#spark_install(version = "3.4.0")
# Cargar las librerías
library(data.table) # Para operaciones eficientes en tablas
library(arrow) # Para trabajar con formatos Parquet y Feather
##
## Attaching package: 'arrow'
## The following object is masked from 'package:utils':
##
## timestamp
library(fst) # Para lectura y escritura ultrarrápida
library(ff) # Para trabajar con datos que exceden la memoria RAM
## Loading required package: bit
##
## Attaching package: 'bit'
## The following object is masked from 'package:data.table':
##
## setattr
## The following object is masked from 'package:base':
##
## xor
## Attaching package ff
## - getOption("fftempdir")=="C:/Users/Diego/AppData/Local/Temp/RtmpywlVNe/ff"
## - getOption("ffextension")=="ff"
## - getOption("ffdrop")==TRUE
## - getOption("fffinonexit")==TRUE
## - getOption("ffpagesize")==65536
## - getOption("ffcaching")=="mmnoflush" -- consider "ffeachflush" if your system stalls on large writes
## - getOption("ffbatchbytes")==16777216 -- consider a different value for tuning your system
## - getOption("ffmaxbytes")==536870912 -- consider a different value for tuning your system
##
## Attaching package: 'ff'
## The following object is masked from 'package:arrow':
##
## boolean
## The following objects are masked from 'package:utils':
##
## write.csv, write.csv2
## The following objects are masked from 'package:base':
##
## is.factor, is.ordered
library(bigmemory) # Para manejar matrices grandes (solo numéricas)
##
## Attaching package: 'bigmemory'
## The following object is masked from 'package:ff':
##
## is.readonly
library(sparklyr) # Para procesamiento distribuido con Apache Spark
library(dplyr) # Para manipulación de datos con sintaxis dplyr
##
## Attaching package: 'dplyr'
## The following object is masked from 'package:bit':
##
## symdiff
## The following objects are masked from 'package:data.table':
##
## between, first, last
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
# ---------------------------------------------------------------------------
# Simulación de datos: 100 millones de filas con distintos tipos de datos
# ---------------------------------------------------------------------------
num_rows <- 100000000 # 100 millones de filas
#num_rows <- 10000000 # 10 millones de filas
set.seed(123)
df <- data.frame(
ID = 1:num_rows,
Valor_Numerico = rnorm(num_rows),
Valor_Entero = sample(1:1000, num_rows, replace = TRUE),
Categoria = sample(c("A", "B", "C", "D"), num_rows, replace = TRUE),
Booleano = sample(c(TRUE, FALSE), num_rows, replace = TRUE)
)
# Convertir variables de tipo character a factor antes de usar ff
df$Categoria <- as.factor(df$Categoria)
#cantidad de filas de la tabla
nrow(df)
## [1] 100000000
# Crear una lista para almacenar los tiempos de ejecución
resultados <- list()
# ---------------------------------------------------------------------------
# 1. data.table: Uso de fwrite y fread para guardar y leer archivos CSV
# ---------------------------------------------------------------------------
cat("data.table:\n")
## data.table:
time_dt_save <- system.time({
fwrite(df, "data_dt.csv")
})
time_dt_read <- system.time({
df_dt <- fread("data_dt.csv")
})
resultados$data_table <- list(save = time_dt_save, read = time_dt_read)
print(resultados$data_table)
## $save
## user system elapsed
## 16.11 1.82 55.97
##
## $read
## user system elapsed
## 9.79 3.00 51.98
# ---------------------------------------------------------------------------
# 2. arrow: Escritura y lectura en formato Parquet
# ---------------------------------------------------------------------------
cat("arrow (Parquet):\n")
## arrow (Parquet):
time_arrow_save <- system.time({
write_parquet(df, "data_arrow.parquet")
})
time_arrow_read <- system.time({
df_arrow <- read_parquet("data_arrow.parquet")
})
resultados$arrow <- list(save = time_arrow_save, read = time_arrow_read)
print(resultados$arrow)
## $save
## user system elapsed
## 11.82 0.71 43.84
##
## $read
## user system elapsed
## 2.49 1.66 9.44
# ---------------------------------------------------------------------------
# 3. fst: Escritura y lectura con formato .fst
# ---------------------------------------------------------------------------
cat("fst:\n")
## fst:
time_fst_save <- system.time({
write_fst(df, "data_fst.fst")
})
time_fst_read <- system.time({
df_fst <- read_fst("data_fst.fst")
})
resultados$fst <- list(save = time_fst_save, read = time_fst_read)
print(resultados$fst)
## $save
## user system elapsed
## 1.05 0.56 36.80
##
## $read
## user system elapsed
## 1.20 0.63 4.91
# ---------------------------------------------------------------------------
# 5. bigmemory: Conversión de columnas numéricas a una big.matrix
# (bigmemory trabaja sobre matrices numéricas, por lo que se seleccionan las
# columnas ID, Valor_Numerico y Valor_Entero)
# ---------------------------------------------------------------------------
cat("bigmemory:\n")
## bigmemory:
time_bigmemory <- system.time({
big_mat <- as.big.matrix(df[, c("ID", "Valor_Numerico", "Valor_Entero")])
})
resultados$bigmemory <- list(create = time_bigmemory)
print(resultados$bigmemory)
## $create
## user system elapsed
## 60.50 37.75 148.83
# ---------------------------------------------------------------------------
# 6. sparklyr: Copiar el data frame a Spark y realizar una operación simple
# ---------------------------------------------------------------------------
cat("sparklyr:\n")
## sparklyr:
# Intentar desconectar cualquier sesión previa
if (exists("sc") && spark_connection_is_open(sc)) {
spark_disconnect(sc)
}
# Conectar a Spark
time_spark_connect <- system.time({
sc <- tryCatch({
spark_connect(master = "local")
}, error = function(e) {
message("Error al conectar con Spark: ", e$message)
NULL
})
})
## Error al conectar con Spark: Gateway in localhost:8880 did not respond.
##
##
## Try running `options(sparklyr.log.console = TRUE)` followed by `sc <- spark_connect(...)` for more debugging info.
# Verificar si la conexión se estableció correctamente
if (!is.null(sc)) {
# Seleccionar una muestra del DataFrame para evitar consumo excesivo de memoria
df_sample <- df[sample(nrow(df), min(100000, nrow(df))), ] # Evita errores si df tiene <100000 filas
# Copiar datos a Spark
time_spark_copy <- system.time({
df_spark <- tryCatch({
copy_to(sc, df_sample, overwrite = TRUE)
}, error = function(e) {
message("Error al copiar datos a Spark: ", e$message)
NULL
})
})
# Ejecutar operación solo si la copia fue exitosa
if (!is.null(df_spark)) {
time_spark_op <- system.time({
df_spark_result <- tryCatch({
df_spark %>%
filter(Valor_Numerico > 0) %>%
summarise(Media = mean(Valor_Numerico)) %>%
collect()
}, error = function(e) {
message("Error en la operación con Spark: ", e$message)
NULL
})
})
} else {
time_spark_op <- NA
}
# Desconectar Spark
spark_disconnect(sc)
} else {
time_spark_copy <- NA
time_spark_op <- NA
}
# Almacenar resultados en la lista
resultados$sparklyr <- list(connect = time_spark_connect, copy = time_spark_copy, operation = time_spark_op)
# Imprimir los resultados
print(resultados$sparklyr)
## $connect
## user system elapsed
## 0.08 0.09 71.75
##
## $copy
## [1] NA
##
## $operation
## [1] NA
# ---------------------------------------------------------------------------
# Mostrar resultados finales de los tiempos de ejecución
# ---------------------------------------------------------------------------
print(resultados)
## $data_table
## $data_table$save
## user system elapsed
## 16.11 1.82 55.97
##
## $data_table$read
## user system elapsed
## 9.79 3.00 51.98
##
##
## $arrow
## $arrow$save
## user system elapsed
## 11.82 0.71 43.84
##
## $arrow$read
## user system elapsed
## 2.49 1.66 9.44
##
##
## $fst
## $fst$save
## user system elapsed
## 1.05 0.56 36.80
##
## $fst$read
## user system elapsed
## 1.20 0.63 4.91
##
##
## $bigmemory
## $bigmemory$create
## user system elapsed
## 60.50 37.75 148.83
##
##
## $sparklyr
## $sparklyr$connect
## user system elapsed
## 0.08 0.09 71.75
##
## $sparklyr$copy
## [1] NA
##
## $sparklyr$operation
## [1] NA
# Cargar librerías necesarias
library(ggplot2)
library(dplyr)
# Convertir la lista de resultados en un data frame
df_resultados <- do.call(rbind, lapply(names(resultados), function(metodo) {
subresultados <- resultados[[metodo]]
do.call(rbind, lapply(names(subresultados), function(accion) {
data.frame(
Metodo = paste(metodo, accion, sep = " - "),
Tiempo = subresultados[[accion]]["elapsed"]
)
}))
}))
# Asegurar que los valores sean numéricos
df_resultados$Tiempo <- as.numeric(df_resultados$Tiempo)
df_resultados
## Metodo Tiempo
## elapsed data_table - save 55.97
## elapsed1 data_table - read 51.98
## elapsed2 arrow - save 43.84
## elapsed11 arrow - read 9.44
## elapsed3 fst - save 36.80
## elapsed12 fst - read 4.91
## elapsed4 bigmemory - create 148.83
## elapsed5 sparklyr - connect 71.75
## 1 sparklyr - copy NA
## 11 sparklyr - operation NA
# Crear gráfico de barras con ggplot2
ggplot(df_resultados, aes(x = reorder(Metodo, Tiempo), y = Tiempo, fill = Metodo)) +
geom_bar(stat = "identity") +
coord_flip() + # Invertir el eje para mejor visualización
labs(title = "Comparación de Tiempos de Ejecución en R para 100 millones de registros",
x = "Método",
y = "Tiempo (segundos)") +
theme_minimal() +
theme(legend.position = "none") # Ocultar la leyenda
El presente análisis comparativo de librerías para el manejo de grandes volúmenes de datos en R, basado en la simulación de 100 millones de registros, ha permitido evaluar el desempeño de diversas herramientas en términos de tiempo de ejecución para las tareas de almacenamiento, lectura y procesamiento de datos. Los resultados obtenidos muestran diferencias significativas en la eficiencia de cada método, lo que sugiere que la elección de la librería más adecuada dependerá de los requerimientos específicos del usuario y las características del conjunto de datos.
data.table
exhibe tiempos de
escritura y lectura relativamente elevados para 100 millones de
registros (22.95s en guardado, 88.40s en lectura), lo que podría
penalizar su uso en escenarios donde la velocidad de acceso a disco sea
esencial.
arrow
(Parquet) mantiene tiempos de
escritura aceptables (21.75s) y mejora notablemente en lectura (11.88s)
en comparación con data.table
, situándose como una buena
alternativa cuando se busca un equilibrio entre velocidad y
compatibilidad con formatos avanzados.
fst
continúa destacándose por su
eficiencia, con 14.10s en escritura y 6.00s en lectura,
constituyendo así la mejor opción para quienes priorizan una rápida
lectura/escritura en archivos locales de gran tamaño.
bigmemory
muestra tiempos altos en
la creación de objetos (97.64s), lo que sugiere que su principal ventaja
es trabajar con subconjuntos de datos en memoria compartida, más que en
la etapa de inicialización, que resulta costosa.
sparklyr
presenta un tiempo
moderado de conexión (20.20s), pero muestra gran rapidez en la
copia de datos (2.27s) y operación (1.02s). Esto lo hace
adecuado para entornos de procesamiento distribuido, donde, tras la fase
de inicialización, las operaciones sucesivas se benefician de la
infraestructura de Spark.
fst
sigue siendo la mejor opción.arrow
se mantiene como una alternativa
sólida para formatos de almacenamiento modernos y un tiempo de
lectura razonable en comparación con
data.table
.data.table
es potente para
manipulación en memoria, pero sus tiempos de lectura y escritura con 100
millones de registros pueden llegar a ser significativamente
altos.bigmemory
puede ser beneficioso cuando
se trabaja con grandes matrices en memoria compartida, aunque la
inicialización sea costosa.sparklyr
resulta ventajoso para
volúmenes de datos muy grandes en entornos distribuidos, especialmente
cuando las operaciones posteriores (filtrado, agregados) compensan el
costo inicial de conexión.En conclusión, la elección de la librería dependerá del tipo
de almacenamiento, disponibilidad de recursos de
hardware y flujo de trabajo requerido. Para la
mayoría de los casos en que se necesita rápida lectura y
escritura de datos muy voluminosos de forma local,
fst
mantiene la mejor relación entre velocidad de guardado
y acceso.
data.frame
. R package version 1.14.2.