El objetivo de este curso es que aprendas a utilizar R como parte del flujo de tu trabajo cotidiano.
Esto con el propósito de permitirte trabajar de forma independiente y autónoma de principio a fin.
De esta manera, se reducen o eliminan tiempos de espera para completar solicitudes, permitiendo así un trabajo más eficiente y compacto. En ocasiones es más sencillo hacerlo tu mism@, que tener que explicar a un analista lo que necesitas, esperar a que complete la petición, revisarlo y repetir el proceso en caso de que la petición no haya sido satisfecha.
Otra gran ventaja de utilizar R es el mundo de la automatización propio de los lenguajes de programación. Si alguna vez te encuentras haciendo el mismo proceso en una tabla de Excel o base de SPSS una y otra vez, lo más probable es que puedas automatizarlo hasta cierto punto, permitiéndote ahorrar tiempo y reducir (incluso eliminar) la probabilidad de error humano.
No existirá un examen. La manera de evaluar el progreso en el curso será qué tan cómod@ te sientes para realizar tareas que son parte de tu trabajo diario en R.
Este curso NO es el tradicional R for beginners que se encuentra en muchos lados. NO vamos a hacer énfasis en las distintas estructuras propias del lenguaje de R. Tampoco será exhaustivo. NO se busca que aprendas R desde el inicio y que llegues a un nivel avanzado.
Este curso es completamente enfocado a tareas muy específicas propias de los estudios con los que trabajamos normalmente. Por esta razón, desde el principio será basado en ejemplos, extraídos de proyectos anteriores.
La forma de trabajo será completamente práctica. Más que un curso, será un taller en el cual habrá entregables y cosas qué hacer desde el principio. No pasaré mucho tiempo hablando de teoría, ni con presentaciones ni compartiendo mi pantalla. Las sesiones serán, más bien, para resolver dudas de entregas anteriores, explorar ideas, y explicar el próximo entregable.
De esta manera, las sesiones serán breves pero frecuentes, buscando que te familiarices con los distintos conceptos de manera gradual pero constante.
Un poco todos los días.
La idea es que al terminar el curso, tengas los elementos necesarios para sentirte cómod@ resolviendo problemas utilizando R, pero también que tengas los recursos para investigar y profundizar el conocimiento adquirido, para seguir progresando en la curva de aprendizaje.
Aunque la idea general de el taller es que aprendas a usar R sobre la marcha, con ejemplos basados en nuestro trabajo cotidiano, es necesario establecer algunas bases, para lo cual utilizaremos ejemplos muy sencillos.
Esta sección está basada en este libro. No dudes en consultarlo frecuentemente.
Tidyverse es un conjunto de paquetes de R que comparten un paradigma de programación en común, y a su vez son fundamentalmente distintos al paradigma tradicional de programación básica en R. Tidyverse está desarrollado para el análisis y visualización de datos en formato tabular, o data frame, con el propósito principal que sea sencillo utilizarse sin necesidad de un background en lenguajes de programación. Esto sucede ya que tiene una sintaxis muy verbal, es decir, que el código es fácilmente entendible por un humano.
El primer paso es cargar el paquete que a su vez cargará todos los paquetes:
library(tidyverse)
Nótese que al imprimirse el output, se muestra una lista de todos los paquetes que se están utilizando. No te preocupes si aún no es claro qué es un paquete. Sólo es necesario saber que es necesario iniciar siempre con el comando anterior.
Para esta sección, utilizaremos el dataset diamonds, que viene precargado en tidyverse.
diamonds
## # A tibble: 53,940 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
## 3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
## 4 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63
## 5 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75
## 6 0.24 Very Good J VVS2 62.8 57 336 3.94 3.96 2.48
## 7 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47
## 8 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53
## 9 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49
## 10 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
## # ... with 53,930 more rows
Observa que al imprimir los datos, se muestran varios pedazos de información importantes.
# A tibble: 53,940 x 10
Nos indica el número total de filas x columnas. Es útil tener presente esta información, ya que en ocasiones es útil verificar las dimensiones de nuestro tibble.
Después, tenemos los nombres de cada columna: carat, cut … y, z.
Justo debajo de los nombres de las columnas, tenemos entre llaves ciertas palabras clave. Por ejemplo, Debajo de carat está <dbl> y debajo de cut está <ord>. Esto se refiere al tipo de columna que se tiene. Es importante tenerlo en mente, pues será útil más adelante.
A continuación, una lista de algunos tipos de datos y lo que significan:
dbl: DOUBLE - Número con precisión de varios decimales.int: INTEGER - Número entero, es decir, sin decimales.chr: CHARACTER - Un pedazo de texto, acepta cualquier tipo de caracteres.fct: FACTOR - Pueden ser de tipo chr o numéricos. Cuando una columna es de tipo factor, se establece que sólo puede tomar valores dentro del conjunto de niveles definidos para la columna. Mejor conocidos como variables categóricas o nominales.ord: ORDERED FACTOR - Similar a factor, pero se establece un ordenamiento, o jerarquía entre los distintos niveles de factores, es decir, variables ordinales.date: DATE. Objeto de tipo fecha. Almacena datos temporales hasta nivel día.dttm: DATETIME. Objeto de tipo fecha hora. A diferencia de date, con este formato se puede almacenar datos temporales hasta el nivel hora, minuto, segundo y milisegundoNo es necesario que memorices las diferencias entre los tipos de datos. Quedarán claras más adelante.
Una vez definidos estos conceptos clave, podemos ver algunas funciones útiles.
Antes de hacer cualquier tipo de modificación, es necesario conocer herramientas para visualizar el tibble de manera sencilla.
Cuando se tienen datos con muchos renglones, es casi imposible ver todos y cada uno de los renglones. Es suficiente ver unos cuantos renglones para entender la estructura que tienen los datos en general.
Por default, al visualizar un tibble se imprimen en la consola las primeras 10 filas
diamonds
## # A tibble: 53,940 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
## 3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
## 4 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63
## 5 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75
## 6 0.24 Very Good J VVS2 62.8 57 336 3.94 3.96 2.48
## 7 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47
## 8 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53
## 9 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49
## 10 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
## # ... with 53,930 more rows
Para modificar este comportamiento y visualizar N filas utilizamos print.
Supongamos que queremos ver 25 filas:
diamonds %>%
print(n = 25)
## # A tibble: 53,940 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
## 3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
## 4 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63
## 5 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75
## 6 0.24 Very Good J VVS2 62.8 57 336 3.94 3.96 2.48
## 7 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47
## 8 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53
## 9 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49
## 10 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
## 11 0.3 Good J SI1 64 55 339 4.25 4.28 2.73
## 12 0.23 Ideal J VS1 62.8 56 340 3.93 3.9 2.46
## 13 0.22 Premium F SI1 60.4 61 342 3.88 3.84 2.33
## 14 0.31 Ideal J SI2 62.2 54 344 4.35 4.37 2.71
## 15 0.2 Premium E SI2 60.2 62 345 3.79 3.75 2.27
## 16 0.32 Premium E I1 60.9 58 345 4.38 4.42 2.68
## 17 0.3 Ideal I SI2 62 54 348 4.31 4.34 2.68
## 18 0.3 Good J SI1 63.4 54 351 4.23 4.29 2.7
## 19 0.3 Good J SI1 63.8 56 351 4.23 4.26 2.71
## 20 0.3 Very Good J SI1 62.7 59 351 4.21 4.27 2.66
## 21 0.3 Good I SI2 63.3 56 351 4.26 4.3 2.71
## 22 0.23 Very Good E VS2 63.8 55 352 3.85 3.92 2.48
## 23 0.23 Very Good H VS1 61 57 353 3.94 3.96 2.41
## 24 0.31 Very Good J SI1 59.4 62 353 4.39 4.43 2.62
## 25 0.31 Very Good J SI1 58.1 62 353 4.44 4.47 2.59
## # ... with 53,915 more rows
Observa que aunque visualizamos 25 renglones, el tibble con el que se está trabajando sigue siendo el original. Hay 53,940 filas, de las cuales sólo se ven en la consola las primeras 25.
BONUS: Los tibbles son una forma más moderna de mostrar dataframes. La principal diferencia es que un dataframe imprime todos sus renglones, mientras que un tibble sólo imprime 10 renglones. Otra diferencia es que en un dataframe no muestra qué tipo de datos compone a cada columna.
Corre lo siguiente en tu consola y observa lo que sucede:
diamonds %>% as.data.frame()
BONUS: Nótese el uso del operador %>%. Su nombre es pipe y lo utilizaremos casi en todas las operaciones que realicemos. El shortcut para ponerlo fácilmente es ctrl + shift + m.
Si quisiéramos ver toda la tabla, en un formato similar a Excel, podemos utilizar View()
diamonds %>%
View()
Corre este comando en la consola y observa lo que sucede.
Esta función debe usarse con precaución pues si se tienen muchos datos, cientos de miles en adelante, el visualizador tardará mucho en cargar e inclusive puede trabar la sesión. Sin embargo, para datos no tan grandes es un recurso útil.
Es importante mencionar que ni print() ni View() modifican los datos. Deben pensarse como herramientas de visualización y no para hacer operaciones.
Una de las operaciones más comunes que se puede realizar a un tibble o dataframe es ordenarlo de acuerdo a los valores de una columna.
Para esto es es útil arrange().
Supongamos que queremos ver el rango de los valores que puede tomar la columna carat. Podemos ordenar de menor a mayor por medio de:
diamonds %>%
arrange(carat)
## # A tibble: 53,940 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.2 Premium E SI2 60.2 62 345 3.79 3.75 2.27
## 2 0.2 Premium E VS2 59.8 62 367 3.79 3.77 2.26
## 3 0.2 Premium E VS2 59 60 367 3.81 3.78 2.24
## 4 0.2 Premium E VS2 61.1 59 367 3.81 3.78 2.32
## 5 0.2 Premium E VS2 59.7 62 367 3.84 3.8 2.28
## 6 0.2 Ideal E VS2 59.7 55 367 3.86 3.84 2.3
## 7 0.2 Premium F VS2 62.6 59 367 3.73 3.71 2.33
## 8 0.2 Ideal D VS2 61.5 57 367 3.81 3.77 2.33
## 9 0.2 Very Good E VS2 63.4 59 367 3.74 3.71 2.36
## 10 0.2 Ideal E VS2 62.2 57 367 3.76 3.73 2.33
## # ... with 53,930 more rows
Ordenó TODA la tabla utilizando carat como índice.
Y luego de mayor a menor:
diamonds %>%
arrange(-carat)
## # A tibble: 53,940 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 5.01 Fair J I1 65.5 59 18018 10.7 10.5 6.98
## 2 4.5 Fair J I1 65.8 58 18531 10.2 10.2 6.72
## 3 4.13 Fair H I1 64.8 61 17329 10 9.85 6.43
## 4 4.01 Premium I I1 61 61 15223 10.1 10.1 6.17
## 5 4.01 Premium J I1 62.5 62 15223 10.0 9.94 6.24
## 6 4 Very Good I I1 63.3 58 15984 10.0 9.94 6.31
## 7 3.67 Premium I I1 62.4 56 16193 9.86 9.81 6.13
## 8 3.65 Fair H I1 67.1 53 11668 9.53 9.48 6.38
## 9 3.51 Premium J VS2 62.5 59 18701 9.66 9.63 6.03
## 10 3.5 Ideal H I1 62.8 57 12587 9.65 9.59 6.03
## # ... with 53,930 more rows
El signo - indica que debe ser en orden descendiente.
Podemos ordenar basado en varias columnas.
diamonds %>%
arrange(cut, -carat)
## # A tibble: 53,940 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 5.01 Fair J I1 65.5 59 18018 10.7 10.5 6.98
## 2 4.5 Fair J I1 65.8 58 18531 10.2 10.2 6.72
## 3 4.13 Fair H I1 64.8 61 17329 10 9.85 6.43
## 4 3.65 Fair H I1 67.1 53 11668 9.53 9.48 6.38
## 5 3.4 Fair D I1 66.8 52 15964 9.42 9.34 6.27
## 6 3.11 Fair J I1 65.9 57 9823 9.15 9.02 5.98
## 7 3.02 Fair I I1 65.2 56 10577 9.11 9.02 5.91
## 8 3.01 Fair H I1 56.1 62 10761 9.54 9.38 5.31
## 9 3.01 Fair I SI2 65.8 56 18242 8.99 8.94 5.9
## 10 3.01 Fair I SI2 65.8 56 18242 8.99 8.94 5.9
## # ... with 53,930 more rows
En este caso, ordena primero todas las filas por cut, pero cuando hay empates, re-ordena de forma descendiente por carat.
Podemos usar varias columnas:
diamonds %>%
arrange(cut, clarity, color, -carat)
## # A tibble: 53,940 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 3.4 Fair D I1 66.8 52 15964 9.42 9.34 6.27
## 2 1.7 Fair D I1 64.7 56 5617 7.46 7.37 4.8
## 3 1.5 Fair D I1 64.7 62 5460 7.19 7.04 4.6
## 4 0.91 Fair D I1 66.2 57 2491 6 5.94 3.95
## 5 1.3 Fair E I1 66.5 58 2571 6.79 6.75 4.5
## 6 1.23 Fair E I1 67.4 56 3692 6.76 6.56 4.49
## 7 1.03 Fair E I1 78.2 54 1262 5.72 5.59 4.42
## 8 1.01 Fair E I1 64.5 58 2788 6.29 6.21 4.03
## 9 1.01 Fair E I1 64.5 54 2036 6.31 6.22 4.03
## 10 0.9 Fair E I1 59.9 66 2138 6.18 6.14 3.69
## # ... with 53,930 more rows
Si queremos obtener los últimos renglones, podemos utilizar tail():
diamonds %>%
arrange(cut, clarity, color, -carat) %>%
tail(15)
## # A tibble: 15 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.4 Ideal J IF 62.5 56 758 4.7 4.74 2.95
## 2 0.4 Ideal J IF 62.4 56 828 4.71 4.74 2.95
## 3 0.4 Ideal J IF 62.6 56 828 4.71 4.75 2.96
## 4 0.4 Ideal J IF 62.4 56 1063 4.74 4.71 2.95
## 5 0.4 Ideal J IF 62.6 56 1063 4.75 4.71 2.96
## 6 0.37 Ideal J IF 61.9 54 601 4.63 4.68 2.88
## 7 0.35 Ideal J IF 61.8 55 569 4.54 4.56 2.81
## 8 0.35 Ideal J IF 61.8 53 569 4.58 4.61 2.84
## 9 0.32 Ideal J IF 62.2 55 684 4.44 4.4 2.75
## 10 0.32 Ideal J IF 62.2 56 684 4.41 4.37 2.73
## 11 0.32 Ideal J IF 61 57 521 4.42 4.46 2.71
## 12 0.32 Ideal J IF 62.2 55 533 4.4 4.44 2.75
## 13 0.32 Ideal J IF 62.2 56 533 4.37 4.41 2.73
## 14 0.3 Ideal J IF 61.5 56 489 4.32 4.33 2.66
## 15 0.3 Ideal J IF 61.5 57 489 4.29 4.36 2.66
Observa que se visualizan en la consola 15 renglones, y también que el tibble con el que trabajamos es de únicamente 15 renglones. Se ve en: a tibble: 15 x 10. A diferencia de print(), tail() sí toma un subconjunto de renglones. Es decir, sí realiza una operación al dataframe, por lo que sirve para recortar los datos y no sólo para visualizar.
head() es análogo a tail(), funciona para obtener los primeros n renglones de los datos.
diamonds %>%
arrange(cut, clarity, color, -carat) %>%
head(15)
## # A tibble: 15 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 3.4 Fair D I1 66.8 52 15964 9.42 9.34 6.27
## 2 1.7 Fair D I1 64.7 56 5617 7.46 7.37 4.8
## 3 1.5 Fair D I1 64.7 62 5460 7.19 7.04 4.6
## 4 0.91 Fair D I1 66.2 57 2491 6 5.94 3.95
## 5 1.3 Fair E I1 66.5 58 2571 6.79 6.75 4.5
## 6 1.23 Fair E I1 67.4 56 3692 6.76 6.56 4.49
## 7 1.03 Fair E I1 78.2 54 1262 5.72 5.59 4.42
## 8 1.01 Fair E I1 64.5 58 2788 6.29 6.21 4.03
## 9 1.01 Fair E I1 64.5 54 2036 6.31 6.22 4.03
## 10 0.9 Fair E I1 59.9 66 2138 6.18 6.14 3.69
## 11 0.8 Fair E I1 65.5 54 1232 5.84 5.68 3.78
## 12 0.74 Fair E I1 58.2 65 1865 6.03 5.89 3.47
## 13 0.7 Fair E I1 66.1 58 1273 5.61 5.51 3.67
## 14 2.11 Fair F I1 67.6 57 7019 7.88 7.83 5.31
## 15 2.03 Fair F I1 65.6 56 6753 7.89 7.86 5.16
En ocasiones queremos obtener los valores únicos de una columna. Para esto es útil distinct():
diamonds %>%
distinct(cut)
## # A tibble: 5 x 1
## cut
## <ord>
## 1 Ideal
## 2 Premium
## 3 Good
## 4 Very Good
## 5 Fair
Si utilizamos 2 o más columnas dentro de dicha función, se imprimen las combinaciones distintas de todos los valores de dichas columnas.
diamonds %>%
distinct(cut,color) %>%
arrange(cut, color)
## # A tibble: 35 x 2
## cut color
## <ord> <ord>
## 1 Fair D
## 2 Fair E
## 3 Fair F
## 4 Fair G
## 5 Fair H
## 6 Fair I
## 7 Fair J
## 8 Good D
## 9 Good E
## 10 Good F
## # ... with 25 more rows
Otra operación común es eliminar columnas, o bien, conservar sólo un subconjunto de ellas.
select() funciona para seleccionar una o más columnas específicas:
diamonds %>%
select(carat, color, x, clarity)
## # A tibble: 53,940 x 4
## carat color x clarity
## <dbl> <ord> <dbl> <ord>
## 1 0.23 E 3.95 SI2
## 2 0.21 E 3.89 SI1
## 3 0.23 E 4.05 VS1
## 4 0.29 I 4.2 VS2
## 5 0.31 J 4.34 SI2
## 6 0.24 J 3.94 VVS2
## 7 0.24 I 3.95 VVS1
## 8 0.26 H 4.07 SI1
## 9 0.22 E 3.87 VS2
## 10 0.23 H 4 VS1
## # ... with 53,930 more rows
Hay formas alternativas de utilizar select.
Podemos quitar columnas en específico utilizando el signo -:
diamonds %>%
select(-carat, -cut)
## # A tibble: 53,940 x 8
## color clarity depth table price x y z
## <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 E SI2 61.5 55 326 3.95 3.98 2.43
## 2 E SI1 59.8 61 326 3.89 3.84 2.31
## 3 E VS1 56.9 65 327 4.05 4.07 2.31
## 4 I VS2 62.4 58 334 4.2 4.23 2.63
## 5 J SI2 63.3 58 335 4.34 4.35 2.75
## 6 J VVS2 62.8 57 336 3.94 3.96 2.48
## 7 I VVS1 62.3 57 336 3.95 3.98 2.47
## 8 H SI1 61.9 55 337 4.07 4.11 2.53
## 9 E VS2 65.1 61 337 3.87 3.78 2.49
## 10 H VS1 59.4 61 338 4 4.05 2.39
## # ... with 53,930 more rows
Podemos referirnos a columnas por su posición, en lugar de su nombre.
diamonds %>%
select(1,3,5,7,9)
## # A tibble: 53,940 x 5
## carat color depth price y
## <dbl> <ord> <dbl> <int> <dbl>
## 1 0.23 E 61.5 326 3.98
## 2 0.21 E 59.8 326 3.84
## 3 0.23 E 56.9 327 4.07
## 4 0.29 I 62.4 334 4.23
## 5 0.31 J 63.3 335 4.35
## 6 0.24 J 62.8 336 3.96
## 7 0.24 I 62.3 336 3.98
## 8 0.26 H 61.9 337 4.11
## 9 0.22 E 65.1 337 3.78
## 10 0.23 H 59.4 338 4.05
## # ... with 53,930 more rows
Es útil también para reordenar las columnas, sin quitar ninguna
diamonds %>%
select(10:1)
## # A tibble: 53,940 x 10
## z y x price table depth clarity color cut carat
## <dbl> <dbl> <dbl> <int> <dbl> <dbl> <ord> <ord> <ord> <dbl>
## 1 2.43 3.98 3.95 326 55 61.5 SI2 E Ideal 0.23
## 2 2.31 3.84 3.89 326 61 59.8 SI1 E Premium 0.21
## 3 2.31 4.07 4.05 327 65 56.9 VS1 E Good 0.23
## 4 2.63 4.23 4.2 334 58 62.4 VS2 I Premium 0.29
## 5 2.75 4.35 4.34 335 58 63.3 SI2 J Good 0.31
## 6 2.48 3.96 3.94 336 57 62.8 VVS2 J Very Good 0.24
## 7 2.47 3.98 3.95 336 57 62.3 VVS1 I Very Good 0.24
## 8 2.53 4.11 4.07 337 55 61.9 SI1 H Very Good 0.26
## 9 2.49 3.78 3.87 337 61 65.1 VS2 E Fair 0.22
## 10 2.39 4.05 4 338 61 59.4 VS1 H Very Good 0.23
## # ... with 53,930 more rows
BONUS: El operador a:b sirve para crear una secuencia de 1 en 1 del número a al número b. Prueba poner 10:20 en tu consola.
Por último, podemos renombrar columnas al seleccionarlas con el comando nombre_nuevo = nombre_original.
diamonds %>%
select(kilates = carat, precio = price)
## # A tibble: 53,940 x 2
## kilates precio
## <dbl> <int>
## 1 0.23 326
## 2 0.21 326
## 3 0.23 327
## 4 0.29 334
## 5 0.31 335
## 6 0.24 336
## 7 0.24 336
## 8 0.26 337
## 9 0.22 337
## 10 0.23 338
## # ... with 53,930 more rows
Nótese que se cambia el nombre y se seleccionan al mismo tiempo.
Si queremos conservar todas las columnas, y sólo cambiar el nombre de algunas, podemos utilizar rename.
diamonds %>%
rename(kilates = carat, precio = price)
## # A tibble: 53,940 x 10
## kilates cut color clarity depth table precio x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
## 3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
## 4 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63
## 5 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75
## 6 0.24 Very Good J VVS2 62.8 57 336 3.94 3.96 2.48
## 7 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47
## 8 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53
## 9 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49
## 10 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
## # ... with 53,930 more rows
Algo que es importante tener en mente es que correr estas funciones realiza cambios que no se conservan en la base original.
Por ejemplo, si ejecutas en R:
diamonds %>%
select(carat)
## # A tibble: 53,940 x 1
## carat
## <dbl>
## 1 0.23
## 2 0.21
## 3 0.23
## 4 0.29
## 5 0.31
## 6 0.24
## 7 0.24
## 8 0.26
## 9 0.22
## 10 0.23
## # ... with 53,930 more rows
E inmediatamente después ejecutas:
diamonds
## # A tibble: 53,940 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
## 3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
## 4 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63
## 5 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75
## 6 0.24 Very Good J VVS2 62.8 57 336 3.94 3.96 2.48
## 7 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47
## 8 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53
## 9 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49
## 10 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
## # ... with 53,930 more rows
Observarás que el dataframe diamonds sigue intacto. Aunque aplicamos una operación en el dataframe, esa operación no cambió la forma original del dataframe.
Si deseamos conservar los cambios. Es necesario asignar el resultado de nuestras operaciones a un nuevo dataframe.
diamonds_carat <- diamonds %>%
select(carat)
El operador <- es el que se encarga de la asignación. Se interpreta como:
“Asigna al objeto llamado diamonds_carat el resultado de las operaciones del lado derecho.”
Al correr esto suceden 2 cosas:
No se imprime el resultado en tu consola. Esto es porque la operación que corriste es de asignación, no de visualización.
En tu environment, aparece un nuevo renglón.
Esto significa que has creado un nuevo objeto, y ahora puedes acceder a él con:
diamonds_carat
## # A tibble: 53,940 x 1
## carat
## <dbl>
## 1 0.23
## 2 0.21
## 3 0.23
## 4 0.29
## 5 0.31
## 6 0.24
## 7 0.24
## 8 0.26
## 9 0.22
## 10 0.23
## # ... with 53,930 more rows
IMPORTANTE:
Observa que el dataframe que creamos tiene un nombre distinto, se llama diamonds_caraty no diamonds. Esto es para no modificar el dataframe original, pues todas las operaciones que se hagan después asumen que diamonds tiene la estructura original.
Se pueden crear tantos objetos se desee, y los que ya se tienen se pueden modificar tantas veces sea necesario.
diamonds_carat <- diamonds %>%
select(carat, price, cut)
diamonds_carat
## # A tibble: 53,940 x 3
## carat price cut
## <dbl> <int> <ord>
## 1 0.23 326 Ideal
## 2 0.21 326 Premium
## 3 0.23 327 Good
## 4 0.29 334 Premium
## 5 0.31 335 Good
## 6 0.24 336 Very Good
## 7 0.24 336 Very Good
## 8 0.26 337 Very Good
## 9 0.22 337 Fair
## 10 0.23 338 Very Good
## # ... with 53,930 more rows
diamonds_cut <- diamonds %>%
select(cut)
diamonds_cut
## # A tibble: 53,940 x 1
## cut
## <ord>
## 1 Ideal
## 2 Premium
## 3 Good
## 4 Premium
## 5 Good
## 6 Very Good
## 7 Very Good
## 8 Very Good
## 9 Fair
## 10 Very Good
## # ... with 53,930 more rows
Una vez más, podemos corroborar estos cambios en nuestro Environment:
Sirve para filtrar renglones basado en una condición específica. Usualmente se utiliza en conjunto de otras funciones u operadores.
diamonds %>%
filter(carat > 3.4)
## # A tibble: 10 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 3.65 Fair H I1 67.1 53 11668 9.53 9.48 6.38
## 2 3.5 Ideal H I1 62.8 57 12587 9.65 9.59 6.03
## 3 4.01 Premium I I1 61 61 15223 10.1 10.1 6.17
## 4 4.01 Premium J I1 62.5 62 15223 10.0 9.94 6.24
## 5 4 Very Good I I1 63.3 58 15984 10.0 9.94 6.31
## 6 3.67 Premium I I1 62.4 56 16193 9.86 9.81 6.13
## 7 4.13 Fair H I1 64.8 61 17329 10 9.85 6.43
## 8 5.01 Fair J I1 65.5 59 18018 10.7 10.5 6.98
## 9 4.5 Fair J I1 65.8 58 18531 10.2 10.2 6.72
## 10 3.51 Premium J VS2 62.5 59 18701 9.66 9.63 6.03
En este caso se utiliza en conjunto del operador > (mayor que).
La principal ventaja de tidyverse es que permite que los comandos puedan leerse fácilmente de manera humana, de izquiera a derecha, de arriba a abajo.
El comando anterior se lee facilmente como:
Del tibble diamonds, filtra los renglones en donde la columna carat tiene valores mayores a 3.4
Solo para contrastar (no es necesario detenerse mucho con el siguiente código) aquí se muestra este mismo comando sin utilizar tidyverse.
diamonds[diamonds$carat > 3.4, ]
## # A tibble: 10 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 3.65 Fair H I1 67.1 53 11668 9.53 9.48 6.38
## 2 3.5 Ideal H I1 62.8 57 12587 9.65 9.59 6.03
## 3 4.01 Premium I I1 61 61 15223 10.1 10.1 6.17
## 4 4.01 Premium J I1 62.5 62 15223 10.0 9.94 6.24
## 5 4 Very Good I I1 63.3 58 15984 10.0 9.94 6.31
## 6 3.67 Premium I I1 62.4 56 16193 9.86 9.81 6.13
## 7 4.13 Fair H I1 64.8 61 17329 10 9.85 6.43
## 8 5.01 Fair J I1 65.5 59 18018 10.7 10.5 6.98
## 9 4.5 Fair J I1 65.8 58 18531 10.2 10.2 6.72
## 10 3.51 Premium J VS2 62.5 59 18701 9.66 9.63 6.03
Aquí comienza a ser relevante el tipo de datos que conforma cada columna.
Si corro lo siguiente:
diamonds %>%
filter(clarity == VS2)
Me marcará un error. ¿Por qué?
La forma de corregirlo es utilizando " " o ' '.
diamonds %>%
filter(clarity == "VS2")
## # A tibble: 12,258 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63
## 2 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49
## 3 0.23 Very Good E VS2 63.8 55 352 3.85 3.92 2.48
## 4 0.3 Very Good J VS2 62.2 57 357 4.28 4.3 2.67
## 5 0.23 Very Good D VS2 60.5 61 357 3.96 3.97 2.4
## 6 0.26 Very Good D VS2 60.8 59 403 4.13 4.16 2.52
## 7 0.26 Good D VS2 65.2 56 403 3.99 4.02 2.61
## 8 0.25 Very Good E VS2 63.3 60 404 4 4.03 2.54
## 9 0.22 Premium E VS2 61.6 58 404 3.93 3.89 2.41
## 10 0.22 Premium D VS2 59.3 62 404 3.91 3.88 2.31
## # ... with 12,248 more rows
Esto es porque las comillas indican que estoy usando texto. En este caso, es útil ver que la columna clarity tiene valores como texto.
BONUS: Nótese que para realizar comparaciones se utiliza ==. Esto es porque el operador = ya es utilizado para asignación. (Como se observó en el ejemplo de renombrar usando select.)
¿Recuerdas que ord representa a ordenado? Veamos qué significa eso:
diamonds %>%
filter(cut >= "Premium")
## # A tibble: 35,342 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
## 3 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63
## 4 0.23 Ideal J VS1 62.8 56 340 3.93 3.9 2.46
## 5 0.22 Premium F SI1 60.4 61 342 3.88 3.84 2.33
## 6 0.31 Ideal J SI2 62.2 54 344 4.35 4.37 2.71
## 7 0.2 Premium E SI2 60.2 62 345 3.79 3.75 2.27
## 8 0.32 Premium E I1 60.9 58 345 4.38 4.42 2.68
## 9 0.3 Ideal I SI2 62 54 348 4.31 4.34 2.68
## 10 0.24 Premium I VS1 62.5 57 355 3.97 3.94 2.47
## # ... with 35,332 more rows
Encontramos los valores únicos de cut del tibble resultante
diamonds %>%
filter(cut >= "Premium") %>%
distinct(cut)
## # A tibble: 2 x 1
## cut
## <ord>
## 1 Ideal
## 2 Premium
Aunque estamos trabajando con texto, R sabe que Ideal está por encima de Premium, y a su vez, estos son los niveles más altos. Esto lo sabe porque el ordenamiento está definido en la estructura de la variable. Lo verificamos por medio de la función attributes().
diamonds$cut %>% attributes()
## $class
## [1] "ordered" "factor"
##
## $levels
## [1] "Fair" "Good" "Very Good" "Premium" "Ideal"
Si la columna cut fuera de formato chr, lo anterior no funcionaría como se espera. Veamos qué sucede:
diamonds_chr
## # A tibble: 53,940 x 10
## carat cut color clarity depth table price x y z
## <dbl> <chr> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
## 3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
## 4 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63
## 5 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75
## 6 0.24 Very Good J VVS2 62.8 57 336 3.94 3.96 2.48
## 7 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47
## 8 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53
## 9 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49
## 10 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
## # ... with 53,930 more rows
Nótese que dice <chr> debajo de cut, en lugar de <ord>.
diamonds_chr %>%
filter(cut >= "Premium")%>%
distinct(cut)
## # A tibble: 2 x 1
## cut
## <chr>
## 1 Premium
## 2 Very Good
Como R no tiene manera de saber qué valor es mayor que Premium toma un ordenamiento alfabético.
Hasta ahora hemos visto cómo obtener un subconjunto de, o recortar, un tibble por medio de filter() para renglones y select() para columnas.
Veamos ahora cómo modificar los datos con los que trabajamos.
Una operación muy frecuente en el proceso exploratorio y de limpieza es la de agregar una columna a tus datos. Para eso se utiliza mutate().
Puede utilizarse de 3 formas.
diamonds %>%
mutate(suma = x+y+z,
producto = x*y*z,
dos_x = 2*x) %>%
select(x:dos_x)
## # A tibble: 53,940 x 6
## x y z suma producto dos_x
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 3.95 3.98 2.43 10.4 38.2 7.9
## 2 3.89 3.84 2.31 10.0 34.5 7.78
## 3 4.05 4.07 2.31 10.4 38.1 8.1
## 4 4.2 4.23 2.63 11.1 46.7 8.4
## 5 4.34 4.35 2.75 11.4 51.9 8.68
## 6 3.94 3.96 2.48 10.4 38.7 7.88
## 7 3.95 3.98 2.47 10.4 38.8 7.9
## 8 4.07 4.11 2.53 10.7 42.3 8.14
## 9 3.87 3.78 2.49 10.1 36.4 7.74
## 10 4 4.05 2.39 10.4 38.7 8
## # ... with 53,930 more rows
BONUS: ¿Notaste que el operador a:b tiene más formas de usarse?
En un mismo mutate() se pueden agregar todas las columnas que necesitemos.
diamonds %>%
mutate(cte = 2,
cte_chr = 'constante') %>%
select(x:cte_chr)
## # A tibble: 53,940 x 5
## x y z cte cte_chr
## <dbl> <dbl> <dbl> <dbl> <chr>
## 1 3.95 3.98 2.43 2 constante
## 2 3.89 3.84 2.31 2 constante
## 3 4.05 4.07 2.31 2 constante
## 4 4.2 4.23 2.63 2 constante
## 5 4.34 4.35 2.75 2 constante
## 6 3.94 3.96 2.48 2 constante
## 7 3.95 3.98 2.47 2 constante
## 8 4.07 4.11 2.53 2 constante
## 9 3.87 3.78 2.49 2 constante
## 10 4 4.05 2.39 2 constante
## # ... with 53,930 more rows
Observa que mutate() repite aútomaticamente el valor dado a lo largo de toda la tabla.
Un vector es la unidad más básica de información en R. Se puede crear de varias maneras, usando c(), :, u otras funciones que regresan vectores, como seq() o rep().
En este caso, crearemos un vector que tenga 53,940 elementos, que es igual al número de filas en el tibble original.
vct <- 1:53940
Observamos los valores obtenidos con la función str().
str(vct)
## int [1:53940] 1 2 3 4 5 6 7 8 9 10 ...
Así, podemos agregar dicho vector a una columna de nuestros datos.
diamonds %>%
mutate(vector = vct) %>%
select(price:vector)
## # A tibble: 53,940 x 5
## price x y z vector
## <int> <dbl> <dbl> <dbl> <int>
## 1 326 3.95 3.98 2.43 1
## 2 326 3.89 3.84 2.31 2
## 3 327 4.05 4.07 2.31 3
## 4 334 4.2 4.23 2.63 4
## 5 335 4.34 4.35 2.75 5
## 6 336 3.94 3.96 2.48 6
## 7 336 3.95 3.98 2.47 7
## 8 337 4.07 4.11 2.53 8
## 9 337 3.87 3.78 2.49 9
## 10 338 4 4.05 2.39 10
## # ... with 53,930 more rows
En ocasiones, nos interesa calcular cifras como promedios, desviación estándar, percentiles, máximos, mínimos, etc. con los datos con los que estamos trabajando.
Veamos un ejemplo calculando el promedio del precio de todos los diamantes.
diamonds %>%
summarise(precio_prom = mean(price))
## # A tibble: 1 x 1
## precio_prom
## <dbl>
## 1 3933.
Observemos que el output se compactó. Pasamos de tener 53,940 filas y 10 columnas a tener un tibble de 1x1.
Summarise signficia resumir, y vemos aquí por qué tiene ese nombre. La idea de esta función es condensar la información de formas útiles, quitando todas las columnas que no se usan, y compactando toda la información a un sólo renglón.
¿Qué pasa si queremos obtener el precio promedio de cada tipo de corte cut, en lugar de el precio promedio a lo largo de toda la base?
Para este tipo de preguntas, se utiliza group_by():
diamonds %>%
group_by(cut) %>%
summarise(precio_prom = mean(price))
## # A tibble: 5 x 2
## cut precio_prom
## <ord> <dbl>
## 1 Fair 4359.
## 2 Good 3929.
## 3 Very Good 3982.
## 4 Premium 4584.
## 5 Ideal 3458.
Ahora tenemos una cifra por cada nivel de cut.
Podemos utilizar varias columnas para agrupar, así como varias funciones para resumir distintas columnas.
diamonds %>%
group_by(cut, color) %>%
summarise(precio_prom = mean(price),
carat_prom = mean(carat),
precio_max = max(price))
## # A tibble: 35 x 5
## cut color precio_prom carat_prom precio_max
## <ord> <ord> <dbl> <dbl> <int>
## 1 Fair D 4291. 0.920 16386
## 2 Fair E 3682. 0.857 15584
## 3 Fair F 3827. 0.905 17995
## 4 Fair G 4239. 1.02 18574
## 5 Fair H 5136. 1.22 18565
## 6 Fair I 4685. 1.20 18242
## 7 Fair J 4976. 1.34 18531
## 8 Good D 3405. 0.745 18468
## 9 Good E 3424. 0.745 18236
## 10 Good F 3496. 0.776 18686
## # ... with 25 more rows
Si alguna vez has trabajado con tablas dinámicas, o pivot tables, en Excel notarás que muchas de estás funciones operan de manera similar, filtrando valores, quitando o agregando columnas y resumiendo con funciones.
Otra operación que a menudo se realiza es la de cambiar la forma que tienen los datos, sin modificar el contenido. Cambiando lo que está como renglon a columnas y vice versa.
Por ejemplo, tomemos los siguientes datos:
diamonds %>%
group_by(cut, color) %>%
summarise(precio_prom = mean(price))
## # A tibble: 35 x 3
## cut color precio_prom
## <ord> <ord> <dbl>
## 1 Fair D 4291.
## 2 Fair E 3682.
## 3 Fair F 3827.
## 4 Fair G 4239.
## 5 Fair H 5136.
## 6 Fair I 4685.
## 7 Fair J 4976.
## 8 Good D 3405.
## 9 Good E 3424.
## 10 Good F 3496.
## # ... with 25 more rows
Para visualizar la información más fácilmente, es lógico querer ver una tabla dónde las columnas sean los niveles de cut, y los renglones correspondan a los niveles de color.
Para esto utilizamos la función pivot_wider():
data_wide <- diamonds %>%
group_by(cut, color) %>%
summarise(precio_prom = mean(price)) %>%
pivot_wider(names_from = "cut", values_from = "precio_prom")
data_wide
## # A tibble: 7 x 6
## color Fair Good `Very Good` Premium Ideal
## <ord> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 D 4291. 3405. 3470. 3631. 2629.
## 2 E 3682. 3424. 3215. 3539. 2598.
## 3 F 3827. 3496. 3779. 4325. 3375.
## 4 G 4239. 4123. 3873. 4501. 3721.
## 5 H 5136. 4276. 4535. 5217. 3889.
## 6 I 4685. 5079. 5256. 5946. 4452.
## 7 J 4976. 4574. 5104. 6295. 4918.
De esta forma se ven los datos de manera tabular, haciendo más fácil la interpretación.
Observa el argumento names_from. Este argumento indica qué variable será la que aparecerá en las columnas, mientras que el argumento values_from indica con qué valores se rellenará la tabla.
Para regresarlo a su forma anterior, utilizamos pivot_longer().
data_wide %>%
pivot_longer(names_to = "cut", values_to = "precio_prom", cols = c(Fair, Good, `Very Good`, Premium, Ideal))
## # A tibble: 35 x 3
## color cut precio_prom
## <ord> <chr> <dbl>
## 1 D Fair 4291.
## 2 D Good 3405.
## 3 D Very Good 3470.
## 4 D Premium 3631.
## 5 D Ideal 2629.
## 6 E Fair 3682.
## 7 E Good 3424.
## 8 E Very Good 3215.
## 9 E Premium 3539.
## 10 E Ideal 2598.
## # ... with 25 more rows
Vemos que ahora los argumentos son names_to, que indica cómo se llamará la columna en dónde se juntarán las categorías de las variables, values_to que indica cómo se llamará la columna donde se juntarán los valores, y cols que indica qué columnas de la data anterior se utilizarán para llenar las columnas de la data nueva.
Podemos transponer la tabla con la ayuda de pivots.
data_wide %>%
pivot_longer(names_to = "cut", values_to = "precio_prom", cols = c(Fair, Good, `Very Good`, Premium, Ideal)) %>%
pivot_wider(names_from = "color", values_from = "precio_prom")
## # A tibble: 5 x 8
## cut D E F G H I J
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 Fair 4291. 3682. 3827. 4239. 5136. 4685. 4976.
## 2 Good 3405. 3424. 3496. 4123. 4276. 5079. 4574.
## 3 Very Good 3470. 3215. 3779. 3873. 4535. 5256. 5104.
## 4 Premium 3631. 3539. 4325. 4501. 5217. 5946. 6295.
## 5 Ideal 2629. 2598. 3375. 3721. 3889. 4452. 4918.
Viendo los outputs, es fácil entender por qué las funciones tienen estos nombres. Wide significa ancho, y en este caso la data que sale de usar pivot_wider es más ancha en el sentido que tiene más columnas y menos renglones.
Su contraparte, long quiere decir largo y hace referencia a que el tibble resultante tendrá más renglones y menos columnas.
Una vez que se establecieron las funciones base del manejo de datos, veremos otras funciones que suelen utilizarse en conjunto con las funciones básicas, que agregan funcionalidades o hacen el proceso más sencillo.
Supongamos que se quieren conservar los diamantes cuya clarity correspone a alguno de estos valores:
Una forma (muy poco práctica) de resolverlo sería la siguiente:
diamonds %>%
filter(clarity == "I1" | clarity == "S1" | clarity == "VS1"| clarity == "VVS1" )
## # A tibble: 12,567 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
## 2 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47
## 3 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
## 4 0.23 Ideal J VS1 62.8 56 340 3.93 3.9 2.46
## 5 0.32 Premium E I1 60.9 58 345 4.38 4.42 2.68
## 6 0.23 Very Good H VS1 61 57 353 3.94 3.96 2.41
## 7 0.24 Premium I VS1 62.5 57 355 3.97 3.94 2.47
## 8 0.23 Very Good F VS1 60.9 57 357 3.96 3.99 2.42
## 9 0.23 Very Good F VS1 60 57 402 4 4.03 2.41
## 10 0.23 Very Good F VS1 59.8 57 402 4.04 4.06 2.42
## # ... with 12,557 more rows
Sin embargo, escribir tanto resulta no ser práctico.
Una forma más práctica es utilizando el operador %in%.
El primer paso es asignar los valores que sí queremos dentro de un vector:
clarities <- c("I1", "S1", "VS1", "VVS1")
clarities
## [1] "I1" "S1" "VS1" "VVS1"
Y después:
diamonds %>%
filter(clarity %in% clarities)
## # A tibble: 12,567 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
## 2 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47
## 3 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
## 4 0.23 Ideal J VS1 62.8 56 340 3.93 3.9 2.46
## 5 0.32 Premium E I1 60.9 58 345 4.38 4.42 2.68
## 6 0.23 Very Good H VS1 61 57 353 3.94 3.96 2.41
## 7 0.24 Premium I VS1 62.5 57 355 3.97 3.94 2.47
## 8 0.23 Very Good F VS1 60.9 57 357 3.96 3.99 2.42
## 9 0.23 Very Good F VS1 60 57 402 4 4.03 2.41
## 10 0.23 Very Good F VS1 59.8 57 402 4.04 4.06 2.42
## # ... with 12,557 more rows
Se lee cómo: De diamonds, conserva los renglones donde clarity está en el vector clarities.
Vemos que de esta manera el código utilizado es más compacto, más claro y más práctico. En esta ocasión, el vector clarities fue definido a mano porque eran pocos valores, sin embargo, en otras ocasiones ese vector puede tener muchísimos más valores y provenir de otro dataframe. Por ejemplo, es útil para ver qué ids de cierta base de datos están en otra.
Otra operación muy común es la negación de una expresión. Por ejemplo, si queremos obtener los diamantes cuya claridad NO pertenece al vector clarities, podemos utilizar el operador ! de la siguiente manera:
diamonds %>%
filter(!(clarity %in% clarities))
## # A tibble: 41,373 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
## 3 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63
## 4 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75
## 5 0.24 Very Good J VVS2 62.8 57 336 3.94 3.96 2.48
## 6 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53
## 7 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49
## 8 0.3 Good J SI1 64 55 339 4.25 4.28 2.73
## 9 0.22 Premium F SI1 60.4 61 342 3.88 3.84 2.33
## 10 0.31 Ideal J SI2 62.2 54 344 4.35 4.37 2.71
## # ... with 41,363 more rows
diamonds %>%
filter(!(clarity %in% clarities)) %>%
distinct(clarity)
## # A tibble: 5 x 1
## clarity
## <ord>
## 1 SI2
## 2 SI1
## 3 VS2
## 4 VVS2
## 5 IF
Observa que se pone al principio de la expresión, y se lee como:
De diamonds, conserva los renglones donde clarity NO está en el vector clarities.
La negación se puede utilizar con otros operadores como |, & o ==.
Por ejemplo:
Tomamos los diamantes cuyo color es E y clarity es VS1:
diamonds %>%
filter(color == "E" & clarity == "VS1")
## # A tibble: 1,281 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
## 2 0.23 Very Good E VS1 60.7 59 402 3.97 4.01 2.42
## 3 0.23 Very Good E VS1 59.5 58 402 4.01 4.06 2.4
## 4 0.23 Good E VS1 64.1 59 402 3.83 3.85 2.46
## 5 0.6 Ideal E VS1 61.7 55 2774 5.41 5.44 3.35
## 6 0.7 Good E VS1 57.2 62 2782 5.81 5.77 3.31
## 7 0.7 Premium E VS1 62.9 60 2782 5.62 5.54 3.51
## 8 0.7 Premium E VS1 62.2 58 2800 5.6 5.66 3.5
## 9 0.29 Very Good E VS1 61.9 55 555 4.28 4.33 2.66
## 10 0.29 Very Good E VS1 62.4 55 555 4.2 4.25 2.63
## # ... with 1,271 more rows
Si queremos tomar el complemento del conjunto anterior, es decir, negar dicha expresión:
diamonds %>%
filter(!(color == "E" & clarity == "VS1"))
## # A tibble: 52,659 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
## 3 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63
## 4 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75
## 5 0.24 Very Good J VVS2 62.8 57 336 3.94 3.96 2.48
## 6 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47
## 7 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53
## 8 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49
## 9 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
## 10 0.3 Good J SI1 64 55 339 4.25 4.28 2.73
## # ... with 52,649 more rows
Observa que fue necesario el uso de paréntesis después de !, para negar toda la expresión.
Una forma equivalente y sin utilizar paréntesis es:
diamonds %>%
filter(color != "E" | clarity != "VS1")
## # A tibble: 52,659 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
## 3 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63
## 4 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75
## 5 0.24 Very Good J VVS2 62.8 57 336 3.94 3.96 2.48
## 6 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47
## 7 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53
## 8 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49
## 9 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
## 10 0.3 Good J SI1 64 55 339 4.25 4.28 2.73
## # ... with 52,649 more rows
Podemos corroborar que es el mismo resultado por el número de renglones.
Observa ahora que para negar la expresión fue necesario cambiar cada == por != y sustituir el & por |.
Esto sucede por las Leyes de DeMorgan. No es necesario que las aprendas todas, sin embargo es importante entender que al utilizar operadores lógicos (&, |, !, ==) es necesario tener cuidado y verificar que el resultado obtenido es el que esperábamos.
Las operaciones lógicas no suelen ser intuitivas, y en ocasiones lo que R hace y lo que queremos que haga pueden ser cosas muy distintas.
Supongamos ahora que se quiere crear una clasificación de cut más sencilla, en la cuál sólo se tengan 2 valores:
cut es Premium o Idealcut es Fair, Good o Very Good.Para esto es útil la función ifelse(), utilizada dentro de un mutate():
diamonds %>%
mutate(clasif_cut = ifelse(test = cut %in% c("Premium", "Ideal"),
yes = "top_quality",
no = "good_quality")) %>%
select(cut, clasif_cut, everything())
## # A tibble: 53,940 x 11
## cut clasif_cut carat color clarity depth table price x y z
## <ord> <chr> <dbl> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 Ideal top_quality 0.23 E SI2 61.5 55 326 3.95 3.98 2.43
## 2 Premium top_quality 0.21 E SI1 59.8 61 326 3.89 3.84 2.31
## 3 Good good_quali~ 0.23 E VS1 56.9 65 327 4.05 4.07 2.31
## 4 Premium top_quality 0.29 I VS2 62.4 58 334 4.2 4.23 2.63
## 5 Good good_quali~ 0.31 J SI2 63.3 58 335 4.34 4.35 2.75
## 6 Very Good good_quali~ 0.24 J VVS2 62.8 57 336 3.94 3.96 2.48
## 7 Very Good good_quali~ 0.24 I VVS1 62.3 57 336 3.95 3.98 2.47
## 8 Very Good good_quali~ 0.26 H SI1 61.9 55 337 4.07 4.11 2.53
## 9 Fair good_quali~ 0.22 E VS2 65.1 61 337 3.87 3.78 2.49
## 10 Very Good good_quali~ 0.23 H VS1 59.4 61 338 4 4.05 2.39
## # ... with 53,930 more rows
Como se observa en el ejemplo, la función if_else() tiene 3 argumentos:
¿Qué pasa si se desea clasificar en 3 o más categorías?
Supongamos que clasificaremos como:
cut es Premium o Idealcut es Very Good o Goodcutes FairUna opción es anidar varias funciones ifelse(). Esto quiere decir que en el lugar del argumento no se establece una nueva condición.
Si alguna vez has realizado esta operación en Excel, reconocerás la semejanza.
diamonds %>%
mutate(clasif_cut = ifelse(test = cut %in% c("Premium", "Ideal"),
yes = "top_quality",
no = ifelse(test = cut %in% c("Very Good", "Good"),
yes = "good_quality",
no = "fair_quality"))) %>%
select(cut, clasif_cut, everything())
## # A tibble: 53,940 x 11
## cut clasif_cut carat color clarity depth table price x y z
## <ord> <chr> <dbl> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 Ideal top_quality 0.23 E SI2 61.5 55 326 3.95 3.98 2.43
## 2 Premium top_quality 0.21 E SI1 59.8 61 326 3.89 3.84 2.31
## 3 Good good_quali~ 0.23 E VS1 56.9 65 327 4.05 4.07 2.31
## 4 Premium top_quality 0.29 I VS2 62.4 58 334 4.2 4.23 2.63
## 5 Good good_quali~ 0.31 J SI2 63.3 58 335 4.34 4.35 2.75
## 6 Very Good good_quali~ 0.24 J VVS2 62.8 57 336 3.94 3.96 2.48
## 7 Very Good good_quali~ 0.24 I VVS1 62.3 57 336 3.95 3.98 2.47
## 8 Very Good good_quali~ 0.26 H SI1 61.9 55 337 4.07 4.11 2.53
## 9 Fair fair_quali~ 0.22 E VS2 65.1 61 337 3.87 3.78 2.49
## 10 Very Good good_quali~ 0.23 H VS1 59.4 61 338 4 4.05 2.39
## # ... with 53,930 more rows
Podemos omitir el nombre de los argumentos para que sea un poco más compacta la sintaxis.
diamonds %>%
mutate(clasif_cut = ifelse(cut %in% c("Premium", "Ideal"),
"top_quality",
ifelse(cut %in% c("Very Good", "Good"),
"good_quality",
"fair_quality"))) %>%
select(cut, clasif_cut, everything())
## # A tibble: 53,940 x 11
## cut clasif_cut carat color clarity depth table price x y z
## <ord> <chr> <dbl> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 Ideal top_quality 0.23 E SI2 61.5 55 326 3.95 3.98 2.43
## 2 Premium top_quality 0.21 E SI1 59.8 61 326 3.89 3.84 2.31
## 3 Good good_quali~ 0.23 E VS1 56.9 65 327 4.05 4.07 2.31
## 4 Premium top_quality 0.29 I VS2 62.4 58 334 4.2 4.23 2.63
## 5 Good good_quali~ 0.31 J SI2 63.3 58 335 4.34 4.35 2.75
## 6 Very Good good_quali~ 0.24 J VVS2 62.8 57 336 3.94 3.96 2.48
## 7 Very Good good_quali~ 0.24 I VVS1 62.3 57 336 3.95 3.98 2.47
## 8 Very Good good_quali~ 0.26 H SI1 61.9 55 337 4.07 4.11 2.53
## 9 Fair fair_quali~ 0.22 E VS2 65.1 61 337 3.87 3.78 2.49
## 10 Very Good good_quali~ 0.23 H VS1 59.4 61 338 4 4.05 2.39
## # ... with 53,930 more rows
Es fácil imaginar cómo la longitud del código aumenta y la facilidad de lectura disminuye al incrementar el número de categorías en las que se busca clasificar. Para evitar esto, utilizamos case_when(), una alternativa que hace el código más fácil de entender y mucho más compacto.
Realicemos el mismo ejemplo anterior pero ahora utilizando case_when().
diamonds %>%
mutate(clasif_cut = case_when(
cut %in% c("Premium", "Ideal") ~ "top_quality",
cut %in% c("Very Good", "Good") ~ "good_quality",
cut == "Fair" ~ "fair_quality")) %>%
select(cut, clasif_cut, everything())
## # A tibble: 53,940 x 11
## cut clasif_cut carat color clarity depth table price x y z
## <ord> <chr> <dbl> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 Ideal top_quality 0.23 E SI2 61.5 55 326 3.95 3.98 2.43
## 2 Premium top_quality 0.21 E SI1 59.8 61 326 3.89 3.84 2.31
## 3 Good good_quali~ 0.23 E VS1 56.9 65 327 4.05 4.07 2.31
## 4 Premium top_quality 0.29 I VS2 62.4 58 334 4.2 4.23 2.63
## 5 Good good_quali~ 0.31 J SI2 63.3 58 335 4.34 4.35 2.75
## 6 Very Good good_quali~ 0.24 J VVS2 62.8 57 336 3.94 3.96 2.48
## 7 Very Good good_quali~ 0.24 I VVS1 62.3 57 336 3.95 3.98 2.47
## 8 Very Good good_quali~ 0.26 H SI1 61.9 55 337 4.07 4.11 2.53
## 9 Fair fair_quali~ 0.22 E VS2 65.1 61 337 3.87 3.78 2.49
## 10 Very Good good_quali~ 0.23 H VS1 59.4 61 338 4 4.05 2.39
## # ... with 53,930 more rows
Aquí la sintaxis es más clara. Basta con definir la condición de lado izquierdo, el valor a aplicar en caso de que dicha condición sea cierta de lado derecho y separar ambas por medio de una tilde ~.
De esta manera podemos agregar cuantas condiciones sean necesarias sin aumentar la complejidad de lectura.
Viendo sólo el código, ¿puedes deducir qué hace la siguiente operación?:
diamonds %>%
mutate(ppk = price/carat) %>%
mutate(clasif_ppk = case_when(
ppk <= 2500 ~ "bara_bara",
ppk <= 5000 ~ "baraton",
ppk <= 7500 ~ "normalito",
ppk <= 10000 ~ "esta_carito",
ppk <= 12500 ~ "pues_que_hace_o_que?",
TRUE ~ "vendere_un_riñon"
))
Nota que las operaciones son evaluadas en orden, por esto no es necesario poner intervalos. Por ejemplo, los diamantes baratones son aquellos que tienen un precio por kilate entre $2501 y $5000, pero como se etiquetó primero a los diamantes cuyo ppk es menor o igual a $2500, no es necesario establecer el límite inferior de este nivel de precio, y lo mismo sucede en cada nivel de precio consecutivo.
OJO: ¿Qué hace la última línea?
TRUE ~ "vendere_un_riñon"
En ocasiones es necesario juntar dos tablas que comparten una o más columnas. Por ejemplo, se tiene un dataframe con las calificaciones que le dió un usuario a ciertos productos:
evaluaciones
## # A tibble: 434 x 5
## folio prod1 prod2 prod3 prod4
## <int> <int> <int> <int> <int>
## 1 1 3 3 6 5
## 2 2 5 5 4 8
## 3 3 6 7 6 4
## 4 4 5 5 4 5
## 5 5 4 8 7 6
## 6 6 3 5 6 8
## 7 7 5 8 5 6
## 8 8 5 8 4 6
## 9 9 5 4 7 6
## 10 10 4 7 6 7
## # ... with 424 more rows
Por otro lado, se tiene la información socioeconómica y demográfica de cada usuario:
demograficos
## # A tibble: 434 x 4
## folio NSE ciudad sexo
## <int> <dbl> <dbl> <dbl>
## 1 1 5 3 2
## 2 2 5 4 2
## 3 3 4 1 2
## 4 4 4 4 1
## 5 5 2 2 1
## 6 6 5 2 2
## 7 7 2 1 1
## 8 8 1 4 1
## 9 9 3 1 1
## 10 10 5 3 1
## # ... with 424 more rows
Si quisiéramos analizar las evaluaciones de producto por nivel socioeconómico necesitaríamos unir o pegar ambas tablas. Para esto utilizamos left_join().
evaluaciones %>%
left_join(demograficos, by = "folio")
## # A tibble: 434 x 8
## folio prod1 prod2 prod3 prod4 NSE ciudad sexo
## <int> <int> <int> <int> <int> <dbl> <dbl> <dbl>
## 1 1 3 3 6 5 5 3 2
## 2 2 5 5 4 8 5 4 2
## 3 3 6 7 6 4 4 1 2
## 4 4 5 5 4 5 4 4 1
## 5 5 4 8 7 6 2 2 1
## 6 6 3 5 6 8 5 2 2
## 7 7 5 8 5 6 2 1 1
## 8 8 5 8 4 6 1 4 1
## 9 9 5 4 7 6 3 1 1
## 10 10 4 7 6 7 5 3 1
## # ... with 424 more rows
De esta manera tenemos todas las columnas en una misma tabla.
Observa el argumento by. En este argumento se especifica la(s) columna(s) que tienen ambos dataframes en común, y se usará para hacer coincidir todos los datos.
Observa que las variables demográficas tienen valores numéricos, lo cual puede no ser muy útil. Podemos pegar las etiquetas reales con left_join y la ayuda de un catálogo.
cat_nse
## # A tibble: 5 x 2
## NSE nse_lbl
## <int> <chr>
## 1 1 A/B
## 2 2 C+
## 3 3 C
## 4 4 C-
## 5 5 D+/D
demograficos %>%
left_join(cat_nse, by = "NSE")
## # A tibble: 434 x 5
## folio NSE ciudad sexo nse_lbl
## <int> <dbl> <dbl> <dbl> <chr>
## 1 1 5 3 2 D+/D
## 2 2 5 4 2 D+/D
## 3 3 4 1 2 C-
## 4 4 4 4 1 C-
## 5 5 2 2 1 C+
## 6 6 5 2 2 D+/D
## 7 7 2 1 1 C+
## 8 8 1 4 1 A/B
## 9 9 3 1 1 C
## 10 10 5 3 1 D+/D
## # ... with 424 more rows
Observa que las dimensiones no tienen que coincidir. En el primer caso el número de renglones era el mismo en ambos dataframes. En este ejemplo, uno de los dataframes tenía sólo 5 renglones, uno por cada nivel de NSE. La función left_join hace coincidir los valores correspondientes, repitiendo los valores las veces que sea necesario.
Si alguna vez has realizado esta tarea en Excel, notarás que es similar a usar VLOOKUP o BUSCARV.
Podemos hacer lo mismo con ciudad y sexo, asumiendo que tengamos los respectivos catálogos.
cat_ciudad
## # A tibble: 4 x 2
## ciudad ciudad_lbl
## <int> <chr>
## 1 1 CDMX
## 2 2 Guadalajara
## 3 3 Monterrey
## 4 4 Queretaro
cat_sexo
## # A tibble: 2 x 2
## sexo sexo_lbl
## <int> <chr>
## 1 1 Mujer
## 2 2 Hombre
demograficos %>%
left_join(cat_nse, by = "NSE") %>%
left_join(cat_ciudad, by = "ciudad") %>%
left_join(cat_sexo, by = "sexo")
## # A tibble: 434 x 7
## folio NSE ciudad sexo nse_lbl ciudad_lbl sexo_lbl
## <int> <dbl> <dbl> <dbl> <chr> <chr> <chr>
## 1 1 5 3 2 D+/D Monterrey Hombre
## 2 2 5 4 2 D+/D Queretaro Hombre
## 3 3 4 1 2 C- CDMX Hombre
## 4 4 4 4 1 C- Queretaro Mujer
## 5 5 2 2 1 C+ Guadalajara Mujer
## 6 6 5 2 2 D+/D Guadalajara Hombre
## 7 7 2 1 1 C+ CDMX Mujer
## 8 8 1 4 1 A/B Queretaro Mujer
## 9 9 3 1 1 C CDMX Mujer
## 10 10 5 3 1 D+/D Monterrey Mujer
## # ... with 424 more rows
Observa que se pueden encadenar varios left_joins por medio del %>%.
Una vez que se tienen las etiquetas para cada valor, se pueden eliminar las columnas numéricas, y unir este nuevo dataframe al de las evaluaciones de producto.
demograficos <- demograficos %>%
left_join(cat_nse, by = "NSE") %>%
left_join(cat_ciudad, by = "ciudad") %>%
left_join(cat_sexo, by = "sexo") %>%
select(folio, nse = nse_lbl, ciudad = ciudad_lbl, sexo = sexo_lbl)
demograficos
## # A tibble: 434 x 4
## folio nse ciudad sexo
## <int> <chr> <chr> <chr>
## 1 1 D+/D Monterrey Hombre
## 2 2 D+/D Queretaro Hombre
## 3 3 C- CDMX Hombre
## 4 4 C- Queretaro Mujer
## 5 5 C+ Guadalajara Mujer
## 6 6 D+/D Guadalajara Hombre
## 7 7 C+ CDMX Mujer
## 8 8 A/B Queretaro Mujer
## 9 9 C CDMX Mujer
## 10 10 D+/D Monterrey Mujer
## # ... with 424 more rows
evaluaciones %>%
left_join(demograficos)
## Joining, by = "folio"
## # A tibble: 434 x 8
## folio prod1 prod2 prod3 prod4 nse ciudad sexo
## <int> <int> <int> <int> <int> <chr> <chr> <chr>
## 1 1 3 3 6 5 D+/D Monterrey Hombre
## 2 2 5 5 4 8 D+/D Queretaro Hombre
## 3 3 6 7 6 4 C- CDMX Hombre
## 4 4 5 5 4 5 C- Queretaro Mujer
## 5 5 4 8 7 6 C+ Guadalajara Mujer
## 6 6 3 5 6 8 D+/D Guadalajara Hombre
## 7 7 5 8 5 6 C+ CDMX Mujer
## 8 8 5 8 4 6 A/B Queretaro Mujer
## 9 9 5 4 7 6 C CDMX Mujer
## 10 10 4 7 6 7 D+/D Monterrey Mujer
## # ... with 424 more rows
Observa, por último, que si no se especifica la variable por medio de by =, se muestra un warning que avisa qué variable está utilizando para hacer el join. Por default R utiliza las columnas que tengan el mismo nombre, lo cual puede o no ser el comportamiento esperado. Siempre es recomendable especificar el argumento by para evitar ambigüedad.
Otro punto muy importante para poder realizar un join es que los tipos de dato de la columna en ambos dataframes debe coincidir. Por ejemplo, no se puede hacer un join cuando en un dataframe folio es numérico y en el otro es texto.
Como se mencionó en la sección anterior, es muy común trabajar con datos externos, escritos por otros programas. R tiene diversos paquetes y funciones para trabajar con datos en otros formatos.
En general, todos los tipos de archivo siguen la misma lógica:
Una función para importar, en general con prefijo read, y una función para exportar, generalmente con prefijo write o save.
Para los siguientes ejemplos, utilizaremos archivos almacenados en una carpeta llamada data. Dicha carpeta la puedes descargar desde aquí (asegurate de haber iniciado sesión con tu cuenta de LasQuinceLetras).
En caso de que no funcione el link, la ubicación desde tu Google Drive de la empresa es Vox Populi > Cursos LQL > marketing leaRning.
Descarga, descomprime y almacena la carpeta dentro del directorio desde donde estás trabajando. Asegurate de estar trabajando dentro de un proyecto de R.
Los datos en formato csv, o comma separated values, son muy comunes al compartir datos, ya que son fácilmente importados por diversos programas.
Para leer un csv, basta con utilizar:
dta <- read_csv('data/mtcars.csv')
##
## -- Column specification --------------------------------------------------------
## cols(
## mpg = col_double(),
## cyl = col_double(),
## disp = col_double(),
## hp = col_double(),
## drat = col_double(),
## wt = col_double(),
## qsec = col_double(),
## vs = col_double(),
## am = col_double(),
## gear = col_double(),
## carb = col_double()
## )
dta
## # A tibble: 32 x 11
## mpg cyl disp hp drat wt qsec vs am gear carb
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 21 6 160 110 3.9 2.62 16.5 0 1 4 4
## 2 21 6 160 110 3.9 2.88 17.0 0 1 4 4
## 3 22.8 4 108 93 3.85 2.32 18.6 1 1 4 1
## 4 21.4 6 258 110 3.08 3.22 19.4 1 0 3 1
## 5 18.7 8 360 175 3.15 3.44 17.0 0 0 3 2
## 6 18.1 6 225 105 2.76 3.46 20.2 1 0 3 1
## 7 14.3 8 360 245 3.21 3.57 15.8 0 0 3 4
## 8 24.4 4 147. 62 3.69 3.19 20 1 0 4 2
## 9 22.8 4 141. 95 3.92 3.15 22.9 1 0 4 2
## 10 19.2 6 168. 123 3.92 3.44 18.3 1 0 4 4
## # ... with 22 more rows
Para exportar una tabla que tengamos directo a un archivo csv se utiliza:
diamonds %>%
write_csv('data/diamonds.csv')
Verifica que se ejecutó correctamente buscando manualmente el archivo en tu directorio data/.
Para leer desde excel utilizamos el paquete openxlsx. Si no lo tienes instalado, puedes instalarlo con:
install.packages('openxlsx', dependencies = TRUE)
library(openxlsx)
read.xlsx('data/CLIENTES.xlsx') %>%
as_tibble()
## # A tibble: 81 x 2
## Nombre.de.cliente Abreviatura
## <chr> <chr>
## 1 "Ach Food" ACH
## 2 "Alpura" ALP
## 3 "AT&T" ATT
## 4 "AT KERNEY " ATK
## 5 "Bayer" BAY
## 6 "Banco Azteca" BAN
## 7 "Be Grand " BEG
## 8 "BIG FOOT" BIG
## 9 "Bioderma " BIO
## 10 "CAMPBELLS" CAM
## # ... with 71 more rows
Exportar una tabla de datos a un archivo de Excel es un poco más complejo que hacerlo con un csv. Más adelante veremos en detalle cómo trabajar en archivos de Excel desde R. Un ejemplo muy sencillo es el siguiente:
wb <- createWorkbook()
wb %>%
addWorksheet('datos diamantes')
wb %>%
writeData(sheet = 'datos diamantes', x = diamonds)
wb %>%
saveWorkbook('data/diamonds.xlsx', overwrite = T)
Primero se crea un objeto Workbook en blanco con la función createWorkbook(). Esto crea todo un libro de Excel. Posteriormente se crea una hoja con addWorksheet, en este caso la llamamos diamonds. En esta hoja que creamos, escribimos los datos que queremos exportar con writeData(), en este caso utilizaremos el tibble diamonds con el que hemos estado trabajando. Por último, con saveWorkbook()guardamos en la carpeta de nuestra preferencia el archivo creado, en este caso será data/.
Una manera mucho más sencilla de exportar un archivo a Excel, en lugar de crear dicho archivo desde R, es copiándolo en tu portapapeles, y pegándolo directo en un nuevo archivo de Excel.
Necesitarás el paquete clipr.
install.packages('clipr')
library(clipr)
## Welcome to clipr. See ?write_clip for advisories on writing to the clipboard in R.
Corre el siguiente comando en tu consola y posteriormente abre un archivo nuevo de Excel y presiona ctrl v o bien, click derecho > pegar:
diamonds %>%
write_clip()
Para leer archivos creados por programas estadísticos y de análisis de datos, como pueden ser SPSS, STATA, SAS, etc. utilizaremos el paquete haven.
install.packages('haven')
library(haven)
Para este ejemplo contamos con una base en formato sav, para esto utilizamos la función read_sav(). En haven existen funciones read_ para varios tipos de archivo, los cuales puedes explorar escribiendo haven:: en tu consola y navegando con las flechas de tu teclado.
dta_sav <- read_sav('data/Vuala.sav')
dta_sav
## # A tibble: 360 x 105
## REGISTRO CIUDAD1 PERFIL F1 F2 RANGO_EDAD F3 F4 F5_1
## <dbl> <dbl+lb> <dbl+lbl> <dbl+lb> <dbl> <dbl+lbl> <dbl+l> <dbl+lbl> <dbl>
## 1 19 1 [CDMX] 3 [Mamá] 2 [Muje~ 31 3 [6 a 12] 1 [Sí] 2 [2 hi~ 11
## 2 20 1 [CDMX] 1 [Niño] 2 [Muje~ 16 1 [16 a 1~ NA NA NA
## 3 21 1 [CDMX] 2 [Jóven~ 1 [Homb~ 20 2 [20 a 2~ NA NA NA
## 4 22 1 [CDMX] 1 [Niño] 1 [Homb~ 19 1 [16 a 1~ NA NA NA
## 5 23 1 [CDMX] 3 [Mamá] 2 [Muje~ 43 3 [6 a 12] 1 [Sí] 3 [3 hi~ 18
## 6 24 1 [CDMX] 3 [Mamá] 2 [Muje~ 36 3 [6 a 12] 1 [Sí] 3 [3 hi~ 15
## 7 25 1 [CDMX] 3 [Mamá] 2 [Muje~ 38 3 [6 a 12] 1 [Sí] 2 [2 hi~ 14
## 8 26 1 [CDMX] 1 [Niño] 1 [Homb~ 17 1 [16 a 1~ NA NA NA
## 9 27 1 [CDMX] 2 [Jóven~ 1 [Homb~ 23 2 [20 a 2~ NA NA NA
## 10 28 1 [CDMX] 3 [Mamá] 2 [Muje~ 45 3 [6 a 12] 1 [Sí] 3 [3 hi~ 18
## # ... with 350 more rows, and 96 more variables: F5_2 <dbl>, F5_3 <dbl>,
## # CUOTA_NSE <dbl+lbl>, CUOTA_NSE_FINAL <dbl+lbl>, F6_1 <dbl+lbl>,
## # F6_2 <dbl+lbl>, F6_3 <dbl+lbl>, F6_4 <dbl+lbl>, F6_5 <dbl+lbl>,
## # F6_6 <dbl+lbl>, F6_7 <dbl+lbl>, F6_8 <dbl+lbl>, F7_1 <dbl+lbl>,
## # F7_2 <dbl+lbl>, F7_3 <dbl+lbl>, F7_4 <dbl+lbl>, F7_5 <dbl+lbl>,
## # F7_6 <dbl+lbl>, F7_7 <dbl+lbl>, F7_8 <dbl+lbl>, F7_9 <dbl+lbl>,
## # F7_10 <dbl+lbl>, F7_11 <dbl+lbl>, RANGO_CUOTA <dbl+lbl>, F8_1 <dbl+lbl>,
## # F8_2 <dbl+lbl>, F8_3 <dbl+lbl>, F8_4 <dbl+lbl>, F8_5 <dbl+lbl>,
## # F8_6 <dbl+lbl>, F8_7 <dbl+lbl>, F8_8 <dbl+lbl>, F8_9 <dbl+lbl>,
## # F8_10 <dbl+lbl>, F8_11 <dbl+lbl>, TIPO_CONSUMIDOR <dbl+lbl>, V1 <dbl+lbl>,
## # V2 <dbl+lbl>, V3_1 <dbl+lbl>, V3_2 <dbl+lbl>, V3_3 <dbl+lbl>,
## # V3_4 <dbl+lbl>, V4 <dbl+lbl>, V5_1 <dbl+lbl>, V5_2 <dbl+lbl>,
## # V5_3 <dbl+lbl>, V5_4 <dbl+lbl>, V5_5 <dbl+lbl>, V5_6 <dbl+lbl>,
## # V5_7 <dbl+lbl>, V6 <dbl+lbl>, V7_1 <dbl+lbl>, V7_2 <dbl+lbl>,
## # V7_3 <dbl+lbl>, PRUEBA <dbl+lbl>, EVAL01 <dbl+lbl>, EVAL02 <dbl+lbl>,
## # P1 <dbl+lbl>, P2 <dbl+lbl>, P3 <dbl+lbl>, P4 <dbl+lbl>, P5 <dbl+lbl>,
## # P6 <dbl+lbl>, P7 <dbl+lbl>, P8 <dbl+lbl>, P9 <dbl+lbl>, P10 <dbl+lbl>,
## # P11 <dbl+lbl>, P12 <dbl+lbl>, EVAL01_1 <dbl+lbl>, P13 <dbl+lbl>,
## # P14 <dbl+lbl>, P15 <dbl+lbl>, P16 <dbl+lbl>, P17 <dbl+lbl>, P18 <dbl+lbl>,
## # P19 <dbl+lbl>, P20 <dbl+lbl>, P21_1 <dbl+lbl>, P21_2 <dbl+lbl>,
## # P21_3 <dbl+lbl>, P21_4 <dbl+lbl>, P22 <dbl+lbl>, P23_1 <dbl+lbl>,
## # P23_2 <dbl+lbl>, P23_3 <dbl+lbl>, P23_4 <dbl+lbl>, P23_5 <dbl+lbl>,
## # P23_6 <dbl+lbl>, P23_7 <dbl+lbl>, P24 <dbl+lbl>, P25_1 <dbl+lbl>,
## # P25_2 <dbl+lbl>, P25_3 <dbl+lbl>, P26 <dbl+lbl>, P27 <dbl+lbl>
Si bien existen las funciones write_ para exportar archivos, no es recomendable utilizarlas, al menos de ser estrictamente necesario. Cualquier tipo de análisis que se vaya a realizar en SPSS o STATA, puede realizarse en R.
En ocasiones será útil guardar objetos generados por R (dataframes, listas, valores, modelos) para leerse en algún punto en el futuro, o bien, para ser compartidos para leerse por otro usuario de R.
Para exportar objetos creados para R, que sólo serán leídos en R, existe el tipo de datos rds, el cuál permite almacenar datos de una manera muy compacta (ocupa menos KBs que un CSV)
El uso más común es cuando se importa un conjunto de datos, generalmente de alguna fuente vista anteriormente, y después de realizar limpieza y preprocesamiento, se desea guardar el dataframe limpio para realizar el análisis posteriormente. De esta manera, no es necesario realizar la limpieza cada vez que se quiera acceder al dataframe.
Utilizamos la función saveRDS() para exportar un tibble:
diamonds %>%
saveRDS('data/diamonds.rds')
Para leerlo utilizamos readRDS():
diamonds_rds <- readRDS('data/diamonds.rds')
diamonds_rds
## # A tibble: 53,940 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
## 3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
## 4 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63
## 5 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75
## 6 0.24 Very Good J VVS2 62.8 57 336 3.94 3.96 2.48
## 7 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47
## 8 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53
## 9 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49
## 10 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
## # ... with 53,930 more rows
Al importar datos, es muy común que existan datos faltantes en algunos renglones de algunas columnas. De hecho, conjuntos de datos sin un solo dato faltante, son la excepción y no la regla.
En R, a los datos faltantes se les denota con NA. Es importante saberlos identificar y qué hacer con ellos, por las razones que veremos a continuación.
Tomemos el tibble diamonds, pero que ha sido modificado de manera que algunos diamantes tienen color faltante:
diamonds_na_color
## # A tibble: 53,940 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <chr> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
## 3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
## 4 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63
## 5 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75
## 6 0.24 Very Good <NA> VVS2 62.8 57 336 3.94 3.96 2.48
## 7 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47
## 8 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53
## 9 0.22 Fair <NA> VS2 65.1 61 337 3.87 3.78 2.49
## 10 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
## # ... with 53,930 more rows
Nos interesa ver cuántos son y en dónde están todos los faltantes. Para esto, existe la función auxiliar is.na() que puede utilizarse dentro de un filter().
diamonds_na_color %>%
filter(is.na(color))
## # A tibble: 9,754 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <chr> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.24 Very Good <NA> VVS2 62.8 57 336 3.94 3.96 2.48
## 2 0.22 Fair <NA> VS2 65.1 61 337 3.87 3.78 2.49
## 3 0.23 Ideal <NA> VS1 62.8 56 340 3.93 3.9 2.46
## 4 0.32 Premium <NA> I1 60.9 58 345 4.38 4.42 2.68
## 5 0.23 Very Good <NA> VS2 63.8 55 352 3.85 3.92 2.48
## 6 0.23 Very Good <NA> VS2 60.5 61 357 3.96 3.97 2.4
## 7 0.23 Very Good <NA> VS1 60.9 57 357 3.96 3.99 2.42
## 8 0.31 Good <NA> SI1 64 54 402 4.29 4.31 2.75
## 9 0.33 Ideal <NA> SI1 61.1 56 403 4.49 4.55 2.76
## 10 0.3 Very Good <NA> SI1 62.6 57 405 4.25 4.28 2.67
## # ... with 9,744 more rows
Vemos que son 9,754 renglones en los que falta color.
Intentemos calcular el precio promedio agrupando por color:
diamonds_na_color %>%
group_by(color) %>%
summarise(mean_price = mean(price))
## # A tibble: 8 x 2
## color mean_price
## <chr> <dbl>
## 1 D 3149.
## 2 E 3089.
## 3 F 3740.
## 4 G 4031.
## 5 H 4451.
## 6 I 5052.
## 7 J 5381.
## 8 <NA> 3922.
Observa que ahora NA es una categoría nueva. Esto puede o no ser útil para nuestro análisis. Por ahora, la mejor opción es quitarlos:
diamonds_na_color %>%
filter(!is.na(color))
## # A tibble: 44,186 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <chr> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
## 3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
## 4 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63
## 5 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75
## 6 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47
## 7 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53
## 8 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
## 9 0.3 Good J SI1 64 55 339 4.25 4.28 2.73
## 10 0.22 Premium F SI1 60.4 61 342 3.88 3.84 2.33
## # ... with 44,176 more rows
Observa el signo ! de negación. Lo que estamos diciendo es: Del tibble diamonds_na_color, conserva los renglones en los cuales color NO es NA. Es importante tener en mente que estamos perdiendo información. Si bien es un manera de resolver el problema, puede o no ser la más adecuada al contexto del problema.
Tomemos ahora el tibble diamonds, pero que ha sido modificado de manera que algunos diamantes tienen precio faltante:
diamonds_na_price
## # A tibble: 53,940 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
## 3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
## 4 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63
## 5 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75
## 6 0.24 Very Good J VVS2 62.8 57 NA 3.94 3.96 2.48
## 7 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47
## 8 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53
## 9 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49
## 10 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
## # ... with 53,930 more rows
Podemos observar que el 6to de los primeros 10 no tiene precio.
diamonds_na_price %>%
filter(is.na(price))
## # A tibble: 5,254 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.24 Very Good J VVS2 62.8 57 NA 3.94 3.96 2.48
## 2 0.3 Good J SI1 64 55 NA 4.25 4.28 2.73
## 3 0.3 Very Good J SI1 62.7 59 NA 4.21 4.27 2.66
## 4 0.23 Very Good E VS1 60.7 59 NA 3.97 4.01 2.42
## 5 0.22 Premium E VS2 61.6 58 NA 3.93 3.89 2.41
## 6 0.3 Very Good I SI1 62.6 57 NA 4.25 4.28 2.67
## 7 0.35 Ideal I VS1 60.9 57 NA 4.54 4.59 2.78
## 8 0.3 Premium H SI1 62.5 57 NA 4.29 4.25 2.67
## 9 0.7 Good E VS2 57.5 58 NA 5.85 5.9 3.38
## 10 0.8 Ideal I VS1 62.9 56 NA 5.94 5.87 3.72
## # ... with 5,244 more rows
Se interpreta como: Del tibble diamonds_na_price, conserva todos los renglones en los cuales price tiene valor de NA.
Observamos que en total son 5254 diamantes cuyo precio es faltante. Es importante hacer algo al respecto, pues si seguimos adelante ignorando estos datos faltantes, muchas operaciones no pueden ser realizadas. Por ejemplo:
diamonds_na_price %>%
group_by(cut) %>%
summarise(mean_price = mean(price))
## # A tibble: 5 x 2
## cut mean_price
## <ord> <dbl>
## 1 Fair NA
## 2 Good NA
## 3 Very Good NA
## 4 Premium NA
## 5 Ideal NA
Vemos que al calcular el precio promedio por cut, nos regresa como resultado NA para todos los niveles. Esto tiene sentido, pues estamos pidiendo que calcule el promedio de datos cuyo valor es desconocido. Es evidente que tenemos que solucionar esto antes de continuar con el análisis. La forma más sencilla de hacerlo es simplemente desechando estos renglones, utilizando filter() de nuevo.
diamonds_na_price %>%
filter(!is.na(price))
## # A tibble: 48,686 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
## 3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
## 4 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63
## 5 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75
## 6 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47
## 7 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53
## 8 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49
## 9 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
## 10 0.23 Ideal J VS1 62.8 56 340 3.93 3.9 2.46
## # ... with 48,676 more rows
Otra solución es imputarlos, es decir, sustituir los valores faltantes por algún valor puntual, en este caso, podemos utilizar la medianade precio por kilate ppk de cada categoría de diamantes, según sus niveles de cut, color y clarity, es decir, todas nuestras variables categóricas.
Calculemos la mediana de precio por kilate ppk de los diamantes cuya información está completa, agrupando por cut, color y clarity.
ppk_mediana <- diamonds_na_price %>%
filter(!is.na(price)) %>%
mutate(ppk = price/carat) %>%
group_by(cut, color, clarity) %>%
summarise(median_ppk = median(ppk)) %>%
ungroup()
## `summarise()` has grouped output by 'cut', 'color'. You can override using the `.groups` argument.
ppk_mediana
## # A tibble: 276 x 4
## cut color clarity median_ppk
## <ord> <ord> <ord> <dbl>
## 1 Fair D I1 3472.
## 2 Fair D SI2 3634
## 3 Fair D SI1 4099.
## 4 Fair D VS2 4368.
## 5 Fair D VS1 4174.
## 6 Fair D VVS2 3588.
## 7 Fair D VVS1 3584
## 8 Fair D IF 4027.
## 9 Fair E I1 1997.
## 10 Fair E SI2 3541.
## # ... with 266 more rows
Una vez calculados, nos gustaría pegar estas medianas al tibble original, para saber qué precio le corresponde en caso de que tenga datos faltantes. Para esto es muy útil left_join(), debido a que busca cuándo coinciden cut, color y clarity de una tabla con las columnas respectivas de la otra tabla y pega el valor correspondiente de, en el caso de nuestro ejemplo, median_ppk.
diamonds_na_price %>%
left_join(ppk_mediana, by = c("cut", "color", "clarity"))
## # A tibble: 53,940 x 11
## carat cut color clarity depth table price x y z median_ppk
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43 3842.
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31 3185.
## 3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31 3974.
## 4 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63 4704.
## 5 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75 3828.
## 6 0.24 Very Good J VVS2 62.8 57 NA 3.94 3.96 2.48 5259.
## 7 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47 2771.
## 8 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53 4331.
## 9 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49 3421.
## 10 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39 3611.
## # ... with 53,930 more rows
De esta forma, ya tenemos una columna con la cual rellenar datos faltantes, sólo falta crear la regla de imputación con la formula \(price = median_ppk*carat\). Recuerda que dividimos \(\frac price carat\) para obtener ppk, por lo que debemos multiplicar \(ppk * carat\) para obtener price.
Esta fórmula la escribiremos dentro de ifelse() que a su vez irá dentro de un mutate().
diamonds_imp <- diamonds_na_price %>%
left_join(ppk_mediana, by = c("cut", "color", "clarity")) %>%
mutate(price_imp = ifelse(is.na(price), median_ppk*carat, price))
diamonds_imp
## # A tibble: 53,940 x 12
## carat cut color clarity depth table price x y z median_ppk
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43 3842.
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31 3185.
## 3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31 3974.
## 4 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63 4704.
## 5 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75 3828.
## 6 0.24 Very Good J VVS2 62.8 57 NA 3.94 3.96 2.48 5259.
## 7 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47 2771.
## 8 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53 4331.
## 9 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49 3421.
## 10 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39 3611.
## # ... with 53,930 more rows, and 1 more variable: price_imp <dbl>
Por último, sustituimos la columna price con price_imp
diamonds_imp <- diamonds_imp %>%
select(-price, -median_ppk) %>%
rename(price = price_imp)
diamonds_imp
## # A tibble: 53,940 x 10
## carat cut color clarity depth table x y z price
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 3.95 3.98 2.43 326
## 2 0.21 Premium E SI1 59.8 61 3.89 3.84 2.31 326
## 3 0.23 Good E VS1 56.9 65 4.05 4.07 2.31 327
## 4 0.29 Premium I VS2 62.4 58 4.2 4.23 2.63 334
## 5 0.31 Good J SI2 63.3 58 4.34 4.35 2.75 335
## 6 0.24 Very Good J VVS2 62.8 57 3.94 3.96 2.48 1262.
## 7 0.24 Very Good I VVS1 62.3 57 3.95 3.98 2.47 336
## 8 0.26 Very Good H SI1 61.9 55 4.07 4.11 2.53 337
## 9 0.22 Fair E VS2 65.1 61 3.87 3.78 2.49 337
## 10 0.23 Very Good H VS1 59.4 61 4 4.05 2.39 338
## # ... with 53,930 more rows
Vemos que los valores imputados pueden ser distintos a los esperados. Es importante tener en mente las limitaciones de los métodos de imputación.
Otros métods incluyen utilizar fuentes de datos externas que puedan complementar o dar una idea de lo que la información faltante pueda ser.
Aunque la forma más frecuente de manejar datos faltantes es eliminar los renglones donde aparecen, es importante analizarlos primero, y entender por qué se están dando, pues el hecho de que falten datos puede ser información útil por sí misma.
Para cambiar el tipo de datos de una columna, utilizamos las funciones auxiliares con prefijo as. dentro de un mutate().
Escribe as. en tu consola y presiona la tecla Tabulación para ver todas estas funciones auxiliares.
Las que se utilizarán más frecuentemente son:
as.character()as.integer()as.double()as.Date()factor(): Equivalente a as.factor() pero es más versatilordered(): Equivalente a as.ordered() pero es más versatilPor ejemplo, regresando a nuestro dataframe de características demográficas:
demograficos
## # A tibble: 434 x 4
## folio nse ciudad sexo
## <int> <chr> <chr> <chr>
## 1 1 D+/D Monterrey Hombre
## 2 2 D+/D Queretaro Hombre
## 3 3 C- CDMX Hombre
## 4 4 C- Queretaro Mujer
## 5 5 C+ Guadalajara Mujer
## 6 6 D+/D Guadalajara Hombre
## 7 7 C+ CDMX Mujer
## 8 8 A/B Queretaro Mujer
## 9 9 C CDMX Mujer
## 10 10 D+/D Monterrey Mujer
## # ... with 424 more rows
Dependiendo del análisis, puede ser más útil trabajar con estos datos como factores o como factores ordenados.
demog_fct <- demograficos %>%
mutate(ciudad = factor(ciudad),
nse = ordered(nse, levels = c("D+/D", "C-", "C", "C+", "A/B")),
sexo = factor(sexo, levels = c("Mujer", "Hombre")))
demog_fct
## # A tibble: 434 x 4
## folio nse ciudad sexo
## <int> <ord> <fct> <fct>
## 1 1 D+/D Monterrey Hombre
## 2 2 D+/D Queretaro Hombre
## 3 3 C- CDMX Hombre
## 4 4 C- Queretaro Mujer
## 5 5 C+ Guadalajara Mujer
## 6 6 D+/D Guadalajara Hombre
## 7 7 C+ CDMX Mujer
## 8 8 A/B Queretaro Mujer
## 9 9 C CDMX Mujer
## 10 10 D+/D Monterrey Mujer
## # ... with 424 more rows
Observa el uso del argumento levels dentro de las funciones ordered() y factor(). Es por medio de este argumento que especificamos la jerarquía u orden de los niveles de cada variable.
Podemos verificar el tipo de datos con str():
demog_fct %>%
str()
## tibble [434 x 4] (S3: tbl_df/tbl/data.frame)
## $ folio : int [1:434] 1 2 3 4 5 6 7 8 9 10 ...
## $ nse : Ord.factor w/ 5 levels "D+/D"<"C-"<"C"<..: 1 1 2 2 4 1 4 5 3 1 ...
## $ ciudad: Factor w/ 4 levels "CDMX","Guadalajara",..: 3 4 1 4 2 2 1 4 1 3 ...
## $ sexo : Factor w/ 2 levels "Mujer","Hombre": 2 2 2 1 1 2 1 1 1 1 ...
Regresando al tibble diamonds, podemos probar otras funciones:
diamonds_mod <- diamonds %>%
mutate(clarity = as.character(clarity),
x = as.integer(x),
y = as.character(y))
diamonds_mod
## # A tibble: 53,940 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <chr> <dbl> <dbl> <int> <int> <chr> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3 3.84 2.31
## 3 0.23 Good E VS1 56.9 65 327 4 4.07 2.31
## 4 0.29 Premium I VS2 62.4 58 334 4 4.23 2.63
## 5 0.31 Good J SI2 63.3 58 335 4 4.35 2.75
## 6 0.24 Very Good J VVS2 62.8 57 336 3 3.96 2.48
## 7 0.24 Very Good I VVS1 62.3 57 336 3 3.98 2.47
## 8 0.26 Very Good H SI1 61.9 55 337 4 4.11 2.53
## 9 0.22 Fair E VS2 65.1 61 337 3 3.78 2.49
## 10 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
## # ... with 53,930 more rows
Observa que cada transformación tuvo un efecto en los datos. Para clarity, se perdió la jerarquía entre niveles, y ahora sólo se tiene texto en esa columna:
diamonds_mod %>%
distinct(clarity) %>%
arrange(clarity)
## # A tibble: 8 x 1
## clarity
## <chr>
## 1 I1
## 2 IF
## 3 SI1
## 4 SI2
## 5 VS1
## 6 VS2
## 7 VVS1
## 8 VVS2
En el caso de x, al transformarse de dbl a int, se perdieron los decimales de cada valor. Observa que NO se redondea, sino que se trunca. El primer valor, por ejemplo, pasó de ser 3.95 a 3.
diamonds_mod
## # A tibble: 53,940 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <chr> <dbl> <dbl> <int> <int> <chr> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3 3.84 2.31
## 3 0.23 Good E VS1 56.9 65 327 4 4.07 2.31
## 4 0.29 Premium I VS2 62.4 58 334 4 4.23 2.63
## 5 0.31 Good J SI2 63.3 58 335 4 4.35 2.75
## 6 0.24 Very Good J VVS2 62.8 57 336 3 3.96 2.48
## 7 0.24 Very Good I VVS1 62.3 57 336 3 3.98 2.47
## 8 0.26 Very Good H SI1 61.9 55 337 4 4.11 2.53
## 9 0.22 Fair E VS2 65.1 61 337 3 3.78 2.49
## 10 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
## # ... with 53,930 more rows
En el caso de y, a simple vista puede parecer que no cambió nada, fuera de la etiqueta <chr> bajo el nombre de la variable. Sin embargo, si intentamos realizar alguna operación matemática, nos regresa un NA y un warning.
diamonds_mod %>%
summarise(mean_y = mean(y))
## Warning in mean.default(y): argument is not numeric or logical: returning NA
## # A tibble: 1 x 1
## mean_y
## <dbl>
## 1 NA
Esto se debe a que es imposible realizar operaciones matemáticas con pedazos de texto. Aunque a simple vista parezcan ser números, es importante no perder de vista cómo lo está interpretando R.
Sin duda la transformación de tipo de datos más común, y también más compleja, es la de cambio a tipo fecha.
Partiendo del siguiente tibble:
air_passengers
## # A tibble: 144 x 2
## date AirPassengers
## <chr> <dbl>
## 1 1949-01-01 112
## 2 1949-02-01 118
## 3 1949-03-01 132
## 4 1949-04-01 129
## 5 1949-05-01 121
## 6 1949-06-01 135
## 7 1949-07-01 148
## 8 1949-08-01 148
## 9 1949-09-01 136
## 10 1949-10-01 119
## # ... with 134 more rows
Se observa que en la columna date se tienen fechas. Sin embargo, tenemos justo debajo del nombre de la columna el tipo de dato <chr>. Esto quiere decir que, aunque un humano vea que hay fechas en esa columna, una computadora lo interpreta como texto. Para corregir esto, utilizamos as.Date().
La prinicipal fuente de dolores de cabeza al trabajar con fechas, o texto que debe ser fecha, es determinar el formato correcto.
El primer paso está en identificar en qué formato están las fechas. En este ejemplo, tenemos un formato ‘yyyy-mm-dd’, es decir, 4 digitos para el año, 2 para el mes, 2 para el día, separados por guión. Esto lo podemos deducir tan sólo viendo las fechas, sin embargo, al utilizar as.Date() es necesario dar explícitamente el formato:
air_passengers %>%
mutate(date = as.Date(date, format = "%Y-%m-%d"))
## # A tibble: 144 x 2
## date AirPassengers
## <date> <dbl>
## 1 1949-01-01 112
## 2 1949-02-01 118
## 3 1949-03-01 132
## 4 1949-04-01 129
## 5 1949-05-01 121
## 6 1949-06-01 135
## 7 1949-07-01 148
## 8 1949-08-01 148
## 9 1949-09-01 136
## 10 1949-10-01 119
## # ... with 134 more rows
Obsérvese el argumento format =. Utilizamos una combinación de caracteres, usualmente que inician con %, para especificar el formato de la fecha.
%Y representa año expresado con 4 dígitos.%m representa mes expresado con 2 dígitos.%d representa día expresado con 2 dígitos.En la práctica, no se suelen tener datos tan limpios ni en el formato óptimo. Es necesario hacer algunas transformaciones.
Veamos el siguiente tibble:
ventas
## # A tibble: 60 x 2
## fecha ventas
## <chr> <dbl>
## 1 Ene 2015 228.
## 2 Feb 2015 253.
## 3 Mar 2015 514.
## 4 Abr 2015 468.
## 5 May 2015 369.
## 6 Jun 2015 461.
## 7 Jul 2015 430.
## 8 Ago 2015 360.
## 9 Sep 2015 352.
## 10 Oct 2015 377.
## # ... with 50 more rows
Al importar un archivo de Excel, es probable que las columnas que contienen fechas se importen de esta o alguna manera similar. Identificando el formato, observamos que no tenemos días, los meses están abreviados y en español, y están separados por un espacio.
Todos estos detalles tenemos que tomarlos en cuenta antes de que podamos convertir a tipo de dato date.
El primer paso es agregar un día. Aunque la fecha sea mensual, es necesario que se especifique el día. En este caso escribiremos 01 para utilizar el primer día de cada mes:
ventas %>%
mutate(fecha_con_dia = paste("01", fecha))
## # A tibble: 60 x 3
## fecha ventas fecha_con_dia
## <chr> <dbl> <chr>
## 1 Ene 2015 228. 01 Ene 2015
## 2 Feb 2015 253. 01 Feb 2015
## 3 Mar 2015 514. 01 Mar 2015
## 4 Abr 2015 468. 01 Abr 2015
## 5 May 2015 369. 01 May 2015
## 6 Jun 2015 461. 01 Jun 2015
## 7 Jul 2015 430. 01 Jul 2015
## 8 Ago 2015 360. 01 Ago 2015
## 9 Sep 2015 352. 01 Sep 2015
## 10 Oct 2015 377. 01 Oct 2015
## # ... with 50 more rows
BONUS: Utilizamos la función paste(), la cual une en una misma cadena cada pedazo de texto (string) que se le provea en los argumentos, separados por un espacio. Puedes ejecutar ?paste para más información.
Una vez que se le agregó el día, podemos utilizar as.Date() para convertirlo a tipo de datos temporal.
Antes de eso, recordemos que las fechas están abreviadas en español. Puede parecer algo irrelevante para nuestro ojo humano, sin embargo, para una computadora es necesario especificarlo.
Si usas Windows, puedes ejecutar:
Sys.setlocale(locale = 'Spanish')
## [1] "LC_COLLATE=Spanish_Spain.1252;LC_CTYPE=Spanish_Spain.1252;LC_MONETARY=Spanish_Spain.1252;LC_NUMERIC=C;LC_TIME=Spanish_Spain.1252"
Si estás en Mac o Linux:
Sys.setlocale(locale = "es_ES.UTF-8")
Una vez establecido el idioma, podemos proceder a usar as.Date() dentro de un mutate().
ventas %>%
mutate(fecha_con_dia = paste("01", fecha)) %>%
mutate(date = as.Date(fecha_con_dia, format = "%d %b %Y"))
## # A tibble: 60 x 4
## fecha ventas fecha_con_dia date
## <chr> <dbl> <chr> <date>
## 1 Ene 2015 228. 01 Ene 2015 2015-01-01
## 2 Feb 2015 253. 01 Feb 2015 2015-02-01
## 3 Mar 2015 514. 01 Mar 2015 2015-03-01
## 4 Abr 2015 468. 01 Abr 2015 2015-04-01
## 5 May 2015 369. 01 May 2015 2015-05-01
## 6 Jun 2015 461. 01 Jun 2015 2015-06-01
## 7 Jul 2015 430. 01 Jul 2015 2015-07-01
## 8 Ago 2015 360. 01 Ago 2015 2015-08-01
## 9 Sep 2015 352. 01 Sep 2015 2015-09-01
## 10 Oct 2015 377. 01 Oct 2015 2015-10-01
## # ... with 50 more rows
Observa que en el argumento format utilizamos ahora %b en el lugar de los meses, ya que es ese el símbolo para especificar que los meses están como texto abreviado. Observa tambien que los símbolos están separados por un espacio, en lugar de un guión, ya que en nuestra columna fecha_con_dia cada parte de la fecha está separada por un espacio.
Ejecuta ?strptime() en tu consola para ver todos los caracteres para especificar formato que pueden ser usados dentro de as.Date() y similares, y qué representa cada uno.
Frecuentemente te econtrarás con el problema de trabajar con strings (pedazos de texto).
En muchas fuentes de datos, gran parte de las columnas serán importadas como texto y antes de poderlo convertir a número, fecha o factor, es necesario realizar transformaciones, usualmente dentro de mutate().
También puede ser útil para conservar o elminar ciertos renglones según una columna que contiene strings de texto cuando se utiliza dentro de filter().
Dos de las transformaciones más comunes son pasar a minúsculas lo que está en mayúsculas o vice versa.
Se usan de la siguiente manera:
diamonds %>%
select(cut) %>%
mutate(lower_cut = tolower(cut),
upper_cut = toupper(cut))
## # A tibble: 53,940 x 3
## cut lower_cut upper_cut
## <ord> <chr> <chr>
## 1 Ideal ideal IDEAL
## 2 Premium premium PREMIUM
## 3 Good good GOOD
## 4 Premium premium PREMIUM
## 5 Good good GOOD
## 6 Very Good very good VERY GOOD
## 7 Very Good very good VERY GOOD
## 8 Very Good very good VERY GOOD
## 9 Fair fair FAIR
## 10 Very Good very good VERY GOOD
## # ... with 53,930 more rows
Supongamos que queremos conservar todos los diamantes (renglones) en los cuales clarity es VVS1 o VVS2. Como vimos anteriormente, podríamos resolver esto con el operador %in%, sin embargo, una forma más versatil es utilizando str_detect().
diamonds %>%
filter(str_detect(clarity, "VVS"))
## # A tibble: 8,721 x 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.24 Very Good J VVS2 62.8 57 336 3.94 3.96 2.48
## 2 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47
## 3 0.23 Very Good G VVS2 60.4 58 354 3.97 4.01 2.41
## 4 0.28 Ideal G VVS2 61.4 56 553 4.19 4.22 2.58
## 5 0.32 Ideal I VVS1 62 55.3 553 4.39 4.42 2.73
## 6 0.24 Premium E VVS1 60.7 58 553 4.01 4.03 2.44
## 7 0.24 Very Good D VVS1 61.5 60 553 3.97 4 2.45
## 8 0.26 Very Good F VVS2 59.2 60 554 4.19 4.22 2.49
## 9 0.26 Very Good E VVS2 59.9 58 554 4.15 4.23 2.51
## 10 0.26 Very Good D VVS2 62.4 54 554 4.08 4.13 2.56
## # ... with 8,711 more rows
diamonds %>%
filter(str_detect(clarity, "VVS")) %>%
distinct(clarity)
## # A tibble: 2 x 1
## clarity
## <ord>
## 1 VVS2
## 2 VVS1
Esta operación se lee como: Del tibble diamonds filtra (conserva) los renglones en los que detectes el string “VVS” en la columna clarity.
Estas funciones (del paquete stringr) toman como primer argumento un vector si se utilizan independientemente, o una columna del dataframe si se utilizan dentro de filter(), de mutate() o de summarise().
El segundo argumento es el pattern (una subcadena de texto) que se buscará dentro del vector o columna.
Dependiendo de la función, el output será distinto.
Veamos qué sucede al usar str_detect() directamente en un vector.
Tomemos los primeros 50 renglones de la columna clarity y almacenémoslo como vector:
clarity_vct <- diamonds %>%
head(50) %>%
pull(clarity)
clarity_vct
## [1] SI2 SI1 VS1 VS2 SI2 VVS2 VVS1 SI1 VS2 VS1 SI1 VS1 SI1 SI2 SI2
## [16] I1 SI2 SI1 SI1 SI1 SI2 VS2 VS1 SI1 SI1 VVS2 VS1 VS2 VS2 VS1
## [31] VS1 VS1 VS1 VS1 VS1 VS1 VS1 SI1 VS2 SI2 SI2 SI1 VS2 VS1 SI2
## [46] SI1 SI2 SI2 VS2 SI2
## Levels: I1 < SI2 < SI1 < VS2 < VS1 < VVS2 < VVS1 < IF
BONUS: Observa la función pull(). Es la forma de tidyverse para extraer vectores. diamonds$clarity es equivalente a diamonds %>% pull(clarity).
Usemos str_detect() en dicho vector:
str_detect(clarity_vct, "VVS")
## [1] FALSE FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE FALSE FALSE FALSE
## [13] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [25] FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [37] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [49] FALSE FALSE
Observamos que regresa un vector de TRUEs y FALSEs.
Lo que está haciendo R, es evaluar si detecta la expresión “VVS” en cada elemento del vector, regresando TRUE si lo encuentra, o FALSE en caso contrario.
Al utilizarse dentro de un filter(), la evaluación se hace directamente sobre los renglones del dataframe, de manera que para los elementos que regresa TRUE simplemente conserva los renglones correspondientes, y para los elementos en los que regresa FALSE descarta dichos renglones.
La detección del pattern se hace en cualquier parte del string evaluado.
diamonds %>%
filter(str_detect(clarity, "S")) %>%
distinct(clarity)
## # A tibble: 6 x 1
## clarity
## <ord>
## 1 SI2
## 2 SI1
## 3 VS1
## 4 VS2
## 5 VVS2
## 6 VVS1
Buscamos los renglones para los cuales detecta una “S” en la columna clarity. Nota que en algunos casos la “S” está al principio, en otros casos en medio y en otros casos más cerca del final.
Veamos el siguiente conjunto de datos (ficticios):
ventas_papitas <- readRDS('data/ventas_papitas.RDS')
ventas_papitas
## # A tibble: 192 x 3
## fecha submarca ventas
## <date> <chr> <dbl>
## 1 2018-01-01 Sabritas Adobadas 361.
## 2 2018-01-01 Sabritas Limon 295.
## 3 2018-01-01 Sabritas Flamin Hot 509.
## 4 2018-01-01 Sabritas Habanero 663.
## 5 2018-01-01 Sabritas Crema y especias 191.
## 6 2018-01-01 Doritos Nacho 373.
## 7 2018-01-01 Doritos Diablo 510.
## 8 2018-01-01 Doritos Flamin Hot 387.
## 9 2018-01-01 Doritos 3D 282.
## 10 2018-01-01 Doritos Incognita 569.
## # ... with 182 more rows
Tenemos las ventas mensuales de distintas variedades de botanas saladas para el año 2018. a nivel submarca.
Queremos ver el total de venta mensual a nivel marca. Veamos los distintos valores que tenemos para submarca:
ventas_papitas %>%
distinct(submarca) %>%
print(n = 16)
## # A tibble: 16 x 1
## submarca
## <chr>
## 1 Sabritas Adobadas
## 2 Sabritas Limon
## 3 Sabritas Flamin Hot
## 4 Sabritas Habanero
## 5 Sabritas Crema y especias
## 6 Doritos Nacho
## 7 Doritos Diablo
## 8 Doritos Flamin Hot
## 9 Doritos 3D
## 10 Doritos Incognita
## 11 Doritos Pizzerolas
## 12 Cheetos Crunchy
## 13 Cheetos Flamin Hot
## 14 Cheetos Poffs
## 15 Cheetos Torciditos
## 16 Cheetos Colmillos
Contamos con 3 marcas principales: Sabritas, Doritos, Cheetos. Para calcular el nivel a total marca, necesitamos primero extraer la marca de los valores de submarca. Hacemos esto por medio de str_extract() dentro de un mutate().
ventas_papitas %>%
mutate(marca = str_extract(submarca,"Sabritas|Cheetos|Doritos"))
## # A tibble: 192 x 4
## fecha submarca ventas marca
## <date> <chr> <dbl> <chr>
## 1 2018-01-01 Sabritas Adobadas 361. Sabritas
## 2 2018-01-01 Sabritas Limon 295. Sabritas
## 3 2018-01-01 Sabritas Flamin Hot 509. Sabritas
## 4 2018-01-01 Sabritas Habanero 663. Sabritas
## 5 2018-01-01 Sabritas Crema y especias 191. Sabritas
## 6 2018-01-01 Doritos Nacho 373. Doritos
## 7 2018-01-01 Doritos Diablo 510. Doritos
## 8 2018-01-01 Doritos Flamin Hot 387. Doritos
## 9 2018-01-01 Doritos 3D 282. Doritos
## 10 2018-01-01 Doritos Incognita 569. Doritos
## # ... with 182 more rows
ventas_papitas %>%
mutate(marca = str_extract(submarca,"Sabritas|Cheetos|Doritos")) %>%
group_by(fecha, marca) %>%
summarise(ventas = sum(ventas)) %>%
ungroup()
## `summarise()` has grouped output by 'fecha'. You can override using the `.groups` argument.
## # A tibble: 36 x 3
## fecha marca ventas
## <date> <chr> <dbl>
## 1 2018-01-01 Cheetos 1702.
## 2 2018-01-01 Doritos 2556.
## 3 2018-01-01 Sabritas 2020.
## 4 2018-02-01 Cheetos 1888.
## 5 2018-02-01 Doritos 2572.
## 6 2018-02-01 Sabritas 1767.
## 7 2018-03-01 Cheetos 2085.
## 8 2018-03-01 Doritos 2013.
## 9 2018-03-01 Sabritas 1759.
## 10 2018-04-01 Cheetos 1557.
## # ... with 26 more rows
Observa el uso de | para separar los distintos patterns. La instrucción se lee como: Crear una nueva variable, en la cual se extraigan de la columna submarca el patrón "Sabritas" o "Cheetos" o "Doritos".
A diferencia de str_detect(), str_extract() regresa el pattern que pedimos que busque. Dicho de otra manera, extrae el pattern del string dado. Podemos verlo operando directamente sobre el vector:
str_extract(ventas_papitas$submarca,"Sabritas|Cheetos|Doritos")
## [1] "Sabritas" "Sabritas" "Sabritas" "Sabritas" "Sabritas" "Doritos"
## [7] "Doritos" "Doritos" "Doritos" "Doritos" "Doritos" "Cheetos"
## [13] "Cheetos" "Cheetos" "Cheetos" "Cheetos" "Sabritas" "Sabritas"
## [19] "Sabritas" "Sabritas" "Sabritas" "Doritos" "Doritos" "Doritos"
## [25] "Doritos" "Doritos" "Doritos" "Cheetos" "Cheetos" "Cheetos"
## [31] "Cheetos" "Cheetos" "Sabritas" "Sabritas" "Sabritas" "Sabritas"
## [37] "Sabritas" "Doritos" "Doritos" "Doritos" "Doritos" "Doritos"
## [43] "Doritos" "Cheetos" "Cheetos" "Cheetos" "Cheetos" "Cheetos"
## [49] "Sabritas" "Sabritas" "Sabritas" "Sabritas" "Sabritas" "Doritos"
## [55] "Doritos" "Doritos" "Doritos" "Doritos" "Doritos" "Cheetos"
## [61] "Cheetos" "Cheetos" "Cheetos" "Cheetos" "Sabritas" "Sabritas"
## [67] "Sabritas" "Sabritas" "Sabritas" "Doritos" "Doritos" "Doritos"
## [73] "Doritos" "Doritos" "Doritos" "Cheetos" "Cheetos" "Cheetos"
## [79] "Cheetos" "Cheetos" "Sabritas" "Sabritas" "Sabritas" "Sabritas"
## [85] "Sabritas" "Doritos" "Doritos" "Doritos" "Doritos" "Doritos"
## [91] "Doritos" "Cheetos" "Cheetos" "Cheetos" "Cheetos" "Cheetos"
## [97] "Sabritas" "Sabritas" "Sabritas" "Sabritas" "Sabritas" "Doritos"
## [103] "Doritos" "Doritos" "Doritos" "Doritos" "Doritos" "Cheetos"
## [109] "Cheetos" "Cheetos" "Cheetos" "Cheetos" "Sabritas" "Sabritas"
## [115] "Sabritas" "Sabritas" "Sabritas" "Doritos" "Doritos" "Doritos"
## [121] "Doritos" "Doritos" "Doritos" "Cheetos" "Cheetos" "Cheetos"
## [127] "Cheetos" "Cheetos" "Sabritas" "Sabritas" "Sabritas" "Sabritas"
## [133] "Sabritas" "Doritos" "Doritos" "Doritos" "Doritos" "Doritos"
## [139] "Doritos" "Cheetos" "Cheetos" "Cheetos" "Cheetos" "Cheetos"
## [145] "Sabritas" "Sabritas" "Sabritas" "Sabritas" "Sabritas" "Doritos"
## [151] "Doritos" "Doritos" "Doritos" "Doritos" "Doritos" "Cheetos"
## [157] "Cheetos" "Cheetos" "Cheetos" "Cheetos" "Sabritas" "Sabritas"
## [163] "Sabritas" "Sabritas" "Sabritas" "Doritos" "Doritos" "Doritos"
## [169] "Doritos" "Doritos" "Doritos" "Cheetos" "Cheetos" "Cheetos"
## [175] "Cheetos" "Cheetos" "Sabritas" "Sabritas" "Sabritas" "Sabritas"
## [181] "Sabritas" "Doritos" "Doritos" "Doritos" "Doritos" "Doritos"
## [187] "Doritos" "Cheetos" "Cheetos" "Cheetos" "Cheetos" "Cheetos"
Supongamos ahora que el conjunto de datos viene a nivel SKU. Además de tener submarca, tiene el gramaje de la presentación. En este caso, buscaríamos calcular las ventas a nivel submarca, es decir, eliminando la presentación.
ventas_papitas_sku <- readRDS('data/ventas_papitas_sku.RDS')
ventas_papitas_sku
## # A tibble: 576 x 3
## fecha sku ventas
## <date> <chr> <dbl>
## 1 2018-01-01 Sabritas Adobadas (40 grs) 333.
## 2 2018-01-01 Sabritas Adobadas (100 grs) 143.
## 3 2018-01-01 Sabritas Adobadas (120 grs) 405.
## 4 2018-01-01 Sabritas Limon (40 grs) 298.
## 5 2018-01-01 Sabritas Limon (100 grs) 128.
## 6 2018-01-01 Sabritas Limon (120 grs) 389.
## 7 2018-01-01 Sabritas Flamin Hot (40 grs) 657.
## 8 2018-01-01 Sabritas Flamin Hot (100 grs) 394.
## 9 2018-01-01 Sabritas Flamin Hot (120 grs) 402.
## 10 2018-01-01 Sabritas Habanero (40 grs) 481.
## # ... with 566 more rows
La forma más sencilla es, para cada valor de sku, quitar la última parte que está entre paréntesis. De esa manera conservaríamos únicamente la marca y submarca.
Para esto, utilizamos str_remove() dentro de un mutate() de la siguiente manera:
ventas_papitas_sku %>%
mutate(submarca = str_remove(string = sku, pattern = " \\(.*\\)"))
## # A tibble: 576 x 4
## fecha sku ventas submarca
## <date> <chr> <dbl> <chr>
## 1 2018-01-01 Sabritas Adobadas (40 grs) 333. Sabritas Adobadas
## 2 2018-01-01 Sabritas Adobadas (100 grs) 143. Sabritas Adobadas
## 3 2018-01-01 Sabritas Adobadas (120 grs) 405. Sabritas Adobadas
## 4 2018-01-01 Sabritas Limon (40 grs) 298. Sabritas Limon
## 5 2018-01-01 Sabritas Limon (100 grs) 128. Sabritas Limon
## 6 2018-01-01 Sabritas Limon (120 grs) 389. Sabritas Limon
## 7 2018-01-01 Sabritas Flamin Hot (40 grs) 657. Sabritas Flamin Hot
## 8 2018-01-01 Sabritas Flamin Hot (100 grs) 394. Sabritas Flamin Hot
## 9 2018-01-01 Sabritas Flamin Hot (120 grs) 402. Sabritas Flamin Hot
## 10 2018-01-01 Sabritas Habanero (40 grs) 481. Sabritas Habanero
## # ... with 566 more rows
Esta instrucción se lee como: Crea una nueva columna del resultado de remover todo lo que esté entre paréntesis de los valores de la columna sku.
¿Pero cómo es que ahí dice “todo lo que está entre paréntesis”, si sólo hay símbolos raros?
Buena pregunta.
Todo lo que está escrito en el argumento pattern es una expresión regular o regular expression, abreviado como REGEX. Regex es una especie de lenguaje, una serie de símbolos con distintos signficados, que sirven para describir diversos patrones que se pueden encontrar en un texto. Como en el caso de nuestro ejemplo, " \\(.*\\)" significa un espacio, seguido de caracteres entre paréntesis.
A simple vista puede verse complicado, pero si analizamos cada parte de la expresión, verás que es más sencillo:
: Parece que no hay nada, pero ahí hay un espacio. Es importante especificar cada parte de la expresión.
\\(: Un paréntesis abierto. Las dos diagonales le dicen a R que busquen literalmente el paréntesis, pues en el lenguaje de Regex, un paréntesis solito (sin las diagonales) representan otra cosa.
.: Ese puntito es un comodín. Puede ser cualquier letra del abecedario, minúscula o mayúscula, o bien, cualquier dígito. A-Z, a-z, 0-9.
*: El asterisco es un multiplicador. Quiere decir que el símbolo que esté a su izquierda, en este caso el comodín, puede aparecer un número indefinido de veces.
\\): Paréntesis cerrado. De igual manera que con el abierto, las díagonales indican que se debe buscar un paréntesis literal.
Poniendo todo junto, vemos que el pattern buscado es un espacio, seguido de un paréntesis abierto, seguido de un número indefinido de caracteres alfanuméricos, (en este caso sirve para buscar los 40 grs, 100 grs, 120 grs), seguido de un paréntesis cerrado. Como dicho pattern está dentro de la función str_remove(), todo lo que coincida con ese patrón sera eliminado.
De nuevo, es fácil ver cómo funciona en un vector:
sku_vect <- ventas_papitas_sku %>%
distinct(sku) %>%
pull(sku)
sku_vect
## [1] "Sabritas Adobadas (40 grs)" "Sabritas Adobadas (100 grs)"
## [3] "Sabritas Adobadas (120 grs)" "Sabritas Limon (40 grs)"
## [5] "Sabritas Limon (100 grs)" "Sabritas Limon (120 grs)"
## [7] "Sabritas Flamin Hot (40 grs)" "Sabritas Flamin Hot (100 grs)"
## [9] "Sabritas Flamin Hot (120 grs)" "Sabritas Habanero (40 grs)"
## [11] "Sabritas Habanero (100 grs)" "Sabritas Habanero (120 grs)"
## [13] "Sabritas Crema y especias (40 grs)" "Sabritas Crema y especias (100 grs)"
## [15] "Sabritas Crema y especias (120 grs)" "Doritos Nacho (40 grs)"
## [17] "Doritos Nacho (100 grs)" "Doritos Nacho (120 grs)"
## [19] "Doritos Diablo (40 grs)" "Doritos Diablo (100 grs)"
## [21] "Doritos Diablo (120 grs)" "Doritos Flamin Hot (40 grs)"
## [23] "Doritos Flamin Hot (100 grs)" "Doritos Flamin Hot (120 grs)"
## [25] "Doritos 3D (40 grs)" "Doritos 3D (100 grs)"
## [27] "Doritos 3D (120 grs)" "Doritos Incognita (40 grs)"
## [29] "Doritos Incognita (100 grs)" "Doritos Incognita (120 grs)"
## [31] "Doritos Pizzerolas (40 grs)" "Doritos Pizzerolas (100 grs)"
## [33] "Doritos Pizzerolas (120 grs)" "Cheetos Crunchy (40 grs)"
## [35] "Cheetos Crunchy (100 grs)" "Cheetos Crunchy (120 grs)"
## [37] "Cheetos Flamin Hot (40 grs)" "Cheetos Flamin Hot (100 grs)"
## [39] "Cheetos Flamin Hot (120 grs)" "Cheetos Poffs (40 grs)"
## [41] "Cheetos Poffs (100 grs)" "Cheetos Poffs (120 grs)"
## [43] "Cheetos Torciditos (40 grs)" "Cheetos Torciditos (100 grs)"
## [45] "Cheetos Torciditos (120 grs)" "Cheetos Colmillos (40 grs)"
## [47] "Cheetos Colmillos (100 grs)" "Cheetos Colmillos (120 grs)"
str_remove(sku_vect, " \\(.*\\)")
## [1] "Sabritas Adobadas" "Sabritas Adobadas"
## [3] "Sabritas Adobadas" "Sabritas Limon"
## [5] "Sabritas Limon" "Sabritas Limon"
## [7] "Sabritas Flamin Hot" "Sabritas Flamin Hot"
## [9] "Sabritas Flamin Hot" "Sabritas Habanero"
## [11] "Sabritas Habanero" "Sabritas Habanero"
## [13] "Sabritas Crema y especias" "Sabritas Crema y especias"
## [15] "Sabritas Crema y especias" "Doritos Nacho"
## [17] "Doritos Nacho" "Doritos Nacho"
## [19] "Doritos Diablo" "Doritos Diablo"
## [21] "Doritos Diablo" "Doritos Flamin Hot"
## [23] "Doritos Flamin Hot" "Doritos Flamin Hot"
## [25] "Doritos 3D" "Doritos 3D"
## [27] "Doritos 3D" "Doritos Incognita"
## [29] "Doritos Incognita" "Doritos Incognita"
## [31] "Doritos Pizzerolas" "Doritos Pizzerolas"
## [33] "Doritos Pizzerolas" "Cheetos Crunchy"
## [35] "Cheetos Crunchy" "Cheetos Crunchy"
## [37] "Cheetos Flamin Hot" "Cheetos Flamin Hot"
## [39] "Cheetos Flamin Hot" "Cheetos Poffs"
## [41] "Cheetos Poffs" "Cheetos Poffs"
## [43] "Cheetos Torciditos" "Cheetos Torciditos"
## [45] "Cheetos Torciditos" "Cheetos Colmillos"
## [47] "Cheetos Colmillos" "Cheetos Colmillos"
Aunque la idea general de REGEX es muy sencilla, existen muchos símbolos y trucos que pueden hacer la vida más sencilla. No es necesario que aprendas todos, pero sí es muy útil saber que este recurso existe y está a tu disposición, si algún dia lo necesitas.