Daten in inkonsistentem Format in R bereinigen?

16

Ich beschäftige mich oft mit unordentlichen Umfragedaten, die eine Menge Aufräumarbeiten erfordern, bevor Statistiken erstellt werden können. Früher habe ich das "manuell" in Excel gemacht, manchmal mit Excel-Formeln und manchmal nacheinander. Ich begann, immer mehr dieser Aufgaben zu erledigen, indem ich Skripte schrieb, um sie in R zu erledigen, was sehr vorteilhaft war (zu den Vorteilen gehörte, dass ich ein Protokoll darüber hatte, was getan wurde, weniger Fehlerwahrscheinlichkeit und in der Lage war, Code wiederzuverwenden, wenn der Datensatz vorhanden war Aktualisiert).

Es gibt jedoch noch einige Datentypen, mit denen ich Probleme habe, effizient umzugehen. Beispielsweise:

> 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.daysoll die durchschnittliche Anzahl von Stunden pro Tag sein, die für eine bestimmte Aktivität aufgewendet wurden, aber wir haben genau das, was das Thema geschrieben hat. Angenommen, ich entscheide, was mit mehrdeutigen Antworten geschehen soll, und ich möchte die aufgeräumte Variable hours.per.day2wie folgt.

   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

Angenommen, die Anzahl der Fälle ist ziemlich groß (sagen wir 1000) und die Versuchspersonen konnten alles schreiben, was sie wollten. Wie kann man das am besten angehen?

mark999
quelle

Antworten:

12

Ich würde gsub () verwenden, um die mir bekannten Zeichenfolgen zu identifizieren und den Rest dann vielleicht von Hand zu erledigen.

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)

Um mit denen zu arbeiten, die Sie per Hand ändern müssen, schlage ich Folgendes vor:

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

Das gibt:

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

Regex kann ein wenig knifflig sein. Jedes Mal, wenn ich etwas mit Regex mache, führe ich ein paar einfache Tests durch. Siehe Regex für das Handbuch. Hier ist ein grundlegendes Verhalten:

> # 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
quelle
Danke für die Antwort Max. Ich kenne mich mit regulären Ausdrücken nicht aus und muss sie daher kennenlernen. Würde es Ihnen etwas ausmachen, kurz zu beschreiben, wie Sie den Rest von Hand erledigen würden? Gibt es einen besseren Weg , als nur etwas zu tun , wie new_var[by.hand] <- c(2, 1, ...)mit zu by.handsein TRUEfür die Fälle , die von Hand gemacht werden?
mark999
@ mark999: Es wurden einige Beispiele und Vorschläge hinzugefügt, wie Sie diese von Hand ausführen können.
Max Gordon
1
Reguläre Ausdrücke sind super-wichtig für jede Art von Datenmanipulation: Reinigung Daten wie die OP hat, oder für Daten aus Dateien, HTML Extrahieren usw. (Für eine korrekte HTML gibt es Bibliotheken, wie XMLzu helfen Sie , Daten zu extrahieren, aber das funktioniert nicht, wenn der HTML-
Wayne
6

@Max's Vorschlag ist gut. Wenn Sie einen Algorithmus schreiben, der sowohl Zahlen als auch gebräuchliche zeitbezogene Wörter / Abkürzungen erkennt, werden Sie den größten Teil des Weges dorthin zurücklegen. Dies wird kein schöner Code sein, aber er wird funktionieren und Sie können ihn im Laufe der Zeit verbessern, wenn Sie auf Problemfälle stoßen.

Versuchen Sie für eine robustere (und anfangs zeitaufwendigere) Vorgehensweise Googeln "Parsen einer Zeitzeichenfolge in natürlicher Sprache". Einige interessante Ergebnisse sind Diese Open- Time-API , ein gutes Python-Modul und einer von vielen wichtigen Threads wie dieser auf Stack Overflow .

Grundsätzlich ist das Parsen natürlicher Sprachen ein häufiges Problem, und Sie sollten nach Lösungen in anderen Sprachen als R suchen. Sie können Tools in einer anderen Sprache erstellen, auf die Sie mit R zugreifen können, oder zumindest gute Ideen für Ihren eigenen Algorithmus erhalten.

Asche
quelle
4

Für so etwas würde ich, wenn es ausreichend lang wäre, eine Liste der regulären Ausdrücke und Transformationsregeln wünschen und die neuen Werte in eine andere Spalte übernehmen (so haben Sie immer die Möglichkeit, die Rohdaten zu überprüfen, ohne sie erneut zu laden). ; Die REs würden so lange auf die noch nicht transformierten Daten angewendet, bis alle Daten transformiert oder alle Regeln erschöpft wären. Am besten ist es wahrscheinlich, eine Liste mit logischen Werten zu führen, aus der hervorgeht, welche Zeilen noch nicht transformiert wurden.

Ein paar solcher Regeln sind natürlich offensichtlich und werden wahrscheinlich 80-90% der Fälle behandeln, aber das Problem ist, dass es immer einige geben wird, von denen Sie nicht wissen, dass sie auftauchen (die Leute sind sehr erfinderisch).

Dann brauchen Sie ein Skript, das die Originale der noch nicht durch die Liste der offensichtlichen Regeln transformierten Werte nacheinander durchläuft und Ihnen die Möglichkeit gibt, einen regulären Ausdruck zu erstellen (sagen wir ), um diese Fälle zu identifizieren und eine neue zu geben Transformation für die passenden Fälle zu erstellen, die der ursprünglichen Liste hinzugefügt und auf die noch nicht transformierten Zeilen des ursprünglichen Vektors angewendet wird, bevor geprüft wird, ob noch Fälle vorhanden sind, die Sie präsentieren können .

Es kann auch sinnvoll sein, die Option zum Überspringen zu haben einen Fall (damit Sie zu einfacheren Fällen übergehen können), damit Sie die sehr schwierigen Fälle bis zum Ende pushen können.

Im schlimmsten Fall machen Sie ein paar von Hand.

Sie können dann die vollständige Liste der von Ihnen generierten Regeln behalten, um sie erneut anzuwenden, wenn die Daten wachsen oder ein neuer, ähnlicher Datensatz hinzukommt.

Ich weiß nicht, ob es sich um eine Best-Practice-Lösung handelt (ich denke, dort wäre etwas viel Formaleres erforderlich), aber im Hinblick auf die schnelle Verarbeitung großer Mengen solcher Daten könnte es von Nutzen sein.

Glen_b - Setzen Sie Monica wieder ein
quelle
Danke für die Antwort, Glen. Das hört sich sehr ansprechend an. Sehen Sie es als großen Vorteil an, wenn die noch nicht transformierten Werte einzeln angezeigt werden, anstatt nur alle anzuzeigen und diese Ausgabe zu betrachten? Ich habe noch nie so etwas gemacht, als ob ich die Dinge einzeln präsentiert hätte.
mark999
1
@ mark999, ich würde denken, es gibt sowohl Vor- als auch Nachteile einer einzelnen Präsentation. Der Vorteil ist die Einfachheit: Die Verwendung von cat () zum Anzeigen einer mehrdeutigen Zeit und scan () zum Aufzeichnen Ihrer Interpretation dieser Zeit ist einfach zu implementieren. Der Nachteil ist, dass Sie möglicherweise das große Bild vieler Einträge verpassen, die Sie mit einer einzigen Zeile Regex-Code massenhaft korrigieren können. Sie können sich überlegen, was Sie sich erhoffen: Wenn Sie dieses Problem nur lösen möchten, tun Sie es von Hand. Wenn Sie mehr über R erfahren möchten, versuchen Sie, eine Lösung zu codieren.
Asche
Entschuldigung für die fehlende Antwort; Ich stimme dem Kommentar von Ash weitgehend zu
Glen_b
4

R enthält einige Standardfunktionen für die Datenmanipulation, die für die Datum Reinigung verwendet werden kann, in seinem Basispaket (gsub , transform, etc.) sowie in verschiedene Fremdpakete, wie stringr , umformen , reshape2 und plyr . Beispiele und Best Practices für die Verwendung dieser Pakete und deren Funktionen sind in folgendem Dokument beschrieben: http://vita.had.co.nz/papers/tidy-data.pdf .

Zusätzlich R bietet einige Pakete speziell fokussiert auf Daten Reinigung und Transformation:

Ein umfassender und kohärenter Ansatz für die Datenbereinigung in R, einschließlich Beispielen und Verwendung von Bearbeitungsregeln und deduzierten Paketen sowie eine Beschreibung des Workflows ( Frameworks ) für die Datenbereinigung in R, wird in dem folgenden Dokument vorgestellt, das ich wärmstens empfehle: http : //cran.r-project.org/doc/contrib/de_Jonge+van_der_Loo-Introduction_to_data_cleaning_with_R.pdf .

Aleksandr Blekh
quelle