Capítulo 4 tidyverse
Hasta ahora hemos estado manipulando vectores reordenándolos y creando subconjuntos mediante la indexación. Sin embargo, una vez comencemos los análisis más avanzados, la unidad preferida para el almacenamiento de datos no es el vector sino el data frame. En este capítulo aprenderemos a trabajar directamente con data frames, que facilitan enormemente la organización de información. Utilizaremos data frames para la mayoría de este libro. Nos enfocaremos en un formato de datos específico denominado tidy y en una colección específica de paquetes que son particularmente útiles para trabajar con data tidy y que se denomina el tidyverse.
Podemos cargar todos los paquetes del tidyverse a la vez al instalar y cargar el paquete tidyverse:
library(tidyverse)
Aprenderemos cómo implementar el enfoque tidyverse a lo largo del libro, pero antes de profundizar en los detalles, en este capítulo presentamos algunos de los aspectos más utilizadas del tidyverse, comenzando con el paquete dplyr para manipular los data frames y el paquete purrr para trabajar con las funciones. Tengan en cuenta que el tidyverse también incluye un paquete para graficar, ggplot2, que presentaremos más adelante en el Capítulo 7 en la parte de visualización de datos del libro, el paquete readr discutido en el Capítulo 5 y muchos otros. En este capítulo, primero presentamos el concepto de datos tidy y luego demostramos cómo usamos el tidyverse para trabajar con data frames en este formato.
4.1 Datos tidy
Decimos que una tabla de datos está en formato tidy si cada fila representa una observación y las columnas representan las diferentes variables disponibles para cada una de estas observaciones. El set de datos murders
es un ejemplo de un data frame tidy.
#> state abb region population total
#> 1 Alabama AL South 4779736 135
#> 2 Alaska AK West 710231 19
#> 3 Arizona AZ West 6392017 232
#> 4 Arkansas AR South 2915918 93
#> 5 California CA West 37253956 1257
#> 6 Colorado CO West 5029196 65
Cada fila representa un estado con cada una de las cinco columnas proveyendo una variable diferente relacionada con estos estados: nombre, abreviatura, región, población y total de asesinatos.
Para ver cómo se puede proveer la misma información en diferentes formatos, consideren el siguiente ejemplo:
#> country year fertility
#> 1 Germany 1960 2.41
#> 2 South Korea 1960 6.16
#> 3 Germany 1961 2.44
#> 4 South Korea 1961 5.99
#> 5 Germany 1962 2.47
#> 6 South Korea 1962 5.79
Este set de datos tidy ofrece tasas de fertilidad para dos países a lo largo de los años. Se considera un set de datos tidy porque cada fila presenta una observación con las tres variables: país, año y tasa de fecundidad. Sin embargo, este set de datos originalmente vino en otro formato y le cambiamos la forma para distribuir a través del paquete dslabs. Originalmente, los datos estaban en el siguiente formato:
#> country 1960 1961 1962
#> 1 Germany 2.41 2.44 2.47
#> 2 South Korea 6.16 5.99 5.79
Se provee la misma información, pero hay dos diferencias importantes en el formato: 1) cada fila incluye varias observaciones y 2) una de las variables, año, se almacena en el encabezado. Para que los paquetes del tidyverse se utilicen de manera óptima, le tenemos que cambiar la forma a los datos para que estén en formato tidy, que aprenderán a hacer en la sección “Wrangling de datos” del libro. Hasta entonces, utilizaremos ejemplos de sets de datos que ya están en formato tidy.
Aunque no es inmediatamente obvio, a medida que avancen en el libro comenzarán a apreciar las ventajas de trabajar usando un acercamiento en el que las funciones usan formatos tidy tanto para inputs como para outputs. Verán cómo esto permite que los analistas de datos se enfoquen en los aspectos más importantes del análisis en lugar del formato de los datos.
4.2 Ejercicios
1. Examine el set de datos co2
incluidos en base R. ¿Cuál de los siguientes es cierto?
co2
son datos tidy: tiene un año para cada fila.co2
no es tidy: necesitamos al menos una columna con un vector de caracteres.co2
no es tidy: es una matriz en lugar de un data frame.co2
no es tidy: para ser tidy tendríamos que cambiarle la forma (wrangle it en inglés) para tener tres columnas (año, mes y valor), y entonces cada observación de CO2 tendría una fila.
2. Examine el set de datos ChickWeight
incluidos en base R. ¿Cuál de los siguientes es cierto?
ChickWeight
no es tidy: cada pollito tiene más de una fila.ChickWeight
es tidy: cada observación (un peso) está representada por una fila. El pollito de donde provino esta medida es una de las variables.ChickWeight
no es tidy: nos falta la columna del año.ChickWeight
es tidy: se almacena en un data frame.
3. Examine el set de datos predefinido BOD
. ¿Cuál de los siguientes es cierto?
BOD
no es tidy: solo tiene seis filas.BOD
no es tidy: la primera columna es solo un índice.BOD
es tidy: cada fila es una observación con dos valores (tiempo y demanda)BOD
es tidy: todos los sets de datos pequeños son tidy por definición.
4. ¿Cuál de los siguientes sets de datos integrados es tidy? Puede elegir más de uno.
BJsales
EuStockMarkets
DNase
Formaldehyde
Orange
UCBAdmissions
4.3 Cómo manipular los data frames
El paquete dplyr del tidyverse ofrece funciones que realizan algunas de las operaciones más comunes cuando se trabaja con data frames y usa nombres para estas funciones que son relativamente fáciles de recordar. Por ejemplo, para cambiar la tabla de datos agregando una nueva columna, utilizamos mutate
. Para filtrar la tabla de datos a un subconjunto de filas, utilizamos filter
. Finalmente, para subdividir los datos seleccionando columnas específicas, usamos select
.
4.3.1 Cómo añadir una columna con mutate
Queremos que toda la información necesaria para nuestro análisis se incluya en la tabla de datos. Entonces, la primera tarea es añadir las tasas de asesinatos a nuestro data frame de asesinatos. La función mutate
toma el data frame como primer argumento y el nombre y los valores de la variable como segundo argumento usando la convención name = values
. Entonces, para añadir tasas de asesinatos, usamos:
library(dslabs)
data("murders")
<- mutate(murders, rate = total/ population * 100000) murders
Recuerden que aquí usamos total
y population
dentro de la función, que son objetos no definidos en nuestro espacio de trabajo. Pero, ¿por qué no recibimos un error?
Esta es una de las principales características de dplyr. Las funciones en este paquete, como mutate
, saben buscar variables en el data frame que el primer argumento les provee. En la llamada a mutate
que vemos arriba, total
tendrá los valores de murders$total
. Este enfoque hace que el código sea mucho más legible.
Podemos ver que se agrega la nueva columna:
head(murders)
#> state abb region population total rate
#> 1 Alabama AL South 4779736 135 2.82
#> 2 Alaska AK West 710231 19 2.68
#> 3 Arizona AZ West 6392017 232 3.63
#> 4 Arkansas AR South 2915918 93 3.19
#> 5 California CA West 37253956 1257 3.37
#> 6 Colorado CO West 5029196 65 1.29
Aunque hemos sobrescrito el objeto original murders
, esto no cambia el objeto que se cargó con data(murders)
. Si cargamos los datos murders
nuevamente, el original sobrescribirá nuestra versión mutada.
4.3.2 Cómo crear subconjuntos con filter
Ahora supongan que queremos filtrar la tabla de datos para mostrar solo las entradas para las cuales la tasa de asesinatos es inferior a 0.71. Para hacer esto, usamos la función filter
, que toma la tabla de datos como primer argumento y luego la declaración condicional como el segundo. Igual que con mutate
, podemos usar los nombres de variables sin comillas de murders
dentro de la función y esta sabrá que nos referimos a las columnas y no a los objetos en el espacio de trabajo.
filter(murders, rate <= 0.71)
#> state abb region population total rate
#> 1 Hawaii HI West 1360301 7 0.515
#> 2 Iowa IA North Central 3046355 21 0.689
#> 3 New Hampshire NH Northeast 1316470 5 0.380
#> 4 North Dakota ND North Central 672591 4 0.595
#> 5 Vermont VT Northeast 625741 2 0.320
4.3.3 Cómo seleccionar columnas con select
Aunque nuestra tabla de datos solo tiene seis columnas, algunas tablas de datos incluyen cientos. Si queremos ver solo algunas columnas, podemos usar la función select
de dplyr. En el siguiente código, seleccionamos tres columnas, asignamos el resultado a un nuevo objeto y luego filtramos este nuevo objeto:
<- select(murders, state, region, rate)
new_table filter(new_table, rate <= 0.71)
#> state region rate
#> 1 Hawaii West 0.515
#> 2 Iowa North Central 0.689
#> 3 New Hampshire Northeast 0.380
#> 4 North Dakota North Central 0.595
#> 5 Vermont Northeast 0.320
En la llamada a select
, el primer argumento murders
es un objeto, pero state
, region
y rate
son nombres de variables.
4.4 Ejercicios
1. Cargue el paquete dplyr y el set de datos de asesinatos de EE.UU.
library(dplyr)
library(dslabs)
data(murders)
Puede añadir columnas usando la función mutate
de dplyr. Esta función reconoce los nombres de la columnas y dentro de la función puede llamarlos sin comillas:
<- mutate(murders, population_in_millions = population/ 10^6) murders
Podemos escribir population
en vez de murders$population
. La función mutate
sabe que estamos agarrando columnas de murders
.
Use la función mutate
para añadir una columna de asesinatos llamada rate
con la tasa de asesinatos por 100,000 como en el código del ejemplo anterior. Asegúrese de redefinir murders
como se hizo en el código del ejemplo anterior (murders <- [su código]) para que podamos seguir usando esta variable.
2. Si rank(x)
le da el rango de las entradas de x
de menor a mayor, rank(-x)
le da los rangos de mayor a menor. Use la función mutate
para añadir una columna rank
que contiene el rango de la tasa de asesinatos de mayor a menor. Asegúrese de redefinir murders
para poder seguir usando esta variable.
3. Con dplyr, podemos usar select
para mostrar solo ciertas columnas. Por ejemplo, con este código solo mostraríamos los estados y los tamaños de población:
select(murders, state, population) |> head()
Utilice select
para mostrar los nombres de los estados y las abreviaturas en murders
. No redefina murders
, solo muestre los resultados.
4. La función filter
de dplyr se utiliza para elegir filas específicas del data frame para guardar. A diferencia de select
que es para columnas, filter
es para filas. Por ejemplo, puede mostrar solo la fila de Nueva York así:
filter(murders, state == "New York")
Puede usar otros vectores lógicos para filtrar filas.
Utilice filter
para mostrar los cinco estados con las tasas de asesinatos más altas. Después de añadir la tasa y el rango de asesinatos, no cambie el set de datos de asesinatos de EE. UU., solo muestre el resultado. Recuerde que puede filtrar basándose en la columna rank
.
5. Podemos eliminar filas usando el operador !=
. Por ejemplo, para eliminar Florida, haríamos esto:
<- filter(murders, state != "Florida") no_florida
Cree un nuevo data frame con el nombre no_south
que elimina los estados del sur. ¿Cuántos estados hay en esta categoría? Puede usar la función nrow
para esto.
6. También podemos usar %in%
para filtrar con dplyr. Por lo tanto, puede ver los datos de Nueva York y Texas de esta manera:
filter(murders, state %in% c("New York", "Texas"))
Cree un nuevo data frame llamado murders_nw
con solo los estados del noreste y oeste. ¿Cuántos estados hay en esta categoría?
7. Suponga que desea vivir en el noreste u oeste y desea que la tasa de asesinatos sea inferior a 1. Queremos ver los datos de los estados que satisfacen estas opciones. Tenga en cuenta que puede usar operadores lógicos con filter
. Aquí hay un ejemplo en el que filtramos para mantener solo estados pequeños en la región noreste.
filter(murders, population < 5000000 & region == "Northeast")
Asegúrese que murders
ha sido definido con rate
y rank
y todavía tiene todos los estados. Cree una tabla llamada my_states
que contiene filas para los estados que satisfacen ambas condiciones: está localizado en el noreste u oeste y la tasa de asesinatos es inferior a 1. Use select
para mostrar solo el nombre del estado, la tasa y el rango.
4.5 El pipe: |>
o %>%
En R podemos realizar una serie de operaciones, por ejemplo select
y entonces filter
, enviando los resultados de una función a otra usando lo que se llama el pipe operator: |>
. Esta función se hizo disponible a partir de la version 4.1.0 de R, pero antes de esto el tidyverse usaba el operador %>%
del paquete magrittr
. Algunos detalles se incluyen a continuación.
Escribimos el código anterior para mostrar tres variables (estado, región, tasa) para los estados que tienen tasas de asesinatos por debajo de 0.71. Para hacer esto, definimos el objeto intermedio new_table
. En dplyr, podemos escribir código que se parece más a una descripción de lo que queremos hacer sin objetos intermedios:
\[ \mbox {original data } \rightarrow \mbox { select } \rightarrow \mbox { filter } \]
Para tal operación, podemos usar el pipe |>
. El código se ve así:
|> select(state, region, rate) |> filter(rate <= 0.71)
murders #> state region rate
#> 1 Hawaii West 0.515
#> 2 Iowa North Central 0.689
#> 3 New Hampshire Northeast 0.380
#> 4 North Dakota North Central 0.595
#> 5 Vermont Northeast 0.320
Esta línea de código es equivalente a las dos líneas de código anteriores. ¿Qué está pasando aquí?
En general, el pipe envía el resultado que se encuentra en el lado izquierdo del pipe para ser el primer argumento de la función en el lado derecho del pipe. Aquí vemos un ejemplo sencillo:
16 |> sqrt()
#> [1] 4
Podemos continuar canalizando (piping en inglés) valores a lo largo de:
16 |> sqrt() |> log2()
#> [1] 2
La declaración anterior es equivalente a log2(sqrt(16))
.
Recuerden que el pipe envía valores al primer argumento, por lo que podemos definir otros argumentos como si el primer argumento ya estuviera definido:
16 |> sqrt() |> log(base = 2)
#> [1] 2
Por lo tanto, al usar el pipe con data frames y dplyr, ya no necesitamos especificar el primer argumento requerido puesto que las funciones dplyr que hemos descrito toman todos los datos como el primer argumento. En el código que escribimos:
|> select(state, region, rate) |> filter(rate <= 0.71) murders
murders
es el primer argumento de la función select
y el nuevo data frame (anteriormente new_table
) es el primer argumento de la función filter
.
Tengan en cuenta que el pipe funciona bien con las funciones donde el primer argumento son los datos de entrada. Las funciones en los paquetes tidyverse y dplyr tienen este formato y se pueden usar fácilmente con el pipe.
4.6 Ejercicios
1. El pipe |>
se puede usar para realizar operaciones secuencialmente sin tener que definir objetos intermedios. Comience redefiniendo murders para incluir la tasa y el rango.
<- mutate(murders, rate = total/ population * 100000,
murders rank = rank(-rate))
En la solución al ejercicio anterior, hicimos lo siguiente:
<- filter(murders, region %in% c("Northeast", "West") &
my_states < 1)
rate
select(my_states, state, rate, rank)
El pipe |>
nos permite realizar ambas operaciones secuencialmente sin tener que definir una variable intermedia my_states
. Por lo tanto, podríamos haber mutado y seleccionado en la misma línea de esta manera:
mutate(murders, rate = total/ population * 100000,
rank = rank(-rate)) |>
select(state, rate, rank)
Note que select
ya no tiene un data frame como primer argumento. Se supone que el primer argumento sea el resultado de la operación realizada justo antes de |>
.
Repita el ejercicio anterior, pero ahora, en lugar de crear un nuevo objeto, muestre el resultado y solo incluya las columnas de estado, velocidad y rango. Use un pipe |>
para hacer esto en una sola línea.
2. Reinicie murders
a la tabla original usando data(murders)
. Use un pipe para crear un nuevo data frame llamado my_states
que considera solo los estados del noreste u oeste que tienen una tasa de asesinatos inferior a 1 y contiene solo las columnas de estado, tasa y rango. El pipe también debe tener cuatro componentes separados por tres |>
. El código debería verse algo similar a lo siguiente:
<- murders |>
my_states |>
mutate SOMETHING |>
filter SOMETHING select SOMETHING
4.7 Cómo resumir datos
Una parte importante del análisis exploratorio de datos es resumir los datos. El promedio y la desviación estándar son dos ejemplos de estadísticas de resumen ampliamente utilizadas. A menudo se pueden obtener resúmenes más informativos dividiendo primero los datos en grupos. En esta sección, cubrimos dos nuevos verbos de dplyr que facilitan estos cálculos: summarize
y group_by
. Aprendemos a acceder a los valores resultantes utilizando la función pull
.
4.7.1 summarize
La función summarize
de dplyr ofrece una forma de calcular estadísticas de resumen con código intuitivo y legible. Comenzamos con un ejemplo sencillo basado en alturas. El set de datos heights
incluye las alturas y el sexo reportado por los estudiantes en una encuesta en clase.
library(dplyr)
library(dslabs)
data(heights)
El siguiente código calcula el promedio y la desviación estándar para las hembras:
<- heights |>
s filter(sex == "Female") |>
summarize(average = mean(height), standard_deviation = sd(height))
s#> average standard_deviation
#> 1 64.9 3.76
Esto toma nuestra tabla de datos original como entrada, la filtra para incluir solo a las filas representando hembras y luego produce una nueva tabla resumida con solo el promedio y la desviación estándar de las alturas. Podemos elegir los nombres de las columnas de la tabla resultante. Por ejemplo, arriba decidimos usar average
y standard_deviation
, pero podríamos haber usado otros nombres de la misma manera.
Como la tabla resultante almacenada en s
es un data frame, podemos acceder a los componentes con el operador de acceso $
:
$average
s#> [1] 64.9
$standard_deviation
s#> [1] 3.76
Igual que con la mayoría de las otras funciones de dplyr, summarize
conoce los nombres de las variables y podemos usarlos directamente. Entonces, cuando escribimos mean(height)
dentro de la llamada a la función summarize
, la función accede a la columna con el nombre “height”, o altura, y luego calcula el promedio del vector numérico resultante. Podemos calcular cualquier otro resumen que opera en vectores y devuelve un solo valor.
Para otro ejemplo de cómo podemos usar la función summarize
, calculemos la tasa promedio de asesinatos en Estados Unidos. Recuerden que nuestra tabla de datos incluye los asesinatos totales y el tamaño de la población para cada estado y ya hemos usado dplyr para añadir una columna de índice de asesinatos:
<- murders |> mutate(rate = total/population*100000) murders
Recuerden que la tasa de asesinatos en EE. UU. no es el promedio de las tasas de asesinatos estatales:
summarize(murders, mean(rate))
#> mean(rate)
#> 1 2.78
Esto se debe a que en el cálculo anterior, los estados pequeños tienen el mismo peso que los grandes. La tasa de asesinatos de Estados Unidos es el número total de asesinatos en Estados Unidos dividido por la población total. Entonces el cálculo correcto es:
<- murders |>
us_murder_rate summarize(rate = sum(total)/ sum(population) * 100000)
us_murder_rate#> rate
#> 1 3.03
Este cálculo cuenta estados más grandes proporcionalmente a su tamaño, lo que da como resultado un valor mayor.
4.7.2 Resúmenes múltiples
Supongamos que queremos tres resúmenes de la misma variable, como las alturas mediana, mínima y máxima. La función quantile
: quantile(x, c(0.5, 0, 1))
devuelve la mediana (percentil 50), el mínimo (percentil 0) y el máximo (percentil 100) del vector x
. Podemos usarlo con summarize
así:
|>
heights filter(sex == "Female") |>
summarize(median_min_max = quantile(height, c(0.5, 0, 1)))
#> median_min_max
#> 1 65
#> 2 51
#> 3 79
Sin embargo, observe que los resúmenes se devuelven en una fila cada uno. Para obtener los resultados en diferentes columnas, tenemos que definir una función que devuelva un marco de datos como este:
<- function(x){
median_min_max <- quantile(x, c(0.5, 0, 1))
qs data.frame(median = qs[1], minimum = qs[2], maximum = qs[3])
}|>
heights filter(sex == "Female") |>
summarize(median_min_max(height))
#> median minimum maximum
#> 1 65 51 79
En la próxima sección veremos lo útil que esto puede ser cuando resumimos por grupo.
4.7.3 Cómo agrupar y luego resumir con group_by
Una operación común en la exploración de datos es dividir primero los datos en grupos y luego calcular resúmenes para cada grupo. Por ejemplo, podemos querer calcular el promedio y la desviación estándar para las alturas de hombres y mujeres por separado. La función group_by
nos ayuda a hacer esto.
Si escribimos esto:
|> group_by(sex)
heights #> # A tibble: 1,050 × 2
#> # Groups: sex [2]
#> sex height
#> <fct> <dbl>
#> 1 Male 75
#> 2 Male 70
#> 3 Male 68
#> 4 Male 74
#> 5 Male 61
#> # … with 1,045 more rows
El resultado no se ve muy diferente de heights
, excepto que vemos Groups: sex [2]
cuando imprimimos el objeto. Aunque no es inmediatamente obvio por su apariencia, esto ahora es un data frame especial llamado un grouped data frame y las funciones de dplyr, en particular summarize
, se comportarán de manera diferente cuando actúan sobre este objeto. Conceptualmente, pueden pensar en esta tabla como muchas tablas, con las mismas columnas pero no necesariamente el mismo número de filas, apiladas juntas en un objeto. Cuando resumimos los datos después de la agrupación, esto es lo que sucede:
|>
heights group_by(sex) |>
summarize(average = mean(height), standard_deviation = sd(height))
#> # A tibble: 2 × 3
#> sex average standard_deviation
#> <fct> <dbl> <dbl>
#> 1 Female 64.9 3.76
#> 2 Male 69.3 3.61
La función summarize
aplica el resumen a cada grupo por separado.
Para ver otro ejemplo, calculemos la mediana, el mínimo y máximo de la tasa de asesinatos en las cuatro regiones del país usando la función median_min_max
definida anteriormente:
|>
murders group_by(region) |>
summarize(median_min_max(rate))
#> # A tibble: 4 × 4
#> region median minimum maximum
#> <fct> <dbl> <dbl> <dbl>
#> 1 Northeast 1.80 0.320 3.60
#> 2 South 3.40 1.46 16.5
#> 3 North Central 1.97 0.595 5.36
#> 4 West 1.29 0.515 3.63
4.8 pull
El objeto us_murder_rate
definido anteriormente representa solo un número. Sin embargo, lo estamos almacenando en un data frame:
class(us_murder_rate)
#> [1] "data.frame"
ya que, como la mayoría de las funciones de dplyr, summarize
siempre devuelve un data frame.
Esto podría ser problemático si queremos usar este resultado con funciones que requieren un valor numérico. Aquí mostramos un truco útil para acceder a los valores almacenados en los datos cuando usamos pipes: cuando un objeto de datos se canaliza (is piped en inglés), ese objeto y sus columnas se pueden acceder usando la función pull
. Para entender lo que queremos decir, miren esta línea de código:
|> pull(rate)
us_murder_rate #> [1] 3.03
Esto devuelve el valor en la columna rate
de us_murder_rate
haciéndolo equivalente a us_murder_rate$rate
.
Para obtener un número de la tabla de datos original con una línea de código, podemos escribir:
<- murders |>
us_murder_rate summarize(rate = sum(total)/ sum(population) * 100000) |>
pull(rate)
us_murder_rate#> [1] 3.03
que ahora es numérico:
class(us_murder_rate)
#> [1] "numeric"
4.9 Cómo ordenar los data frames
Al examinar un set de datos, a menudo es conveniente ordenar, numérica o alfabéticamente, basado en una o más de las columnas de la tabla. Conocemos las funciones order
y sort
, pero para ordenar tablas enteras, la función arrange
de dplyr es útil. Por ejemplo, aquí ordenamos los estados según el tamaño de la población:
|>
murders arrange(population) |>
head()
#> state abb region population total rate
#> 1 Wyoming WY West 563626 5 0.887
#> 2 District of Columbia DC South 601723 99 16.453
#> 3 Vermont VT Northeast 625741 2 0.320
#> 4 North Dakota ND North Central 672591 4 0.595
#> 5 Alaska AK West 710231 19 2.675
#> 6 South Dakota SD North Central 814180 8 0.983
Con arrange
podemos decidir cuál columna usar para ordenar. Para ver los estados por tasa de asesinatos, desde menor a mayor, organizamos por el rate
:
|>
murders arrange(rate) |>
head()
#> state abb region population total rate
#> 1 Vermont VT Northeast 625741 2 0.320
#> 2 New Hampshire NH Northeast 1316470 5 0.380
#> 3 Hawaii HI West 1360301 7 0.515
#> 4 North Dakota ND North Central 672591 4 0.595
#> 5 Iowa IA North Central 3046355 21 0.689
#> 6 Idaho ID West 1567582 12 0.766
Tengan en cuenta que el comportamiento por defecto es ordenar en orden ascendente. En dplyr, la función desc
transforma un vector para que esté en orden descendente. Para ordenar la tabla en orden descendente, podemos escribir:
|>
murders arrange(desc(rate))
4.9.1 Cómo ordenar anidadamente
Si estamos ordenando una columna cuando hay empates, podemos usar una segunda columna para romper el empate. Del mismo modo, se puede usar una tercera columna para romper empates entre la primera y la segunda, y así sucesivamente. Aquí ordenamos por region
y entonces, dentro de la región, ordenamos por tasa de asesinatos:
|>
murders arrange(region, rate) |>
head()
#> state abb region population total rate
#> 1 Vermont VT Northeast 625741 2 0.320
#> 2 New Hampshire NH Northeast 1316470 5 0.380
#> 3 Maine ME Northeast 1328361 11 0.828
#> 4 Rhode Island RI Northeast 1052567 16 1.520
#> 5 Massachusetts MA Northeast 6547629 118 1.802
#> 6 New York NY Northeast 19378102 517 2.668
4.9.2 Los primeros \(n\)
En el código anterior, usamos la función head
para evitar que la página se llene con todo el set de datos. Si queremos ver una mayor proporción, podemos usar la función top_n
. Esta función toma un data frame como primer argumento, el número de filas para mostrar en el segundo y la variable para filtrar en el tercero. Aquí hay un ejemplo de cómo ver las 5 filas superiores:
|> top_n(5, rate)
murders #> state abb region population total rate
#> 1 District of Columbia DC South 601723 99 16.45
#> 2 Louisiana LA South 4533372 351 7.74
#> 3 Maryland MD South 5773552 293 5.07
#> 4 Missouri MO North Central 5988927 321 5.36
#> 5 South Carolina SC South 4625364 207 4.48
Tengan en cuenta que las filas no están ordenadas por rate
, solo filtradas. Si queremos ordenar, necesitamos usar arrange
. Recuerden que si el tercer argumento se deja en blanco, top_n
filtra por la última columna.
4.10 Ejercicios
Para estos ejercicios, utilizaremos los datos de la encuesta recopilada por el Centro Nacional de Estadísticas de Salud de Estados Unidos (NCHS por sus siglas en inglés). Este centro ha realizado una serie de encuestas de salud y nutrición desde la década de 1960. A partir de 1999, alrededor de 5,000 individuos de todas las edades han sido entrevistados cada año y completan el componente de examen de salud de la encuesta. Parte de los datos está disponible a través del paquete NHANES. Una vez que instale el paquete NHANES, puede cargar los datos así:
library(NHANES)
data(NHANES)
Los datos NHANES tienen muchos valores faltantes. Las funciones mean
y sd
devolverán NA
si alguna de las entradas del vector de entrada es un NA
. Aquí hay un ejemplo:
library(dslabs)
data(na_example)
mean(na_example)
#> [1] NA
sd(na_example)
#> [1] NA
Para ignorar los NA
s, podemos usar el argumento na.rm
:
mean(na_example, na.rm = TRUE)
#> [1] 2.3
sd(na_example, na.rm = TRUE)
#> [1] 1.22
Exploremos ahora los datos de NHANES.
1. Le ofrecemos algunos datos básicos sobre la presión arterial. Primero, seleccionemos un grupo para establecer el estándar. Utilizaremos hembras de 20 a 29 años. AgeDecade
es una variable categórica con estas edades. Tenga en cuenta que la categoría está codificada como ” 20-29”, ¡con un espacio al frente! ¿Cuál es el promedio y la desviación estándar de la presión arterial sistólica según se guarda en la variable BPSysAve
? Guárdela en una variable llamada ref
.
Sugerencia: use filter
y summarize
y use el argumento na.rm = TRUE
al calcular el promedio y la desviación estándar. También puede filtrar los valores de NA utilizando filter
.
2. Usando un pipe, asigne el promedio a una variable numérica ref_avg
. Sugerencia: use el código similar al anterior y luego pull
.
3. Ahora indique los valores mínimo y máximo para el mismo grupo.
4. Calcule el promedio y la desviación estándar para las hembras, pero para cada grupo de edad por separado en lugar de una década seleccionada como en la pregunta 1. Tenga en cuenta que los grupos de edad se definen por AgeDecade
. Sugerencia: en lugar de filtrar por edad y género, filtre por Gender
y luego use group_by
.
5. Repita el ejercicio 4 para los varones.
6. Podemos combinar ambos resúmenes para los ejercicios 4 y 5 en una línea de código. Esto es porque group_by
nos permite agrupar por más de una variable. Obtenga una gran tabla de resumen usando group_by(AgeDecade, Gender)
.
7. Para los varones entre las edades de 40-49, compare la presión arterial sistólica según raza, como aparece en la variable Race1
. Ordene la tabla resultante según la presión arterial sistólica promedio de más baja a más alta.
4.11 Tibbles
Los datos tidy deben almacenarse en data frames. Discutimos el data frame en la Sección 2.4.1 y hemos estado usando el data frame murders
en todo el libro. En la sección 4.7.3, presentamos la función group_by
, que permite estratificar los datos antes de calcular las estadísticas de resumen. Pero, ¿dónde se almacena la información del grupo en el data frame?
|> group_by(region)
murders #> # A tibble: 51 × 6
#> # Groups: region [4]
#> state abb region population total rate
#> <chr> <chr> <fct> <dbl> <dbl> <dbl>
#> 1 Alabama AL South 4779736 135 2.82
#> 2 Alaska AK West 710231 19 2.68
#> 3 Arizona AZ West 6392017 232 3.63
#> 4 Arkansas AR South 2915918 93 3.19
#> 5 California CA West 37253956 1257 3.37
#> # … with 46 more rows
Observen que no hay columnas con esta información. Pero si miran el output anterior, verán la línea A tibble
seguida por unas dimensiones. Podemos aprender la clase del objeto devuelto usando:
|> group_by(region) |> class()
murders #> [1] "grouped_df" "tbl_df" "tbl" "data.frame"
El tbl
es un tipo especial de data frame. Las funciones group_by
y summarize
siempre devuelven este tipo de data frame. La función group_by
devuelve un tipo especial de tbl
, el grouped_df
. Discutiremos esto más adelante. Por coherencia, los verbos de manipulación dplyr ( select
, filter
, mutate
y arrange
) preservan la clase del input: si reciben un data frame regular, devuelven un data frame regular, mientras que si reciben un tibble, devuelven un tibble. Pero los tibbles son el formato preferido por el tidyverse y, como resultado, las funciones tidyverse que producen un data frame desde cero devuelven un tibble. Por ejemplo, en el Capítulo 5, veremos que las funciones del tidyverse que se usan para importar datos crean tibbles.
Los tibbles son muy similares a los data frames. De hecho, pueden pensar en ellos como una versión moderna de data frames. Sin embargo, hay tres diferencias importantes que describiremos a continuación.
4.11.1 Los tibbles se ven mejor
El método de impresión para tibbles es más legible que el de un data frame. Para ver esto, comparen el output de escribir murders
y el output de asesinatos si los convertimos en un tibble. Podemos hacer esto usando as_tibble(murders)
. Si usan RStudio, el output para un tibble se ajusta al tamaño de sus ventanas. Para ver esto, cambien el ancho de su consola R y observen cómo se muestran más/menos columnas.
4.11.2 Los subconjuntos de tibbles son tibbles
Si creamos subconjuntos de las columnas de un data frame, le pueden devolver un objeto que no es un data frame, como un vector o escalar. Por ejemplo:
class(murders[,4])
#> [1] "numeric"
no es un data frame. Con tibbles, esto no sucede:
class(as_tibble(murders)[,4])
#> [1] "tbl_df" "tbl" "data.frame"
Esto es útil en el tidyverse ya que las funciones requieren data frames como input.
Con tibbles, si desean acceder al vector que define una columna y no recuperar un data frame, deben usar el operador de acceso $
:
class(as_tibble(murders)$population)
#> [1] "numeric"
Una característica relacionada es que tibbles les dará una advertencia si intentan acceder a una columna que no existe. Por ejemplo, si escribimos accidentalmente Population
en lugar de population
vemos que:
$Population
murders#> NULL
devuelve un NULL
sin advertencia, lo que puede dificultar la depuración. Por el contrario, si intentamos esto con un tibble, obtenemos una advertencia informativa:
as_tibble(murders)$Population
#> Warning: Unknown or uninitialised column: `Population`.
#> NULL
4.11.3 Los tibbles pueden tener entradas complejas
Si bien las columnas del data frame deben ser vectores de números, cadenas o valores lógicos, los tibbles pueden tener objetos más complejos, como listas o funciones. Además, podemos crear tibbles con funciones:
tibble(id = c(1, 2, 3), func = c(mean, median, sd))
#> # A tibble: 3 × 2
#> id func
#> <dbl> <list>
#> 1 1 <fn>
#> 2 2 <fn>
#> 3 3 <fn>
4.11.4 Los tibbles se pueden agrupar
La función group_by
devuelve un tipo especial de tibble: un tibble agrupado. Esta clase almacena información que les permite saber qué filas están en qué grupos. Las funciones tidyverse, en particular summarize
, están al tanto de la información del grupo.
4.11.5 Cómo crear un tibble usando tibble
en lugar de data.frame
A veces es útil para nosotros crear nuestros propios data frames. Para crear un data frame en formato tibble, pueden utilizar la función tibble
.
<- tibble(names = c("John", "Juan", "Jean", "Yao"),
grades exam_1 = c(95, 80, 90, 85),
exam_2 = c(90, 85, 85, 90))
Noten que la base R (sin paquetes cargados) tiene una función con un nombre muy similar, data.frame
, que se puede usar para crear un data frame regular en vez de un tibble.
<- data.frame(names = c("John", "Juan", "Jean", "Yao"),
grades exam_1 = c(95, 80, 90, 85),
exam_2 = c(90, 85, 85, 90))
Para convertir un data frame normal en un tibble, pueden usar la función as_tibble
.
as_tibble(grades) |> class()
#> [1] "tbl_df" "tbl" "data.frame"
4.12 El marcador de posición
Una de las ventajas de utilizar el pipe |>
es que no tenemos que seguir nombrando nuevos objetos mientras manipulamos el data frame. El objeto del lado izquierdo del pipe se utiliza como primer argumento de la función del lado derecho del pipe. Pero, ¿y si queremos pasarlo como argumento a la función del lado derecho que no es la primera? La respuesta es el operador de marcador de posición _
(para la canalización %>%
, el marcador de posición es .
). A continuación se muestra un ejemplo simple que pasa el argumento base
a la función log
. Los tres siguientes son equivalentes:
<- filter(murders, region == "South")
tab_1 <- mutate(tab_1, rate = total/ population * 10^5)
tab_2 <- tab_2$rate
rates median(rates)
#> [1] 3.4
podemos evitar definir nuevos objetos intermedios escribiendo:
filter(murders, region == "South") |>
mutate(rate = total/ population * 10^5) |>
summarize(median = median(rate)) |>
pull(median)
#> [1] 3.4
4.13 El paquete purrr
En la Sección 3.5, aprendimos sobre la función sapply
, que nos permitió aplicar la misma función a cada elemento de un vector. Construimos una función y utilizamos sapply
para calcular la suma de los primeros n
enteros para varios valores de n
así:
<- function(n){
compute_s_n <- 1:n
x sum(x)
}<- 1:25
n <- sapply(n, compute_s_n) s_n
Este tipo de operación, que aplica la misma función o procedimiento a elementos de un objeto, es bastante común en el análisis de datos. El paquete purrr incluye funciones similares a sapply
, pero que interactúan mejor con otras funciones del tidyverse. La principal ventaja es que podemos controlar mejor el tipo de resultado de las funciones. En cambio, sapply
puede devolver varios tipos de objetos diferentes, convirtiéndolos cuando sea conveniente. Las funciones de purrr nunca harán esto: devolverán objetos de un tipo específico o devolverán un error si esto no es posible.
La primera función de purrr que aprenderemos es map
, que funciona muy similar a sapply
pero siempre, sin excepción, devuelve una lista:
library(purrr)
<- map(n, compute_s_n)
s_n class(s_n)
#> [1] "list"
Si queremos un vector numérico, podemos usar map_dbl
que siempre devuelve un vector de valores numéricos.
<- map_dbl(n, compute_s_n)
s_n class(s_n)
#> [1] "numeric"
Esto produce los mismos resultados que la llamada sapply
que vemos arriba.
Una función de purrr particularmente útil para interactuar con el resto del tidyverse es map_df
, que siempre devuelve un tibble data frame. Sin embargo, la función que llamamos debe devolver un vector o una lista con nombres. Por esta razón, el siguiente código daría como resultado un error Argument 1 must have names
:
<- map_df(n, compute_s_n) s_n
Necesitamos cambiar la función para arreglar esto:
<- function(n){
compute_s_n <- 1:n
x tibble(sum = sum(x))
}<- map_df(n, compute_s_n) s_n
El paquete purrr ofrece mucha más funcionalidad no discutida aquí. Para obtener más detalles, pueden consultar recursos en línea16.
4.14 Los condicionales de tidyverse
Un análisis de datos típicos frecuentemente implicará una o más operaciones condicionales. En la Sección 3.1, describimos la función ifelse
, que utilizaremos ampliamente en este libro. Ahora presentamos dos funciones de dplyr que ofrecen una funcionalidad adicional para realizar operaciones condicionales.
4.14.1 case_when
La función case_when
es útil para vectorizar declaraciones condicionales. Esto es similar a ifelse
, pero puede generar cualquier cantidad de valores, en lugar de solo TRUE
o FALSE
. Aquí hay un ejemplo que divide los números en negativo, positivo y 0:
<- c(-2, -1, 0, 1, 2)
x case_when(x < 0 ~ "Negative",
> 0 ~ "Positive",
x TRUE ~ "Zero")
#> [1] "Negative" "Negative" "Zero" "Positive" "Positive"
Un uso común de esta función es definir unas variables categóricas basadas en variables existentes. Por ejemplo, supongan que queremos comparar las tasas de asesinatos en cuatro grupos de estados: New England, West Coast, South y Other. Para cada estado, primero preguntamos si está en New England. Si la respuesta es no, entonces preguntamos si está en el West Coast, y si no, preguntamos si está en el South y, si no, entonces asignamos ninguna de las anteriores (Other). Aquí vemos como usamos case_when
para hacer esto:
|>
murders mutate(group = case_when(
%in% c("ME", "NH", "VT", "MA", "RI", "CT") ~ "New England",
abb %in% c("WA", "OR", "CA") ~ "West Coast",
abb == "South" ~ "South",
region TRUE ~ "Other")) |>
group_by(group) |>
summarize(rate = sum(total)/ sum(population) * 10^5)
#> # A tibble: 4 × 2
#> group rate
#> <chr> <dbl>
#> 1 New England 1.72
#> 2 Other 2.71
#> 3 South 3.63
#> 4 West Coast 2.90
4.14.2 between
Una operación común en el análisis de datos es determinar si un valor cae dentro de un intervalo. Podemos verificar esto usando condicionales. Por ejemplo, para verificar si los elementos de un vector x
están entre a
y b
, podemos escribir:
>= a & x <= b x
Sin embargo, esto puede volverse complicado, especialmente dentro del enfoque tidyverse. La función between
realiza la misma operación:
between(x, a, b)
4.15 Ejercicios
1. Cargue el set de datos murders
. ¿Cuál de los siguientes es cierto?
murders
está en formato tidy y se almacena en un tibble.murders
está en formato tidy y se almacena en un data frame.murders
no está en formato tidy y se almacena en un tibble.murders
no está en formato tidy y se almacena en un data frame.
2. Utilice as_tibble
para convertir la tabla de datos murders
en un tibble y guárdelo en un objeto llamado murders_tibble
.
3. Utilice la función group_by
para convertir murders
en un tibble que se agrupa por región.
4. Escriba el código tidyverse que es equivalente a este código:
exp(mean(log(murders$population)))
Escríbalo usando el pipe para que cada función se llame sin argumentos. Use el operador punto para acceder a la población. Sugerencia: el código debe comenzar con murders |>
.
5. Utilice el map_df
para crear un data frame con tres columnas que se denominan n
, s_n
y s_n_2
. La primera columna debe contener los números del 1 al 100. La segunda y la tercera columna deben contener la suma del 1 al 100 \(n\) con \(n\) representando el número de fila.