Limpeza de Bases de Dados
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.csve 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.csvalgures 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 <- 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.csvpara 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.csvalgures 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 visualstr(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 funcionaAs 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 <- 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.csvque 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
[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.