Capítulo 4 Importação e Exportação de Dados Locais

Sem dúvida, a primeira etapa de um script de pesquisa é carregar os seus dados em uma sessão do R. Neste capítulo iremos aprender a importar e exportar dados contidos em arquivos locais. Apesar de não ser uma tarefa particularmente difícil, um analista de dados deve entender as diferentes características de cada formato de arquivo e como tirar vantagem deste conhecimento em cada situação. Enquanto algumas facilitam a colaboração e troca de dados, outras podem oferecer um ganho significativo em tempo de execução na leitura e gravação.

Aqui iremos traçar uma lista abrangente com os seguintes formatos e extensões de arquivos:

  • Dados delimitados em texto (csv);
  • Microsoft Excel (xls, xlsx);
  • Arquivos de dados nativos do R (RData e rds)
  • Formato fst (fst)
  • SQLite (SQLITE)
  • Texto não estruturado (txt).

A primeira lição na importação de dados para o R é que o local do arquivo deve ser indicado explicitamente no código. Este endereço é passado para a função que irá ler o arquivo. Veja a definição a seguir:

my_file <- 'C:/Data/MyData.csv'

Note o uso de barras (/) para designar o diretório do arquivo. Referências relativas também funcionam, tal como em:

my_file <- 'Data/MyData.csv'

Neste caso, assume-se que na pasta atual de trabalho existe um diretório chamado Data e, dentro desse, um arquivo denominado MyData.csv. Se o endereço do arquivo é simplesmente o seu nome, assume-se que o mesmo encontra-se na raiz da pasta de trabalho. Para verificar o endereço atual de trabalho, utilize a função getwd.

Aqui novamente reforço o uso do tab e autocomplete do RStudio. É muito mais fácil e prático encontrar arquivos do disco rígido do computador usando a navegação via tab do que copiar e colar o endereço do seu explorador de arquivos. Para usar, abra aspas no RStudio, coloque o cursor do mouse entre as aspas e aperte tab.

Um ponto importante aqui é que os dados serão importados e exportados no R como objetos do tipo dataframe. Isto é, uma tabela contida em um arquivo Excel ou .csv se transformará em um objeto do tipo dataframe no ambiente de trabalho do R. Quando exportarmos dados, o formato mais comum é esse mesmo tipo de objeto. Convenientemente, dataframes são nada mais que tabelas, com linhas e colunas.

Cada coluna do dataframe importado terá a sua própria classe, sendo as mais comuns numérica (numeric), texto (character), fator (factor) e data (Date). Quando realizando a importação, é de fundamental importância que os dados sejam representados na classe correta. Uma vasta quantidade de erros podem ser evitados pela simples checagem das classes das colunas no dataframe resultante do processo de importação. Por enquanto somente é necessário entender esta propriedade básica de dataframes. Estudaremos esse objeto mais profundamente no capítulo 6.

4.1 Pacote adfeR

Nas seções futuras iremos utilizar o pacote do livro – adfeR – para carregar diversos exemplos de arquivos. Se você seguiu as instruções da seção Material Suplementar localizada no prefácio do livro, já deves ter o pacote instalado. Caso contrário, execute o seguinte código:

# install devtools (if not installed)
if (!require(devtools)) install.packages ('devtools')

# install book package
devtools::install_github ('msperlin/adfeR')

Uma vez que você instalou o pacote adfeR, todos os arquivos de dados usados no livro foram baixados. Podemos verificar os cinco primeiros arquivos disponíveis com o comando adfeR::list_available_data:

# list available data files
print(adfeR::list_available_data()[1:5])
R> [1] "batchgetsymbols_parallel_example.rds"
R> [2] "Brazil_footbal_games.csv"            
R> [3] "example_tsv.csv"                     
R> [4] "FileWithLatinChar_Latin1.txt"        
R> [5] "FileWithLatinChar_UTF-8.txt"

Os arquivos anteriores estão salvos na pasta de instalação dos pacote adfeR. Para ter o caminho completo, basta usar função adfeR::get_data_file tendo o nome do arquivo como entrada:

# get location of file
my_f <- adfeR::get_data_file('grunfeld.csv')

# print it
print(my_f)

A partir de agora iremos usar a função adfeR::get_data_file para obter o caminho dos arquivos utilizados nos exemplos. Note que, desde que tenha o pacote adfeR instalado, podes facilmente reproduzir todos os exemplos do livro no seu computador.

4.2 Arquivos csv

Considere o arquivo de dados no formato csv chamado 'Ibov.csv', pertencente ao repositório do livro. Vamos copiar o mesmo para a pasta “Meus Documentos” com o uso do tilda (~):

# get location of file
my_f <- adfeR::get_data_file('Ibov.csv')

# copy to ~
file.copy(from = my_f, 
          to = '~' )
R> [1] TRUE
R> [1] TRUE

Caso seja a primeira vez trabalhando com arquivos do tipo .csv, sugiro usar o explorador de arquivos do Windows e abrir Ibov.csv com qualquer editor de texto instalado, tal como o Notepad (veja figura 4.1). Observe que as primeiras linhas do arquivo definem os nomes das colunas: “ref.date” e “price.close”. Conforme notação internacional, as linhas são definidas pela quebra do texto e as colunas pelo uso da vírgula (,).

Ibov.csv no Notepad

Figura 4.1: Ibov.csv no Notepad

Quando trabalhando com dados brasileiros, a notação internacional pode gerar uma confusão desnecessária. Dados locais tendem a usar a vírgula para indicar valores decimais em números. Assim, é comum que dados locais do Brasil sejam exportados usando a semi-vírgula (;) como separador de colunas e a própria vírgula como símbolo de decimais. Como regra de bolso, nunca mude o formato no arquivo de texto original. Deixe esse serviço para o próprio R e seus pacotes.

O conteúdo de Ibov.csv é bastante conservador e não será difícil importar o seu conteúdo. Porém, saiba que muitas vezes o arquivo .csv vem com informações extras de cabeçalho – o chamado metadata – ou diferentes formatações que exigem adaptações. Como sugestão para evitar problemas, antes de prosseguir para a importação de dados em um arquivo .csv, abra o arquivo em um editor de texto qualquer e siga os seguintes passos:

  1. Verifique a existência de texto antes dos dados e a necessidade de ignorar algumas linhas iniciais. A maioria dos arquivos .csv não contém cabeçalho, porém deves sempre checar. No R, a função de leitura de arquivos .csv possui uma opção para ignorar um definido número de linhas antes de começar a leitura do arquivo;

  2. Verifique a existência ou não dos nomes das colunas na primeira linha com os dados. Em caso negativo, verifique com o autor qual o nome (e significado) das colunas;

  3. Verifique qual o símbolo de separador de colunas. Comumente, seguindo notação internacional, será a vírgula, porém nunca se tem certeza sem checar;

  4. Para dados numéricos, verifique o símbolo de decimal, o qual deve ser o ponto (.) tal como em 2.5. Caso necessário, podes ajustar o símbolo na própria função de leitura;

  5. Verifique a codificação do arquivo de texto. Normalmente é UTF-8, Latin1 (ISO-8859) ou windows1252. Esses são formatos amplos e devem ser suficientes para a maioria dos idiomas. Sempre que você encontrar símbolos estranhos nas colunas de texto do dataframe resultante, o problema é devido a uma diferença na codificação entre o arquivo e o R. Os usuários do Windows podem verificar a codificação de um arquivo de texto abrindo-o no software Notepad++15. As informações sobre a codificação estarão disponíveis no canto inferior direito do editor. No entanto, você precisa estar ciente de que o Notepad++ não faz parte da instalação do Windows e pode ser necessário instalá-lo em seu computador. Os usuários de Linux e Mac podem encontrar as mesmas informações em qualquer software editor de texto avançado, como o Kate16.

Sempre que você encontrar uma estrutura de texto inesperada em um arquivo .csv, use os argumentos da função de leitura csv para importar as informações corretamente. Repetindo, nunca modifique dados brutos manualmente. É muito mais eficiente usar o código R para lidar com diferentes estruturas de arquivos em .csv. Pode parecer mais trabalhoso, mas essa política vai economizar muito tempo no futuro, pois, em algumas semanas, você provavelmente esquecerá como limpou manualmente aquele arquivo .csv utilizado em pesquisa passada. Com o uso de código para a adaptação da importação de dados, sempre que você precisar atualizar o arquivo de dados, o código irá resolver todos os problemas, automatizando o processo.

4.2.1 Importação de Dados

O R possui uma função nativa chamada base::read.csv para importar dados de arquivos .csv. Porém, esse é um dos muitos casos em que a alternativa do tidyversereadr::read_csv – é mais eficiente e mais fácil de trabalhar. Resumindo, readr::read_csv lê arquivos mais rapidamente que base::read.csv, além de usar regras mais inteligentes para definir as classes das colunas importadas.

Este é a primeira vez que usamos um pacote do tidyverse, neste caso o readr. Antes de fazer isso, é necessário instalá-lo em sua sessão R. Uma maneira simples de instalar todos os pacotes pertencentes ao tidyverse é instalar o módulo de mesmo nome:

install.packages('tidyverse')

Após executar o código anterior, todos os pacotes tidyverse serão instalados em seu computador. Você também deve ter em mente que alguns aspectos dessa instalação podem demorar um pouco. Assim que terminar, carregue o conjunto de pacotes tidyverse.

# load library
library(tidyverse)

De volta à importação de dados de arquivos .csv, use a função readr::read_csv para carregar o conteúdo do arquivo Ibov.csv no R:

# set file to read
my_f <- adfeR::get_data_file('Ibov.csv')

# read data
my_df_ibov <- read_csv(my_f)
R> Rows: 2716 Columns: 2
R> ── Column specification ────────────────────────────────────
R> Delimiter: ","
R> dbl  (1): price.close
R> date (1): ref.date
R> 
R> ℹ Use `spec()` to retrieve the full column specification for this data.
R> ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

O conteúdo do arquivo importado é convertido para um objeto do tipo dataframe no R. Conforme mencionado no capítulo anterior, cada coluna de um dataframe tem uma classe. Podemos verificar as classes de my_df_ibov usando a função glimpse do pacote dplyr, que também faz parte do tidyverse:

# check content
glimpse(my_df_ibov)
R> Rows: 2,716
R> Columns: 2
R> $ ref.date    <date> 2010-01-04, 2010-01-05, 2010-01-06, 2…
R> $ price.close <dbl> 70045, 70240, 70729, 70451, 70263, 704…

Observe que a coluna de datas (ref.date) foi importada como um vetor Date e os preços de fechamento como numéricos (dbl, precisão dupla). Isso é exatamente o que esperávamos. Internamente, a função read_csv identifica as classes das colunas de acordo com seu conteúdo.

Observe também como o código anterior apresentou a mensagem “Parsed with column specification: …”. Essa mensagem mostra como a função identifica as classes das colunas lendo as primeiras 1000 linhas do arquivo. Regras inteligentes tentam prever a classe com base no conteúdo importado. Podemos usar essas informações em nosso próprio código copiando o texto e atribuindo-o a uma variável:

# set cols from readr import message
my_cols <- cols(
  price.close = col_double(),
  ref.date = col_date(format = "")
)

# read file with readr::read_csv
my_df_ibov <- read_csv(my_f,
                       col_types = my_cols)

Como um exercício, vamos importar os mesmos dados, porém usando a classe character (texto) para colunas ref.date:

# set cols from readr import message
my_cols <- cols(
  price.close = col_double(),
  ref.date = col_character()
)

# read file with readr::read_csv
my_df_ibov <- read_csv(my_f,
                       col_types = my_cols)

# check content
glimpse(my_df_ibov)
R> Rows: 2,716
R> Columns: 2
R> $ ref.date    <chr> "2010-01-04", "2010-01-05", "2010-01-0…
R> $ price.close <dbl> 70045, 70240, 70729, 70451, 70263, 704…

Como esperado, a coluna de datas – ref.date – agora foi importada como texto. Assim, o uso de readr::read_csv pode ser resumido em duas etapas: 1) leia o arquivo sem argumentos em read_csv; 2) copie o texto das classes de coluna padrão da mensagem de saída e adicione como entrada col_types. O conjunto de passos anterior é suficiente para a grande maioria dos casos. O uso da mensagem com as classes das colunas é particularmente útil quando o arquivo importado tem várias colunas e a definição manual de cada classe exige muita digitação.

Uma alternativa mais prática no uso do read_csv é confiar na heurística da função e usar a definição padrão das colunas automaticamente. Para isto, basta definir a entrada col_types como cols(). Veja a seguir:

# read file with readr::read_csv
my_df_ibov <- read_csv(my_f,
                       col_types = cols())

Agora, vamos estudar um caso mais anormal de arquivo .csv. No pacote do livro temos um arquivo chamado funky_csv_file.csv onde:

  • o cabeçalho possui texto com informações dos dados;
  • o arquivo usará a vírgula como decimal;
  • o texto do arquivo conterá caracteres latinos.

As primeiras 10 linhas dos arquivos contém o seguinte conteúdo:

R> Exemplo de arquivo .csv com formato alternativo:
R> - colunas separadas por ";"
R> - decimal como ","
R> 
R> Dados retirados em 2021-01-13
R> Origem: www.funkysite.com.br
R> 
R> COD.UF;COD;NOME;state;SIGLA;number_col
R> 35;3546306;Santa Cruz das Palmeiras;São Paulo; SP;1,90208656713367
R> 21;2103109;Cedral;Maranhão; MA;69,8087496915832

Note a existência do cabeçalho até linha de número 7 e as colunas sendo separadas pela semi-vírgula (“;”).

Ao importar os dados com opções padrões (e erradas), teremos o resultado a seguir:

my_f <- adfeR::get_data_file('funky_csv_file.csv')

df_funky <- read_csv(my_f, 
                     col_types = cols())

glimpse(df_funky)
R> Rows: 2
R> Columns: 1
R> $ `Exemplo de arquivo .csv com formato alternativo:` <chr> …

Claramente a importação deu errado, com a emissão de diversas mensagens de warning. Para resolver, utilizamos o seguinte código, estruturando todas as particularidades do arquivo:

df_not_funky <- read_delim(file = my_f, 
                           skip = 7, # how many lines do skip
                           delim = ';', # column separator
                           col_types = cols(), # column types
                           locale = locale(decimal_mark = ',')# locale
)

glimpse(df_not_funky)
R> Rows: 100
R> Columns: 6
R> $ COD.UF     <dbl> 35, 21, 35, 35, 41, 31, 31, 21, 29, 26,…
R> $ COD        <dbl> 3546306, 2103109, 3514700, 3538105, 411…
R> $ NOME       <chr> "Santa Cruz das Palmeiras", "Cedral", "…
R> $ state      <chr> "São Paulo", "Maranhão", "São Paulo", "…
R> $ SIGLA      <chr> " SP", " MA", " SP", " SP", " PR", " MG…
R> $ number_col <dbl> 1.902087, 69.808750, 81.509312, 56.8400…

Veja que agora os dados foram corretamente importados, com as classes corretas das colunas. Para isso, usamos a função alternativa readr::read_delim. O pacote readr também possui várias outras funções para situações específicas de importação. Caso a função read_csv não resolva o seu problema na leitura de algum arquivo de dados estruturado em texto, certamente outra função desse pacote resolverá.

4.2.2 Exportação de Dados

Para exportar tabelas em um arquivo .csv, basta utilizar a função readr::write_csv. No próximo exemplo iremos criar dados artificiais, salvar em um dataframe e exportar para um arquivo .csv temporário. Veja a seguir:

library(readr)

# set number of observations
N <- 100

# create dataframe with random data
my_df <- data.frame(y = runif(N),
                    z = rep('a', N))

# write to file
f_out <- tempfile(fileext = '.csv')
write_csv(x = my_df, file = f_out)

No exemplo anterior, salvamos o dataframe chamado my_df para o arquivo file11cf64c4af67.csv, localizado na pasta temporária do computador. Podemos verificar o arquivo importando o seu conteúdo:

my_df <- read_csv(f_out,
                  col_types = cols(y = col_double(),
                                   z = col_character() ) )
print(head(my_df))
R> # A tibble: 6 × 2
R>        y z    
R>    <dbl> <chr>
R> 1 0.810  a    
R> 2 0.953  a    
R> 3 0.120  a    
R> 4 0.0578 a    
R> 5 0.821  a    
R> 6 0.312  a

O resultado está conforme o esperado, um dataframe com duas colunas, a primeira com números e a segunda com texto.

Note que toda exportação com função write_csv irá ser formatada, por padrão, com a notação internacional. Caso quiser algo diferentes, verifique as opções disponíveis na função write_delim, a qual é muito mais flexível.

4.3 Arquivos Excel (xls e xlsx)

Em Finanças e Economia, é bastante comum encontrarmos dados salvos em arquivos do tipo Microsoft Excel, com extensão .xls ou .xlsx. Apesar de não ser um formato de armazenamento de dados eficiente, esse é um programa de planilhas bastante popular devido às suas funcionalidades. É muito comum que informações sejam armazenadas e distribuídas dessa forma. Por exemplo, dados históricos do Tesouro Direto são disponibilizados como arquivos .xls no site do tesouro nacional. A CVM (Comissão de Valores Mobiliários) e ANBIMA (Associação Brasileira das Entidades dos Mercados Financeiro e de Capitais) também tem preferência por esse tipo de formato em alguns dados publicados em seu site.

A desvantagem de usar arquivos do Excel para armazenar dados é sua baixa portabilidade e o maior tempo necessário para leitura e gravação. Isso pode não ser um problema para tabelas pequenas, mas ao lidar com um grande volume de dados, o uso de arquivos Excel é frustrante e não aconselhável. Se possível, evite o uso de arquivos do Excel em seu ciclo de trabalho.

4.3.1 Importação de Dados

O R não possui uma função nativa para importar dados do Excel e, portanto, deve-se instalar e utilizar certos pacotes para realizar essa operação. Existem diversas opções, porém, os principais pacotes são XLConnect (Mirai Solutions GmbH 2022), xlsx (Dragulescu and Arendt 2020), readxl (Wickham and Bryan 2022) e tidyxl (Garmonsway 2022). .

Apesar de os pacotes anteriores terem objetivos semelhantes, cada um tem suas peculiaridades. Caso a leitura de arquivos do Excel seja algo importante no seu trabalho, aconselho-o fortemente a estudar as diferenças entre esses pacotes. Por exemplo, pacote tidyxl permite a leitura de dados não-estruturados de um arquivo Excel, enquanto XLConnect possibilita a abertura de uma conexão ativa entre o R e o Excel, onde o usuário pode transmitir dados entre um e o outro, formatar células, criar gráficos no Excel e muito mais.

Nesta seção, daremos prioridade para funções do pacote readxl, que é um dos mais fáceis e diretos de se utilizar, além de não necessitar de outros softwares instalados (tal como o Java). Para instalar o referido pacote, basta utilizar a função install.packages:

Imagine agora a existência de um arquivo chamado Ibov_xls.xlsx que contenha os mesmos dados do Ibovespa que importamos na seção anterior. A importação das informações contidas nesse arquivo para o R será realizada através da função read_excel:

library(readxl)
library(dplyr)

# set file
my_f <- '00-text-resources/data/Ibov_xlsx.xlsx'

# read xlsx into dataframe
my_df <- read_excel(my_f, sheet = 'Sheet1')

# glimpse contents
glimpse(my_df)
R> Rows: 2,721
R> Columns: 2
R> $ ref.date    <dttm> 2010-01-04, 2010-01-05, 2010-01-06, 2…
R> $ price.close <dbl> 70045, 70240, 70729, 70451, 70263, 704…

Observe que, nesse caso, as datas já foram importadas com a formatação correta na classe dttm (datetime). Essa é uma vantagem ao utilizar arquivos do Excel: a classe dos dados do arquivo original é levada em conta no momento da importação. O lado negativo desse formato é a baixa portabilidade dos dados e o maior tempo necessário para a execução da importação. Como regra geral, dados importados do Excel apresentarão um tempo de carregamento mais alto do que dados importados de arquivos .csv.

4.3.2 Exportação de Dados

A exportação para arquivo Excel também é fácil. Assim como para a importação, não existe uma função nativa do R que execute esse procedimento. Para tal tarefa, temos pacotes xlsx e writexl (Ooms 2022). Uma diferença aqui é que o pacote xlsx oferece mais funcionalidade mas exige a instalação do Java JDK no sistema operacional. No caso do Windows, basta visitar o site do Java e instalar o software na versão 64 bits (opção Windows Off-line (64 bits)). Logo após, instale o pacote xlsx normalmente no R com o comando install.packages('xlsx').

Vamos começar com um exemplo para xlsx

library(xlsx)

# set number of rows
N <- 50

# create random dataframe
my_df <- data.frame(y = seq(1,N),
                    z = rep('a',N))

# write to xlsx
f_out <- tempfile(fileext = '.xlsx')
write.xlsx(x = my_df,
           file = f_out,
           sheetName = "my df")

Note que uma diferença nos argumentos da função write.xlsx é que é necessário incluir o nome da aba do arquivo Excel onde os dados da tabela serão exportados. Para exportar várias informações para um mesmo arquivo, é necessário utilizar o argumento append da função write.xlsx. Caso contrário, a função irá criar um novo arquivo em cada chamada da mesma. Veja o exemplo a seguir, onde exportamos dois dataframes para duas abas diferentes do mesmo arquivo Excel:

# set number of rows
N <- 25

# create random dfs
my_df_A <- data.frame(y = seq(1,N),
                      z = rep('a',N))

my_df_B <- data.frame(z = rep('b',N))

# write both df to single file
f_out <- tempfile(fileext = '.xlsx')
write.xlsx(x = my_df_A,
           file = f_out,
           sheetName = "Tabela A")

write.xlsx(x = my_df_B,
           file = f_out,
           sheetName = "Tabela B",
           append = TRUE )

Após a exportação, podes verificar as abas disponíveis no arquivo exportado com função xlsx::getSheets:

readxl::excel_sheets(f_out)
R> [1] "Tabela A" "Tabela B"

O diferencial do pacote writexl em relação a xlsx é a não necessidade do Java, e a rapidez de execução. O lado negativo é que, na versão atual (1.4.1 – 2022-11-23), não permite a escrita em arquivos já existentes. Veja a seguir:

library(writexl)
# set number of rows
N <- 25

# create random dfs
my_df_A <- data.frame(y = seq(1,N),
                      z = rep('a',N))

write_xlsx(x = my_df_A,
           file = f_out)

Para comparar o desempenho, vamos verificar a diferença de tempo de execução entre um e outro:

library(writexl)
library(readxl)
library(xlsx)

# set number of rows
N <- 2500

# create random dfs
my_df_A <- data.frame(y = seq(1,N),
                      z = rep('a',N))

# set files
my_file_1 <- '00-text-resources/data/temp_writexl.xlsx'
my_file_2 <- '00-text-resources/data/temp_xlsx.xlsx'

# test export
time_write_writexl <- system.time(write_xlsx(x = my_df_A,
                                             path = my_file_1))

time_write_xlsx <- system.time(write.xlsx(x = my_df_A,
                                          file = my_file_2))

# test read
time_read_readxl <- system.time(read_xlsx(path = my_file_1 ))
time_read_xlsx <- system.time(read.xlsx(file = my_file_2,
                                        sheetIndex = 1 ))

Após a execução, vamos verificar a diferença de tempo:

# results
my_formats <- c('xlsx', 'readxl')
results_read <- c(time_read_xlsx[3], time_read_readxl[3])
results_write<- c(time_write_xlsx[3], time_write_writexl[3])

# print text
my_text <- paste0('\nTime to WRITE dataframe with ',
                  my_formats, ': ',
                  format(results_write, digits = 4),
                  ' seconds', collapse = '')
message(my_text)
R> 
R> Time to WRITE dataframe with xlsx: 1.545 seconds
R> Time to WRITE dataframe with readxl: 0.011 seconds
my_text <- paste0('\nTime to READ dataframe with ',
                  my_formats, ': ',
                  format(results_read, digits = 4),
                  ' seconds', collapse = '')
message(my_text)
R> 
R> Time to READ dataframe with xlsx: 2.547 seconds
R> Time to READ dataframe with readxl: 0.007 seconds

Como podemos ver, mesmo para dados de pouco volume, um dataframe com 2500 linhas e 2 colunas, a diferença de tempo de execução é significativa. Caso estiveres trabalhando com grandes planilhas, o uso de pacotes readxl e writexl é fortemente recomendado. Porém, como já mostrado anteriormente, as funções de xlsx oferecem algumas funcionalidades extras.

4.4 Formato .RData e .rds

O R possui dois formatos nativos para salvar objetos de sua área de trabalho para um arquivo local com extensão RData ou rds. O grande benefício, em ambos os casos, é que o arquivo resultante é compacto e o seu acesso é muito rápido. A desvantagem é que os dados perdem portabilidade para outros programas. A diferença entre um formato e outro é que arquivos RData podem salvar mais de um objeto, enquanto o formato .rds salva apenas um. Na prática, porém, essa não é uma restrição forte. No R existe um objeto do tipo lista que incorpora outros. Portanto, caso salvarmos uma lista em um arquivo .rds, podemos gravar no disco quantos objetos forem necessário.

4.4.1 Importação de Dados

Para carregar os dados de um aquivo RData, utilizamos a função load:

# set a object
my_x <- 1:100

# set temp name of RData file
my_file <- adfeR::get_data_file('temp.RData')

# load it
load(file = my_file)

O arquivo temp.RData possui dois objetos, my_x e my_y, os quais se tornam disponíveis na área de trabalho depois da chamada de load.

O processo de importação para arquivos .rds é muito semelhante. A diferença é no uso da função readr::read_rds:

# set file path
my_file <- adfeR::get_data_file('temp.rds')

# load content into workspace
my_x <- readr::read_rds(file = my_file)

Comparando o código entre o uso de arquivos .RData e .rds, note que um benefício no uso de .rds é a explícita definição do objeto na área de trabalho. Isto é, o conteúdo de my_file em readr::read_rds é explicitamente salvo em my_x. Quando usamos a função load, no código não fica claro qual o nome do objeto que foi importado. Isso é particularmente inconveniente quando é necessário modificar o nome do objeto importado.

Como sugestão, dê preferência ao uso do formato .rds, o qual resulta em códigos mais transparentes. A diferença de velocidade de acesso e gravação entre um e outro é mínima. O benefício de importar vários objetos em um mesmo arquivo com o formato RData torna-se irrelevante quando no uso de objetos do tipo lista, os quais podem incorporar outros objetos no seu conteúdo.

4.4.2 Exportação de Dados

Para criar um novo arquivo RData, utilizamos a função save. Veja o exemplo a seguir, onde criamos um arquivo RData com dois objetos:

# set vars
my_x <- 1:100
my_y <- 1:100

# write to RData
my_file <- tempfile(fileext = '.RData')
save(list = c('my_x', 'my_y'),
     file = my_file)

Podemos verificar a existência do arquivo com a função file.exists:

file.exists(my_file)
R> [1] TRUE

Observe que o arquivo file11cf1ededbdc.RData está disponível na pasta temporária.

Já para arquivos .rds, salvamos o objeto com função saveRDS:

# set data and file
my_x <- 1:100
my_file <- '00-text-resources/data/temp.rds'

# save as .rds
saveRDS(object = my_x,
        file = my_file)

# read it
my_x2 <- readRDS(file = my_file)

# test equality
print(identical(my_x, my_x2))
R> [1] TRUE

O comando identical testa a igualdade entre os objetos e, como esperado, verificamos que my_x e my_x2 são exatamente iguais.

4.5 Arquivos fst (pacote fst)

O formato fst foi especialmente desenhado para possibilitar a gravação e leitura de dados tabulares de forma rápida e com mínimo uso do espaço no disco. O uso deste formato é particularmente benéfico quando se está trabalhando com volumosas bases de dados em computadores potentes. O grande truque do formato fst é usar todos núcleos do computador para importar e exportar dados, enquanto todos os demais formatos se utilizam de apenas um. Como logo veremos, o ganho em velocidade é bastante significativo.

4.5.1 Importação de Dados

O uso do formato fst é bastante simples. Utilizamos a função read_fst para ler arquivos:

library(fst)

my_file <- adfeR::get_data_file('temp.fst')
my_df <- read_fst(my_file)

glimpse(my_df)
R> Rows: 1,000
R> Columns: 1
R> $ x <dbl> 0.70968891, 0.83903044, 0.70026554, 0.78120026, …

Assim como para os demais casos, os dados estão disponíveis na área de trabalho após a importação.

4.5.2 Exportação de Dados

Utilizamos a função write_fst para gravar arquivos no formato fst, :

library(fst)

# create dataframe
N <- 1000
my_file <- tempfile(fileext = '.fst')
my_df <- data.frame(x = runif(N))

# write to fst
write_fst(x = my_df, path = my_file)

4.5.3 Testando o Tempo de Execução do Formato fst

Como um teste do potencial do pacote fst, a seguir vamos cronometrar o tempo de leitura e gravação entre fst e rds para um dataframe com grande quantidade de dados: 5,000,000 linhas e 2 colunas. Iremos reportar também o tamanho do arquivo resultante.

library(fst)

# set number of rows
N <- 5000000

# create random dfs
my_df <- data.frame(y = seq(1,N),
                    z = rep('a',N))

# set files
my_file_1 <- '00-text-resources/data/temp_rds.rds'
my_file_2 <- '00-text-resources/data/temp_fst.fst'

# test write
time_write_rds <- system.time(write_rds(my_df, my_file_1 ))
time_write_fst <- system.time(write_fst(my_df, my_file_2 ))

# test read
time_read_rds <- system.time(readRDS(my_file_1))
time_read_fst <- system.time(read_fst(my_file_2))

# test file size (MB)
file_size_rds <- file.size(my_file_1)/1000000
file_size_fst <- file.size(my_file_2)/1000000

Após a execução, vamos verificar o resultado:

# results
my_formats <- c('.rds', '.fst')
results_read <- c(time_read_rds[3], time_read_fst[3])
results_write<- c(time_write_rds[3], time_write_fst[3])
results_file_size <- c(file_size_rds , file_size_fst)

# print text
my_text <- paste0('\nTime to WRITE dataframe with ',
                  my_formats, ': ',
                  results_write, ' seconds', collapse = '')
message(my_text)
R> 
R> Time to WRITE dataframe with .rds: 1.04099999999994 seconds
R> Time to WRITE dataframe with .fst: 0.0950000000000273 seconds
my_text <- paste0('\nTime to READ dataframe with ',
                  my_formats, ': ',
                  results_read, ' seconds', collapse = '')
message(my_text)
R> 
R> Time to READ dataframe with .rds: 1.03099999999995 seconds
R> Time to READ dataframe with .fst: 0.0939999999998236 seconds
my_text <- paste0('\nResulting FILE SIZE for ',
                  my_formats, ': ',
                  results_file_size, ' MBs', collapse = '')
message(my_text)
R> 
R> Resulting FILE SIZE for .rds: 65.01011 MBs
R> Resulting FILE SIZE for .fst: 14.791938 MBs

A diferença é gritante! O formato fst não somente lê e grava com mais rapidez mas o arquivo resultante também é menor. Porém, saiba que os resultados anteriores foram compilados em um computador com 16 núcleos. É possível que a diferença de tempo para um computador mais modesto não seja tão significativa.

Devido ao uso de todos os núcleos do computador, o formato fst é altamente recomendado quando estiver trabalhando com dados volumosos em um computador potente. Não somente os arquivos resultantes serão menores, mas o processo de gravação e leitura será consideravelmente mais rápido.

4.6 Arquivos SQLite

O uso de arquivos csv, rds e fst para armazenar conjuntos de dados tem seus limites a medida que o tamanho dos arquivos aumenta e os dados fragmentam-se em várias tabelas. Se você está esperando muito tempo para ler apenas uma tabela de um arquivo com várias tabelas, deves procurar alternativas mais eficientes. Da mesma forma, se você estiver trabalhando em uma rede de computadores e muitas pessoas estão usando os mesmos dados, faz sentido manter e distribuir as informações de um servidor central. Dessa forma, cada usuário pode ter acesso à mesma informação, simultaneamente.

Isso nos leva ao tópico de programas de armazenamento e distribuição de banco de dados. Esses programas específicos geralmente funcionam com uma linguagem de consulta chamada SQL (Structured Query Language), e permitem ao usuário ler partes dos dados e mesmo manipulá-lo de forma eficiente. Existem muitas opções de software de banco de dados que se integra muito bem com R. A lista inclui mySQL, SQLite e MariaDB. Aqui, forneceremos um tutorial rápido sobre esse tópico usando o SQLite, que é o mais fácil de usar, uma vez que não precisa de nenhuma configuração do servidor e todos dados estão contidos em um único arquivo.

Antes de irmos para os exemplos, precisamos entender como se usa o software de banco de dados. Primeiro, um banco de dados deve existir em seu computador ou rede. Segundo, o R se conectará ao banco de dados e retornará um objeto de conexão. Com base nessa conexão, enviaremos consultas para importar dados desse banco de dados usando a linguagem SQL. A principal vantagem é que podemos ter um grande banco de dados de, digamos, 10 GB e carregar apenas uma pequena porção dele na área de trabalho do R. Essa operação também é muito rápida, permitindo um acesso eficiente às tabelas disponíveis.

4.6.1 Importação de Dados

Assumindo a existência de um arquivo com formato SQLite, podemos importar suas tabelas com o pacote RSQLite:

library(RSQLite)

# set name of SQLITE file
f_sqlite <- adfeR::get_data_file('SQLite_db.SQLITE')

# open connection
my_con <- dbConnect(drv = SQLite(), f_sqlite)

# read table
my_df <- dbReadTable(conn = my_con,
                     name = 'MyTable1') # name of table in sqlite

# print with str
glimpse(my_df)
R> Rows: 1,000,000
R> Columns: 2
R> $ x <dbl> 0.007504194, 0.439465174, 0.178387480, 0.9857759…
R> $ G <chr> "B", "B", "B", "B", "A", "B", "A", "B", "B", "B"…

Outro exemplo do uso do SQLITE é com instruções de um comando SQL. Observe que, no código anterior, usamos função dbReadTable para obter o conteúdo de todas as linhas da tabela MyTable1. Agora, vamos usar o comando dbGetQuery para obter dados da tabela myTable2 apenas quando a coluna G é igual a A:

# set sql statement
my_SQL_statement <- "select * from myTable2 where G='A'"

# get query
my_df_A <- dbGetQuery(conn = my_con, 
                      statement = my_SQL_statement)

# disconnect from db
dbDisconnect(my_con)

# print with str
print(str(my_df_A))
R> 'data.frame':    499522 obs. of  2 variables:
R>  $ x: num  0.0637 0.1982 0.2894 0.7389 0.0669 ...
R>  $ G: chr  "A" "A" "A" "A" ...
R> NULL

Nesse exemplo simples podemos ver como é fácil criar uma conexão com um banco de dados, recuperar tabelas e desconectar. Se você estiver trabalhando com dados volumosos e diversas tabelas, vale a pena utilizar um software de banco de dados apropriado. Caso existir um servidor de banco de dados disponível em seu local de trabalho, eu recomendo fortemente aprender a conectar-se a ele e carregar dados diretamente de uma sessão do R.

4.6.2 Exportação de Dados

Como exemplo, vamos criar dois dataframes com dados aleatórios e salvar ambos em um arquivo SQLite usando pacote RSQLite.

library(RSQLite)

# set number of rows in df
N = 10^6 

# create simulated dataframe
my_large_df_1 <- data.frame(x=runif(N), 
                            G= sample(c('A','B'),
                                      size = N,
                                      replace = TRUE))

my_large_df_2 <- data.frame(x=runif(N), 
                            G = sample(c('A','B'),
                                       size = N,
                                       replace = TRUE))

# set name of SQLITE file
f_sqlite <- tempfile(fileext = '.SQLITE')

# open connection
my_con <- dbConnect(drv = SQLite(), f_sqlite)

# write df to sqlite
dbWriteTable(conn = my_con, name = 'MyTable1', 
             value = my_large_df_1)
dbWriteTable(conn = my_con, name = 'MyTable2', 
             value = my_large_df_2)

# disconnect
dbDisconnect(my_con)

A saída TRUE de dbWriteTable indica que tudo ocorreu bem. Uma conexão foi aberta usando a função dbConnect e os dataframes foram escritos em um arquivo SQLITE temporário chamado file11cf144d347f.SQLITE. É boa política de programação sempre se desconectar do banco de dados após a utilização. Fizemos isso com a função dbDisconnect.

4.7 Dados Não-Estruturados e Outros Formatos

Os pacotes e formatos anteriores são suficientes para resolver o problema de importação de dados na grande maioria das situações. Apesar disso, vale destacar que o R possui outras funções específicas para diferentes formatos. Isso inclui arquivos exportados de outros softwares, tal como SPSS, Matlab, entre vários outros. Se esse for o seu caso, sugiro um estudo aprofundado do pacote foreign (R Core Team 2022).

Em alguns casos nos deparamos com dados armazenados de uma forma não estruturada, tal como um texto qualquer. Pode-se importar o conteúdo de um arquivo de texto linha por linha através da função readr::read_lines. Veja o exemplo a seguir, onde importamos o conteúdo inteiro do livro Pride and Prejudice:

# set file to read
my_f <- adfeR::get_data_file('pride_and_prejudice.txt')

# read file line by line
my_txt <- read_lines(my_f)

# print 50 characters of first fifteen lines
print(str_sub(string = my_txt[1:15], 
              start = 1, 
              end = 50))
R>  [1] "The Project Gutenberg EBook of Pride and Prejudice"
R>  [2] ""                                                  
R>  [3] "This eBook is for the use of anyone anywhere at no"
R>  [4] "almost no restrictions whatsoever.  You may copy i"
R>  [5] "re-use it under the terms of the Project Gutenberg"
R>  [6] "with this eBook or online at www.gutenberg.org"    
R>  [7] ""                                                  
R>  [8] ""                                                  
R>  [9] "Title: Pride and Prejudice"                        
R> [10] ""                                                  
R> [11] "Author: Jane Austen"                               
R> [12] ""                                                  
R> [13] "Posting Date: August 26, 2008 [EBook #1342]"       
R> [14] "Release Date: June, 1998"                          
R> [15] "Last Updated: March 10, 2018"

Neste exemplo, arquivo pride_and_prejudice.txt contém todo o conteúdo do livro Pride and Prejudice de Jane Austen, disponível gratuitamente pelo projeto Gutenberg17. Importamos todo o conteúdo do arquivo como um vetor de texto denominado my_txt. Cada elemento de my_txt é uma linha do arquivo do texto original. Com base nisso, podemos calcular o número de linhas do livro e o número de vezes que o nome 'Bennet', um dos protagonistas, aparece no texto:

# count number of lines
n_lines <- length(my_txt)

# set target text
name_to_search <- 'Bennet'

# set function for counting words
fct_count_bennet <- function(str_in, target_text) {
  
  require(stringr)
  
  
  n_words <- length(str_locate_all(string = str_in, 
                                   pattern = target_text)[[1]])
  
  return(n_words)
}

# use fct for all lines of Pride and Prejudice
n_times <- sum(sapply(X = my_txt, 
                      FUN = fct_count_bennet, 
                      target_text = name_to_search))

# print results
my_msg <- paste0('The number of lines found in the file is ', 
                 n_lines, '.\n',
                 'The word "', name_to_search, '" appears ', 
                 n_times, ' times in the book.')
message(my_msg)
R> The number of lines found in the file is 13427.
R> The word "Bennet" appears 664 times in the book.

No exemplo, mais uma vez usamos sapply. Neste caso, a função nos permitiu usar outra função para cada elemento de my_txt. Neste caso, procuramos e contamos o número de vezes que a palavra “Bennet” foi encontrada no texto. Observe que poderíamos simplesmente mudar name_to_search por qualquer outro nome, caso quiséssemos.

4.7.1 Exportando de Dados Não-Estruturados

Em algumas situações, é necessário exportar algum tipo de texto para um arquivo. Por exemplo: quando se precisa salvar o registro de um procedimento em um arquivo de texto; ou quando se precisa gravar informações em um formato específico não suportado pelo R. Esse procedimento é bastante simples. Junto à função readr::write_lines, basta indicar um arquivo de texto para a saída com o argumento file. Veja a seguir:

# set file
my_f <- tempfile(fileext = '.txt')

# set some string
my_text <- paste0('Today is ', Sys.Date(), '\n', 
                  'Tomorrow is ', Sys.Date()+1)

# save string to file
write_lines(x = my_text, file = my_f, append = FALSE)

No exemplo, criamos um objeto de texto com uma mensagem sobre a data atual e gravamos a saída no arquivo temporário file11cf6e0160ac.txt. Podemos checar o resultado com a função readr::read_lines:

R> [1] "Today is 2022-11-23"    "Tomorrow is 2022-11-24"

4.8 Selecionando o Formato

Após entendermos a forma de salvar e carregar dados de arquivos locais em diferentes formatos, é importante discutirmos sobre a escolha do formato. O usuário deve levar em conta três pontos nessa decisão:

  • velocidade de importação e exportação;
  • tamanho do arquivo resultante;
  • compatibilidade com outros programas e sistemas.

Na grande maioria das situações, o uso de arquivos csv satisfaz esses quesitos. Ele nada mais é do que um arquivo de texto que pode ser aberto, visualizado e importado em qualquer programa. Desse modo, fica muito fácil compartilhar dados compatíveis com outros usuários. Além disso, o tamanho de arquivos csv geralmente não é exagerado em computadores modernos. Caso necessário, podes compactar o arquivo .csv usando o programa 7zip, por exemplo, o qual irá diminuir consideravelmente o tamanho do arquivo. Por esses motivos, o uso de arquivos csv para importações e exportações é preferível na grande maioria das situações.

Existem casos, porém, onde a velocidade de importação e exportação pode fazer diferença. Caso abrir mão de portabilidade não faça diferença ao projeto, o formato rds é ótimo e prático. Se este não foi suficiente, então a melhor alternativa é partir para o fst, o qual usa maior parte do hardware do computador para importar os dados. Como sugestão, caso puder, apenas evite o formato do Excel, o qual é o menos eficiente de todos.

4.9 Exercícios


Q.1

Crie um dataframe com o código a seguir:

library(dplyr)

my_N <- 10000
my_df <- tibble(x = 1:my_N,
                y = runif(my_N))

Exporte o dataframe resultante para cada um dos cinco formatos: csv, rds, xlsx, fst. Qual dos formatos ocupou maior espaço na memória do computador? Dica: file.size calcula o tamanho de arquivos dentro do próprio R.


Q.2

Melhore o código anterior com a mensuração do tempo de execução necessário para gravar os dados nos diferentes formatos. Qual formato teve a gravação mais rápida? Dica: use função system.time ou pacote tictoc para calcular os tempos de execução.

# do notice that this chunk requires the execution of previous solution
my_msg <- paste0('The format with least execution time for N = ', my_N, ' is ', 
                 tab$Result[which.min(tab$Time)], '.')
message(my_msg)

Q.3

Para o código anterior, redefina o valor de my_N para 1000000. Esta mudança modifica as respostas das duas últimas perguntas?

# do notice that this chunk requires the execution of previous solution
my_N <- 1000000

tab <- do_tests(my_N)
print(tab)

my_msg <- paste0('The format with largest disk space for N = ', my_N, ' is ', 
                 tab$Result[which.max(tab$Size)], '.')

message(my_msg)

my_msg <- paste0('The format with least execution time for N = ', my_N, ' is ', 
                 tab$Result[which.min(tab$Time)], '.')
message(my_msg)

Q.4

Use função adfeR::get_data_file para acessar o arquivo SP500.csv no repositório de dados do livro. Importe o conteúdo do arquivo no R com função readr::read_csv. Quantas linhas existem no dataframe resultante?



Resposta:

A solução é 2264. Para chegar no resultado anterior, deves executar o código abaixo. Para isso, abra um novo script no RStudio (Control+shift+N), copie e cole o código, e rode o script inteiro apertando Control+Shift+Enter ou por linha com Control+Enter.
my_f <- adfeR::get_data_file('SP500.csv')

df_SP500 <- readr::read_csv(my_f, 
                     col_types = readr::cols())

my_msg <- paste0('There are ', nrow(df_SP500), ' rows and ', 
                 ncol(df_SP500), ' columns in file ', basename(my_f))
message(my_msg)

my_sol <- nrow(df_SP500)

Q.5

No link https://eeecon.uibk.ac.at/~zeileis/grunfeld/Grunfeld.csv você encontrará um arquivo .csv para os dados Grunfeld. Esta é uma tabela particularmente famosa devido ao seu uso como dados de referência em modelos econométricos. Usando função readr::read_csv, leia este arquivo usando o link direto como entrada em read_csv. Quantas colunas você encontra no dataframe resultante?



Resposta:

A solução é 5. Para chegar no resultado anterior, deves executar o código abaixo. Para isso, abra um novo script no RStudio (Control+shift+N), copie e cole o código, e rode o script inteiro apertando Control+Shift+Enter ou por linha com Control+Enter.

Q.6

Use função adfeR::get_data_file para acessar o arquivo example_tsv.csv no repositório de dados do livro. Note que as colunas dos dados estão separadas pelo símbolo de tabulação ('\t'). Após ler o manual do readr::read_delim, importe as informações deste arquivo para o seu computador. Quantas linhas o arquivo contém?



Resposta:

A solução é 5570. Para chegar no resultado anterior, deves executar o código abaixo. Para isso, abra um novo script no RStudio (Control+shift+N), copie e cole o código, e rode o script inteiro apertando Control+Shift+Enter ou por linha com Control+Enter.

Q.7

No pacote do livro existe um arquivo de dados chamado 'funky_csv2.csv'. Este possui um formato particularmente bizarro para os dados. Abra o mesmo em um editor de texto e procure entender como as colunas são separadas e qual o símbolo para o decimal. Após isso, veja as entradas da função read.table e importe a tabela na sessão do R. Caso somarmos o número de linhas com o número de colunas da tabela importada, qual o resultado?



Resposta:

A solução é 19. Para chegar no resultado anterior, deves executar o código abaixo. Para isso, abra um novo script no RStudio (Control+shift+N), copie e cole o código, e rode o script inteiro apertando Control+Shift+Enter ou por linha com Control+Enter.