El data wrangling

Hay tres partes del data wrangling:

  1. Importación

  2. Limmpieza ¿Cómo podemos saber si un dato esta limpio o no?

  3. Transformación para crear visualizaciones y modelos

Para hacer este proceso haremos uso de la herramiento TIBBLE de Tidyverse.

Diferentes tipos de datos para el data wrangling:

El dataframe de los tibbles

Un tibble es una versión especial de un dataframe para tidyverse. Para ver toda la información de un tibble puedes escribir en consola: vignette(“tibble”)

La mayoria de funciones con tidyverse aceptan tibbles, pero no las funciones que estan fuera del paquete tidyverse no aceptan este formato mejorado de los dataframes. Ejemplo de tibble y data.frame:

library(tidyverse)
## -- Attaching packages ----------------------------------------------------------------------------------------------------------- tidyverse 1.2.1 --
## v ggplot2 3.1.0     v purrr   0.2.5
## v tibble  1.4.2     v dplyr   0.7.8
## v tidyr   0.8.2     v stringr 1.3.1
## v readr   1.1.1     v forcats 0.3.0
## -- Conflicts -------------------------------------------------------------------------------------------------------------- tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()
class(iris)
## [1] "data.frame"

Si queremos convertir el data.frame iris a una estructura tibble, lo haremos:

iris_tibble <- as_tibble(iris)
class(iris_tibble)
## [1] "tbl_df"     "tbl"        "data.frame"

Creando tibbles de Tidyverse

¿Cómo los podemos crear? Claro, con tibble():

t <- tibble(
  x = 1:10,
  y = pi, # repetira automaticamente 10 "pi"
  z = pi * x ^ 2
)
t

Una tibble nunca cambia el tipo de dato de una variable a diferencia de un data frame. No cambia los nombres de las variables sin motivo aparente. Tampoco crea nombres a la filas.

El acceso a las tibbles es lo mismo que con los dataframes.

También podemos dar nombres “no sintáctico” a las variables. Por ejemplo que empezara con espacio o un emoticono. Ejemplo:

t2 <- tibble(
  ':)' = "smile", # los ' son los backsticks
  ' ' = "space",
  '1988' = "number"
)
t2

También se pueden crear tribble(), que se utiliza para transponer los datos. Se definen las cabezeras con fórmulas.

tribble(
  ~x,~y,~z,
#----/-/----/  
  "a",4,3.14,
  "b",8,6.28,
  "c",9,-1.25
)

Imprimir una tibble por pantalla

¿Qué diferencias hay entre un tibble y un dataframe clásico?

  • Los tibbles solo muestran los 10 primeros resultados a la hora de imprimir por consola.

Vamos a crear un tibble para ver cómo se puede trabajar con él:

library(lubridate)
## 
## Attaching package: 'lubridate'
## The following object is masked from 'package:base':
## 
##     date
t <- tibble(
  a = lubridate::now() + runif(1e3)*24*60, # generar horas aleatorias
  b = 1:1e3,
  c = lubridate::today() + runif(1e3)+30,
  d = runif(1e3),
  e = sample(letters, 1e3, replace = T)
)
head(t)

Por consola solo saldrían 10 resultados.

Si ponemos options() en la consola, se podrían modificar los parámetros por defecto de las tibbles. Por ejemplo, si quiseramos que como máximo imprimiera por pantalla 12 filas, y como mínimo 8 filas:

options(tibble.print_max = 12, tibble.print_min=8)

Subconjuntos y accesos a tibbles

[…]

El paquete readr para la importación de datos

Funciones del paquete readr:

  • read_csv() -> ficheros separados por “,”

  • read_csv2() -> ficheros separados por “;”

  • read_tsv() -> separados por el tabulador " " o “”

  • read_delim() -> separado por el delimitador que tu creas. Ejemplo: read_delim(delim = ‘n.’)

  • read_fwf() -> fichero ‘fixed with file’, todas y cada una de las filas tiene la misma anchura y el mismo número de carácteres. Se debe especificar la anchura por donde se debe cortar dentro de la función fwf_widths() y fwf_positions()

  • read_table -> busca en espacio en blanco entre valores de cada linea

  • read_log() -> ficheros creados por apache para leer errores de un servidor (install.packages(“webreadr”))

Nos centraremos en read_csv.

Guardar y leer un CSV con el paquete de base de R

Usaremos el siguiente código para guardar los datos:

¿Cómo cargar el archivo?:

cars <- read_csv("cars.csv") # en els warnings nos advierte que la primera columna no tiene nombre de variable y se le ha asignado X1
## Warning: Missing column names filled in: 'X1' [1]
## Parsed with column specification:
## cols(
##   X1 = col_character(),
##   mpg = col_double(),
##   cyl = col_integer(),
##   disp = col_double(),
##   hp = col_integer(),
##   drat = col_double(),
##   wt = col_double(),
##   qsec = col_double(),
##   vs = col_integer(),
##   am = col_integer(),
##   gear = col_integer(),
##   carb = col_integer()
## )

Cuando hagamos una importación con readr se nos mostrará el formato de las variables como información.

También podemos creear tibbles con la función readr:

read_csv("x,y,z
         1,2,3
         4,5,6
         7,8,9")

Metadato: descripción de la tabla, de donde ha sido sacado…, información general. Para evitar que read lea la parte informativa al inicio, podemos usar el parametro skip() el número de lineas que quieres evitar:

read_csv("texto con informacion del 
         archivo interesante
         x,y,z
         1,2,3
         4,5,6", skip = 2)

si tenemos columnas con #, es decir, con comentarios, tenemos que indicarlo como parametro, para que cada vez que encuentre un # se lo salte.

read_csv("#Esto es un comentario
         x,y,z
         1,2,3
         4,5,6", comment = "#")

Si queremos creear un tibble sin nombre de las columnas (las generara R automáticamente)

read_csv("1,2,3\n4,5,6\n7,8,9", col_names = FALSE)

Si queremos añadir las columnas como un vector:

read_csv("1,2,3\n4,5,6\n7,8,9", col_names = c("primera","segunda","tercera"))

¿Cómo tratar los NA o valores desconocidos al cargar el archivo?:

read_csv("x,y,z\n1,2,.\n4,#,6", na = c(".","#"))

Los parsers de readr

Las funciones que empiezan por parse_[…]. Estas funciones no cogen un vector cualquiera, te devuelve el vector procesado de vectores lógicos. Te permite tranformar cualquier tipo de text en el tipo de dato que corresponde.

str(parse_logical(c("TRUE", "FALSE", "FALSE","NA")))
##  logi [1:4] TRUE FALSE FALSE NA

Por números:

str(parse_integer(c("1", "2", "3","4")))
##  int [1:4] 1 2 3 4

Por fechas:

str(parse_date(c("1988-05-19", "2018-05-12")))
##  Date[1:2], format: "1988-05-19" "2018-05-12"

Es decir, parse_[cualquier tipo de dato] nos servirá para procesar toda la información necesaria.

También podemos especificar si hay algun carácter que debe ser NA.

str(parse_integer(c("1", "2", "#","5"), na = "#"))
##  int [1:4] 1 2 NA 5

¿Qué pasa cuando tenemos números enteros con otros valores que no comparten su naturaleza?

data <- parse_integer(c("1", "2","hola","5","3.141592"))
## Warning in rbind(names(probs), probs_f): number of columns of result is not
## a multiple of vector length (arg 1)
## Warning: 2 parsing failures.
## row # A tibble: 2 x 4 col     row   col expected               actual  expected   <int> <int> <chr>                  <chr>   actual 1     3    NA an integer             hola    row 2     5    NA no trailing characters .141592
problems(data)

En este caso usamos la función problems para localizar los problemas, como podemos ver en la posición 3 y 5 del vector.

¿Qué tipos tenemos de procesadores de tipos de datos?

  • parse_logical()

  • parse_integer()

Procesado de números

  • parse_double() -> procesa valores SOLO numéricos

  • parse_number() -> es un poco más flexible que el parse_double()

Tenemos que tener en cuenta que en diferentes puntos del mundo se escriben de forma distinta. Por ejemplo pueden tener un carácter monetario a su lado. Reunimos todos los casos que pueden variar:

  • decimales -> . ,
parse_double("12.345") # funcionará de forma correcta
## [1] 12.345
parse_double("12,345", locale = locale(decimal_mark = ",")) # no funcionará si no se especifíca el separador que da problemas
## [1] 12.345
  • monetarios 100???, 56$

  • porcentajes 12%

# funciona perfectamente:
parse_number("100???") 
## [1] 100
parse_number("???100")
## [1] 100
parse_number("12%")
## [1] 12
parse_number("Cuesta unos 45???")
## [1] 45
  • agrupaciones 1,000,000
parse_number("$1,000,000") # funciona perfectamente en este caso en concreto
## [1] 1e+06
parse_number("1.000.000", locale = locale(grouping_mark = "."))
## [1] 1e+06
parse_number("123'456'789", locale = locale(grouping_mark = "'"))
## [1] 123456789

Procesado de strings

  • parse_character() -> es importante dependiendo de la codificacions del archivo

¿Cómo se representa la información intermitentemente en los ordenadores?

charToRaw("JoanClaverolRomero") #usa el formato ASCII para que sean leidos para el ordenador
##  [1] 4a 6f 61 6e 43 6c 61 76 65 72 6f 6c 52 6f 6d 65 72 6f

Las codificaciones más clásicas para la correcta interpretación de los carácteres:

  1. Latin1 (ISO-8859-1) para idiomas de Europa del Oeste

  2. Latin2 (ISO-8859-2) para idiomas de Europa del Oeste

Hoy día ya existe un formato que puede codificar cualquier idioma del mundo:

  1. UTF-8

Este sistema lo usa readr, entoces podría dar problemas si hicieramos frente a dataset codificados con un sistema antiguo. Ejemplo de errores que podemos tener:

x1 <- "El Ni\xf1o ha estado enfermo"
parse_character(x1,locale = locale(encoding = "Latin1"))
## [1] "El Niño ha estado enfermo"

Otro caso sería:

x2 <- "\x82\xb1\x82\xf1\x82\xb2\x82\xcd"
parse_character(x2, locale = locale(encoding = "Shift-JIS"))
## [1] "<U+3053><U+3093><U+3054><U+306F>"

En muchos casos no sabremos como está codificado. En este caso podemos usar la función guess_encoding():

guess_encoding(charToRaw(x1))

O en el otro caso:

guess_encoding(charToRaw(x2))

Contra más carácteres tenga, podrá hacer una predicción más buena sobre la codificacion que usa.

Procesado de factores

  • parse_factor() -> estructura de datos para representar las variables categóricas con valores fijos(conocidos)
months <- c("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")
parse_factor(c("May","Jun","Jul","Aug","Sep","Oct","Nov"), levels = months)
## [1] May Jun Jul Aug Sep Oct Nov
## Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec

Procesado de fechas y horas

Número de días desde que han pasado desde la epoca EPOCH, 1 de enero de 1970, origen de las fechas de la informática.

  • parse_datetime() ISO-8601 va del mayor valor al menor year/month/day/hour/minute/second

  • parse_ date() -> el formato que necesita:

# Los dos funcionan de forma correcta
parse_date("2015-12-07")
## [1] "2015-12-07"
parse_date("2017/05/18")
## [1] "2017-05-18"
  • parse_time()
# Funcionan correctamente
parse_time("03:00 pm")
## 15:00:00
parse_time("20:00:34")
## 20:00:34

Interpretación del tiempo segun R:

Años:

%Y -> año con 4 dígitos
%y -> año con 2 dígitos (00-69) -> 2000-2069  //  (70-99) -> 1970-1999

Meses:

%m -> mes en formato de dos digitos de 01-12
%b -> abreviación del mes "Ene","Feb"
%B -> nombre completo del mes "Enero","Febrero",...

Día:

%d -> número del día con dos digitos 01-31
%e -> de formato opcional, los dígitos 1-9 pueden llevar espacio en blanco

Horas:

%H -> hora entre 0-23
%I -> hora entre 0-12 siempre va con %p
%p -> am/pm
%M -> minutos 0-59
%s -> segundos enteros 0-59
%OS -> segundos reales
%Z -> zona horaria America/Chicago, Canadá, France, Spain
%z -> zona horaria con respecto a la UTC (zona horaria internacional)

Carácteres especiales de no dígitos

%. -> eliminar un carácter no dígito
%* -> eliminar cualquier número de carácteres que no sean dígitos

Ejemplos:

parse_date("05/08/15", format = "%d/%m/%y")
## [1] "2015-08-05"
parse_date("08/05/15", format = "%m/%d/%y")
## [1] "2015-08-05"
parse_date("01-05-2018", format = "%d-%m-%Y")
## [1] "2018-05-01"
parse_date("01 Jan 2018", format = "%d %b %Y")
## [1] "2018-01-01"
parse_date("03 March 17", format = "%d %B %y")
## [1] "2017-03-03"
parse_date("5 janvier 2012", format = "%d %B %Y", locale = locale("fr"))
## [1] "2012-01-05"
parse_date("3 Septiembre 2014", format = "%d %B %Y", locale = locale("es"))
## [1] "2014-09-03"

Cargar un archivo entero con readr haciendo uso de parse

Readr usa la heurística (probabilidad) para ver que tipo de dato tiene cada columna. El orden que sigui es el siguiente:

  • lógico -> integer -> double -> number -> time -> date -> datetime -> strings

Veamos un ejemplo donde podamos definir el tipo de variable para cada columna:

challenge <- read_csv(
  readr_example("challenge.csv"),
  col_types = cols(
    x = col_double(), # parse double
    y = col_date() # parse_date()
  )
)

Podemos también hacer uso de la función stop_for_problems(), que parara la ejecución de la carga de un archivo si encuentra algún problema.

Elegir más muestras y la función de type_convert

Vamos a ver que otros problemas podemos evitar en la carga del archivo:

challenge3 <- read_csv(readr_example("challenge.csv"),
                       col_types = cols(.default = col_character()))

Usamos la función type_convert():

head(type.convert(challenge3)) # la y debería ser una "date"

Otros ficheros especiales

Formas de guardar los data frames tratados:

  • write_csv()

  • write_tsv()

Siempre se deben guardar los strings en UTF8 para eviar problemas de codificación

Guardar siempre los date y/o datetimes en ISO08601

Para importarlo especialmente a excel csv, como:

  • write_excel_csv()

Ejemplo como guardarlo:

write_csv(challenge, path = "challenge.csv")

Cuando se realiza un guardado en csv, se pierden las características de cada variable. Para hacer que lo cargue de forma correcta:

challenge <- read_csv("challenge.csv", guess_max = 1001)
## Parsed with column specification:
## cols(
##   x = col_double(),
##   y = col_date(format = "")
## )
head(challenge)

Podemos usar los ficheros rds para conservar todos los datos generados con R:

write_rds(challenge, path = "challenge.rds")

Ahora podemos cargarlo y se mantienen la información tratada:

head(read_rds("challenge.rds")) # mirar el formato de y que es date

Podemos hacer uso también de las siguientes librerías:

  • paquete haven, que sirve para trabajar con SPSS, Stata y SAS

  • readxl -> .xml, .xmls

  • DBI -> RMySQL, RSQLite, RPostreSQL

  • jsonlite

  • xml2