Nettoyage des données de format incohérent dans R?

16

Je traite souvent des données d'enquête en désordre qui nécessitent beaucoup de nettoyage avant de pouvoir effectuer des statistiques. J'avais l'habitude de le faire "manuellement" dans Excel, parfois en utilisant des formules Excel, et parfois en vérifiant les entrées une par une. J'ai commencé à faire de plus en plus de ces tâches en écrivant des scripts pour les faire dans R, ce qui a été très bénéfique (les avantages incluent avoir un enregistrement de ce qui a été fait, moins de risques d'erreurs et pouvoir réutiliser du code si l'ensemble de données est mis à jour).

Mais il existe encore certains types de données que j'ai du mal à gérer efficacement. Par exemple:

> 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.dayest censé être le nombre moyen d'heures par jour consacrées à une certaine activité, mais ce que nous avons est exactement ce que le sujet a écrit. Supposons que je prenne certaines décisions sur ce qu'il faut faire avec des réponses ambiguës, et que je veux la variable rangée hours.per.day2comme suit.

   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

En supposant que le nombre de cas est assez important (disons 1000) et sachant que les sujets étaient libres d'écrire ce qu'ils voulaient, quelle est la meilleure façon d'aborder cela?

mark999
la source

Réponses:

12

J'utiliserais gsub () pour identifier les chaînes que je connais, puis je ferais peut-être le reste à la main.

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)

Pour travailler avec ceux que vous devez changer à la main, je suggère quelque chose comme ceci:

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

Cela donne:

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

Regex peut être un peu délicat, chaque fois que je fais quelque chose avec regex, je lance quelques tests simples. Se? Regex pour le manuel. Voici un comportement de 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"
Max Gordon
la source
Merci pour la réponse Max. Je ne connais pas les expressions régulières, je devrai donc en apprendre davantage à leur sujet. Pourriez-vous donner une brève description de la façon dont vous allez faire le reste à la main? Y at - il une meilleure façon que de simplement faire quelque chose comme new_var[by.hand] <- c(2, 1, ...)avec by.handêtre TRUEpour les cas qui sont faites à la main?
mark999
@ mark999: Ajout d'exemples et d'une suggestion sur la façon de faire ceux-ci à la main.
Max Gordon
1
Les expressions régulières sont super importantes pour tout type de manipulation de données: nettoyer les données comme l'OP, ou pour extraire des données de fichiers, HTML, etc. (Pour un HTML correct, il existe des bibliothèques, comme XMLpour vous aider à extraire des données, mais cela ne fonctionne pas lorsque le HTML est mal formé.)
Wayne
6

La suggestion de @ Max est bonne. Il semble que si vous écrivez un algorithme qui reconnaît les nombres ainsi que les mots / abréviations associés au temps, vous obtiendrez la plupart du chemin. Ce ne sera pas un beau code, mais cela fonctionnera et vous pouvez l'améliorer au fil du temps lorsque vous rencontrez des cas problématiques.

Mais pour une approche plus robuste (et qui prend du temps au départ), essayez de googler «analyser une chaîne temporelle en langage naturel». Certaines découvertes intéressantes sont cette API temps ouvert , un bon module Python et l'un des nombreux threads pertinents comme celui-ci sur Stack Overflow .

Fondamentalement, l'analyse syntaxique en langage naturel est un problème commun et vous devez rechercher des solutions dans des langues autres que R. Vous pouvez créer des outils dans une autre langue à laquelle vous pouvez accéder à l'aide de R, ou à tout le moins, vous pouvez obtenir de bonnes idées pour votre propre algorithme.

Cendre
la source
4

Pour quelque chose comme ça, s'il était suffisamment long, je pense que je voudrais une liste d'expressions régulières et de règles de transformation, et prendre les nouvelles valeurs dans une autre colonne (donc vous avez toujours la possibilité de vérifier à nouveau sans recharger les données brutes) ; les ER seraient appliqués pour les données pas encore transformées jusqu'à ce que toutes les données soient transformées ou que toutes les règles soient épuisées. Il est probablement préférable de conserver également une liste de valeurs logiques qui indiquent quelles lignes n'ont pas encore été transformées.

Bien sûr, quelques règles de ce type sont évidentes et gèreront probablement 80 à 90% des cas, mais le problème est qu'il y en aura toujours dont vous ne savez pas qu'elles apparaîtront (les gens sont très inventifs).

Ensuite, vous avez besoin d'un script qui passe en revue et vous présente les originaux des valeurs de règles non encore transformées par la liste des règles évidentes, vous donnant la possibilité de faire une expression régulière (disons ) pour identifier ces cas et donner une nouvelle transformation pour les cas qui lui correspondent, qu'il ajoute à la liste d'origine et s'applique aux lignes non encore transformées du vecteur d'origine avant de vérifier s'il reste des cas à vous présenter .

Il peut également être raisonnable d'avoir l'option d' ignorer un cas (afin que vous puissiez passer aux plus faciles), afin de pouvoir pus les cas très durs jusqu'à la fin.

Pire cas, vous en faites quelques-uns à la main.

Vous pouvez ensuite conserver la liste complète des règles que vous générez, pour les appliquer à nouveau lorsque les données augmentent ou qu'un nouvel ensemble de données similaire arrive.

Je ne sais pas si cela s'approche à distance des meilleures pratiques (je pense que quelque chose de beaucoup plus formel serait nécessaire là-bas), mais en termes de traitement rapide de grandes quantités de ces données, cela pourrait avoir une certaine valeur.

Glen_b -Reinstate Monica
la source
Merci pour la réponse, Glen. Cela semble très attrayant. Considérez-vous comme un grand avantage de présenter les valeurs non encore transformées une par une, au lieu de simplement les afficher toutes et de regarder cette sortie? Je n'ai jamais rien fait de tel que de présenter les choses une par une.
mark999
1
@ mark999, je pense qu'il y a à la fois des avantages et des inconvénients dans une présentation à la fois. L'avantage est la simplicité - l'utilisation de cat () pour afficher une heure ambiguë et scan () pour enregistrer votre interprétation de cette heure est facile à mettre en œuvre. L'inconvénient est que vous risquez de manquer la vue d'ensemble de nombreuses entrées que vous pourriez corriger en masse avec une seule ligne de code regex. Vous pourriez avoir une idée de ce que vous espérez obtenir: si vous voulez simplement résoudre ce problème, faites-le à la main. Si vous souhaitez en savoir plus sur R, essayez de coder une solution.
Ash
Désolé pour le manque de réponse; Je suis largement d'accord avec le commentaire d'Ash
Glen_b -Reinstate Monica
4

R contient des standards fonctions pour la manipulation de données, qui peuvent être utilisés pour le nettoyage des données, dans sa base de paquet ( gsub, transform, etc.), ainsi que dans divers logiciels tiers, tels que stringr , Reshape , reshape2 et plyr . Des exemples et les meilleures pratiques d'utilisation de ces packages et de leurs fonctions sont décrits dans le document suivant: http://vita.had.co.nz/papers/tidy-data.pdf .

De plus, R propose des packages spécifiquement axés sur le nettoyage et la transformation des données:

Une approche complète et cohérente du nettoyage des données dans R, y compris des exemples et l'utilisation de règles d' édition et de packages de déduplication , ainsi qu'une description du flux de travail ( cadre ) du nettoyage des données dans R, est présentée dans le document suivant, que je recommande fortement: http : //cran.r-project.org/doc/contrib/de_Jonge+van_der_Loo-Introduction_to_data_cleaning_with_R.pdf .

Aleksandr Blekh
la source