Tratamiento, modelizacion y visualizacion con R parte 2

Tomado del archivo de Nestor Montaño - Agosto 2019, R User Group Ecuador

Tidy: Ordenar los datos

#### LIBRERIAS ------
library(openxlsx)  # para importar desde excel
library(dplyr)  # manipulacion de datos
library(ggplot2)  # graficos
library(lubridate)  # Manipulacion de fechas
library(stringr)  # Manipulacion de texto
library(tidyr)  # Ordenar y juntar data
library(readr)  # Importar data, modificar tipo variables
library(purrr)  # Mapear funciones
# Los 4 incluidos en el tidyverse
library(magrittr)  # %>% 

En la mayoría de los casos, antes de realizar nuestros análisis debemos reordenar los datos en algún software como excel para cumplir lo siguiente

  • Que cada columna sea una variable
  • Que cada fila sea una observación (granularidad)
  • Que cada celda sea el valor de la variable para la observación

En el conjunto table1 se puede ver que la granularidad de la información es País, Año, mientras que las variables son: Casos y Población.

table1 <- data.frame(country = c("Afganistan", "Afganistan", "Brazil", "Brazil", 
    "China", "China"), year = c(1999, 2000, 1999, 2000, 1999, 2000), cases = c(745, 
    2666, 37737, 80488, 212258, 213766), population = c(19987071, 20595360, 172006362, 
    174504898, 1272915272, 1280428583))

# hacemos un data frame mejorado con tibbles

table1 <- tbl_df(table1)
table1
# A tibble: 6 x 4
  country     year  cases population
  <chr>      <dbl>  <dbl>      <dbl>
1 Afganistan  1999    745   19987071
2 Afganistan  2000   2666   20595360
3 Brazil      1999  37737  172006362
4 Brazil      2000  80488  174504898
5 China       1999 212258 1272915272
6 China       2000 213766 1280428583

Datos ordenados nos permiten trabajar fácilmente con ellos.

# Calcular un ratio por cada 10 mil habitantes
table1 %>% mutate(rate = cases/population * 10000)
# A tibble: 6 x 5
  country     year  cases population  rate
  <chr>      <dbl>  <dbl>      <dbl> <dbl>
1 Afganistan  1999    745   19987071 0.373
2 Afganistan  2000   2666   20595360 1.29 
3 Brazil      1999  37737  172006362 2.19 
4 Brazil      2000  80488  174504898 4.61 
5 China       1999 212258 1272915272 1.67 
6 China       2000 213766 1280428583 1.67 
# Calcular casos por año
table1 %>% count(year, wt = cases)
# A tibble: 2 x 2
   year      n
  <dbl>  <dbl>
1  1999 250740
2  2000 296920

spread()

Se lo usa cuando una observación está en diferentes filas

table2 <- data.frame(country = c("Afganistan", "Afganistan", "Afganistan", "Afganistan", 
    "Brazil", "Brazil", "Brazil", "Brazil", "China", "China", "China", "China"), 
    year = c(1999, 1999, 2000, 2000, 1999, 1999, 2000, 2000, 1999, 1999, 2000, 2000), 
    type = c("cases", "population", "cases", "population", "cases", "population", 
        "cases", "population", "cases", "population", "cases", "population"), count = c(745, 
        19987071, 2666, 20595360, 37737, 172006362, 80488, 174504898, 212258, 1272915272, 
        213766, 1280428583))

table2 <- tbl_df(table2)
table2
# A tibble: 12 x 4
   country     year type            count
   <chr>      <dbl> <chr>           <dbl>
 1 Afganistan  1999 cases             745
 2 Afganistan  1999 population   19987071
 3 Afganistan  2000 cases            2666
 4 Afganistan  2000 population   20595360
 5 Brazil      1999 cases           37737
 6 Brazil      1999 population  172006362
 7 Brazil      2000 cases           80488
 8 Brazil      2000 population  174504898
 9 China       1999 cases          212258
10 China       1999 population 1272915272
11 China       2000 cases          213766
12 China       2000 population 1280428583
str(table2)
tibble [12 x 4] (S3: tbl_df/tbl/data.frame)
 $ country: chr [1:12] "Afganistan" "Afganistan" "Afganistan" "Afganistan" ...
 $ year   : num [1:12] 1999 1999 2000 2000 1999 ...
 $ type   : chr [1:12] "cases" "population" "cases" "population" ...
 $ count  : num [1:12] 745 19987071 2666 20595360 37737 ...

Comparando table2 con table1

¿Qué columna tiene los nombres de las columnas? type

¿Qué columna contiene los valores observados? count

Para convertir table2 en datos ordenados se usa spread()

# Aplicar spread a table2
spread(table2, key = type, value = count)
# A tibble: 6 x 4
  country     year  cases population
  <chr>      <dbl>  <dbl>      <dbl>
1 Afganistan  1999    745   19987071
2 Afganistan  2000   2666   20595360
3 Brazil      1999  37737  172006362
4 Brazil      2000  80488  174504898
5 China       1999 212258 1272915272
6 China       2000 213766 1280428583
table3 <- data.frame(country = c("Afganistan", "Afganistan", "Brazil", "Brazil", 
    "China", "China"), year = c(1999, 2000, 1999, 2000, 1999, 2000), rate = c("745/19987071", 
    "2666/20595360", "37737/172006362", "80488/174504898", "212258/1272915272", "213766/1280428583"))

table3
     country year              rate
1 Afganistan 1999      745/19987071
2 Afganistan 2000     2666/20595360
3     Brazil 1999   37737/172006362
4     Brazil 2000   80488/174504898
5      China 1999 212258/1272915272
6      China 2000 213766/1280428583

¿Qué columna parece estar concatenada? rate

separate()

Permite disociar alguna columna que esté concatenada, unicamente los separa; es decir, los deja como character

table3 %>% separate(rate, into = c("cases", "population"), sep = "/")
     country year  cases population
1 Afganistan 1999    745   19987071
2 Afganistan 2000   2666   20595360
3     Brazil 1999  37737  172006362
4     Brazil 2000  80488  174504898
5      China 1999 212258 1272915272
6      China 2000 213766 1280428583

Con convert= TRUE se transforma a número

table3 %>% separate(rate, into = c("cases", "population"), sep = "/", convert = TRUE)
     country year  cases population
1 Afganistan 1999    745   19987071
2 Afganistan 2000   2666   20595360
3     Brazil 1999  37737  172006362
4     Brazil 2000  80488  174504898
5      China 1999 212258 1272915272
6      China 2000 213766 1280428583

fill()

Cuando nos pasan información procedentes de tablas dinámicas puede pasar que se nombra el primer valor y lo de abajo se asume es lo mismo

treatment <- tribble(~person, ~treatment, ~response, "Derrick Whitmore", 1, 7, NA, 
    2, 10, NA, 3, 9, "Katherine Burke", 1, 4)
treatment
# A tibble: 4 x 3
  person           treatment response
  <chr>                <dbl>    <dbl>
1 Derrick Whitmore         1        7
2 <NA>                     2       10
3 <NA>                     3        9
4 Katherine Burke          1        4

Con fill() se pueden llenar los NA

treatment %>% fill(person)
# A tibble: 4 x 3
  person           treatment response
  <chr>                <dbl>    <dbl>
1 Derrick Whitmore         1        7
2 Derrick Whitmore         2       10
3 Derrick Whitmore         3        9
4 Katherine Burke          1        4

Ejemplo: Transacciones bancarias

El Banco del Pacífico requiere mejorar los tiempos de atención al cliente en ventanilla, para ello ha recolectado esta información anónimamente para cada cajero y transacción realizada.

Le suministran un excel con dos hojas:

  1. Tiene los datos de las transacciones, columnas: Sucursal, Cajero, ID_Transaccion, Transaccion, Tiempo_Servicio_seg, Nivel de satisfacción, Monto de la transaccion.

  2. Otra hoja que indica si en la sucursal se ha puesto o no el nuevo sistema.

# Cargar la libreria a utilizar
library(openxlsx)
# Leer el archivo de excel y asignarlo al objeto data_banco
data_banco <- read.xlsx(xlsxFile = "Data/Data_Banco.xlsx", sheet = "Sucursal")
data_sucursal <- read.xlsx(xlsxFile = "Data/Data_Banco.xlsx", sheet = "Data_Sucursal")

Convertir a tibbles (un dataframe mejorado):

# Convertir el data_banco a un tibble
data_banco <- tbl_df(data_banco)
# Convertir el data_sucursal a un tibble
data_sucursal <- tbl_df(data_sucursal)

Lo primero que necesitamos es corregir los tipos de datos, nótese que Monto tiene una mezcla de “,” y “.”

data_banco <- data_banco %>% mutate(Monto = str_replace(Monto, pattern = ",", replacement = ".")) %>% 
    mutate(Sucursal = as.character(Sucursal), Cajero = as.character(Cajero), Satisfaccion = parse_factor(Satisfaccion, 
        levels = c("Muy Malo", "Malo", "Regular", "Bueno", "Muy Bueno")), Monto = parse_number(Monto, 
        locale = locale(decimal_mark = ".")))

Obtener la media del tiempo de servicio y del monto pero mostrar las variables como filas

# Obtener la media del tiempo de servicio y del monto Pero mostrar las variables
# como filas Se debe tener cuidado con los nombres
data_banco %>% rename(TiempoServicioSeg = Tiempo_Servicio_seg) %>% summarise_at(vars(TiempoServicioSeg, 
    Monto), funs(Media = mean(., na.rm = TRUE), MediaAcot = mean(., na.rm = TRUE, 
    trim = 0.05), Cantidad = n())) %>% gather %>% separate(key, c("Var", "Medida"), 
    sep = "_") %>% spread(Medida, value)
# A tibble: 2 x 4
  Var               Cantidad Media MediaAcot
  <chr>                <dbl> <dbl>     <dbl>
1 Monto                24299 1996.     1983.
2 TiempoServicioSeg    24299  156.      142.

Construir reportes específicos

# Obtener la media del tiempo de servicio y del monto Pero mostrar las variables
# como columnas Se debe tener cuidado con los nombres
data_banco %>% rename(TiempoServicioSeg = Tiempo_Servicio_seg) %>% summarise_at(vars(TiempoServicioSeg, 
    Monto), funs(Media = mean(., na.rm = TRUE), MediaAcot = mean(., na.rm = TRUE, 
    trim = 0.05), Cantidad = n())) %>% gather %>% separate(key, c("Var", "Medida"), 
    sep = "_") %>% spread(Var, value)
# A tibble: 3 x 3
  Medida     Monto TiempoServicioSeg
  <chr>      <dbl>             <dbl>
1 Cantidad  24299             24299 
2 Media      1996.              156.
3 MediaAcot  1983.              142.

Unir dos datas: bind_rows()

Si tenemos dos bases de datos, y una es continuacion de otra usamos

# Crear un Data frame
df_1 <- data.frame(Nombre = c("Ana", "Berni", "Carlos", "Daniel", "Ericka"), Edad = c(20, 
    19, 20, 19, 18), Ciudad = factor(c("Gye", "Uio", "Cue", "Gye", "Cue")))
df_1
  Nombre Edad Ciudad
1    Ana   20    Gye
2  Berni   19    Uio
3 Carlos   20    Cue
4 Daniel   19    Gye
5 Ericka   18    Cue
# Crear un Data frame
df_2 <- data.frame(Nombre = c("Fulton", "Gilda"), Ciudad = factor(c("Mach", "Gye")), 
    Edad = c(21, 18))
df_2
  Nombre Ciudad Edad
1 Fulton   Mach   21
2  Gilda    Gye   18
# unimos en una sola base
bind_rows(df_1, df_2)
  Nombre Edad Ciudad
1    Ana   20    Gye
2  Berni   19    Uio
3 Carlos   20    Cue
4 Daniel   19    Gye
5 Ericka   18    Cue
6 Fulton   21   Mach
7  Gilda   18    Gye
# Aumentar columna que indica origen de la fila
bind_rows(list(df_1 = df_1, df_2 = df_2), .id = "DF_ORIGEN")
  DF_ORIGEN Nombre Edad Ciudad
1      df_1    Ana   20    Gye
2      df_1  Berni   19    Uio
3      df_1 Carlos   20    Cue
4      df_1 Daniel   19    Gye
5      df_1 Ericka   18    Cue
6      df_2 Fulton   21   Mach
7      df_2  Gilda   18    Gye

Aumentar columna: bind_cols()

# Crear un Data frame
df_3 <- data.frame(Estado_Civil = c("S", "D", "S", "C", "D"))
df_3
  Estado_Civil
1            S
2            D
3            S
4            C
5            D
# Bind más de dos dataframes
bind_cols(df_1, df_3)
  Nombre Edad Ciudad Estado_Civil
1    Ana   20    Gye            S
2  Berni   19    Uio            D
3 Carlos   20    Cue            S
4 Daniel   19    Gye            C
5 Ericka   18    Cue            D

Unir datos - Merge|Join|Buscarv

  • Se tienen dos data.frames con columnas o variables que hacen las veces de “key” o “id” de los mismos

  • Se desea agregar al primer conjunto el contenido del segundo conjunto de datos si y sólo si el “key” o “id” del segundo conjunto corresponde con el “key” o “id” del primer conjunto de datos.

  • Parecido al Buscarv y Vlookup de excel

  • Equivalente al Join de Bases de datos

Inner Join

df_6 <- data.frame(A = c("Ana", "Daniel", "Jose"), B = c(100, 200, 300))
df_6
       A   B
1    Ana 100
2 Daniel 200
3   Jose 300
# unimos con la INTERSECCION ENTRE DF_1 Y DF_6
df_1 %>% inner_join(df_6, by = c(Nombre = "A"))
  Nombre Edad Ciudad   B
1    Ana   20    Gye 100
2 Daniel   19    Gye 200

Left Join

Se unen todos, pero los que no tienen coneccion va con NA

df_1 %>% left_join(df_6, by = c(Nombre = "A"))
  Nombre Edad Ciudad   B
1    Ana   20    Gye 100
2  Berni   19    Uio  NA
3 Carlos   20    Cue  NA
4 Daniel   19    Gye 200
5 Ericka   18    Cue  NA

Right Join

df_1 %>% right_join(df_6, by = c(Nombre = "A"))
  Nombre Edad Ciudad   B
1    Ana   20    Gye 100
2 Daniel   19    Gye 200
3   Jose   NA   <NA> 300

Full Join

df_1 %>% full_join(df_6, by = c(Nombre = "A"))
  Nombre Edad Ciudad   B
1    Ana   20    Gye 100
2  Berni   19    Uio  NA
3 Carlos   20    Cue  NA
4 Daniel   19    Gye 200
5 Ericka   18    Cue  NA
6   Jose   NA   <NA> 300

Join - Ejemplo transacciones bancarias

¿Qué join necesitamos realizar con nuestros datos?

View(data_banco)
View(data_sucursal)
# debemos tener el mismo tipo de datos
data_sucursal <- data_sucursal %>% mutate(ID_Sucursal = as.character(ID_Sucursal))

Sucursal esta en los dos datos, y puede generar confusion, vamos a renombrar la columna sucursal por ID_Sucursal, para evitar esa confusion.

data_banco <- data_banco %>% rename(ID_Sucursal = "Sucursal") %>% left_join(data_sucursal, 
    by = c("ID_Sucursal"))
data_banco
# A tibble: 24,299 x 9
   ID_Sucursal Cajero ID_Transaccion Transaccion Tiempo_Servicio~ Satisfaccion
   <chr>       <chr>  <chr>          <chr>                  <dbl> <fct>       
 1 62          4820   2              Cobro/Pago~              311 Muy Bueno   
 2 62          4820   2              Cobro/Pago~              156 Malo        
 3 62          4820   2              Cobro/Pago~              248 Regular     
 4 62          4820   2              Cobro/Pago~               99 Regular     
 5 62          4820   2              Cobro/Pago~              123 Muy Bueno   
 6 62          4820   2              Cobro/Pago~              172 Bueno       
 7 62          4820   2              Cobro/Pago~              140 Regular     
 8 62          4820   2              Cobro/Pago~              247 Bueno       
 9 62          4820   2              Cobro/Pago~              183 Muy Bueno   
10 62          4820   2              Cobro/Pago~               91 Muy Bueno   
# ... with 24,289 more rows, and 3 more variables: Monto <dbl>, Sucursal <chr>,
#   Nuevo_Sistema <chr>

Manipulación de datos - Avanzado

Aplicar funciones a las columnas Con summarise aplicábamos funciones a las columnas, con las funciones del paquete purrr (parte del tidyverse) se extienden dichas carácterísticas

  • map() resulta en una lista.
  • map_lgl() devuelve un vector de tipo logical.
  • map_int() devuelve un vector de tipo integer.
  • map_dbl() devuelve un vector de tipo double.
  • map_chr() devuelve un vector de tipo character.
  • map_dfr () y map_dfc () devuelven data.frame creadas unión de fila o columna respectivamente.
# Obtener la media de las columnas del data.frame
data_banco %>% map_dbl(mean)
        ID_Sucursal              Cajero      ID_Transaccion         Transaccion 
                 NA                  NA                  NA                  NA 
Tiempo_Servicio_seg        Satisfaccion               Monto            Sucursal 
            155.580                  NA            1996.156                  NA 
      Nuevo_Sistema 
                 NA 
data_banco %>% select_if(is.numeric) %>% map_dbl(mean)
Tiempo_Servicio_seg               Monto 
            155.580            1996.156 
# Obtener la media y la mediana de las columnas del data.frame
data_banco %>% select_if(is.numeric) %>% map_dfc(~c(mean(., na.rm = TRUE), median(., 
    na.rm = TRUE)))
# A tibble: 2 x 2
  Tiempo_Servicio_seg Monto
                <dbl> <dbl>
1                156. 1996.
2                122. 2087.

Obtener la media y la mediana de las columnas del data.frame agregando una columna con los nombres del estadístico obtenido

# Obtener la media y la mediana de las columnas del data.frame
data_banco %>% select_if(is.numeric) %>% map_dfc(~c(mean(., na.rm = TRUE), median(., 
    na.rm = TRUE))) %>% mutate(Medida = c("Media", "Mediana"))
# A tibble: 2 x 3
  Tiempo_Servicio_seg Monto Medida 
                <dbl> <dbl> <chr>  
1                156. 1996. Media  
2                122. 2087. Mediana

Obtener la media y la mediana de las columnas del data.frame agregando una columna con los nombres del estadístico obtenido y en el formato requerido

# Obtener la media y la mediana de las columnas del data.frame
data_banco %>% select_if(is.numeric) %>% map_dfc(~c(mean(., na.rm = TRUE), median(., 
    na.rm = TRUE))) %>% mutate(Medida = c("Media", "Mediana")) %>% gather(var, val, 
    1:(ncol(.) - 1)) %>% spread(key = Medida, val)
# A tibble: 2 x 3
  var                 Media Mediana
  <chr>               <dbl>   <dbl>
1 Monto               1996.   2087.
2 Tiempo_Servicio_seg  156.    122.

Evaluar, para cada sucursal, la hipótesis de que el tiempo promedio es menor a 100 segundos

# Obtener la media y la mediana de las columnas del data.frame
data_banco %>% split(.$Sucursal) %>% map(~t.test(.$Tiempo_Servicio_seg, mu = 100, 
    alternative = "less"))
$Alborada

    One Sample t-test

data:  .$Tiempo_Servicio_seg
t = 36.441, df = 3328, p-value = 1
alternative hypothesis: true mean is less than 100
95 percent confidence interval:
     -Inf 186.3578
sample estimates:
mean of x 
 182.6271 


$Centro

    One Sample t-test

data:  .$Tiempo_Servicio_seg
t = 60.418, df = 12043, p-value = 1
alternative hypothesis: true mean is less than 100
95 percent confidence interval:
     -Inf 168.2032
sample estimates:
mean of x 
 166.3955 


$`Mall del Sol`

    One Sample t-test

data:  .$Tiempo_Servicio_seg
t = 39.307, df = 4189, p-value = 1
alternative hypothesis: true mean is less than 100
95 percent confidence interval:
     -Inf 184.4024
sample estimates:
mean of x 
 181.0116 


$`Riocentro Sur`

    One Sample t-test

data:  .$Tiempo_Servicio_seg
t = -9.7567, df = 2837, p-value < 2.2e-16
alternative hypothesis: true mean is less than 100
95 percent confidence interval:
    -Inf 91.1814
sample estimates:
mean of x 
 89.39253 


$`Via Daule`

    One Sample t-test

data:  .$Tiempo_Servicio_seg
t = -15.666, df = 1897, p-value < 2.2e-16
alternative hypothesis: true mean is less than 100
95 percent confidence interval:
     -Inf 84.19022
sample estimates:
mean of x 
 82.33456 

Evaluar, para cada sucursal, la hipótesis de que el tiempo promedio es menor a 100 segundos

# Obtener la media y la mediana de las columnas del data.frame
data_banco %>% split(.$Sucursal) %>% map(~t.test(.$Tiempo_Servicio_seg, mu = 100, 
    alternative = "less")) %>% map_dbl(~.$p.value)
     Alborada        Centro  Mall del Sol Riocentro Sur     Via Daule 
 1.000000e+00  1.000000e+00  1.000000e+00  1.916814e-22  2.050798e-52