Dati di pulizia di formato incoerente in R?


16

Mi occupo spesso di dati di sondaggi disordinati che richiedono molta pulizia prima di poter fare statistiche. Lo facevo "manualmente" in Excel, a volte usando le formule di Excel e talvolta controllando le voci una per una. Ho iniziato a svolgere sempre più di questi compiti scrivendo script per eseguirli in R, il che è stato molto vantaggioso (i vantaggi includono la registrazione di ciò che è stato fatto, meno possibilità di errori e la possibilità di riutilizzare il codice se il set di dati è aggiornato).

Ma ci sono ancora alcuni tipi di dati che ho difficoltà a gestire in modo efficiente. Per esempio:

> 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.dayè pensato per essere il numero medio di ore al giorno dedicate a una determinata attività, ma ciò che abbiamo è esattamente ciò che l'argomento ha scritto. Supponiamo che prenda alcune decisioni su cosa fare con risposte ambigue e che desidero la variabile ordinata hours.per.day2come segue.

   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

Supponendo che il numero di casi sia piuttosto elevato (diciamo 1000) e sapendo che i soggetti erano liberi di scrivere tutto ciò che gli piaceva, qual è il modo migliore per affrontarlo?

Risposte:


12

Vorrei usare gsub () per identificare le stringhe che conosco e quindi forse fare il resto a mano.

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)

Per lavorare con quelli che è necessario modificare manualmente, suggerisco qualcosa del genere:

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

Questo da:

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

Regex può essere un po 'complicato, ogni volta che faccio qualcosa con regex, eseguo alcuni semplici test. Se? Regex per il manuale. Ecco alcuni comportamenti di base:

> # 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"

Grazie per la risposta Max. Non ho familiarità con le espressioni regolari, quindi dovrò conoscerle. Ti dispiacerebbe dare una breve descrizione di come faresti a fare il resto a mano? C'è un modo migliore rispetto solo facendo qualcosa di simile new_var[by.hand] <- c(2, 1, ...)con by.handessendo TRUEper i casi che sono fatte a mano?
mark999,

@ mark999: aggiunti alcuni esempi e un suggerimento su come puoi fare quelli a mano.
Max Gordon,

1
Le espressioni regolari sono estremamente importanti per qualsiasi tipo di manipolazione dei dati: ripulire i dati come ha fatto l'OP, o per estrarre dati da file, HTML, ecc. (Per un corretto HTML, ci sono librerie, come XMLper aiutarti a estrarre i dati, ma questo non funziona quando l'HTML non è valido.)
Wayne,

6

Il suggerimento di Max è buono. Sembra che se scrivi un algoritmo che riconosca i numeri così come le parole / abbreviazioni associate al tempo comuni, otterrai la maggior parte del percorso. Questo non sarà un bellissimo codice, ma funzionerà e puoi migliorarlo nel tempo quando ti imbatti in casi problematici.

Ma per un approccio più solido (e inizialmente dispendioso in termini di tempo), prova a cercare su Google "l'analisi di una stringa temporale in linguaggio naturale". Alcuni risultati interessanti sono questa API open time , un buon modulo Python e uno dei tanti thread germani come questo su Stack Overflow .

Fondamentalmente, l'analisi del linguaggio naturale è un problema comune e dovresti cercare soluzioni in lingue diverse da R. Puoi costruire strumenti in un'altra lingua a cui puoi accedere usando R, o almeno puoi ottenere buone idee per il tuo algoritmo.


4

Per qualcosa del genere, se fosse sufficientemente lungo, penso che vorrei un elenco di espressioni regolari e regole di trasformazione e portare i nuovi valori in un'altra colonna (in modo da avere sempre la possibilità di ricontrollare senza ricaricare i dati grezzi) ; gli RE verrebbero applicati al fine di non trasformare i dati fino a quando tutti i dati non fossero stati trasformati o tutte le regole fossero esaurite. Probabilmente è meglio tenere anche un elenco di valori logici che indicano quali righe non sono ancora state trasformate.

Alcune di queste regole sono ovviamente ovvie e probabilmente gestiranno l'80-90% dei casi, ma il problema è che ce ne saranno sempre alcune che non conosci verranno fuori (le persone sono molto inventive).

Quindi hai bisogno di uno script che passi attraverso e presenti gli originali dei valori non ancora trasformati dall'elenco delle regole ovvie uno alla volta, dandoti la possibilità di fare un'espressione regolare (ad esempio ) per identificare quei casi e dare una nuova trasformazione per i casi che si adattano, che aggiunge all'elenco originale e si applica alle righe non ancora trasformate del vettore originale prima di verificare se ci sono altri casi da presentare .

Potrebbe anche essere ragionevole avere la possibilità di saltare un caso (in modo da poter passare a quelli più semplici), in modo da poter sospendere i casi molto difficili fino alla fine.

Nel peggiore dei casi, ne fai alcuni a mano.

È quindi possibile mantenere l'elenco completo delle regole generate, da applicare nuovamente quando i dati crescono o arriva un nuovo set di dati simile.

Non so se si sta avvicinando in remoto alle migliori pratiche (penso che ci sarebbe bisogno di qualcosa di molto più formale lì), ma in termini di elaborazione rapida di grandi quantità di tali dati, potrebbe avere un certo valore.


Grazie per la risposta, Glen. Sembra molto attraente. Consideri un grande vantaggio avere i valori non ancora trasformati presentati uno alla volta, invece di mostrarli tutti e guardare quell'output? Non ho mai fatto nulla di simile che sia stato presentato uno alla volta.
mark999,

1
@ Mark999, penso che ci siano sia vantaggi che svantaggi della presentazione individuale. Il vantaggio è la semplicità: usare cat () per visualizzare un tempo ambiguo e scan () per registrare la tua interpretazione di quel tempo è facile da implementare. Lo svantaggio è che potresti perdere il quadro generale di molte voci che potresti correggere in massa con una singola riga di codice regex. Potresti pensare a cosa speri di ottenere: se vuoi solo risolvere questo problema, fallo a mano. Se vuoi saperne di più su R, prova a codificare una soluzione.
Ash,

Ci scusiamo per la mancanza di risposta; Sono ampiamente d'accordo con il commento di Ash
Glen_b -Restate Monica

4

R contiene alcune normali funzioni per la manipolazione dei dati, che possono essere utilizzati per la pulizia dei dati, nella sua base di pacchetto ( gsub, transformecc), così come i vari pacchetti di terze parti, come stringr , rimodellare , reshape2 e plyr . Esempi e migliori pratiche d'uso di questi pacchetti e delle loro funzioni sono descritti nel seguente documento: http://vita.had.co.nz/papers/tidy-data.pdf .

Inoltre, offre alcuni pacchetti R specificamente focalizzati sulla pulizia dei dati e la trasformazione:

Un approccio completo e coerente alla pulizia dei dati in R, inclusi esempi e uso di editruli e pacchetti deducibili , nonché una descrizione del flusso di lavoro ( quadro ) della pulizia dei dati in R, è presentato nel seguente documento, che consiglio vivamente: http : //cran.r-project.org/doc/contrib/de_Jonge+van_der_Loo-Introduction_to_data_cleaning_with_R.pdf .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.