Limpando dados de formato inconsistente em R?

16

Costumo lidar com dados confusos da pesquisa, que exigem muita limpeza antes que qualquer estatística possa ser feita. Eu costumava fazer isso "manualmente" no Excel, às vezes usando fórmulas do Excel e, às vezes, verificando as entradas uma a uma. Comecei a executar cada vez mais essas tarefas escrevendo scripts para executá-las em R, o que tem sido muito benéfico (os benefícios incluem ter um registro do que foi feito, menor chance de erros e ser capaz de reutilizar o código se o conjunto de dados for Atualizada).

Mas ainda existem alguns tipos de dados com problemas para lidar com eficiência. Por exemplo:

> d <- data.frame(subject = c(1,2,3,4,5,6,7,8,9,10,11),
+   hours.per.day = c("1", "2 hours", "2 hr", "2hr", "3 hrs", "1-2", "15 min", "30 mins", "a few hours", "1 hr 30 min", "1 hr/week"))
> d
   subject hours.per.day
1        1             1
2        2       2 hours
3        3          2 hr
4        4           2hr
5        5         3 hrs
6        6           1-2
7        7        15 min
8        8       30 mins
9        9   a few hours
10      10   1 hr 30 min
11      11     1 hr/week

hours.per.daydeve ser o número médio de horas por dia gasto em uma determinada atividade, mas o que temos é exatamente o que o sujeito escreveu. Suponha que eu tome algumas decisões sobre o que fazer com respostas ambíguas e que queira a variável organizada da hours.per.day2seguinte maneira.

   subject hours.per.day hours.per.day2
1        1             1      1.0000000
2        2       2 hours      2.0000000
3        3          2 hr      2.0000000
4        4           2hr      2.0000000
5        5         3 hrs      3.0000000
6        6           1-2      1.5000000
7        7        15 min      0.2500000
8        8       30 mins      0.5000000
9        9   a few hours      3.0000000
10      10   1 hr 30 min      1.5000000
11      11     1 hr/week      0.1428571

Supondo que o número de casos seja bastante grande (digamos 1000) e sabendo que os sujeitos estavam livres para escrever o que quisessem, qual é a melhor maneira de abordar isso?

mark999
fonte

Respostas:

12

Eu usaria gsub () para identificar as strings que eu conheço e, talvez, faça o resto manualmente.

test <- c("15min", "15 min", "Maybe a few hours", 
          "4hr", "4hour", "3.5hr", "3-10", "3-10")
new_var <- rep(NA, length(test))

my_sub <- function(regex, new_var, test){
    t2 <- gsub(regex, "\\1", test)
    identified_vars <- which(test != t2)
    new_var[identified_vars] <- as.double(t2[identified_vars])
    return(new_var)    
}

new_var <- my_sub("([0-9]+)[ ]*min", new_var, test)
new_var <- my_sub("([0-9]+)[ ]*(hour|hr)[s]{0,1}", new_var, test)

Para trabalhar com os que você precisa alterar manualmente, sugiro algo como isto:

# Which have we not found
by.hand <- which(is.na(new_var))

# View the unique ones not found
unique(test[by.hand])
# Create a list with the ones
my_interpretation <- list("3-10"= 5, "Maybe a few hours"=3)
for(key_string in names(my_interpretation)){
    new_var[test == key_string] <- unlist(my_interpretation[key_string])
}

Isto dá:

> new_var
[1] 15.0 15.0  3.0  4.0  4.0  3.5  5.0  5.0

O regex pode ser um pouco complicado, toda vez que faço algo com o regex, faço alguns testes simples. Se regex para o manual. Aqui está um comportamento básico:

> # Test some regex
> grep("[0-9]", "12")
[1] 1
> grep("[0-9]", "12a")
[1] 1
> grep("[0-9]$", "12a")
integer(0)
> grep("^[0-9]$", "12a")
integer(0)
> grep("^[0-9][0-9]", "12a")
[1] 1
> grep("^[0-9]{1,2}", "12a")
[1] 1
> grep("^[0-9]*", "a")
[1] 1
> grep("^[0-9]+", "a")
integer(0)
> grep("^[0-9]+", "12222a")
[1] 1
> grep("^(yes|no)$", "yes")
[1] 1
> grep("^(yes|no)$", "no")
[1] 1
> grep("^(yes|no)$", "(yes|no)")
integer(0)
> # Test some gsub, the \\1 matches default or the found text within the ()
> gsub("^(yes|maybe) and no$", "\\1", "yes and no")
[1] "yes"
Max Gordon
fonte
Obrigado pela resposta Eu não estou familiarizado com expressões regulares, então terá que aprender sobre elas. Você se importaria de dar uma breve descrição de como você faria o resto manualmente? Existe uma maneira melhor do que apenas fazer algo parecido new_var[by.hand] <- c(2, 1, ...)com by.handestar TRUEpara os casos que são feitas à mão?
mark999
@ mark999: Adicionados alguns exemplos e uma sugestão de como você pode fazer os manualmente.
Max Gordon
1
Expressões regulares são super importantes para qualquer tipo de manipulação de dados: limpar dados como o OP ou extrair dados de arquivos, HTML etc. (para HTML adequado, existem bibliotecas, como XMLpara ajudá-lo a extrair dados, mas isso não funciona quando o HTML é mal formado).
Wayne
6

A sugestão de @ Max é boa. Parece que, se você escrever um algoritmo que reconheça números, bem como palavras / abreviações comuns associadas ao tempo, será o caminho mais longo. Não será um código bonito, mas funcionará e você poderá aprimorá-lo ao longo do tempo à medida que se deparar com casos de problemas.

Mas, para uma abordagem mais robusta (e inicialmente demorada), tente pesquisar no Google "analisando uma sequência de tempo no idioma natural". Algumas descobertas interessantes são: Esta API de tempo aberto , um bom módulo Python e um dos muitos threads relevantes como este no Stack Overflow .

Basicamente, a análise de linguagem natural é um problema comum e você deve procurar soluções em idiomas diferentes de R. Você pode criar ferramentas em outro idioma que possa acessar usando R ou, no mínimo, obter boas idéias para seu próprio algoritmo.

Cinza
fonte
4

Para algo assim, se fosse suficientemente longo, acho que desejaria uma lista de expressões regulares e regras de transformação e levaria os novos valores para outra coluna (para que você sempre tenha a chance de verificar novamente sem recarregar os dados brutos) ; os ERs seriam aplicados a dados não transformados até o momento até que todos os dados fossem transformados ou todas as regras estivessem esgotadas. Provavelmente, é melhor também manter uma lista de valores lógicos que indicam quais linhas ainda não foram transformadas.

Algumas dessas regras são óbvias, é claro, e provavelmente lidarão com 80-90% dos casos, mas a questão é que sempre haverá algumas que você não sabe que serão exibidas (as pessoas são muito inventivas).

Então, você precisa de um script que seja apresentado e apresente os originais dos valores de regras ainda não transformados pela lista de regras óbvias, um de cada vez, dando a você a chance de fazer uma expressão regular (por exemplo, ) para identificar esses casos e fornecer uma nova transformação para os casos que se ajustam a ele, que ele adiciona à lista original e se aplica às linhas ainda não transformadas do vetor original antes de verificar se há algum caso a ser apresentado a você .

Também pode ser razoável ter a opção de pular um caso (para que você possa prosseguir para casos mais fáceis), para poder enviar os casos mais difíceis até o fim.

Na pior das hipóteses, você faz alguns manualmente.

Você pode manter a lista completa de regras que gerar, para aplicar novamente quando os dados aumentarem ou um novo conjunto de dados semelhante aparecer.

Não sei se é uma abordagem remota das melhores práticas (acho que algo muito mais formal seria necessário lá), mas em termos de processamento de grandes quantidades desses dados rapidamente, pode ter algum valor.

Glen_b -Reinstate Monica
fonte
Obrigado pela resposta, Glen. Isso parece muito atraente. Você vê como uma grande vantagem apresentar os valores ainda não transformados, um de cada vez, em vez de apenas exibir todos eles e observar essa saída? Eu nunca fiz nada como ter coisas apresentadas uma de cada vez.
mark999
1
@ mark999, eu acho que existem vantagens e desvantagens da apresentação única. A vantagem é a simplicidade - é fácil implementar o uso de cat () para exibir um horário ambíguo, e scan () para registrar sua interpretação desse horário. A desvantagem é que você pode perder o quadro geral de muitas entradas que pode corrigir em massa com uma única linha de código regex. Você pode ter uma idéia do que espera obter: se quiser apenas resolver esse problema, faça-o manualmente. Se você quiser saber mais sobre o R, tente codificar uma solução.
Ash
Desculpe pela falta de resposta; Concordo amplamente com o comentário de Ash
Glen_b -Reinstate Monica
4

R contém algumas padrão funções para manipulação de dados, que podem ser utilizados para limpeza de dados, na sua base de pacote ( gsub, transform, etc.), bem como em vários pacotes de terceiros, tais como stringr , remodelagem , reshape2 , e plyr . Exemplos e práticas recomendadas de uso para esses pacotes e suas funções são descritos no seguinte documento: http://vita.had.co.nz/papers/tidy-data.pdf .

Além disso, o R oferece alguns pacotes especificamente focados na limpeza e transformação de dados:

Uma abordagem abrangente e coerente para a limpeza de dados em R, incluindo exemplos e uso de editrules e pacotes com deducorreção , bem como uma descrição do fluxo de trabalho ( estrutura ) da limpeza de dados em R, é apresentada no documento a seguir, que eu recomendo: http : //cran.r-project.org/doc/contrib/de_Jonge+van_der_Loo-Introduction_to_data_cleaning_with_R.pdf .

Aleksandr Blekh
fonte