Limpeza de Bases de Dados

Programação
Tratamento de dados
workflows
Como “limpar” bases de dados no R para as preparar para análise.
Autores
Afiliações

João O. Santos

ISPA

Cristina Mendonça

ISPA; WJCR

Vitória Melita

FP-UL

Mariona Pascual Peñas

UIB

João Raposo

ISPA

Marta Barros

FP-UL

0 Limpeza de Bases de Dados

Neste tutorial vamos aprender a “limpar” bases de dados. A “limpeza” da base de dados é o primeiro passo fundamental na preparação duma base de dados. Podemos dividir a preparação duma base de dados em dois passos: (1) limpeza, (2) edição/reformatação. Na limpeza removemos da base de dados linhas, colunas, células que não queremos que lá estejam. Por exemplo, removemos dados que podem identificar os participantes, ou linhas extra que podem causar erros na importação da base de dados. Na edição/reformatação podemos recodificar variáveis, ou reorganizar a base de dados. Este tutorial foca-se apenas na primeira parte—a limpeza dos dados. Depois teremos outro tutorial que se vai focar na reformatação dos dados.

A “Prática” é Diferente da “Aula Prática”

Nas aulas de estatística normalmente dividimos o tempo entre teoria e prática. Nas aulas teóricas aprendemos quais as análises a usar, os seus pressupostos, as estatísticas/resultados que essas análises nos dão e aprendemos a interpretar esses resultados. Nas aulas práticas aprendemos a testar os pressupostos das análises, a fazer as análises e a interpretar os resultados no contexto para alguns exemplos de problemas de investigação. Ficamos então com a sensação que na prática da estatística, o que faremos será pegar na base de dados do problema de investigação, testar os pressupostos, fazer a análise e reportar os resultados. Contudo, na realidade, a prática é diferente das aulas práticas. Na prática raramente temos bases de dados bem arranjadinhas ou até as temos mas porque primeiro gastámos horas da nossa vida a arranjá-las! Na prática a quantidade de horas de vida que gastamos a arranjar a base de dados é muito superior às que gastamos a fazer as análises. Para além disso, aulas práticas o procedimento parecia linear, ou seja, primeiro testávamos pressupostos, depois analisávamos dados, e depois reportávamos os resultados. Na prática o processo é muito mais circular. Posso decidir fazer a análise primeiro—computar o modelo no R—e depois testar os pressupostos no objecto/variável que guarda o resultado dessa computação (o ajuste/fit do modelo). Posso estar a escrever a discussão dum artigo e lembrar-me duma análise exploratória que seria interessante fazer e voltar ao R para a fazer. De qualquer das formas, antes disso tudo tenho sempre de preparar a base de dados. Para preparar a base de dados, tenho primeiro de a limpar, que é o que vamos aprender a fazer neste tutorial.

O Que Limpar?

A “limpeza” das bases de dados refere-se àquelas operações que fazemos para remover observações (e.g., participantes), colunas (e.g., variáveis), ou outras coisas da base de dados. Antes de vermos como limpar os dados, vale a pena ver o que costumamos querer remover dos dados.

Aqui ficam alguns exemplos do que remover:

  • Participantes que não deram o seu consentimento para que analisássemos os seus dados.

  • Dados potencialmente identificativos dos participantes.

  • Respostas que não correspondam a participantes reais. Por exemplo, respostas de “teste” que correspondem a quando eu estava a testar se o software de recolha de dados executava a experiência e gravava os dados sem problemas.

  • Linhas ou colunas extra que o software de recolha de dados acrescenta mas que não são úteis para análise e que até podem causar erros na importação dos dados.

Como Limpar?

Para limparmos as nossas bases de dados, podemos usar o que aprendemos sobre data.frames. Já sabemos como apagar colunas, como apagar/seleccionar apenas as linhas que queremos, etc…

AVISO: Sugiro que guardem sempre a versão original (i.e., sem limpeza) da vossa base de dados. Para isso basta gravarem a vossa base de dados com um novo nome de ficheiro porque se usarem o mesmo nome de ficheiro a nova versão (i.e., limpa) substituirá a antiga.

Chega então a altura dum novo desafio! Será que conseguem resolver os exercícios abaixo com base no que já aprenderam?

Exercícios

Os exercícios que se seguem são muito semelhantes aos que já fizeram, mas as alíneas estão escritas numa linguagem menos técnica do R (e.g., exclua as linhas com valores maior que x na coluna y, ds <- subset(ds, y < x)) e mais próxima da usada em investigação (e.g., exclua os participantes menores de idade). Ou seja, os exercícios não pretendem tanto testar o vosso domínio do R e da sua terminologia, mas sim a vossa capacidade de usar o R no dia-a-dia. Portanto, tentem pensar de que maneira o que aprenderam pode ser usado para resolver cada desafio proposto.

Exercício 1

  • Importe a base de dados demo_ds.csv e guarde-a numa variável.

  • Exclua os participantes que não consentiram em participar.

  • Exclua os participantes menores de idade. Nota: já vos dei uma pista no início desta secção de exercícios.

  • Escreva uma só linha de código para resolver as duas alíneas anteriores.

  • Grave os dados “limpos” num novo ficheiro clean_demo.csv algures no seu computador.

Pergunta Bónus: Explique porque razão dados <- dados[!dados$age < 18, ] também seria uma solução válida para a terceira alínea.

Soluções
Clique para ver as soluções
dados <- read.csv("../data/demo_ds.csv")

dados <- dados[dados$age >= 18, ]
# dados <- dados[!dados$age < 18, ] também funcionaria.

dados <- dados[dados$consents == "Yes", ]

dados <- subset(dados, consents == "Yes" & age >= 18)

write.csv(dados, "../data/clean_demo.csv", row.names = FALSE)

dados <- dados[!dados$age < 18, ] também resolveria a segunda alínea porque ! nega o valor lógico que o sucede (e.g., !TRUE é FALSE), e os participantes que não têm menos de 18 anos são os que têm 18 ou mais anos.



Exercício 2

  • Importe a base de dados raw_qualtrics.csv para uma variável.

  • Descubra a classe (class()) de todas as variáveis dessa base de dados, indicando potenciais desvios do que esperava.

  • Descubra os cabeçalhos (headers) a mais e elimine-os da variável.

  • Grave os dados já sem esses cabeçalhos num ficheiro clean_qualtrics.csv algures no seu computador.

  • Importe esse novo ficheiro para a variável onde inicialmente tinha guardado os dados que importou do raw_qualtrics.csv.

  • Descubra novamente a classe de todas as variáveis, indicando as alterações e potenciais problemas.

Bónus: Explique porque razão acha que a classe das variáveis sofreu as alterações e padece ainda dos problemas que reportou.

Soluções

Clique para ver as soluções
dados <- read.csv("../data/raw_qualtrics.csv")
# dados <- read.csv(file.choose()) # para interface visual
str(dados)
'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" ...
#sapply(dados, class) também funciona

As variáveis estão todas a ser registadas como character, assim não poderemos fazer operações numéricas com as que são numéricas.

head(dados) 
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

O Qualtrics acrescenta dois cabeçalhos extra desnecessários, nas duas primeiras linhas.

dados <- dados[-c(1:2), ]
write.csv(dados, "../data/clean_qualtrics.csv", row.names = FALSE)
# Com uma interface gráfica.
# write.csv(dados, file.choose(new = TRUE), row.names = FALSE)
dados <- read.csv("../data/clean_qualtrics.csv")
# dados <- read.csv(file.choose()) # para uma interface gráfica.
str(dados)
'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             : int  100 100 100 100
 $ Duration..in.seconds.: int  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          : int  83 30 NA 41
 $ X2_rating_1          : int  69 19 NA 25
 $ X3_rating_1          : int  87 20 NA 30
 $ X4_rating_1          : int  17 68 NA 38
 $ X5_rating_1          : int  80 86 NA 53
 $ X6_rating_1          : int  36 79 NA 34
 $ X7_rating_1          : int  40 83 NA 57
 $ X8_rating_1          : int  87 89 NA 65
 $ X9_rating_1          : int  75 86 NA 88
 $ X10_rating_1         : int  21 66 NA 93
 $ X11_rating_1         : int  27 80 NA 70
 $ X12_rating_1         : int  83 81 NA 89
 $ X13_rating_1         : int  56 45 NA 92
 $ X14_rating_1         : int  75 74 NA 31
 $ X15_rating_1         : int  NA 87 NA 75
 $ X16_rating_1         : int  51 33 NA 28
 $ X17_rating_1         : int  45 24 NA 59
 $ X18_rating_1         : int  92 83 NA 78
 $ X19_rating_1         : int  37 29 NA 74
 $ X20_rating_1         : int  72 20 NA 82
 $ X21_rating_1         : int  31 NA NA 81
 $ X22_rating_1         : int  41 91 NA 92
 $ X23_rating_1         : int  84 80 NA 81
 $ X24_rating_1         : int  89 91 NA 85
 $ X25_rating_1         : int  72 85 NA 76
 $ pp_age               : int  26 17 NA 19
 $ pp_gender            : chr  "Masculino" "Feminino" "" "Feminino"

As variáveis já não estão a todas ser registadas como characters. Contudo, ainda as variáveis factoriais são registadas como numéricas. As datas são registadas como character e não como datas.

O R não conseguiu reconhecer correctamente classe das variáveis porque ao importar os cabeçalhos extra como linhas normais passou a ter strings em todas as colunas e portanto todas tiveram de ser consideradas da classe character.



Exercício 3

  • Exclua da variável onde tem guardada a base de dados os participantes que cumpram qualquer um das seguintes condições: (1) não tenham consentido em participar, (2) tenham menos de 18 anos, (3) tenham realizado menos de 10% da experiência. Indique quantos participantes foram excluídos no processo.

  • Grave a base de dados, já sem os participantes que exclui na alínea anterior, no ficheiro clean_qualtrics.csv que criou anteriormente.

  • Indique potenciais vantagens deste workflow (i.e., forma de trabalhar) para a limpeza de participantes. Indique também potenciais limitações e sugira formas das colmatar.

Bónus: Exclua os participantes que tenham começado a experiência antes do dia 24 de Novembro de 2021.

Soluções

Clique para ver as soluções
antes <- nrow(dados)
dados <- subset(dados, consents == "Yes" & pp_age >= 18 & Progress > 10)
nrow(dados) - antes
[1] -2
write.csv(dados, "../data/clean_qualtrics.csv", row.names = FALSE)
# write.csv(dados, file.choose(), row.names = FALSE) # com interface gráfica.

Neste workflow as exclusões dos participantes ficam registadas num script que pode ser auditado, melhorado, testado, corrigido. Quando estivermos a escrever a secção de resultados podemos ler o script para reavivar a nossa memória. Temos também a certeza que nenhuns participantes que cumpram os critérios foram incluídos indevidamente, nem que participantes que não os cumpram foram excluídos indevidamente. Por outro lado, se o nosso script tiver erros semânticos (e.g., idade > 18 em vez de idade >= 18) podemos acabar por excluir participantes sem querer mas com uma falsa sensação de confiança só porque o fizemos com código. Para contornar a limitação devemos testar sempre o nosso código e explorar visualmente a base de dados para confirmar que os inputs e outputs são os esperados.

dados <- subset(dados,
                as.Date(StartDate, "%Y-%m-%d") >= as.Date("2021-11-24", "%Y-%m-%d"))