Importação e Exportação de Dados
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.
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:
- Crie uma pasta
r-practicecom duas subpastas,dataeprogramming. - Descarregue
demo_ds.csvparadata, 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 pastalobsters-2016.tsv,raw_qualtrics.csve, se quiser praticar Excel,global-shark-attack.xlsx. - Crie um script e guarde-o como
import.Rdentro deprogramming. 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.
| 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.
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 | 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.
| 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.
| 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
colnames(ds) [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.
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
- Use
file.exists()egetwd()para explicar por que razão um caminho relativo pode funcionar num computador e falhar noutro.
Solução 1
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.- Importe
demo_ds.csv, inspeccione-o e escreva três verificações que confirmem que a importação corresponde à sua expectativa.
Solução 2
| 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
[1] TRUE TRUE TRUE
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.
- 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
| 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" ...
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)results, sem substituir o original.
- Importe um ficheiro Excel com
readxle 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 ...
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.
- 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)