Czyścić dane o niespójnym formacie w R?

16

Często mam do czynienia z niechlujnymi danymi ankiet, które wymagają dużo czyszczenia, zanim będzie można wykonać statystyki. Robiłem to „ręcznie” w programie Excel, czasami używając formuł Excela, a czasem sprawdzając wpisy jeden po drugim. Zacząłem robić coraz więcej tych zadań, pisząc skrypty do ich wykonania w języku R, co było bardzo korzystne (korzyści obejmują rejestrowanie tego, co zostało zrobione, mniejsze prawdopodobieństwo błędów i możliwość ponownego użycia kodu, jeśli zestaw danych jest zaktualizowane).

Ale nadal istnieją pewne rodzaje danych, z którymi mam problemy z wydajną obsługą. Na przykład:

> 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.dayma oznaczać średnią liczbę godzin dziennie poświęconych na określoną aktywność, ale mamy dokładnie to, co napisał ten temat. Załóżmy, że podejmuję pewne decyzje, co zrobić z niejednoznacznymi odpowiedziami, i chcę uporządkować zmienną hours.per.day2w następujący sposób.

   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

Zakładając, że liczba przypadków jest dość duża (powiedzmy 1000) i wiedząc, że badani mogli swobodnie pisać cokolwiek im się podoba, jaki jest najlepszy sposób na to?

mark999
źródło

Odpowiedzi:

13

Użyłbym gsub (), aby zidentyfikować łańcuchy, które znam, a następnie może zrobić resztę ręcznie.

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)

Aby uzyskać pracę z tymi, które musisz zmienić ręcznie, sugeruję coś takiego:

# 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])
}

To daje:

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

Regex może być trochę trudny, za każdym razem, gdy robię cokolwiek z wyrażeniem regularnym, przeprowadzam kilka prostych testów. Wyrażenie regularne instrukcji. Oto kilka podstawowych zachowań:

> # 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
źródło
Dzięki za odpowiedź Max. Nie znam wyrażeń regularnych, więc będę musiał się o nich dowiedzieć. Czy mógłbyś podać krótki opis tego, jak zająłbyś się resztą ręcznie? Czy istnieje lepszy sposób niż po prostu robi coś takiego new_var[by.hand] <- c(2, 1, ...)z by.handczym TRUEw odniesieniu do przypadków, które są wykonywane ręcznie?
mark999
@ mark999: Dodano kilka przykładów i sugestię, jak możesz zrobić te ręcznie.
Max Gordon
1
Wyrażenia regularne są bardzo ważne dla wszelkiego rodzaju manipulacji danymi: czyszczenia danych tak, jak OP, lub do wyodrębniania danych z plików, HTML itp. (Dla prawidłowego HTML istnieją biblioteki, XMLktóre pomagają wyodrębnić dane, ale to nie działa, gdy HTML jest źle sformułowany.)
Wayne
6

@ Sugestia Maxa jest dobra. Wydaje się, że jeśli napiszesz algorytm, który rozpoznaje liczby, a także typowe słowa / skróty związane z czasem, uzyskasz większość możliwości. To nie będzie piękny kod, ale będzie działał i możesz go z czasem ulepszyć, gdy napotkasz problemy.

Aby jednak zastosować bardziej niezawodne (i początkowo czasochłonne) podejście, wypróbuj Googling „analizując ciąg czasu naturalnego języka”. Ciekawymi odkryciami są: Ten API czasu otwartego , dobry moduł Pythona i jeden z wielu niemądrych wątków, takich jak ten na przepełnieniu stosu .

Zasadniczo parsowanie języka naturalnego jest powszechnym problemem i powinieneś szukać rozwiązań w językach innych niż R. Możesz budować narzędzia w innym języku, do którego można uzyskać dostęp za pomocą R, lub przynajmniej możesz uzyskać dobre pomysły na własny algorytm.

Popiół
źródło
4

W przypadku czegoś takiego, jeśli byłby wystarczająco długi, myślę, że potrzebowałbym listy wyrażeń regularnych i reguł transformacji i przeniósłbym nowe wartości do innej kolumny (więc zawsze masz szansę podwójnie sprawdzić bez ponownego ładowania surowych danych) ; RE byłyby stosowane w odniesieniu do danych, które nie zostały jeszcze przekształcone, dopóki wszystkie dane nie zostaną przekształcone lub wszystkie zasady zostaną wyczerpane. Prawdopodobnie najlepiej jest również przechowywać listę wartości logicznych wskazujących, które wiersze nie zostały jeszcze przekształcone.

Kilka takich zasad jest oczywiście oczywistych i prawdopodobnie zajmie się 80-90% przypadków, ale problem polega na tym, że zawsze będą takie, o których nie wiesz, że pojawią się (ludzie są bardzo pomysłowi).

Następnie potrzebujesz skryptu, który przejdzie i przedstawi Ci oryginały wartości, które nie zostały jeszcze przekształcone według listy oczywistych reguł, pojedynczo, dając ci szansę na wyrażenie regularne (powiedzmy ), aby zidentyfikować te przypadki i nadać nową transformację przypadkom, które do niej pasują, która dodaje do oryginalnej listy i stosuje się do nieprzekształconych jeszcze wierszy oryginalnego wektora przed sprawdzeniem, czy są jeszcze jakieś przypadki do przedstawienia .

Rozsądna może być również opcja pominięcia sprawy (abyś mógł przejść do łatwiejszych), dzięki czemu możesz przerzucić bardzo trudne sprawy do samego końca.

W najgorszym przypadku robisz kilka ręcznie.

Następnie możesz zachować pełną listę generowanych reguł, aby zastosować je ponownie, gdy dane rosną lub pojawia się nowy, podobny zestaw danych.

Nie wiem, czy zdalnie zbliża się do najlepszej praktyki (sądzę, że byłoby tam potrzebne coś znacznie bardziej formalnego), ale jeśli chodzi o szybkie przetwarzanie dużych ilości takich danych, może mieć pewną wartość.

Glen_b - Przywróć Monikę
źródło
Dzięki za odpowiedź, Glen. Brzmi bardzo atrakcyjnie. Czy uważasz, że dużą zaletą jest prezentowanie niezmienionych wartości pojedynczo, a nie tylko wyświetlanie ich wszystkich i analizowanie wyników? Nigdy nie robiłem czegoś takiego jak prezentowanie pojedynczych rzeczy.
mark999
1
@ mark999, myślę, że zarówno zalety, jak i wady pojedynczej prezentacji. Zaletą jest prostota - użycie cat () do wyświetlania niejednoznacznego czasu, a scan () do zarejestrowania twojej interpretacji tego czasu jest łatwe do wdrożenia. Wadą jest to, że możesz przeoczyć duży obraz wielu pozycji, które możesz poprawić masowo za pomocą jednego wiersza kodu regularnego. Możesz pomyśleć o tym, co masz nadzieję: jeśli chcesz tylko rozwiązać ten problem, zrób to ręcznie. Jeśli chcesz dowiedzieć się więcej o R, spróbuj napisać rozwiązanie.
Ash
Przepraszam za brak odpowiedzi; Zasadniczo zgadzam się z komentarzem Asha
Glen_b
4

R zawiera kilka standardowych funkcji manipulacji danych, co może być wykorzystywane do czyszczenia danych w swojej podstawowej opakowania ( gsub, transformetc.), jak również w różnych Takie pakiety, takie jak stringr , zmiany kształtu , reshape2 i plyr . Przykłady i najlepsze praktyki użycia tych pakietów i ich funkcji opisano w następującym artykule: http://vita.had.co.nz/papers/tidy-data.pdf .

Ponadto R oferuje niektóre pakiety specjalnie ukierunkowane na czyszczenie i transformację danych:

Kompleksowe i spójne podejście do czyszczenia danych w języku R, w tym przykłady i zastosowanie editrules i deducorrect pakietów, a także opis przepływu pracy ( ramy ) czyszczenia danych w języku R, przedstawiono w następującym artykule, który gorąco polecam: http : //cran.r-project.org/doc/contrib/de_Jonge+van_der_Loo-Introduction_to_data_cleaning_with_R.pdf .

Aleksandr Blekh
źródło