Manipulacion de data frames con tidyr

 

 

Introducción


En el post anterior hemos hablado sobre como manipular un data frame mediante al paquete dplyr. Ahora haremos lo propio con el paquete tidyr. No obstante, antes de ponernos a ver las funcionalidades que nos provee este, debemos conocer algunos conceptos sobre la forma en que debe estar organizada la información en un data frame de modo que pueda ser utilizada por diversos paquetes que iremos viendo en próximos post.

Los temas presentados en este post se basan en el trabajo Tidy Data, publicado en the Journal of Statistical Software: http://www.jstatsoft.org/v59/i10/paper.

Entonce, lo primero, instalamos el paquete y lo cargamos.

install.packages("tidyr")
library(tidyr)

 

Qué significa que un data frame esté ordenado


La misma información puede ser representada de muchas maneras, es decir, diversos data frames pueden contener la misma información, cada uno ordenándola de una forma diferente. Para ejemplificar esto, he tomado algo de información demográfica de Argentina desde Wikipedia. Como se observa, tenemos tres data frames que contienen la misma información. Cada uno muestra los mismos valores para cada una de las variables que se representan (país, censo, población urbana, población total), pero cada uno los organiza de manera diferente.

df1
     Pais Censo Población.urbana  Población
Argentina  1980       23.198.068 27.949.480
Argentina  1991       28.832.127 32.615.528
Argentina  2001       32.380.296 36.260.130
Argentina  2010       36.907.728 40.117.096

df2
     Pais Censo   Tipo  Población
Argentina  1980 urbana 23.198.068
Argentina  1980  total 27.949.480
Argentina  1991 urbana 28.832.127
Argentina  1991  total 32.615.528
Argentina  2001 urbana 32.380.296
Argentina  2001  total 36.260.130
Argentina  2010 urbana 36.907.728
Argentina  2010  total 40.117.096

df3
     Pais Censo Tasa.de.población.urbana
Argentina  1980  23.198.068 / 27.949.480
Argentina  1991  28.832.126 / 32.615.528
Argentina  2001  32.380.296 / 36.260.130
Argentina  2010  36.907.728 / 40.117.096

No obstante, no todos los casos presentan la misma facilidad para utilizar dicha información. En particular, el primer data frame se dice que está «ordenado», lo que hace que presente mayor facilidad para trabajar con él mediante diferentes herramientas que iremos viendo.

Hay 3 reglas que se deben cumplir para considerar que un data frame está ordenado:

  1. Cada variable debe tener su propia columna
  2. Cada observación debe tener su propia fila
  3. Cada valor deber tener su propia celda

Estas tres reglas están interrelacionadas debido a que es imposible satisfacer solo dos de las tres. En la siguiente imagen se observan visualmente estas reglas.

tidy data diagram

Las funcionalidades que iremos viendo del paquete tidyr tienen como finalidad hacer transformaciones de un data frame de modo que podemos llevarlo de una a otra estructura. En particular, lo que buscaremos hacer es, cuando nos enfrentemos con información representada como en el caso de df2 o df3, poder transformarla a la forma de df1. Es decir, cada fila es una observación y cada columna una variable.

Ahora bien, ¿porqué estos paquetes que veremos más adelante necesitan que la información esté organizada con este criterio?

Por un lado está el hecho de que tener un único criterio para considerar a la información ordenada «correctamente» hace que el diseño de paquetes y nuevas funcionalidades se simplifique. Esto es debido a que las funciones siempre esperarán a la información de entrada con una estructura conocida, y a su vez entregaran los resultados con la misma estructura. Por otro lado, el hecho de tener que cada columna represente a una variable permite la utilización de funciones vectorizadas como hemos ido viendo a los largo del curso.

 

El paquete tidyr


El paquete tidyr brinda muchas funcionalidades para realizar transformaciones en un data frame con el fin de poder transformarlo a la estructura que deseemos. El primer paso para esto es averiguar en el dataset cuales son las variables y cuales son las observaciones. Una vez que las hemos identificado, nos enfrentamos a tres tipos de problemas:

  • Una variable está dividida en múltiples columnas.
  • Una observación está dispersa en múltiples filas.
  • Múltiples variables están metidas en una única celda.

Usualmente un dataset solo tendrá unos de estos problemas. Para remediar esto deberemos utilizar las funciones que veremos a continuacion.

 

gather

La función gather toma múltiples columnas y las une en pares clave-valor. Esto permite resolver las situaciones en que tenemos columnas que realmente no representan variables, sino valores de una variable.

Supongamos que tenemos el siguiente data frame:

df
   Provincia Censo_1991 Censo_2001 Censo_2010
Buenos Aires   10934727   11460575   13596320
     Cordoba    1208554    1368301    1466823
     Rosario    1118905    1161188    1236089

Las columnas Censo_1991, Censo_2001Censo_2010 representan a cada censo (o el año en que se realizó cada censo), es decir, el valor de una variable nominal que podría llamarse censo, y cada fila representa tres observaciones, y no una sola.

Para ordenar este dataset necesitamos emplear la función gather.

gather(data = df, key = "censo", value = "poblacion", 2:4)
   Provincia      censo poblacion
Buenos Aires Censo_1991  10934727
     Cordoba Censo_1991   1208554
     Rosario Censo_1991   1118905
Buenos Aires Censo_2001  11460575
     Cordoba Censo_2001   1368301
     Rosario Censo_2001   1161188
Buenos Aires Censo_2010  13596320
     Cordoba Censo_2010   1466823
     Rosario Censo_2010   1236089

Lo que hemos hecho es decir a la función que tome el data frame df, y una las columnas 2:4 (las que tienen los valores obtenidos en cada censo), metiendo el resultado en el par clave (censo) – valor (población).

 

gather diagram

 

 

spread

La función spread es usada cuando tenemos una observación dispersa en múltiples filas. En el siguiente ejemplo tenemos un dataset donde una observación es el resultado de un censo, pero cada observación (Censo) está esparcido en dos filas (una fila para la población total, y otro para la población urbana).

df
     Pais Censo   Tipo  Población
Argentina  1980 urbana 23.198.068
Argentina  1980  total 27.949.480
Argentina  1991 urbana 28.832.127
Argentina  1991  total 32.615.528
Argentina  2001 urbana 32.380.296
Argentina  2001  total 36.260.130
Argentina  2010 urbana 36.907.728
Argentina  2010  total 40.117.096

Para llevar este dataset a la forma de una observación por fila debemos usar la función spread.

spread(data = df, key = Tipo, value = Población)
     Pais Censo      total     urbana
Argentina  1980 27.949.480 23.198.068
Argentina  1991 32.615.528 28.832.127
Argentina  2001 36.260.130 32.380.296
Argentina  2010 40.117.096 36.907.728

La columna que contiene los nombre de las variables corresponde al argumento key, mientras que la columna que contiene el valor de la variable es value.

spread diagram

Como se observa, la función spread hace lo opuesto a gather. Estas son funciones complementarias, es decir, si al resultado de aplicar la función spread le aplicamos la función gather llegamos al dataset original. Otra observación interesante es que gather alarga los data frames, mientras que spread los hace más ancho.

 

separate

Ahora supongamos que tenemos el siguiente dataset.

df
     Pais Censo Tasa.de.población.urbana
Argentina  1980  23198068 / 27949480
Argentina  1991  28832126 / 32615528
Argentina  2001  32380296 / 36260130
Argentina  2010  36907728 / 40117096

Lo que observamos es que tenemos una columna (Tasa.de.población.urbana) que contiene más de un valor. Para solucionar esto tenemos que emplear la función separate. Esta función lo que hace es dividir una columnas en múltiples columnas, tomando como separador algún símbolo.

separate(data =  df, 
         col  =  Tasa.de.población.urbana,  
         into =  c("urbana", " total"), 
         sep  =  "/")

     Pais Censo    urbana     total
Argentina  1980 23198068   27949480
Argentina  1991 28832126   32615528
Argentina  2001 32380296   36260130
Argentina  2010 36907728   40117096

Por defecto, la función separete usa como separador cualquier valor no alfa-numerico. En este ejemplo he indicado explícitamente el separador (sep) solo para claridad.

Por otro lado, el tipo de dato que se asigna a las nuevas columnas es el mismo que el original.

str(df)
'data.frame':	4 obs. of  3 variables:
 $ Pais                    : chr  "Argentina" "Argentina" "Argentina" "Argentina"
 $ Censo                   : chr  "1980" "1991" "2001" "2010"
 $ Tasa.de.población.urbana: chr  "23198068 / 27949480" "28832126 / 32615528" "32380296 / 36260130" "36907728 / 40117096"

str(separate(data =  df, 
             col  =  Tasa.de.población.urbana,  
             into =  c("urbana", " total"), 
             sep  =  "/"))
'data.frame':	4 obs. of  4 variables:
 $ Pais  : chr  "Argentina" "Argentina" "Argentina" "Argentina"
 $ Censo : chr  "1980" "1991" "2001" "2010"
 $ urbana: chr  "23198068 " "28832126 " "32380296 " "36907728 "
 $ total:  chr  " 27949480" " 32615528" " 36260130" " 40117096"

Como es de esperar, las columnas resultantes de la división son de tipo character, pero dado su significado lo que deseamos es que sean tratadas como numeric o integer. Para indicar esto podemos hacer la conversion del tipo de dato manualmente, o se puede agregar la opcion convert = TRUE a la función separate.

str(separate(data =  df, 
             col  =  Tasa.de.población.urbana,  
             into =  c("urbana", " total"), 
             sep  =  "/",
             convert = TRUE))
'data.frame':	4 obs. of  4 variables:
 $ Pais  : chr  "Argentina" "Argentina" "Argentina" "Argentina"
 $ Censo : chr  "1980" "1991" "2001" "2010"
 $ urbana: num  23198068 28832126 32380296 36907728
 $  total: int  27949480 32615528 36260130 40117096

 

unite

Así como teníamos a las funciones complementarias gatherspread, tenemos a la función unite que complementa a separate. Esta función lo que hace es tomar múltiples columnas (...) y las colapsa en una única columna (col), separando los elementos mediante sep.

df

ID dia mes anio
 1  25   9 2015
 2  30   7 2015
 3   7   3 2015
 4  15   8 2015

unite(data = df, 
      col = Fecha, 
      sep = "-",
      dia,mes,anio)

ID     Fecha
 1 25-9-2015
 2 30-7-2015
 3  7-3-2015
 4 15-8-2015

 

 

Comentarios

12 Comments

  1. Oreste Bruno

    Muchas gracias, muy didactica la clase.

    Reply
  2. Daniel

    Buenisima tu explicacion! muy clara y simple! los 4 ejemplos son buenisimo! muchas gracias me ayudaste mucho a comprender esto!

    Reply
  3. Mario

    Hola, espero te encuentres bien

    tengo este código: coalcl <- gather(coalc,year,coal_carbon,-región)
    me funciona bien, sin embargo, la variable "year" no me deja convertirla porque no encuentra el objeto. se puede convertir a entero?

    Reply
  4. roger

    como realizar la separacion de : rojo1986 donde la letra sea una columna y la numeracion otro. gracias

    Reply
  5. Catherine

    Hola, muchas gracias por dedicar tiempo a compartir tu conocimiento, muy útil, incluso para mi que hasta ahora estoy empezando con R 😉

    Reply
  6. Jorge Leonardo López Martínez

    Hola Mauricio, muchas gracias por compartir lo que sabes. Es muy útil y practico lo que enseñas. Muchas gracias!!!!

    Reply
  7. roger

    La transformación con spread no la ejecuta xq me dice que tengo filas duplicadas.

    Reply
    • Mauricio

      Hola Roger, ¿me puedes dejar un ejemplo del dataset con el que estas trabajando y te produce el error? Así lo miro y te comento que es lo que está sucediendo. Si se te complica compartirlo por acá envíamelo por mail. contacto@mauricioanderson.com

      Reply
  8. roger

    Hola mauricio he querido realizar un spread con cuatro diferentes valores en la columna pero me sale un erro de datos duplicadas , alguna solución o ayuda

    Reply
  9. Ivan Acosta

    Saludos.

    Excelente post, me ha servido mucho para entender como puedo ordenar los datos de acuerdo a la logica del negocio. !! Gracias por compartir conocimiento.

    Solo una sugerencia. Podrias incluir o crear un nuevo post donde puedas explicar la combinacion de varios de estas funciones en un solo comando?.

    Gracias. Saludos.

    Reply
    • Mauricio

      Hola Ivan, Gracias!! Me alegro que te haya resultado de utilidad.

      Sobre tu sugerencia, la tendré en cuenta para hacer un post especialmente de ese tema.

      Saludos

      Reply

Submit a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

This site uses Akismet to reduce spam. Learn how your comment data is processed.