¿Limpieza de datos de formato inconsistente en R?

16

A menudo trato con datos de encuestas desordenados que requieren mucha limpieza antes de que se puedan realizar estadísticas. Solía ​​hacer esto "manualmente" en Excel, a veces usando fórmulas de Excel, y otras comprobando las entradas una por una. Comencé a hacer cada vez más estas tareas escribiendo guiones para hacerlas en R, lo cual ha sido muy beneficioso (los beneficios incluyen tener un registro de lo que se hizo, menos posibilidades de errores y poder reutilizar el código si el conjunto de datos es actualizado).

Pero todavía hay algunos tipos de datos que tengo problemas para manejar de manera eficiente. Por ejemplo:

> 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á destinado a ser el número promedio de horas por día dedicadas a una determinada actividad, pero lo que tenemos es exactamente lo que escribió el sujeto. Supongamos que tomo algunas decisiones sobre qué hacer con respuestas ambiguas, y quiero la variable ordenada de la hours.per.day2siguiente manera.

   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

Suponiendo que el número de casos es bastante grande (digamos 1000) y sabiendo que los sujetos eran libres de escribir lo que quisieran, ¿cuál es la mejor manera de abordar esto?

mark999
fuente

Respuestas:

12

Usaría gsub () para identificar las cadenas que conozco y luego tal vez haga el 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)

Para trabajar con los que necesita cambiar a mano, sugiero algo como esto:

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

Esto da:

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

Regex puede ser un poco complicado, cada vez que hago algo con regex ejecuto algunas pruebas simples. Segegege para el manual. Aquí hay un comportamiento 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
fuente
Gracias por la respuesta Max. No estoy familiarizado con las expresiones regulares, así que tendré que aprender sobre ellas. ¿Te importaría dar una breve descripción de cómo harías el resto a mano? ¿Hay una manera mejor que simplemente hacer algo así new_var[by.hand] <- c(2, 1, ...)con by.handestar TRUEen los casos que se hacen a mano?
mark999
@ mark999: se agregaron algunos ejemplos y una sugerencia de cómo puedes hacerlos a mano.
Max Gordon
1
Las expresiones regulares son muy importantes para cualquier tipo de manipulación de datos: limpiar datos como lo ha hecho el OP, o extraer datos de archivos, HTML, etc. (Para HTML adecuado, hay bibliotecas, como XMLpara ayudarlo a extraer datos, pero esto no funciona cuando el HTML está mal formado.)
Wayne
6

La sugerencia de @ Max es buena. Parece que si escribe un algoritmo que reconoce números, así como palabras / abreviaturas comunes asociadas con el tiempo, obtendrá la mayor parte del camino. Este no será un código hermoso, pero funcionará y puede mejorarlo con el tiempo a medida que se encuentre con casos problemáticos.

Pero para un enfoque más robusto (y que inicialmente consume mucho tiempo), intente buscar en Google "analizando una cadena de tiempo de lenguaje natural". Algunos hallazgos interesantes son esta API de tiempo abierto , un buen módulo de Python y uno de los muchos hilos relacionados como este en Stack Overflow .

Básicamente, el análisis del lenguaje natural es un problema común y debe buscar soluciones en otros idiomas además de R. Puede crear herramientas en otro idioma al que pueda acceder utilizando R, o al menos puede obtener buenas ideas para su propio algoritmo.

Ceniza
fuente
4

Para algo así, si fuera lo suficientemente largo, creo que querría una lista de expresiones regulares y reglas de transformación, y llevar los nuevos valores a otra columna (para que siempre tenga la oportunidad de verificar dos veces sin volver a cargar los datos sin procesar) ; los RE se aplicarían a los datos no tan transformados hasta que se hayan transformado todos los datos o se hayan agotado todas las reglas. Probablemente sea mejor también mantener una lista de valores lógicos que indiquen qué filas aún no se han transformado.

Algunas de esas reglas son obvias, por supuesto, y probablemente manejarán el 80-90% de los casos, pero el problema es que siempre habrá algunas que no sabes que surgirán (las personas son muy ingeniosas).

Luego, necesita una secuencia de comandos que revise y presente los originales de los valores aún no transformados por la lista de reglas obvias de uno en uno, lo que le brinda la oportunidad de hacer una expresión regular (digamos ) para identificar esos casos y dar una nueva transformación para los casos que se ajustan a él, que se agrega a la lista original y se aplica a las filas del vector original que aún no se han transformado antes de verificar si quedan casos para presentarle .

También podría ser razonable tener la opción de omitir un caso (para poder pasar a otros más fáciles), de modo que pueda llevar los casos muy difíciles hasta el final.

En el peor de los casos, haces algunos a mano.

Luego puede mantener la lista completa de reglas que genera, para aplicar nuevamente cuando los datos crecen o aparece un nuevo conjunto de datos similar.

No sé si se está acercando remotamente a las mejores prácticas (creo que se necesitaría algo mucho más formal allí), pero en términos de procesar grandes cantidades de dichos datos rápidamente, podría tener algún valor.

Glen_b -Reinstate a Monica
fuente
Gracias por la respuesta, Glen. Eso suena muy atractivo. ¿Considera que es una gran ventaja tener los valores aún no transformados presentados uno por uno, en lugar de solo mostrarlos todos y observar esa salida? Nunca he hecho algo así como presentar las cosas una a la vez.
mark999
1
@ mark999, creo que hay ventajas y desventajas de la presentación individual. La ventaja es la simplicidad: usar cat () para mostrar un tiempo ambiguo y scan () para grabar su interpretación de ese tiempo es fácil de implementar. La desventaja es que puede perderse el panorama general de muchas entradas que podría corregir en masa con una sola línea de código regex. Puede pensar en lo que espera obtener: si solo desea resolver este problema, hágalo a mano. Si desea obtener más información sobre R, intente codificar una solución.
Ash
Perdón por la falta de respuesta; En general, estoy de acuerdo con el comentario de Ash
Glen_b: reinstalar a Monica
4

R contiene algunos estándar funciones para la manipulación de datos, que pueden ser utilizados para la limpieza de datos, en su base de paquete ( gsub, transform, etc.), así como en varios paquetes de terceros, tales como stringr , reshape , reshape2 , y plyr . En el siguiente documento se describen ejemplos y mejores prácticas de uso de estos paquetes y sus funciones: http://vita.had.co.nz/papers/tidy-data.pdf .

Además, R ofrece algunos paquetes específicamente enfocados en la limpieza y transformación de datos:

Un enfoque integral y coherente de datos de limpieza en R, incluyendo ejemplos y el uso de editrules y deducorrect paquetes, así como una descripción de flujo de trabajo ( marco ) de los datos de limpieza en R, se presenta en el siguiente documento, que recomiendo encarecidamente: http : //cran.r-project.org/doc/contrib/de_Jonge+van_der_Loo-Introduction_to_data_cleaning_with_R.pdf .

Aleksandr Blekh
fuente