Tema 02 - Tratamiento de datos (una tabla)

Pedro Albarrán

Dpto. de Fundamentos del Análisis Económico. Universidad de Alicante

Limpieza y “doma” de datos

  • Un análisis de datos adecuado requiere (mucho) tiempo de trabajo “sucio”

  • tidyverse incluye una colección de bibliotecas con herramientes eficientes para el proceso de “tratamiento de datos” (“data wrangling”)

  • El objetivo es tener un conjunto de datos ordenado y limpio para poder realizar análisis de manera eficiente

  • Esto puede requerir seleccionar columnas, filtrar filas, crear nuevas variables, ordenar, agrupar, resumir, etc.

¿Qué son datos ordenados (‘tidy data’)?

1.- Cada columna es una variable: mide el mismo atributo entre unidades

2.- Cada fila es una observación (caso): misma unidad a través de atributos

3.- Cada celda es un valor

  • Tenemos información similar y no redundante en una misma tabla
  • Es una forma natural (variable = vector columna) para trabajar con datos
  • tidyverse es eficiente con datos ordenados: ej., gráfico temporal
ggplot(table1, aes(x = year, y = cases)) +  
  geom_line(aes(colour = country))

Datos no ordenados

  • Otras estructuras como esta pueden tener sentido para mostrar información (o por convenciones)
  • La visualización es atractiva, PERO sobran filas para analizar los datos: ej., total de personas con hijos y sin pareja entre 30 y 39 años

Funciones de transformación de datos

  • La mayoría de operaciones pueden realizarse combinando 5 “verbos”

    • NOTA: existe una colección de “chuletas” de R, p.e., para transformación.
  • Todos tienen como primer argumento un data frame, los siguientes describen qué hacer (con columnas o filas) y devuelven otro data frame

1.- select(): selecciona variables por nombres o posiciones de columnas, separados por comas

select(presidential, name, party)
select(presidential, 1:2, 4)

Filtrar filas

2.- filter(): conserva filas en las que la condición lógica es verdadera

filter(presidential, party == "Republican")
filter(presidential, start > 1973 & party == "Democratic")
  • Se pueden combinar (anidar) porque ambas toman y devuelve un data frame, pero así son difíciles de leer
select(filter(presidential, start > 1973), name)

El operador de tubería %>%

  • datos %>% filter(condition) equivale a filter(datos, condition)

  • El anidamiento es fácil:
    - Tomar presidential y pasarlo a filtrar (produce un nuevo data frame);
    - Tomar este resultado y pasarlo a seleccionar.

presidential %>% 
  filter(start > 1973) %>% 
  select(name)
  • Atajo de teclado: Cmd / Ctrl + Mays + M

  • Se puede aplicar a cualquier función: 10 %>% log() es log(10)

  • También existe una tubería en R base: |>

Crear nuevas variables

3.-mutate(): añade nuevas columnas, creando variables según una fórmula a partir de otras

  • también rename(): cambiar el nombre de una columna

# evitar "machacar" la fuente original
mypresidents <- presidential %>%          
                  mutate(duracion = end - start) 

# crear varias, separadas por coma
presidential %>% mutate(sigloXXI = start > 2000,   
                        duracion = end - start,    
                        duracio2 = duracion*2   )  

presidential %>% rename(nombre = name) 

Ordenar filas

4.- arrange(): re-ordena las filas todas las columnas de un data frame

  • en orden ascendente (por defecto) o descendente con desc()
mypresidents %>% arrange(desc(duracion))

# ordenar por más de una columna: primero por duración, 
# en caso de empate por partido
mypresidents %>% arrange(desc(duracion), party) 

Resumir todo el conjunto de datos

5.- summarize(): colapsa valores de un data frame en una sola fila resumen

  • Especificando cómo se reducirá una columna entera de datos en un solo valor.
library(lubridate)
mypresidents %>%
  summarize(media_duracion = mean(duracion),
            N = n(),                        # n() cuenta núm. total de filas
            first_year = min(year(start)),  # year() extrae el año de una fecha
            num_dems = sum(party == "Democratic") )
  • summarize() suele usarse en conjunción con group_by()

group_by()

  • group_by(): cambia el alcance de cada función para que no actúe sobre todo el data frame sino en grupos individuales

  • ¿Cuál es la duración media de los demócratas y de los republicanos? Hacerlo por separado no es eficiente: especificamos que las filas deben ser agrupadas

mypresidents %>% group_by(party) %>%          # "marca" dos grupos en los datos 
  summarize(N = n(),                          # incluye estas variables 
            media_duracion = mean(duracion))  # mas las de group_by()
  • Nuevo data frame con distinto nivel de observación (fila): una fila para cada valor de la variable por la que se agrupa (ej., de presidentes a partidos)
  • ungroup() elimina la agrupación: volvemos a operar en datos desagrupados
mypresidents %>% group_by(party) %>% 
  mutate(media_duracion = mean(duracion)) %>% 
  ungroup() %>% arrange(duracion) %>%  slice(1)

Seleccionar muchas variables

library(nycflights13)           # incluye flights:  19 variables
select(flights, year:arr_time)    # desde variable "year" hasta "arr_time"
select(flights, -(year:day))      # todas menos "year, month, day"
  • Funciones a utilizar dentro de select():
    • starts_with("abc"): nombres que comienzan con “abc”.
    • ends_with("xyz"): nombres que acaban con “xyz”.
    • contains("ijk"): nombres que contienen “ijk”.
    • num_range("x", 1:3): para x1, x2 y x3.
    • matches(): nombres que coinciden con una expresión regular

Algunos verbos adicionales

  • slice(), slice_sample(): extrae filas por posición o aleatoriamente
mypresidents %>% slice(1, 4)
  • drop_na() y replace_na(): elimina filas con valores ausentes o los reemplaza

  • distinct(): extrae sólo las filas únicas (una o varias variables)

mypresidents %>% distinct(party)
  • count(): cuenta los valores únicos de una o más variables
mypresidents %>% count(party)    # mypresidents %>% group_by(party) %>% summarize(n=n())
mypresidents %>% count(party, sort = TRUE)
  • across(): aplica la misma transformación a múltiples columnas
flights %>% mutate(across(air_time:distance, ~ log(.x)+1))
flights %>% mutate(across(is.character, ~ parse_factor(.x)))

Funciones para crear variables

  • Operadores aritméticos (+, -, *, /, ^, %/%, %%) y lógicos (<, <=, >, >=, !=)

  • Funciones como log(), lag(), lead(), cumsum(), row_number() etc.

  • if_else(): ejecución condicional (también case_when())
flights %>% mutate(retraso = if_else(dep_delay > 0, "tarde", "bien")) 
flights %>% mutate(retraso = if_else(dep_delay > 0, "tarde",    # encadenados
                                if_else(dep_delay <0, "bien", "normal")))
  • Discretizar variables: cut_interval(), cut_number(), cut_width()
  • Nota: dplyr tiene muchas funciones equivalentes a otras de R base:

    • parse_number(), parse_factor(), etc. por as.number(), as.factor(), etc.
    • bind_cols() y bind_rows() por cbind() y rbind()

Funciones de resumen útiles

  • Medidas de centralidad y de dispersión: mean(x), median(x), sd(x), IQR(x)

  • Medidas de rango: min(x), quantile(x, 0.25), max(x)

  • Medidas de posición: first(x), nth(x, 2), last(x).

  • Conteos:

    • n(): observaciones totales (tamaño del grupo)

    • sum(!is.na(x)): observaciones no ausentes

    • n_distinct(x): filas distintas en x

Cuatro representaciones de los mismos datos

library(tidyverse)
table1     # datos ordenados
table2     # varios valores por celda
table3     # más de una variable en una columna
table4a 
table4b
  • table4a y table4b ofrecen información útil para presentación

Mismos datos, dos formatos: ancho o largo

  • La utilidad de almacenar los datos en un rectángulo ancho (“wide”) o en uno largo (“long”“) depende de qué queramos hacer

  • El cambio de forma entre formatos es una tarea habitual del analista de datos.

  • Cambiar entre representación larga y ancha se conoce como pivotar (o girar)

table4a        # formato ancho
table1         # formato largo

pivot_longer(): de ancho a largo

  • Pivotar las variables en dos nuevas columnas (deben crearse)
pivot_longer(table4a, 
             cols=2:3, 
             names_to = "year", 
             values_to = "cases") 
  1. data frame a cambiar de forma

  2. nombres o índices de las columnas que representan valores, no variables

  3. los nombres de esas antiguas variables van como valores a nueva variable

  4. los valores de las antiguas celdas van a otra nueva variable

pivot_longer(): de ancho a largo (cont.)

  • Recordad que existen formas equivalentes de hacer lo mismo
table4a %>% pivot_longer(`1999`:`2000`), 
                         values_to = "cases", names_to = "year")
  • Notar que los nombres de columna son caracteres y cuando son números van entre ` (evita confusión con índice de posición)
  • Deberíamos cambiar el tipo de las nuevas variables
table4a %>% 
  pivot_longer(2:3, names_to = "year", values_to = "cases") %>%
  mutate(year= parse_number(year))

pivot_wider(): de largo a ancho

table2 %>%
    pivot_wider(names_from = type,  
                values_from = count)
  1. el data frame a cambiar de forma
  2. nombre de la variable de cuyos valores vienen los nuevos nombres de columnas
  3. nombre de la variable de la que tomar los valores para las nuevas columnas
table1 %>% select(-population) %>%            # Tabla de presentación 
    pivot_wider(names_from = year, values_from = cases)   

Dos funciones útiles

  • separate(): dividir una columna en múltiples variables indicando un separador o vector de posiciones en las que dividir
table3 %>% separate(rate, into = c("cases", "population"), sep = "/")
table3 %>% separate(year, into = c("century", "year"), sep = 2)
  • Con convert = TRUE intenta convertir el tipo (no mantener carácter)
table3 %>% separate(rate, into = c("cases", "population"), 
                    convert = TRUE)
  • unite(): combinar múltiples columnas en una
table5 %>% 
  unite(new, century, year, sep = "-")