Limpiando datos en R

read this post in English here

La siguente tabla es una muestra reducida de los datos contenidos en un directorio de centros de adopción canina en Canadá. Los datos son verdaderos y toda la información de contacto (correos, teléfonos, etc.) es reciente, y proviene de la página Speaking of Dogs.

Para este ejemplo, desacomodé el formato orgiginal de los datos para ponerlos en un formato medio complicado con el que he tenido que lidiar recientemente. Dejé el contenido de la tabla en inglés, el resto del texto en este ejemplo es en español.

Organization Contact name phone website Organization Contact name phone website
“Small Breed”       “Bulldog (English)”      
Happy Tails Rescue Judy 905-357-5096 www.happytailsrescue.ca Homeward Bound Rescue* Kathy 905-987-1104 www.homewardboundrescue.ca
LOYAL Rescue Inc. Anne 888-739-1221 www.loyalrescue.com unknown Joan† 416-738-6059 unknown
Pomeranian and Small Breed Rescue Shelley 416-225-6808 www.psbrescue.com “Labrador Retriever “      
Tiny Paws Rescue Brenda 1-800-774-8315 www.tpdr.ca Labrador Retriever Adoption Service Laura or Karen 289-997-5227 www.lab-rescue.ca
“Senior Dogs”       Dog Rescuers Inc Joan 416-567-6249 ‡ www.thedogrescuersinc.ca
Speaking of Dogs Rescue Lorraine 705-444-7637 www.speakingofdogs.com        

La tabla tiene unos cuantos datos adicionales:

  • * includes other Flat faced dogs: Bulldogs, Boxers, Bostons, Pugs etc
  • † limited foster care available
  • ‡ phone may not be up to date

En este formato, la tabla no está lista para ser analizada. Hay tres problemitas que hay que resolver primero.

  • Hay columnas repetidas en la tabla. Es como si alguien (yo) hubiera partido la tabla en dos (verticalmente) para después acomodar las dos mitades lado a lado en un formacho ‘ancho’. No conviene tener columnas/variables repetidas porque esta manera de guardar datos es medio riesgosa e incómoda.

  • Hay encabezados metidos dentro de la columna Organization. Estas filas se usan para avisar que los datos en las filas que siguen pertenencen a un grupo, pero estas variables que agrupan observaciones en realidad no pertenenen dentro de la misma variable. La práctica de usar encabezados de esta forma es muy común, y en realidad es fácil de seguir visualmente pero complica la manipulación automatizada de datos. Aquí hay una mejor descripción.

  • Hay caracteres especiales en algunas celdas de la tabla, que se están usando para hacer referencia a algunos datos adicionales que están afuera de la misma (a manera de notas al pie de página).

Reestructuración de datos

Aquí explico algunos pasos que se pueden hacer para reacomodar y reestructurar la tabla usando R. Sólo hace falta instalar algunos paquetes adicionales, y el resto del código se puede sequir copiando y pegando.

Para reproducir el ejemplo, lo primero que hay que hacer es pegar la tabla. Aquí viene como vector, con las columnas y filas delimitadas por tabulaciones y saltos de línea, respectivamente.

# cargar paquetes
library(dplyr)
library(magrittr)
library(tidyr)
library(rlang)
library(purrr)

# vector de datos 
resc <- 
c("Organization	Contact name	phone	website	Organization	Contact name	phone	website
'Small Breed'				'Bulldog (English)'			
Happy Tails Rescue	Judy	905-357-5096	www.happytailsrescue.ca	Homeward Bound Rescue*	Kathy	905-987-1104	www.homewardboundrescue.ca
LOYAL Rescue Inc.	Anne	888-739-1221	www.loyalrescue.com	unknown	Joan†	416-738-6059 	unknown
Pomeranian and Small Breed Rescue	Shelley	416-225-6808	www.psbrescue.com	'Labrador Retriever'			
Tiny Paws Rescue	Brenda	1-800-774-8315	www.tpdr.ca	Labrador Retriever Adoption Service	Laura or Karen 	289-997-5227	www.lab-rescue.ca
'Senior Dogs'				Dog Rescuers Inc	Joan	416-567-6249‡	www.thedogrescuersinc.ca
Speaking of Dogs Rescue	Lorraine	705-444-7637	www.speakingofdogs.com")				

Ahora podemos hacer que cada linea sea una fila dentro de una ‘tibble’ para después separar las columnas, y que de esta manera siguen habiendo variables repetidas.

# pasar a filas
rescDF <- data_frame(unsep=unlist(strsplit(resc,"\n")))

# separar variables
rescDF %<>% separate(unsep,into=unlist(strsplit(rescDF$unsep[1],"\t")),sep ="\t")

Ahora toca apilar la tabla para que quede en formato ‘largo’ y no ‘ancho’. En algún momento pregunté en Twitter cómo podía hacer ésto, y la recomendación general fue que usara la función gather de tidyr. Esta solución sólo sirve si primero resolvemos el problema de las columnas repetidas o lo evitamos desde el principio.

No siempre vamos a tener datos limpios y columnas que no se repitan, y por eso aquí vamos a seguir la propuesta que encontré en esta discusión. El usuario akrun propone una solución bastante ingeniosa:

  • extraer todas las observaciones para cada nombre de columna único (iterativamente)
  • desvincularlas (unlist)
  • acomodar todo en una tabla

Le hice algunos cambios a la propuesta original. Más que nada cambié el lapply y en su lugar usé map porque estoy tratando de aprender purrr.

# apilar
rescDFstacked <- 
map(unique(names(rescDF)), ~
      unlist(rescDF[names(rescDF)==.x], use.names = FALSE)) %>% 
  as.data.frame(stringsAsFactors = FALSE) %>% 
  set_names(unique(names(rescDF)))

Los datos ya van tomando mejor forma pero aún falta sacar los encabezados que están metidos dentro de la columna Organization. La función untangle2 que está descrita aquí es justo lo que necesitamos. Muchas gracias a Jenny Bryan por mejorar la versión original.

En nuestro ejemplo los encabezados están entre comillas simples, y por eso es bastante fácil sacarlos y ponerlos en su lugar.

# definir la función untangle2
untangle2 <- function(df, regex, orig, new) {
  orig <- enquo(orig)
  new <- sym(quo_name(enquo(new)))
  
  df %>%
    mutate(
      !!new := if_else(grepl(regex, !! orig), !! orig, NA_character_)
    ) %>%
    fill(!! new) %>%
    filter(!grepl(regex, !! orig))
}


# sacar los encabezados (cualquier cosa entre comillas en la variable Organization)
rescDFstacked %<>% untangle2("'",Organization,Category)

Ahora hay que limipar las filas, quitando las que están vacías or repetidas.

# quitar filas repetidas, NA, o vacías
rescDFstacked %<>% filter(Organization != "Organization" & Organization != " ", !is.na(Organization))

Las notas al pie de página son el último problema. Para incorporar estos datos en la tabla, podemos usar case_when y mutate y así meter las notas en las filas en las que corresponden. No me gustó tanto esta forma de definir manualmente las columnas en las que había que buscar los diferentes caracteres especiales, pero no sabía qué más hacer.

# metar las notas a la tabla
rescDFstacked %<>% mutate(observation = case_when(
  grepl("\\*",Organization)~"includes other Flat faced dogs: Bulldogs, Boxers, Bostons, Pugs etc",
  grepl("\u0086",`Contact name`)~"limited foster care available",
  grepl("\u0087",phone)~"phone may not be up to date"
  ))

# para saber en qué columnas buscar con el regex
rescDFstacked %>% map(~grepl("\\*",.x)) %>% map(~.x[.x==TRUE]) %>% unlist() %>% names()
rescDFstacked %>% map(~grepl("\u0086",.x)) %>% map(~.x[.x==TRUE]) %>% unlist() %>% names()
rescDFstacked %>% map(~grepl("\u0087",.x)) %>% map(~.x[.x==TRUE]) %>% unlist() %>% names()

# no sirvió
# map2(rescDFstacked,c("\\*","\u0086","\u0087"),~ grepl(.y,.x))

Finalmente, sólo hay que borrar los caracteres especiales.

# quitar símbolos raros
rescDFstacked %<>% mutate_all(funs(gsub("[†|‡|'|\\*]","",.)))

Así queda la tabla reestructurada:

Organization Contact name phone website Category observation
Happy Tails Rescue Judy 905-357-5096 www.happytailsrescue.ca Small Breed NA
LOYAL Rescue Inc. Anne 888-739-1221 www.loyalrescue.com Small Breed NA
Pomeranian and Small Breed Rescue Shelley 416-225-6808 www.psbrescue.com Small Breed NA
Tiny Paws Rescue Brenda 1-800-774-8315 www.tpdr.ca Small Breed NA
Speaking of Dogs Rescue Lorraine 705-444-7637 www.speakingofdogs.com Senior Dogs NA
Homeward Bound Rescue Kathy 905-987-1104 www.homewardboundrescue.ca Bulldog (English) includes other Flat faced dogs: Bulldogs
unknown Joan 416-738-6059 unknown Bulldog (English) limited foster care available
Labrador Retriever Adoption Service Laura or Karen 289-997-5227 www.lab-rescue.ca Labrador Retriever NA
Dog Rescuers Inc Joan 416-567-6249 www.thedogrescuersinc.ca Labrador Retriever phone may not be up to date

Listo. Si hay alguna duda me pueden escribir.