Lendo dados rapidamente no R

Introdução

Anos atrás respondi a pergunta Como ler os microdados do ENEM no R? no Stack Overflow. Basicamente, ela questionava como seria possível ler, de forma eficiente, os dados do ENEM em um PC comum, destes que normalmente temos em casa. Como respondi esta pergunta há quase cinco anos, acredito que é uma boa hora para revisitá-la.

Nesta comparação estou utilizando os dados sobre filmes registrados no IMDb. Embora não sejam big data per se (afinal, são apenas 85855 linhas e 22 colunas), são dados em uma quantidade grande o suficiente para que a diferença no tempo entre leituras seja significante para o usuário.

Estratégias Disponíveis

Nesta comparação irei utilizar as seguintes maneira de importar dados para dentro do R:

  • read.csv: o padrão de leitura de arquivos csv dentro do R
  • read_csv: leitura de arquivos csv do tidyverse
  • read_delim: função do tidyverse com mais opções de personalização do que read_csv
  • fread: função do pacote data.table otimizada para leitura rápida de dados
  • import: comando para importar dados que adivinha o formato baseado na extensão do arquivo
  • readRDS: leitura de arquivos binários criados pelo próprio R

Tirando a função readRDS, todas as outras funções serão testadas no mesmo arquivo csv com dados do IMDb. A função readRDS vai ler os mesmos dados, mas em um formato binário, próprio do R.

Benchmark

Vou rodar uma simulação a fim de comparar os resultados entre as funções de leitura de dados. O pacote microbenchmark vai ser utilizado para simplificar o código e armazenar automaticamente os resultados de tempo de leitura obtidos. Cada função de leitura de dados será repetida 100 vezes e seus tempos de execução serão analisados posteriormente.

library(microbenchmark)
library(tidyverse)
theme_set(theme_bw())
library(scales)
library(data.table)
library(rio)

arquivo <- "data/IMDb_movies.csv"
arquivoRDS <- "data/IMDb_movies.rds"

tempos <- 
    microbenchmark(
    "read.csv" = {
        dados <- read.csv(arquivo)
        },
    "read_csv" = {
        dados <- read_csv(arquivo)
        },
    "read_delim" = {
        dados <- read_delim(arquivo)
    },
    "fread" = {
        dados <- fread(arquivo)
    },
    "import" = {
        dados <- import(arquivo)
    },
    "readRDS" = {
        dados <- readRDS(arquivoRDS)
    },
    times = 100)

Com a simulação realizada, basta organizar os resultados para vermos qual dos métodos é mais rápido para a leitura dos dados.

resultado <- data.frame(metodo = tempos$expr,
                                                tempo = tempos$time)

resultado %>%
    mutate(tempo = tempo/1000000) %>%
    group_by(metodo) %>%
    summarise(media = mean(tempo),
                        minimo = min(tempo),
                        q1 = quantile(tempo, .25),
                        mediana = median(tempo),
                        q3 = quantile(tempo, .75),
                        maximo = max(tempo)) %>%
    mutate(relativo = media/min(media)) %>%
    arrange(relativo)
## # A tibble: 6 × 8
##   metodo     media minimo    q1 mediana    q3 maximo relativo
##   <fct>      <dbl>  <dbl> <dbl>   <dbl> <dbl>  <dbl>    <dbl>
## 1 import      573.   461.  512.    552.  581.  1007.     1   
## 2 fread       578.   455.  514.    548.  611.   978.     1.01
## 3 readRDS    1007.   934.  951.    982. 1034.  1418.     1.76
## 4 read_csv   1057.  1006. 1019.   1032. 1055.  1329.     1.85
## 5 read_delim 1065.  1010. 1017.   1030. 1081.  1387.     1.86
## 6 read.csv   1380.  1155. 1248.   1321. 1472.  2188.     2.41

Dentre os seis métodos testados, import é o método mais rápido para leitura de dados. Em média, ele levou 573 milissegundos para ler o arquivo testado. É interessante notar que ele foi mais rápido que a função readRDS, que lê dados binários e, supostamente, deveria ser mais rápida, por não precisar fazer conversão de formatos.

Por outro lado, como é possível ver acima, na coluna relativo, o método mais lento demora, em média, 2.41 vezes mais tempo do que o método mais rápido. A função read.csv, portanto, não é a opção mais rápida para importar dados para dentro do R.

Comparando graficamente os resultados da simulação, temos o seguinte:

ggplot(resultado, aes(x = tempo, y = fct_reorder(metodo, tempo, .fun = mean))) + 
    geom_violin() +
    scale_x_continuous(labels = label_number(accuracy = 1, unit = "", scale = 1e-6)) +
    labs(x = "Tempo de Leitura (milissegundos)", y = "Método de Leitura")

É possível ver, no gráfico acima, como a distribuição dos tempos é diferente entre os métodos.

Conclusão

Aparentemente, a melhor opção para o usuário é utilizar a função rio::import. Além de obter os menores tempos de leitura, ela não exige que o usuário saiba o formato dos dados, importando-os de maneira rápida e efetiva para a memória do programa.