Importação e Exportação de Dados

Programação
Referência guiada
Importação de dados
workflows
Importar, verificar e gravar dados quando os ficheiros não cooperam.

0 Importação e Exportação de Dados

Para importar dados, escolhemos uma função adequada ao formato do ficheiro e verificamos o resultado. Mesmo que o R consiga abri-lo, pode tê-lo lido de uma forma inesperada. Convém confirmar que é o ficheiro certo, no caminho certo, com o separador, a codificação e os tipos que esperávamos.

Nesta página vamos partir de alguns problemas comuns e ver que verificações os detectam. Se ainda não trabalhou com tabelas no R, a introdução aos data.frames explica o tipo de objecto que estas funções vão criar.

NotaFicheiros, Memória e Caminhos

Um ficheiro no disco e um objecto na memória são coisas diferentes:

file on disk  ≠  R object in memory

O working directory é o ponto de partida para caminhos relativos, como ../data/demo_ds.csv. Neste caminho, .. significa a pasta acima. Um caminho absoluto indica toda a localização e pode funcionar apenas no computador de quem o escreveu. Num projecto, uma estrutura previsível e caminhos relativos costumam ser mais portáveis.

O objecto vive na memória RAM, dentro da sessão R. Alterá-lo não altera o ficheiro original. Só uma operação de escrita, como write.csv(), cria ou substitui um ficheiro. Saber onde está cada versão ajuda-nos a evitar confusões quando começamos a importar e exportar dados.

1 Preparar os Ficheiros

Para repetir os exemplos no seu computador, prepare uma pasta de trabalho:

  1. Crie uma pasta r-practice com duas subpastas, data e programming.
  2. Descarregue demo_ds.csv para data, mantendo o nome e a extensão. Se o navegador mostrar o conteúdo, use Guardar ligação como… na ligação. Para os exemplos seguintes, guarde nessa mesma pasta lobsters-2016.tsv, raw_qualtrics.csv e, se quiser praticar Excel, global-shark-attack.xlsx.
  3. Crie um script e guarde-o como import.R dentro de programming. No RStudio, com esse script aberto, escolha Session → Set Working Directory → To Source File Location. Repita esta escolha ao abrir uma sessão nova. Esta escolha é feita no interpretador (a consola), antes de executar o script. Se ainda não criou um script, siga o pequeno teste da instalação.

Assim, ../data/demo_ds.csv significa «subir de programming para r-practice e entrar em data». Os exemplos de importação e limpeza usam esta estrutura. Se já tem os seus próprios ficheiros, adapte os caminhos à sua pasta de trabalho; não precisa de reproduzir a estrutura do site para os analisar.

Vamos usar dplyr, introduzido na página de data.frames, para seleccionar linhas e comparar tabelas. Se ainda não estiver instalado, instale-o uma vez na consola, fora do script. Carregamo-lo no início do script:

2 O Fluxo Mínimo

Na prática, o fluxo é simples: ler, inspeccionar, só depois escrever. Para não confundirmos a origem com o resultado, damos nomes e caminhos diferentes aos dois. Experimente primeiro na consola: escolha o ficheiro, leia-o e inspeccione o resultado. Depois copie para o script a leitura com o caminho concreto e as operações que quer repetir. Não copie o convite para escolher um ficheiro de cada vez. Abrir a folha de cálculo à mão pode ajudar a explorar, mas não deixa um rasto fácil de repetir.

ds <- read.csv("../data/demo_ds.csv")
head(ds)
pp_number consents age gender cond mood rts phase1 phase2 lab
1 Yes 19 female high 31 2106 18 80 B
2 Yes 23 female high 25 2249 27 83 A
3 Yes 41 female high 28 3248 23 95 B
4 Yes 26 female high 29 1931 30 81 A
5 Yes 26 female high 39 2410 28 88 B
6 Yes 42 female high 27 3451 18 89 A
str(ds)
'data.frame':   120 obs. of  10 variables:
 $ pp_number: int  1 2 3 4 5 6 7 8 9 10 ...
 $ consents : chr  "Yes" "Yes" "Yes" "Yes" ...
 $ age      : int  19 23 41 26 26 42 30 18 18 21 ...
 $ gender   : chr  "female" "female" "female" "female" ...
 $ cond     : chr  "high" "high" "high" "high" ...
 $ mood     : int  31 25 28 29 39 27 31 30 25 30 ...
 $ rts      : int  2106 2249 3248 1931 2410 3451 2450 1820 2168 2743 ...
 $ phase1   : int  18 27 23 30 28 18 24 11 29 15 ...
 $ phase2   : int  80 83 95 81 88 89 90 74 98 76 ...
 $ lab      : chr  "B" "A" "B" "A" ...

Depois de verificar os dados, podemos guardar uma cópia numa pasta de resultados. Os blocos que escrevem ficheiros ficam por executar nesta página; execute-os no seu computador quando quiser guardar a saída.

# Only after checking and transforming the data:
dir.create("results", showWarnings = FALSE)
write.csv(ds, "results/demo_ds_copy.csv", row.names = FALSE)

A pasta results fica dentro de programming, o directório de trabalho que preparámos. O original em ../data não é alterado. Se repetir a escrita com o mesmo caminho, substitui a cópia anterior: confirme sempre o destino antes de executar.

Erro: Ficheiro Não Encontrado

Um caminho relativo parte do working directory da sessão, que pode ser diferente da pasta aberta no gestor de ficheiros. Antes de mudar nomes ao acaso, veja qual é esse directório e teste se o ficheiro existe.

[1] "/builds/rggd/nrb/programming"
path <- "../data/demo_ds.csv"
file.exists(path)
[1] TRUE

Se file.exists() devolver FALSE, confirme o directório, a grafia do nome (incluindo maiúsculas e minúsculas) e a localização do ficheiro. Para uma escolha pontual, file.choose() devolve o caminho seleccionado na interface:

path <- file.choose()
ds <- read.csv(path)
dput(path)

Execute este bloco na consola, não no script. Depois de escolher o ficheiro, dput(path) mostra o caminho como texto R, já com as aspas e os caracteres especiais necessários. Copie esse texto para a atribuição a path no script. Para a estrutura de pastas preparada acima, podemos conservar uma versão relativa:

path <- "../data/demo_ds.csv"
ds <- read.csv(path)

Para escolher uma pasta, use o menu Session → Set Working Directory → Choose Directory… do RStudio, ou To Source File Location se já guardou ali o script. O RStudio envia um comando setwd() para a consola. Pode copiá-lo para um script de treino no seu computador, mas esse caminho pessoal terá de ser adaptado noutro computador. getwd() permite confirmar a pasta escolhida. Registe a pasta de partida do projecto e conserve no script os caminhos de entrada e saída, sem caixas de selecção interactivas. No fim, execute o script de cima para baixo numa sessão nova, depois de preparar o directório de trabalho.

Num projecto partilhado, é normalmente preferível manter uma estrutura previsível e usar caminhos relativos. setwd() pode ser útil numa sessão, mas um setwd() com o caminho pessoal de quem escreveu o script torna-o frágil noutra máquina.

Erro: Formato ou Separador

CSV significa valores separados por um delimitador, mas esse delimitador nem sempre é uma vírgula. Se todas as colunas aparecerem coladas umas às outras, confirme o separador e a codificação antes de concluir que os dados estão estragados.

# Comma: the usual option for read.csv().
ds <- read.csv("../data/demo_ds.csv")

# A tab-separated file uses read.delim().
ds_tsv <- read.delim("../data/lobsters-2016.tsv")

head(ds)
pp_number consents age gender cond mood rts phase1 phase2 lab
1 Yes 19 female high 31 2106 18 80 B
2 Yes 23 female high 25 2249 27 83 A
3 Yes 41 female high 28 3248 23 95 B
4 Yes 26 female high 29 1931 30 81 A
5 Yes 26 female high 39 2410 28 88 B
6 Yes 42 female high 27 3451 18 89 A
head(ds_tsv)
Year Tonnes Pounds.M. Pounds Value Value.M. Price.lb license.holders Traps.M. Boothbay
1950 8325 18.3526 18352600 6412311 6.4123 0.35 5152 0.430 9.9
1951 9416 20.7595 20759500 7214107 7.2141 0.35 4653 0.383 10.8
1952 9088 20.0363 20036300 8511821 8.5118 0.42 5032 0.417 10.1
1953 10115 22.3003 22300300 8411229 8.4112 0.38 5497 0.490 11.1
1954 9828 21.6677 21667700 8087156 8.0872 0.37 5794 0.488 10.2
1955 10305 22.7181 22718100 8716012 8.7160 0.38 6051 0.532 10.0

Se os acentos ou outros caracteres aparecerem corrompidos, confirme também a codificação em que o ficheiro foi gravado. Funções como read.csv() aceitam o argumento fileEncoding. Indique-o depois de identificar a codificação e volte a inspeccionar nomes e valores para confirmar a leitura.

A extensão .xlsx requer um pacote. Instale-o uma vez, fora do script. Depois pode carregar readxl no início da sessão ou chamar a função com o nome do pacote:

# Run installation once, outside the analysis script.
install.packages("readxl")
ds_excel <- readxl::read_excel("../data/global-shark-attack.xlsx")
dim(ds_excel)
[1] 6890   21
names(ds_excel)
 [1] "Date"                   "Year"                   "Type"                  
 [4] "Country"                "Area"                   "Location"              
 [7] "Activity"               "Name"                   "Sex"                   
[10] "Age"                    "Injury"                 "Fatal (Y/N)"           
[13] "Time"                   "Species"                "Investigator or Source"
[16] "pdf"                    "href formula"           "href"                  
[19] "Case Number...19"       "Case Number...20"       "original order"        

Sem outros argumentos, read_excel() lê a primeira folha. Num livro com várias folhas, convém escolher a que contém os dados pretendidos. Podemos consultar os nomes e indicar um deles em sheet:

readxl::excel_sheets("../data/global-shark-attack.xlsx")
[1] "Feuil1"
ds_sheet <- readxl::read_excel(
  "../data/global-shark-attack.xlsx", sheet = "Feuil1"
)
head(ds_sheet)
Date Year Type Country Area Location Activity Name Sex Age Injury Fatal (Y/N) Time Species Investigator or Source pdf href formula href Case Number…19 Case Number…20 original order
2023-07-29 2023 Unprovoked USA Florida Tampa Bay Swimming Natalie Branda F 26 Superficial injuries to abomen and thighs N 20h00 NA Fox12, 8/1/2023 NA NA NA NA NA NA
2023-04-22 2023 Unprovoked AUSTRALIA Western Australia Lucy’s Beach Surfing Max Marsden M 30 Bite to right arm N 07h15 Bronze whaler shark, 1.5 m The West Australian, 4/22/2023 NA NA NA NA NA NA
2023-03-02 2023 Unprovoked SEYCHELLES Praslin Island NA Snorkeling Arthur … M 6 Left foot bitten UNKNOWN Afternoon Lemon shark Midlibre, 3/18/2023 NA NA NA NA NA NA
2023-02-18 2023 Questionable ARGENTINA Patagonia Chubut Province NA Diego Barría M 32 Death by misadventure UNKNOWN NA NA El Pais, 2/27/2023 NA NA NA NA NA NA
2022-07-28 2022 Unprovoked ENGLAND Cornwall Penzance Snorkeling female F NA Minor injury to leg N 12h30 Blue shark NA 2022.07.28-Cornwall.pdf http://sharkattackfile.net/spreadsheets/pdf_directory/2022.07.28-Cornwall.pdf http://sharkattackfile.net/spreadsheets/pdf_directory/2022.07.28-Cornwall.pdf 2022.07.28 2022.7.28 6792
2022-03-09 2022 Provoked USA Florida New Smyrna Beach, Volusia County Fishing male M 21.0 M inor injuries to knee and calf of left leg PROVOKED INCIDENT N 15h30 Blacktip shark, 5’ to 6’ ClickOrlando, 3/11/20212 2022.03.09-NewSmyrnaBeach.pdf http://sharkattackfile.net/spreadsheets/pdf_directory/2022.03.09-NewSmyrnaBeach.pdf http://sharkattackfile.net/spreadsheets/pdf_directory/2022.03.09-NewSmyrnaBeach.pdf 2022.03.09 2022.03.09 6743

Neste ficheiro há uma só folha, Feuil1, pelo que as duas leituras usam a mesma. Também podemos indicar a posição, como sheet = 1. Para explorar apenas o início de um ficheiro grande, n_max = 10 limita a leitura a dez linhas. Por defeito, read_excel() repara nomes vazios ou repetidos; inspeccione os nomes resultantes antes de seleccionar colunas.

Se precisar de inspeccionar os nomes sem a reparação automática, pode acrescentar .name_repair = "minimal" a read_excel(). Isso conserva nomes vazios ou repetidos, pelo que terá de os resolver antes de usar funções como select(). Para a leitura habitual, conserve o valor por defeito e confirme os nomes que a função devolveu.

Ficheiros .sav e outros formatos de programas estatísticos também precisam de uma função ou pacote adequado. Consulte a documentação e, sobretudo, inspeccione o objecto depois da leitura. Mesmo que a função termine sem erro, os nomes, as classes ou os valores podem não corresponder ao que esperávamos.

Erro: Linhas Extra

Algumas plataformas de recolha acrescentam linhas de metadados entre os nomes das colunas e as respostas dos participantes. Quando isso acontece, números e datas podem entrar no R como character porque as primeiras linhas contêm texto.

qualtrics <- read.csv("../data/raw_qualtrics.csv")
head(qualtrics)
StartDate EndDate Status Progress Duration..in.seconds. Finished RecordedDate ResponseId DistributionChannel UserLanguage consents X1_rating_1 X2_rating_1 X3_rating_1 X4_rating_1 X5_rating_1 X6_rating_1 X7_rating_1 X8_rating_1 X9_rating_1 X10_rating_1 X11_rating_1 X12_rating_1 X13_rating_1 X14_rating_1 X15_rating_1 X16_rating_1 X17_rating_1 X18_rating_1 X19_rating_1 X20_rating_1 X21_rating_1 X22_rating_1 X23_rating_1 X24_rating_1 X25_rating_1 pp_age pp_gender
Start Date End Date Response Type Progress Duration (in seconds) Finished Recorded Date Response ID Distribution Channel User Language Click to write the question text [Field-1] - a - ${lm://Field/1} [Field-1] - b - ${lm://Field/1} [Field-1] - c - ${lm://Field/1} [Field-1] - d - ${lm://Field/1} [Field-1] - e - ${lm://Field/1} [Field-1] - f - ${lm://Field/1} [Field-1] - g - ${lm://Field/1} [Field-1] - h - ${lm://Field/1} [Field-1] - j - ${lm://Field/1} [Field-1] - k - ${lm://Field/1} [Field-1] - l - ${lm://Field/1} [Field-1] - m - ${lm://Field/1} [Field-1] - n - ${lm://Field/1} [Field-1] - o - ${lm://Field/1} [Field-1] - p - ${lm://Field/1} [Field-1] - q - ${lm://Field/1} [Field-1] - r - ${lm://Field/1} [Field-1] - s - ${lm://Field/1} [Field-1] - t - ${lm://Field/1} [Field-1] - u - ${lm://Field/1} [Field-1] - v - ${lm://Field/1} [Field-1] - x - ${lm://Field/1} [Field-1] - w - ${lm://Field/1} [Field-1] - y - ${lm://Field/1} [Field-1] - z - ${lm://Field/1} Indique, por favor, a sua idade (utilize apenas números na resposta) Indique, por favor, o seu género
{“ImportId”:“startDate”,“timeZone”:“Europe/London”} {“ImportId”:“endDate”,“timeZone”:“Europe/London”} {“ImportId”:“status”} {“ImportId”:“progress”} {“ImportId”:“duration”} {“ImportId”:“finished”} {“ImportId”:“recordedDate”,“timeZone”:“Europe/London”} {“ImportId”:“_recordId”} {“ImportId”:“distributionChannel”} {“ImportId”:“userLanguage”} {“ImportId”:“QID1”} {“ImportId”:“1_QID4_1”} {“ImportId”:“2_QID4_1”} {“ImportId”:“3_QID4_1”} {“ImportId”:“4_QID4_1”} {“ImportId”:“5_QID4_1”} {“ImportId”:“6_QID4_1”} {“ImportId”:“7_QID4_1”} {“ImportId”:“8_QID4_1”} {“ImportId”:“9_QID4_1”} {“ImportId”:“10_QID4_1”} {“ImportId”:“11_QID4_1”} {“ImportId”:“12_QID4_1”} {“ImportId”:“13_QID4_1”} {“ImportId”:“14_QID4_1”} {“ImportId”:“15_QID4_1”} {“ImportId”:“16_QID4_1”} {“ImportId”:“17_QID4_1”} {“ImportId”:“18_QID4_1”} {“ImportId”:“19_QID4_1”} {“ImportId”:“20_QID4_1”} {“ImportId”:“21_QID4_1”} {“ImportId”:“22_QID4_1”} {“ImportId”:“23_QID4_1”} {“ImportId”:“24_QID4_1”} {“ImportId”:“25_QID4_1”} {“ImportId”:“QID6_TEXT”} {“ImportId”:“QID7”}
2021-11-24 09:55:35 2021-11-24 09:57:05 IP Address 100 89 True 2021-11-24 09:57:05 R_OCLWvHc3mmom6lP anonymous EN Yes 83 69 87 17 80 36 40 87 75 21 27 83 56 75 51 45 92 37 72 31 41 84 89 72 26 Masculino
2021-11-24 09:57:17 2021-11-24 09:58:22 IP Address 100 64 True 2021-11-24 09:58:23 R_3JlTQBcMudLTmJr anonymous EN Yes 30 19 20 68 86 79 83 89 86 66 80 81 45 74 87 33 24 83 29 20 91 80 91 85 17 Feminino
2021-11-24 09:58:25 2021-11-24 09:58:27 IP Address 100 2 True 2021-11-24 09:58:27 R_28Sb2uxBnGAvdQH anonymous EN No
2021-11-24 09:58:28 2021-11-24 10:00:02 IP Address 100 93 True 2021-11-24 10:00:02 R_2dWJC1L9ystwcv3 anonymous EN Yes 41 25 30 38 53 34 57 65 88 93 70 89 92 31 75 28 59 78 74 82 81 92 81 85 76 19 Feminino
str(qualtrics)
'data.frame':   6 obs. of  38 variables:
 $ StartDate            : chr  "Start Date" "{\"ImportId\":\"startDate\",\"timeZone\":\"Europe/London\"}" "2021-11-24 09:55:35" "2021-11-24 09:57:17" ...
 $ EndDate              : chr  "End Date" "{\"ImportId\":\"endDate\",\"timeZone\":\"Europe/London\"}" "2021-11-24 09:57:05" "2021-11-24 09:58:22" ...
 $ Status               : chr  "Response Type" "{\"ImportId\":\"status\"}" "IP Address" "IP Address" ...
 $ Progress             : chr  "Progress" "{\"ImportId\":\"progress\"}" "100" "100" ...
 $ Duration..in.seconds.: chr  "Duration (in seconds)" "{\"ImportId\":\"duration\"}" "89" "64" ...
 $ Finished             : chr  "Finished" "{\"ImportId\":\"finished\"}" "True" "True" ...
 $ RecordedDate         : chr  "Recorded Date" "{\"ImportId\":\"recordedDate\",\"timeZone\":\"Europe/London\"}" "2021-11-24 09:57:05" "2021-11-24 09:58:23" ...
 $ ResponseId           : chr  "Response ID" "{\"ImportId\":\"_recordId\"}" "R_OCLWvHc3mmom6lP" "R_3JlTQBcMudLTmJr" ...
 $ DistributionChannel  : chr  "Distribution Channel" "{\"ImportId\":\"distributionChannel\"}" "anonymous" "anonymous" ...
 $ UserLanguage         : chr  "User Language" "{\"ImportId\":\"userLanguage\"}" "EN" "EN" ...
 $ consents             : chr  "Click to write the question text" "{\"ImportId\":\"QID1\"}" "Yes" "Yes" ...
 $ X1_rating_1          : chr  "[Field-1] - a - ${lm://Field/1}" "{\"ImportId\":\"1_QID4_1\"}" "83" "30" ...
 $ X2_rating_1          : chr  "[Field-1] - b - ${lm://Field/1}" "{\"ImportId\":\"2_QID4_1\"}" "69" "19" ...
 $ X3_rating_1          : chr  "[Field-1] - c - ${lm://Field/1}" "{\"ImportId\":\"3_QID4_1\"}" "87" "20" ...
 $ X4_rating_1          : chr  "[Field-1] - d - ${lm://Field/1}" "{\"ImportId\":\"4_QID4_1\"}" "17" "68" ...
 $ X5_rating_1          : chr  "[Field-1] - e - ${lm://Field/1}" "{\"ImportId\":\"5_QID4_1\"}" "80" "86" ...
 $ X6_rating_1          : chr  "[Field-1] - f - ${lm://Field/1}" "{\"ImportId\":\"6_QID4_1\"}" "36" "79" ...
 $ X7_rating_1          : chr  "[Field-1] - g - ${lm://Field/1}" "{\"ImportId\":\"7_QID4_1\"}" "40" "83" ...
 $ X8_rating_1          : chr  "[Field-1] - h - ${lm://Field/1}" "{\"ImportId\":\"8_QID4_1\"}" "87" "89" ...
 $ X9_rating_1          : chr  "[Field-1] - j - ${lm://Field/1}" "{\"ImportId\":\"9_QID4_1\"}" "75" "86" ...
 $ X10_rating_1         : chr  "[Field-1] - k - ${lm://Field/1}" "{\"ImportId\":\"10_QID4_1\"}" "21" "66" ...
 $ X11_rating_1         : chr  "[Field-1] - l - ${lm://Field/1}" "{\"ImportId\":\"11_QID4_1\"}" "27" "80" ...
 $ X12_rating_1         : chr  "[Field-1] - m - ${lm://Field/1}" "{\"ImportId\":\"12_QID4_1\"}" "83" "81" ...
 $ X13_rating_1         : chr  "[Field-1] - n - ${lm://Field/1}" "{\"ImportId\":\"13_QID4_1\"}" "56" "45" ...
 $ X14_rating_1         : chr  "[Field-1] - o - ${lm://Field/1}" "{\"ImportId\":\"14_QID4_1\"}" "75" "74" ...
 $ X15_rating_1         : chr  "[Field-1] - p - ${lm://Field/1}" "{\"ImportId\":\"15_QID4_1\"}" "" "87" ...
 $ X16_rating_1         : chr  "[Field-1] - q - ${lm://Field/1}" "{\"ImportId\":\"16_QID4_1\"}" "51" "33" ...
 $ X17_rating_1         : chr  "[Field-1] - r - ${lm://Field/1}" "{\"ImportId\":\"17_QID4_1\"}" "45" "24" ...
 $ X18_rating_1         : chr  "[Field-1] - s - ${lm://Field/1}" "{\"ImportId\":\"18_QID4_1\"}" "92" "83" ...
 $ X19_rating_1         : chr  "[Field-1] - t - ${lm://Field/1}" "{\"ImportId\":\"19_QID4_1\"}" "37" "29" ...
 $ X20_rating_1         : chr  "[Field-1] - u - ${lm://Field/1}" "{\"ImportId\":\"20_QID4_1\"}" "72" "20" ...
 $ X21_rating_1         : chr  "[Field-1] - v - ${lm://Field/1}" "{\"ImportId\":\"21_QID4_1\"}" "31" "" ...
 $ X22_rating_1         : chr  "[Field-1] - x - ${lm://Field/1}" "{\"ImportId\":\"22_QID4_1\"}" "41" "91" ...
 $ X23_rating_1         : chr  "[Field-1] - w - ${lm://Field/1}" "{\"ImportId\":\"23_QID4_1\"}" "84" "80" ...
 $ X24_rating_1         : chr  "[Field-1] - y - ${lm://Field/1}" "{\"ImportId\":\"24_QID4_1\"}" "89" "91" ...
 $ X25_rating_1         : chr  "[Field-1] - z - ${lm://Field/1}" "{\"ImportId\":\"25_QID4_1\"}" "72" "85" ...
 $ pp_age               : chr  "Indique, por favor, a sua idade (utilize apenas números na resposta)" "{\"ImportId\":\"QID6_TEXT\"}" "26" "17" ...
 $ pp_gender            : chr  "Indique, por favor, o seu género" "{\"ImportId\":\"QID7\"}" "Masculino" "Feminino" ...
colnames(qualtrics)
 [1] "StartDate"             "EndDate"               "Status"               
 [4] "Progress"              "Duration..in.seconds." "Finished"             
 [7] "RecordedDate"          "ResponseId"            "DistributionChannel"  
[10] "UserLanguage"          "consents"              "X1_rating_1"          
[13] "X2_rating_1"           "X3_rating_1"           "X4_rating_1"          
[16] "X5_rating_1"           "X6_rating_1"           "X7_rating_1"          
[19] "X8_rating_1"           "X9_rating_1"           "X10_rating_1"         
[22] "X11_rating_1"          "X12_rating_1"          "X13_rating_1"         
[25] "X14_rating_1"          "X15_rating_1"          "X16_rating_1"         
[28] "X17_rating_1"          "X18_rating_1"          "X19_rating_1"         
[31] "X20_rating_1"          "X21_rating_1"          "X22_rating_1"         
[34] "X23_rating_1"          "X24_rating_1"          "X25_rating_1"         
[37] "pp_age"                "pp_gender"            

Se a inspecção mostrar que há linhas extra, retire-as do objecto e volte a examinar o resultado. Podemos guardar o resultado no mesmo nome: isso substitui o objecto na memória, sem alterar o ficheiro recebido. Conservamos dois objectos quando precisamos de comparar versões; de resto, reutilizar o nome evita acumular versões que já não usamos.

O número de linhas a retirar depende do ficheiro que tem à frente. Use o -c(1, 2) deste exemplo apenas se forem essas as linhas extra. Nos colchetes, -c(1, 2) retira essas linhas; o espaço vazio depois da vírgula conserva todas as colunas.

qualtrics <- qualtrics[-c(1, 2), ]
head(qualtrics)
StartDate EndDate Status Progress Duration..in.seconds. Finished RecordedDate ResponseId DistributionChannel UserLanguage consents X1_rating_1 X2_rating_1 X3_rating_1 X4_rating_1 X5_rating_1 X6_rating_1 X7_rating_1 X8_rating_1 X9_rating_1 X10_rating_1 X11_rating_1 X12_rating_1 X13_rating_1 X14_rating_1 X15_rating_1 X16_rating_1 X17_rating_1 X18_rating_1 X19_rating_1 X20_rating_1 X21_rating_1 X22_rating_1 X23_rating_1 X24_rating_1 X25_rating_1 pp_age pp_gender
3 2021-11-24 09:55:35 2021-11-24 09:57:05 IP Address 100 89 True 2021-11-24 09:57:05 R_OCLWvHc3mmom6lP anonymous EN Yes 83 69 87 17 80 36 40 87 75 21 27 83 56 75 51 45 92 37 72 31 41 84 89 72 26 Masculino
4 2021-11-24 09:57:17 2021-11-24 09:58:22 IP Address 100 64 True 2021-11-24 09:58:23 R_3JlTQBcMudLTmJr anonymous EN Yes 30 19 20 68 86 79 83 89 86 66 80 81 45 74 87 33 24 83 29 20 91 80 91 85 17 Feminino
5 2021-11-24 09:58:25 2021-11-24 09:58:27 IP Address 100 2 True 2021-11-24 09:58:27 R_28Sb2uxBnGAvdQH anonymous EN No
6 2021-11-24 09:58:28 2021-11-24 10:00:02 IP Address 100 93 True 2021-11-24 10:00:02 R_2dWJC1L9ystwcv3 anonymous EN Yes 41 25 30 38 53 34 57 65 88 93 70 89 92 31 75 28 59 78 74 82 81 92 81 85 76 19 Feminino
str(qualtrics)
'data.frame':   4 obs. of  38 variables:
 $ StartDate            : chr  "2021-11-24 09:55:35" "2021-11-24 09:57:17" "2021-11-24 09:58:25" "2021-11-24 09:58:28"
 $ EndDate              : chr  "2021-11-24 09:57:05" "2021-11-24 09:58:22" "2021-11-24 09:58:27" "2021-11-24 10:00:02"
 $ Status               : chr  "IP Address" "IP Address" "IP Address" "IP Address"
 $ Progress             : chr  "100" "100" "100" "100"
 $ Duration..in.seconds.: chr  "89" "64" "2" "93"
 $ Finished             : chr  "True" "True" "True" "True"
 $ RecordedDate         : chr  "2021-11-24 09:57:05" "2021-11-24 09:58:23" "2021-11-24 09:58:27" "2021-11-24 10:00:02"
 $ ResponseId           : chr  "R_OCLWvHc3mmom6lP" "R_3JlTQBcMudLTmJr" "R_28Sb2uxBnGAvdQH" "R_2dWJC1L9ystwcv3"
 $ DistributionChannel  : chr  "anonymous" "anonymous" "anonymous" "anonymous"
 $ UserLanguage         : chr  "EN" "EN" "EN" "EN"
 $ consents             : chr  "Yes" "Yes" "No" "Yes"
 $ X1_rating_1          : chr  "83" "30" "" "41"
 $ X2_rating_1          : chr  "69" "19" "" "25"
 $ X3_rating_1          : chr  "87" "20" "" "30"
 $ X4_rating_1          : chr  "17" "68" "" "38"
 $ X5_rating_1          : chr  "80" "86" "" "53"
 $ X6_rating_1          : chr  "36" "79" "" "34"
 $ X7_rating_1          : chr  "40" "83" "" "57"
 $ X8_rating_1          : chr  "87" "89" "" "65"
 $ X9_rating_1          : chr  "75" "86" "" "88"
 $ X10_rating_1         : chr  "21" "66" "" "93"
 $ X11_rating_1         : chr  "27" "80" "" "70"
 $ X12_rating_1         : chr  "83" "81" "" "89"
 $ X13_rating_1         : chr  "56" "45" "" "92"
 $ X14_rating_1         : chr  "75" "74" "" "31"
 $ X15_rating_1         : chr  "" "87" "" "75"
 $ X16_rating_1         : chr  "51" "33" "" "28"
 $ X17_rating_1         : chr  "45" "24" "" "59"
 $ X18_rating_1         : chr  "92" "83" "" "78"
 $ X19_rating_1         : chr  "37" "29" "" "74"
 $ X20_rating_1         : chr  "72" "20" "" "82"
 $ X21_rating_1         : chr  "31" "" "" "81"
 $ X22_rating_1         : chr  "41" "91" "" "92"
 $ X23_rating_1         : chr  "84" "80" "" "81"
 $ X24_rating_1         : chr  "89" "91" "" "85"
 $ X25_rating_1         : chr  "72" "85" "" "76"
 $ pp_age               : chr  "26" "17" "" "19"
 $ pp_gender            : chr  "Masculino" "Feminino" "" "Feminino"

Se uma variável numérica continuar como texto, veja os valores antes de a converter. Uma conversão cega pode transformar valores mal formados em NA e fazer desaparecer precisamente a pista de que havia um problema.

Erro: Importação Não Verificada

Mesmo quando o ficheiro abre, convém confirmar pelo menos:

  • o caminho e o número de linhas e colunas;
  • os nomes das colunas e as suas classes;
  • algumas linhas do início e do fim;
  • valores em falta, intervalos e identificadores duplicados, quando forem relevantes para a pergunta.
file.exists("../data/demo_ds.csv")
[1] TRUE
dim(ds)
[1] 120  10
 [1] "pp_number" "consents"  "age"       "gender"    "cond"      "mood"     
 [7] "rts"       "phase1"    "phase2"    "lab"      
str(ds)
'data.frame':   120 obs. of  10 variables:
 $ pp_number: int  1 2 3 4 5 6 7 8 9 10 ...
 $ consents : chr  "Yes" "Yes" "Yes" "Yes" ...
 $ age      : int  19 23 41 26 26 42 30 18 18 21 ...
 $ gender   : chr  "female" "female" "female" "female" ...
 $ cond     : chr  "high" "high" "high" "high" ...
 $ mood     : int  31 25 28 29 39 27 31 30 25 30 ...
 $ rts      : int  2106 2249 3248 1931 2410 3451 2450 1820 2168 2743 ...
 $ phase1   : int  18 27 23 30 28 18 24 11 29 15 ...
 $ phase2   : int  80 83 95 81 88 89 90 74 98 76 ...
 $ lab      : chr  "B" "A" "B" "A" ...
tail(ds)
pp_number consents age gender cond mood rts phase1 phase2 lab
115 115 Yes 30 male low 57 3553 88 36 B
116 116 Yes 28 female low 53 4167 92 17 A
117 117 Yes 26 male low 59 4206 107 29 B
118 118 Yes 19 female low 62 3983 87 9 A
119 119 Yes 18 male low 62 2767 94 43 B
120 120 Yes 18 female low 56 5286 88 50 A
pp_number  consents       age    gender      cond      mood       rts    phase1 
        0         0         0         0         0         0         0         0 
   phase2       lab 
        0         0 

Leia os resultados na consola: o ficheiro existe? As dimensões correspondem à exportação? As colunas esperadas estão presentes? Estas consultas dão-nos um ponto concreto de partida quando alguma coisa muda. A validade dos dados exige também que conheçamos a sua origem e o modo como foram recolhidos. A limpeza de dados começa precisamente quando uma destas perguntas revela algo que exige uma decisão.

3 Exportar sem Perder a Origem

Depois de documentar as decisões, escrevemos uma nova versão. row.names = FALSE evita acrescentar ao CSV uma coluna com os nomes automáticos das linhas.

dir.create("results", showWarnings = FALSE)
write.csv(qualtrics, "results/qualtrics_checked.csv", row.names = FALSE)
qualtrics_reimported <- read.csv("results/qualtrics_checked.csv")

names(qualtrics)
names(qualtrics_reimported)
dim(qualtrics)
dim(qualtrics_reimported)

Aqui conservamos qualtrics e qualtrics_reimported porque queremos comparar o objecto antes da exportação com o que foi lido do CSV. Execute este bloco no seu computador. Compare as duas saídas de names() e as duas de dim(): esperamos os mesmos nomes, pela mesma ordem, e as mesmas dimensões. Se faltarem colunas ou houver diferenças, investigue antes de usar a nova versão. Exportar para CSV e voltar a importar não preserva necessariamente todas as classes e atributos do R. As datas, por exemplo, podem precisar de nova conversão depois da leitura. É por isso que vale a pena verificar mais do que a simples existência do novo ficheiro.

No seu projecto, guarde o original, registe as transformações e escreva a versão nova noutro caminho. Assim pode repetir a análise ou rever uma decisão sem depender da memória ou de cliques que já ninguém sabe reconstruir.

4 Exercícios

  1. Use file.exists() e getwd() para explicar por que razão um caminho relativo pode funcionar num computador e falhar noutro.
Solução 1
path <- "../data/demo_ds.csv"
getwd()
[1] "/builds/rggd/nrb/programming"
[1] TRUE
Um caminho relativo é interpretado a partir do working directory. Se duas sessões começarem em pastas diferentes, a mesma string pode apontar para ficheiros diferentes ou para nenhum ficheiro.
  1. Importe demo_ds.csv, inspeccione-o e escreva três verificações que confirmem que a importação corresponde à sua expectativa.
Solução 2
demo <- read.csv("../data/demo_ds.csv")
head(demo)
pp_number consents age gender cond mood rts phase1 phase2 lab
1 Yes 19 female high 31 2106 18 80 B
2 Yes 23 female high 25 2249 27 83 A
3 Yes 41 female high 28 3248 23 95 B
4 Yes 26 female high 29 1931 30 81 A
5 Yes 26 female high 39 2410 28 88 B
6 Yes 42 female high 27 3451 18 89 A
str(demo)
'data.frame':   120 obs. of  10 variables:
 $ pp_number: int  1 2 3 4 5 6 7 8 9 10 ...
 $ consents : chr  "Yes" "Yes" "Yes" "Yes" ...
 $ age      : int  19 23 41 26 26 42 30 18 18 21 ...
 $ gender   : chr  "female" "female" "female" "female" ...
 $ cond     : chr  "high" "high" "high" "high" ...
 $ mood     : int  31 25 28 29 39 27 31 30 25 30 ...
 $ rts      : int  2106 2249 3248 1931 2410 3451 2450 1820 2168 2743 ...
 $ phase1   : int  18 27 23 30 28 18 24 11 29 15 ...
 $ phase2   : int  80 83 95 81 88 89 90 74 98 76 ...
 $ lab      : chr  "B" "A" "B" "A" ...
file.exists("../data/demo_ds.csv")
[1] TRUE
dim(demo)
[1] 120  10
c("pp_number", "consents", "age") %in% names(demo)
[1] TRUE TRUE TRUE
Esperamos TRUE para a existência do ficheiro, dimensões com registos e três valores TRUE para as colunas esperadas. Se algum resultado não corresponder, voltamos à importação antes de analisar. A validade científica dos valores exige outras verificações.
  1. Inspeccione raw_qualtrics.csv. Identifique, com código e não apenas à vista, sinais de cabeçalhos extra ou classes inesperadas. Grave uma cópia corrigida sem substituir o original.
Solução 3
raw_exercise <- read.csv("../data/raw_qualtrics.csv", stringsAsFactors = FALSE)
raw_flagged <- raw_exercise |>
  mutate(row = row_number(),
         metadata = StartDate == "Start Date" |
           grepl("ImportId", StartDate, fixed = TRUE))

raw_flagged |>
  filter(metadata) |>
  select(row, StartDate)
row StartDate
1 Start Date
2 {“ImportId”:“startDate”,“timeZone”:“Europe/London”}
str(raw_exercise)
'data.frame':   6 obs. of  38 variables:
 $ StartDate            : chr  "Start Date" "{\"ImportId\":\"startDate\",\"timeZone\":\"Europe/London\"}" "2021-11-24 09:55:35" "2021-11-24 09:57:17" ...
 $ EndDate              : chr  "End Date" "{\"ImportId\":\"endDate\",\"timeZone\":\"Europe/London\"}" "2021-11-24 09:57:05" "2021-11-24 09:58:22" ...
 $ Status               : chr  "Response Type" "{\"ImportId\":\"status\"}" "IP Address" "IP Address" ...
 $ Progress             : chr  "Progress" "{\"ImportId\":\"progress\"}" "100" "100" ...
 $ Duration..in.seconds.: chr  "Duration (in seconds)" "{\"ImportId\":\"duration\"}" "89" "64" ...
 $ Finished             : chr  "Finished" "{\"ImportId\":\"finished\"}" "True" "True" ...
 $ RecordedDate         : chr  "Recorded Date" "{\"ImportId\":\"recordedDate\",\"timeZone\":\"Europe/London\"}" "2021-11-24 09:57:05" "2021-11-24 09:58:23" ...
 $ ResponseId           : chr  "Response ID" "{\"ImportId\":\"_recordId\"}" "R_OCLWvHc3mmom6lP" "R_3JlTQBcMudLTmJr" ...
 $ DistributionChannel  : chr  "Distribution Channel" "{\"ImportId\":\"distributionChannel\"}" "anonymous" "anonymous" ...
 $ UserLanguage         : chr  "User Language" "{\"ImportId\":\"userLanguage\"}" "EN" "EN" ...
 $ consents             : chr  "Click to write the question text" "{\"ImportId\":\"QID1\"}" "Yes" "Yes" ...
 $ X1_rating_1          : chr  "[Field-1] - a - ${lm://Field/1}" "{\"ImportId\":\"1_QID4_1\"}" "83" "30" ...
 $ X2_rating_1          : chr  "[Field-1] - b - ${lm://Field/1}" "{\"ImportId\":\"2_QID4_1\"}" "69" "19" ...
 $ X3_rating_1          : chr  "[Field-1] - c - ${lm://Field/1}" "{\"ImportId\":\"3_QID4_1\"}" "87" "20" ...
 $ X4_rating_1          : chr  "[Field-1] - d - ${lm://Field/1}" "{\"ImportId\":\"4_QID4_1\"}" "17" "68" ...
 $ X5_rating_1          : chr  "[Field-1] - e - ${lm://Field/1}" "{\"ImportId\":\"5_QID4_1\"}" "80" "86" ...
 $ X6_rating_1          : chr  "[Field-1] - f - ${lm://Field/1}" "{\"ImportId\":\"6_QID4_1\"}" "36" "79" ...
 $ X7_rating_1          : chr  "[Field-1] - g - ${lm://Field/1}" "{\"ImportId\":\"7_QID4_1\"}" "40" "83" ...
 $ X8_rating_1          : chr  "[Field-1] - h - ${lm://Field/1}" "{\"ImportId\":\"8_QID4_1\"}" "87" "89" ...
 $ X9_rating_1          : chr  "[Field-1] - j - ${lm://Field/1}" "{\"ImportId\":\"9_QID4_1\"}" "75" "86" ...
 $ X10_rating_1         : chr  "[Field-1] - k - ${lm://Field/1}" "{\"ImportId\":\"10_QID4_1\"}" "21" "66" ...
 $ X11_rating_1         : chr  "[Field-1] - l - ${lm://Field/1}" "{\"ImportId\":\"11_QID4_1\"}" "27" "80" ...
 $ X12_rating_1         : chr  "[Field-1] - m - ${lm://Field/1}" "{\"ImportId\":\"12_QID4_1\"}" "83" "81" ...
 $ X13_rating_1         : chr  "[Field-1] - n - ${lm://Field/1}" "{\"ImportId\":\"13_QID4_1\"}" "56" "45" ...
 $ X14_rating_1         : chr  "[Field-1] - o - ${lm://Field/1}" "{\"ImportId\":\"14_QID4_1\"}" "75" "74" ...
 $ X15_rating_1         : chr  "[Field-1] - p - ${lm://Field/1}" "{\"ImportId\":\"15_QID4_1\"}" "" "87" ...
 $ X16_rating_1         : chr  "[Field-1] - q - ${lm://Field/1}" "{\"ImportId\":\"16_QID4_1\"}" "51" "33" ...
 $ X17_rating_1         : chr  "[Field-1] - r - ${lm://Field/1}" "{\"ImportId\":\"17_QID4_1\"}" "45" "24" ...
 $ X18_rating_1         : chr  "[Field-1] - s - ${lm://Field/1}" "{\"ImportId\":\"18_QID4_1\"}" "92" "83" ...
 $ X19_rating_1         : chr  "[Field-1] - t - ${lm://Field/1}" "{\"ImportId\":\"19_QID4_1\"}" "37" "29" ...
 $ X20_rating_1         : chr  "[Field-1] - u - ${lm://Field/1}" "{\"ImportId\":\"20_QID4_1\"}" "72" "20" ...
 $ X21_rating_1         : chr  "[Field-1] - v - ${lm://Field/1}" "{\"ImportId\":\"21_QID4_1\"}" "31" "" ...
 $ X22_rating_1         : chr  "[Field-1] - x - ${lm://Field/1}" "{\"ImportId\":\"22_QID4_1\"}" "41" "91" ...
 $ X23_rating_1         : chr  "[Field-1] - w - ${lm://Field/1}" "{\"ImportId\":\"23_QID4_1\"}" "84" "80" ...
 $ X24_rating_1         : chr  "[Field-1] - y - ${lm://Field/1}" "{\"ImportId\":\"24_QID4_1\"}" "89" "91" ...
 $ X25_rating_1         : chr  "[Field-1] - z - ${lm://Field/1}" "{\"ImportId\":\"25_QID4_1\"}" "72" "85" ...
 $ pp_age               : chr  "Indique, por favor, a sua idade (utilize apenas números na resposta)" "{\"ImportId\":\"QID6_TEXT\"}" "26" "17" ...
 $ pp_gender            : chr  "Indique, por favor, o seu género" "{\"ImportId\":\"QID7\"}" "Masculino" "Feminino" ...
raw_corrected <- raw_flagged |>
  filter(!metadata | is.na(metadata)) |>
  select(-row, -metadata)

Aqui o critério identifica as duas linhas de metadados pelos seus valores, em vez de depender apenas da posição. mutate() acrescenta a posição e a marca de metadados; filter() escolhe linhas e select() escolhe colunas. grepl(..., fixed = TRUE) procura o texto literal ImportId, sem regras especiais de pesquisa. Conservamos as linhas em que a marca ficou em falta para as investigar, em vez de as excluir sem justificação. Depois de inspeccionar o resultado, guardamos a cópia:

dir.create("results", showWarnings = FALSE)
write.csv(raw_corrected, "results/raw_qualtrics_corrected.csv", row.names = FALSE)
A cópia fica em results, sem substituir o original.
  1. Importe um ficheiro Excel com readxl e explique qual é a dependência do script e que verificações faria antes de o analisar.
Solução 4
excel <- readxl::read_excel("../data/global-shark-attack.xlsx")
dim(excel)
[1] 6890   21
names(excel)
 [1] "Date"                   "Year"                   "Type"                  
 [4] "Country"                "Area"                   "Location"              
 [7] "Activity"               "Name"                   "Sex"                   
[10] "Age"                    "Injury"                 "Fatal (Y/N)"           
[13] "Time"                   "Species"                "Investigator or Source"
[16] "pdf"                    "href formula"           "href"                  
[19] "Case Number...19"       "Case Number...20"       "original order"        
str(excel)
tibble [6,890 × 21] (S3: tbl_df/tbl/data.frame)
 $ Date                  : chr [1:6890] "2023-07-29" "2023-04-22" "2023-03-02" "2023-02-18" ...
 $ Year                  : chr [1:6890] "2023" "2023" "2023" "2023" ...
 $ Type                  : chr [1:6890] "Unprovoked" "Unprovoked" "Unprovoked" "Questionable" ...
 $ Country               : chr [1:6890] "USA" "AUSTRALIA" "SEYCHELLES" "ARGENTINA" ...
 $ Area                  : chr [1:6890] "Florida" "Western Australia" "Praslin Island" "Patagonia" ...
 $ Location              : chr [1:6890] "Tampa Bay" "Lucy's Beach" NA "Chubut Province" ...
 $ Activity              : chr [1:6890] "Swimming" "Surfing" "Snorkeling" NA ...
 $ Name                  : chr [1:6890] "Natalie Branda" "Max Marsden" "Arthur …" "Diego Barría" ...
 $ Sex                   : chr [1:6890] "F" "M" "M" "M" ...
 $ Age                   : chr [1:6890] "26" "30" "6" "32" ...
 $ Injury                : chr [1:6890] "Superficial injuries to abomen and thighs" "Bite to right arm" "Left foot bitten" "Death by misadventure" ...
 $ Fatal (Y/N)           : chr [1:6890] "N" "N" "UNKNOWN" "UNKNOWN" ...
 $ Time                  : chr [1:6890] "20h00" "07h15" "Afternoon" NA ...
 $ Species               : chr [1:6890] NA "Bronze whaler shark, 1.5 m" "Lemon shark" NA ...
 $ Investigator or Source: chr [1:6890] "Fox12, 8/1/2023" "The West Australian, 4/22/2023" "Midlibre, 3/18/2023" "El Pais,  2/27/2023" ...
 $ pdf                   : chr [1:6890] NA NA NA NA ...
 $ href formula          : chr [1:6890] NA NA NA NA ...
 $ href                  : chr [1:6890] NA NA NA NA ...
 $ Case Number...19      : chr [1:6890] NA NA NA NA ...
 $ Case Number...20      : chr [1:6890] NA NA NA NA ...
 $ original order        : chr [1:6890] NA NA NA NA ...
A dependência é o pacote readxl. Instala-se durante a preparação do ambiente, fora do script de análise. Antes de analisar, confirmaria o ficheiro, as colunas, as classes, algumas linhas e os valores em falta.
  1. Exporte uma pequena tabela para results/table.csv, volte a importá-la e compare nomes, dimensões e classes. Que propriedades não pode concluir que um CSV preservará sempre?
Solução 5
example_table <- data.frame(id = 1:2, value = c(2.5, 4.0))
dir.create("results", showWarnings = FALSE)
write.csv(example_table, "results/table.csv", row.names = FALSE)
reimported <- read.csv("results/table.csv")

names(example_table)
names(reimported)
dim(example_table)
dim(reimported)
sapply(example_table, class)
sapply(reimported, class)
Um CSV conserva texto e uma representação tabular simples, mas não garante todas as classes e atributos do R. Por exemplo, datas, factores e atributos específicos podem precisar de conversão ou reconstrução.