Um workflow reproduzível para inspeccionar e limpar dados no R.
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 Limpar É Decidir
Dados reais raramente chegam prontos para análise. Nesta página vamos seguir um único workflow com um ficheiro exportado do Qualtrics: importar, inspeccionar, registar decisões, filtrar e guardar uma nova versão. Limpar não é apagar observações sem justificação. É decidir, com critérios que possamos explicar, o que entra e o que fica de fora.
A limpeza inclui tarefas rotineiras, como retirar respostas usadas para testar o formulário, metadados acrescentados pelo exportador e registos tecnicamente inutilizáveis. Inclui também exclusões que afectam a amostra, como aplicar um critério de consentimento ou de elegibilidade. Nem todos os pormenores técnicos precisam de ocupar espaço num artigo; quando forem rotineiros, pode bastar uma descrição breve ou podem ficar implícitos, conforme o contexto. Ainda assim, devem continuar visíveis no script e nos registos do projecto.
Acompanhe o código num script. Guarde sempre o ficheiro original e não grave por cima dele. Não precisa de saber estatística avançada para seguir os passos, mas cada filtro deve ter uma razão que conseguimos recuperar. O tutorial de importação ajuda quando o problema ainda é trazer o ficheiro para o R; a referência de data.frames ajuda a inspeccionar a tabela.
Uma Importação com Problemas de Estrutura
O ficheiro raw_qualtrics.csv tem duas linhas de metadados depois dos nomes das colunas. Se as importarmos como se fossem observações, o R deixa de conseguir interpretar muitas colunas como números. Começamos por conservar o ficheiro recebido e olhar para aquilo que entrou, sem alterar nada.
Inspeccionar faz parte da análise. Aqui, as duas primeiras linhas são cabeçalhos do exportador, não participantes. Registamos essa decisão no script para que outra pessoa, ou nós próprios daqui a três meses, consiga reconstituir o caminho.
Aviso
Não remova linhas só porque parecem estranhas. Primeiro escreva o critério, confirme-o com a documentação do instrumento e, se possível, mantenha uma cópia ou um registo da decisão.
NotaTrês Tipos de Decisão
Uma correcção técnica rotineira seria retirar as duas linhas de metadados que o exportador acrescentou: não estamos a excluir uma pessoa, estamos a corrigir a estrutura do ficheiro. Uma exclusão prevista no protocolo seria retirar respostas sem consentimento ou fora da idade elegível. Uma escolha exploratória seria experimentar um novo limiar de progresso depois de ver os dados.
As três decisões devem ficar registadas, mas não têm o mesmo estatuto. Na última, guarde a análise planeada, identifique a alternativa como exploratória e compare as conclusões quando isso for relevante.
Um Workflow de Limpeza
O R não arruma a casa por nós. Neste caso, pelo menos, deixa a limpeza dos dados num script que conseguimos repetir.
Começamos por remover os cabeçalhos extra. Depois verificamos classes, valores e critérios de inclusão. O filtro abaixo é apenas um exemplo de decisão de estudo: consentimento, idade mínima e pelo menos 10% de progresso. No seu trabalho, os critérios devem vir do protocolo e ser documentados quando mudam.
# Decision 1: the first two rows are not responses.dados<-raw[-c(1, 2), ]# Check: after removing the metadata, inspect the classes.str(dados)
# These columns were read as character because of the extra rows.# Keep their original values so that the conversion can be audited.colunas_numericas<-c("Progress", "pp_age")valores_originais<-dados[, colunas_numericas, drop =FALSE]na_antes_coercao<-colSums(is.na(valores_originais))dados$Progress<-as.numeric(dados$Progress)dados$pp_age<-as.numeric(dados$pp_age)# Identify cells that became NA, rather than checking only the total.valores_convertidos<-dados[, colunas_numericas, drop =FALSE]novos_na<-!is.na(valores_originais)&is.na(valores_convertidos)auditoria_coercao<-data.frame( coluna =colunas_numericas, em_falta_antes =na_antes_coercao, em_falta_depois =colSums(is.na(valores_convertidos)), novos_na =colSums(novos_na), row.names =NULL)auditoria_coercao
# Decision 2: keep only consented adult responses with progress >= 10.incluido<-!is.na(dados$consents)&dados$consents=="Yes"&!is.na(dados$pp_age)&dados$pp_age>=18&!is.na(dados$Progress)&dados$Progress>=10# Before/after counts make the decision auditable.n_antes<-nrow(dados)dados_limpos<-dados[incluido, ]n_depois<-nrow(dados_limpos)n_excluidos<-n_antes-n_depoisc(antes =n_antes, depois =n_depois, excluidos =n_excluidos)
A conversão de Progress e pp_age é uma decisão técnica. Só a fazemos depois de retirar as linhas que continham texto de apresentação. Neste ficheiro, a auditoria encontra uma célula vazia em pp_age, que passa a NA; o critério de inclusão trata-a então como idade em falta e exclui essa resposta.
Se em vez disso aparecesse, por exemplo, "vinte", a situação já seria outra. Antes de filtrar, teríamos de perceber se era uma resposta inválida, uma codificação esperada ou um problema de importação. A contagem diz-nos que há algo a investigar; olhar para os valores afectados ajuda a perceber o quê.
# Simple audit of missing values in the columns used for filtering.colSums(is.na(dados_limpos[, c("Progress", "pp_age")]))
Progress pp_age
0 0
Neste projecto, a versão limpa tem um destino explícito: data/clean_qualtrics.csv. O bloco seguinte fica sem avaliação durante o render para que construir o site nunca volte a gravar por cima desse ficheiro rastreado.
Num projecto próprio, adopte a mesma regra: escolha um caminho de saída persistente e documentado, diferente do original, e faça da escrita uma etapa explícita do workflow.
Folhas de Cálculo Já Limpas
Uma folha de cálculo limpa manualmente não é proibida nem deixa de poder ser usada. Pode ser o único formato recebido dum colaborador ou representar trabalho legítimo já realizado. Nesse caso, preserve o ficheiro tal como o recebeu, trabalhe numa cópia e documente a origem e as alterações manuais que conseguir reconstruir. A versão limpa passa a ser uma entrada do workflow, não um substituto silencioso do ficheiro de origem.
Para trabalho novo ou repetido, fazer as transformações num script de R costuma ser mais fácil de reproduzir e auditar a longo prazo: os critérios, a ordem e as contagens ficam disponíveis para voltar a executar. Isto não torna o R infalível, nem apaga uma decisão manual que já aconteceu; torna mais claro o rasto que precisamos de conservar.
Um script reproduzível não torna as decisões automaticamente correctas. Pode ter escrito > quando queria >=, escolhido a coluna errada ou aplicado um critério que afinal não estava no protocolo. Por isso vale a pena olhar também para casos perto do limite e anotar no caderno de análise o motivo, a data e a pessoa que aprovou cada exclusão relevante.
Exercícios
Refaça o workflow usando demo_ds.csv. Inclua apenas participantes que consentiram e têm pelo menos 18 anos. Registe quantos casos entram e saem e escreva clean_demo.csv numa pasta de resultados, sem alterar o original.
Solução 1
demo<-read.csv("../data/demo_ds.csv", stringsAsFactors =FALSE)antes<-nrow(demo)incluido_demo<-!is.na(demo$consents)&demo$consents=="Yes"&!is.na(demo$age)&demo$age>=18clean_demo<-demo[incluido_demo, , drop =FALSE]depois<-nrow(clean_demo)c(antes =antes, depois =depois, excluidos =antes-depois)
O ficheiro original é apenas lido; a saída é escrita separadamente em results/clean_demo.csv.
Escolha um critério do seu próprio estudo. Escreva-o em comentário antes do filtro, mostre uma contagem antes/depois e inspeccione pelo menos dois casos junto ao limite. Explique uma limitação da decisão.
Solução 2 — exemplo aberto
# Example criterion: include participants aged 18 or older.antes_idade<-nrow(demo)perto_do_limite<-demo[order(abs(demo$age-18)), c("pp_number", "age")]head(perto_do_limite, 2)
pp_number
age
8
8
18
9
9
18
incluido_idade<-!is.na(demo$age)&demo$age>=18depois_idade<-sum(incluido_idade)c(antes =antes_idade, depois =depois_idade)
antes depois
120 120
Este é apenas um exemplo aberto. A idade auto-reportada pode estar incorrecta e o limiar de 18 anos pode não corresponder ao protocolo de outro estudo; substitua ambos pelo critério justificado no seu projecto.
Use valores_originais, valores_convertidos e novos_na para localizar uma conversão que tenha produzido NA. Decida se o valor deve ser corrigido, excluído segundo o protocolo ou investigado, e registe a razão sem alterar raw.
# Example decision: investigate before changing raw.razao<-"Conversão produziu NA; confirmar o valor na fonte antes de decidir."razao
[1] "Conversão produziu NA; confirmar o valor na fonte antes de decidir."
A tabela localiza os casos sem alterar raw. A decisão final é aberta: corrija apenas com uma fonte justificável ou aplique o protocolo documentado.
Escreva uma entrada de registo para cada uma destas decisões: retirar uma resposta de teste, aplicar uma exclusão prevista no protocolo e experimentar um limiar novo. Inclua critério, data, versão do ficheiro e estatuto (rotineiro, previsto ou exploratório). Explique qual delas exigiria também comparação com a análise planeada.
Solução 4 — exemplo de registo
registo<-data.frame( decisao =c("retirar resposta de teste","aplicar exclusão prevista","experimentar limiar novo"), criterio =c("identificada como teste na documentação","consentimento e elegibilidade do protocolo","Progress >= 20, além do plano original"), data =rep(as.character(Sys.Date()), 3), ficheiro =rep("raw_qualtrics.csv", 3), estatuto =c("rotineiro", "previsto", "exploratório"), stringsAsFactors =FALSE)registo
decisao
criterio
data
ficheiro
estatuto
retirar resposta de teste
identificada como teste na documentação
2026-09-29
raw_qualtrics.csv
rotineiro
aplicar exclusão prevista
consentimento e elegibilidade do protocolo
2026-09-29
raw_qualtrics.csv
previsto
experimentar limiar novo
Progress >= 20, além do plano original
2026-09-29
raw_qualtrics.csv
exploratório
O limiar novo é exploratório e deve ser comparado com a análise planeada. As outras entradas também devem ser documentadas, mas não representam a mesma alteração exploratória da amostra.
Recebeu uma folha de cálculo que um colaborador já limpou manualmente. Desenhe um pequeno inventário de proveniência: ficheiro recebido, cópia de trabalho, versão limpa, pessoa/data e alterações conhecidas. Indique ainda duas perguntas que faria antes de a usar como entrada da análise.
Solução 5 — exemplo aberto
inventario<-data.frame( ficheiro_recebido ="dados_recebidos.xlsx", copia_trabalho ="dados_trabalho.xlsx", versao_limpa ="dados_limpos.xlsx", pessoa =NA_character_, data =NA_character_, alteracoes_conhecidas ="A confirmar com o colaborador", stringsAsFactors =FALSE)inventario
ficheiro_recebido
copia_trabalho
versao_limpa
pessoa
data
alteracoes_conhecidas
dados_recebidos.xlsx
dados_trabalho.xlsx
dados_limpos.xlsx
NA
NA
A confirmar com o colaborador
Perguntaria: quais foram exactamente as linhas ou valores alterados? E existe uma lista ou regra que permita repetir e verificar essas alterações? Os campos de pessoa e data são preenchidos a partir da informação real, não inventados.