Tracks
/
R
R
/
Ejercicios
/
Árboles y tibble-aciones
Árboles y tibble-aciones

Árboles y tibble-aciones

Ejercicio de aprendizaje

Introducción

En otras partes del temario, hemos visto varios tipos de datos con características diferentes.

  • Los vectores atómicos son unidimensionales y homogéneos en su tipo.
  • Las listas son unidimensionales y sus elementos pueden ser de tipos heterogéneos.
  • Las matrices y los arrays son multidimensionales y homogéneos.

Este concepto analizará formas de almacenar datos multidimensionales y heterogéneos. En la práctica, la mayoría de los datos del mundo real son así, por lo que ahora llegamos al centro de cómo se usa R (en su mayoría) en la práctica.

Variantes de dataframe

A lo largo de las décadas, R ha agregado múltiples tipos de datos para manejar datos tabulares.

Este temario se centrará principalmente en los tibbles, pero es útil conocer las alternativas.

El data.frame

En R base, un data.frame es una list de vectors de igual longitud. Se puede pensar en él como una tabla rectangular de datos, en la que cada columna es homogénea, pero cada fila puede (y por lo general lo hace) contener diferentes tipos de datos.

Un ejemplo para ilustrar esto:

# create the column vectors
languages <- c("Fortran", "R", "Python", "Julia")
created <- c(1957, 1993, 1991, 2012)
has.syllabus <- c(FALSE, TRUE, TRUE, TRUE)

# join columns to create the dataframe
df <- data.frame(languages, created, has.syllabus)
df
#>   languages created has.syllabus
#> 1   Fortran    1957        FALSE
#> 2         R    1993         TRUE
#> 3    Python    1991         TRUE
#> 4     Julia    2012         TRUE

Tenemos una columna de strings de caracteres, una columna de números y una columna de booleanos. Cuando se escala, esta es una forma intuitiva de representar muchas colecciones de datos del mundo real.

El tibble

El diseño de data.frame es antiguo.

La experiencia de varias décadas, más los patrones cambiantes de cómo se usa R, llevaron a un rediseño, creando una alternativa modernizada en el Tidyverse: los tibbles.

En comparación con R base, los tibbles tienen:

  • Valores predeterminados diferentes, para reducir problemas comunes.
  • Menos disposición a forzar tipos de datos durante la entrada.
  • Mensajes de error más numerosos y claros.
  • Formatos de visualización diferentes, generalmente mejores.

En resumen, un tibble pretende «hacer menos y quejarse más», también descrito como «perezoso y malhumorado».

Los tipos suelen ser intercambiables: cualquier función que acepte un data.frame también aceptará un tibble, y viceversa.

Las conversiones entre los tipos son fáciles, con as_tibble(df) y as.data.frame(tbl).

Para trabajo nuevo, usar tibbles probablemente te ayudará a crear código más robusto. Sin embargo, el código heredado y los datos heredados son muy abundantes en el mundo de R, por lo que es probable que data.frame siga siendo común durante mucho tiempo.

# column vectors are same as for data.frame
library(tibble)
tbl <- tibble(languages, created, has.syllabus)
tbl
  # A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 R            1993 TRUE        
#> 3 Python       1991 TRUE        
#> 4 Julia        2012 TRUE      

Observa el formato de impresión predeterminado: la línea de comentario con las dimensiones se imprime automáticamente, y los tipos de columna también se muestran.

Trabajar con tibbles

Los tibbles son una parte central del Tidyverse, así que agrégalos con library(tibble) o library(tidyverse).

Crear un tibble

De la forma más simple, podemos usar la función tibble() para unir vectores columna, como con data.frame(). Un ejemplo de esto se mostró en una sección anterior.

Si es más conveniente ingresar valores por filas, la función correspondiente es tribble().

En la práctica, hay docenas de formas de crear tibbles, ya que son el formato de salida predeterminado de una diversa gama de funciones del Tidyverse.

Manipular un tibble

El concepto de Programación Funcional discutió la biblioteca purrr para manipular vectores y listas (estructuras de datos unidimensionales).

Para los dataframes (ya sean tradicionales o tibbles), la biblioteca correspondiente a usar es dplyr.

Subconjuntos

Los dataframes, incluidos los tibbles, se pueden tratar como listas de vectores columna, por lo que la indexación de listas recupera una columna especificada.

tbl
# A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 R            1993 TRUE        
#> 3 Python       1991 TRUE        
#> 4 Julia        2012 TRUE    

tbl$created
#> [1] 1957 1993 1991 2012

Un dataframe también se puede indexar con indexación estilo matriz.

tbl[c(2, 4), 1:2]
  # A tibble: 2 × 2
#>   languages created
#>   <chr>       <dbl>
#> 1 R            1993
#> 2 Julia        2012

En el R moderno con el ecosistema Tidyverse, las funciones de dplyr son generalmente más flexibles y convenientes, y serán el enfoque durante el resto de este concepto.

Operaciones por columnas

Obtén una sola columna con pull() con el nombre o el número secuencial (números negativos para contar de derecha a izquierda).

tbl |> pull(created)
#> [1] 1957 1993 1991 2012

Este es el mismo resultado que tbl$created, pero usando una función compatible con pipelines.

Para obtener varias columnas, la función adecuada es select(), que es muy versátil. Obtén (o elimina) columnas según las propiedades de su nombre o tipo.

# Range with position and/or name
tbl |> select(1:created)
  # A tibble: 4 × 2
#>   languages created
#>   <chr>       <dbl>
#> 1 Fortran      1957
#> 2 R            1993
#> 3 Python       1991
#> 4 Julia        2012      

# Use type of column
tbl |> select(where(is.numeric))
  # A tibble: 4 × 1
#>   created
#>     <dbl>
#> 1    1957
#> 2    1993
#> 3    1991
#> 4    2012

Se permiten múltiples criterios, usando operadores Boolean &, | y ! (y, o no).

Tal poder parece bastante absurdo con nuestro dataframe de juguete de lenguajes. Afortunadamente, el tibble starwars está incluido en dplyr, dándonos algo más grande con qué practicar.

# pick a subset of columns
starwars |> 
  select(name | ends_with("color")) |> 
  head(5)
  # A tibble: 5 × 4
#>   name           hair_color skin_color  eye_color
#>   <chr>          <chr>      <chr>       <chr>    
#> 1 Luke Skywalker blond      fair        blue     
#> 2 C-3PO          NA         gold        yellow   
#> 3 R2-D2          NA         white, blue red      
#> 4 Darth Vader    none       white       yellow   
#> 5 Leia Organa    brown      light       brown    

Operaciones por filas

Obtén filas que coincidan con algún criterio con filter(), o exclúyelas con filter_out().

starwars |> 
  select(name:mass) |> 
  filter(between(height, 150, 165) & !is.na(mass))
  # A tibble: 4 × 3
#>   name               height  mass
#>   <chr>               <int> <dbl>
#> 1 Leia Organa           150    49
#> 2 Beru Whitesun Lars    165    75
#> 3 Nien Nunb             160    68
#> 4 Ben Quadinaros        163    65

Los criterios de filtro pueden ser arbitrariamente complejos, pero siempre se basan en el contenido de las filas.

Si se conocen los números de las filas, podemos usar una variedad de funciones slice().

starwars |> 
  select(name | homeworld) |> 
  slice(20:25)
  # A tibble: 6 × 2
#>   name             homeworld
#>   <chr>            <chr>    
#> 1 Palpatine        Naboo    
#> 2 Boba Fett        Kamino   
#> 3 IG-88            NA       
#> 4 Bossk            Trandosha
#> 5 Lando Calrissian Socorro  
#> 6 Lobot            Bespin   

Modificar un tibble

Primera advertencia: el valor predeterminado de copiar al modificar significa que el tibble original por lo general permanece sin cambios.

La mayoría de las modificaciones se aplican por columnas.

Los nombres de las columnas se pueden cambiar con rename(newname = oldname), o rename_with() para aplicar una función. Observa la sintaxis dentro de rename(). El contenido de la columna oldname se vincula al nombre newname, de ahí el orden.

El orden de las columnas se puede cambiar con relocate(). Las columnas especificadas se mueven a la posición más a la izquierda por defecto, pero se puede usar un argumento .before o .after para un posicionamiento más preciso.

sw <- starwars |> select(name:species) |> slice(1:4)
sw
  # A tibble: 4 × 11
#>   name           height  mass hair_color skin_color  eye_color birth_year sex   gender    homeworld species
#>   <chr>           <int> <dbl> <chr>      <chr>       <chr>          <dbl> <chr> <chr>     <chr>     <chr>  
#> 1 Luke Skywalker    172    77 blond      fair        blue            19   male  masculine Tatooine  Human  
#> 2 C-3PO             167    75 NA         gold        yellow         112   none  masculine Tatooine  Droid  
#> 3 R2-D2              96    32 NA         white, blue red             33   none  masculine Naboo     Droid  
#> 4 Darth Vader       202   136 none       white       yellow          41.9 male  masculine Tatooine  Human  

sw |> relocate(c(species, homeworld), .after = name)
  # A tibble: 4 × 11
#>   name           species homeworld height  mass hair_color skin_color  eye_color birth_year sex   gender   
#>   <chr>          <chr>   <chr>      <int> <dbl> <chr>      <chr>       <chr>          <dbl> <chr> <chr>    
#> 1 Luke Skywalker Human   Tatooine     172    77 blond      fair        blue            19   male  masculine
#> 2 C-3PO          Droid   Tatooine     167    75 NA         gold        yellow         112   none  masculine
#> 3 R2-D2          Droid   Naboo         96    32 NA         white, blue red             33   none  masculine
#> 4 Darth Vader    Human   Tatooine     202   136 none       white       yellow          41.9 male  masculine

Para cambios más grandes, mutate() te permite:

  • Crear nuevas columnas que son funciones de columnas existentes.
  • Reemplazar una columna existente, creando una nueva columna con el mismo nombre.
  • Eliminar una columna, estableciendo su valor en NULL.

Claramente, mutate() es potente, potencialmente confuso, y una razón para estar muy agradecido por la copia al modificar.

No hay una razón obvia para preocuparse por el índice de masa corporal de los personajes de Star Wars, pero por si acaso:

starwars |> 
  select(c(name, species, height, mass)) |> 
  mutate(BMI = mass / (height / 100)^2) |> 
  head(4)
  # A tibble: 4 × 5
#>   name           species height  mass   BMI
#>   <chr>          <chr>    <int> <dbl> <dbl>
#> 1 Luke Skywalker Human      172    77  26.0
#> 2 C-3PO          Droid      167    75  26.9
#> 3 R2-D2          Droid       96    32  34.7
#> 4 Darth Vader    Human      202   136  33.3

Las operaciones por filas son menos comunes para modificar tibbles individuales (la fusión de múltiples tibbles se discutirá en un concepto posterior).

Una excepción: arrange() ordena las filas por los valores de una o más columnas.

tbl
#>       A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 R            1993 TRUE        
#> 3 Python       1991 TRUE        
#> 4 Julia        2012 TRUE  

tbl |> arrange(languages)
#>       A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 Julia        2012 TRUE        
#> 3 Python       1991 TRUE        
#> 4 R            1993 TRUE     

Instrucciones

Un gran huerto de cerezos suele tener un pequeño inventario de árboles sobrantes que le gustaría ofrecer como madera. Esto ayuda a mantener el huerto saludable y, al mismo tiempo, genera ingresos extra. Te han pedido que ayudes a programar algunas funcionalidades para el proceso de selección de árboles cuando llama un posible cliente.

Note

La biblioteca datasets integrada tiene muchos conjuntos de datos de juguete, y este ejercicio usa el conjunto de datos trees. Para llamar al conjunto de datos, simplemente usa el nombre trees.

library(datasets) # loading is optional

trees |> head(3)
#>    A data.frame: 3 × 3 	
#>   Girth	Height	Volume
#>	 <dbl>	 <dbl>	 <dbl>
#> 1   8.3	    70	  10.3  
#> 2   8.6	    65	  10.3
#> 3   8.8	    63	  10.2

Fíjate en las tres columnas: Girth, Height y Volume.

El conjunto de datos está inicialmente en forma de data.frame, pero quizás quieras trabajar con él como un tibble.

1. Renombra una columna

Lamentablemente hubo un error administrativo que nunca se corrigió, y una columna del conjunto de datos se llama Girth, pero en realidad es el Diameter.

Define un data.frame/tibble llamado tree_data que corrija el nombre de la columna Girth en trees a Diameter.

head(tree_data, 3)
#>     A tibble: 3 × 3 
#> Diameter	 Height	Volume
#>    <dbl>	  <dbl>	 <dbl>
#>      8.3	     70	  10.3
#>      8.6	     65	  10.3
#>      8.8	     63	  10.2

2. Agrega las columnas Girth y Weight

La circunferencia sigue siendo una medida útil, y el peso de la madera también. La circunferencia se calcula fácilmente con πD, donde D es el diámetro. El peso de toda la madera se puede calcular con ρV, donde ρ = 35 es la densidad de la madera de cerezo y V es el volumen de la madera.

Define la función girth_n_weight(data, rnd_digits), que toma un dataframe y argumentos numéricos. La función debe devolver un nuevo dataframe con las dos columnas adicionales: Girth y Weight, redondeadas al número de dígitos que indique rnd_digits.

girth_n_weight(tree_data, 1) |> head(3)
#>           A tibble: 3 × 5 
#> Diameter  Height	 Volume	 Girth	Weight
#>    <dbl>	  <dbl>	  <dbl>	 <dbl>	 <dbl>
#>      8.3	     70	   10.3	  26.1	 360.5
#>      8.6	     65	   10.3	  27.0	 360.5
#>      8.8	     63	   10.2	  27.6	 357.0

3. Copia del conjunto de datos para el huerto

Para cada posible cliente, el huerto guarda una copia especial del conjunto de datos que ayuda con la venta. Este conjunto de datos tiene las columnas más relevantes, Weight y Height, movidas al frente y está ordenado por Weight.

Define la función orchard_copy(data, important_cols), que toma un dataframe como argumento. Debe devolver un nuevo dataframe con Weight y Height al frente y las filas ordenadas por Weight.

tree_data |> girth_n_weight() |> orchard_copy() |> head(3)
#>            A tibble: 3 × 5 
#> Weight	Height	Diameter	Volume	Girth
#>  <dbl>	 <dbl>	   <dbl>	 <dbl>	<dbl>
#>    357	    63	       9	    10	   28
#>    360	    70	       8	    10	   26
#>    360	    65	       9	    10	   27

4. Copia del conjunto de datos para el cliente

Cada posible cliente recibirá una versión personal del conjunto de datos según sus preferencias. Para eso, el cliente puede especificar: la altura mínima, la altura máxima y el peso máximo de un solo árbol.

Define la función customer_copy(data, min_height, max_height, max_weight). Devuelve una copia del dataframe para el cliente con las columnas Height, Weight, Diameter y Girth, con las filas que estén dentro de las restricciones.

tree_data |> 
    girth_n_weight(1) |> 
    orchard_copy() |>
    customer_copy(65, 75, 1500) |> 
    head(3)
#>        A tibble: 3 × 4 
#> Height	Weight	Diameter	Girth
#>  <dbl>	 <dbl>	   <dbl>	<dbl>
#>     70	 360.5	     8.3	 26.1
#>     65	 360.5	     8.6	 27.0
#>     66	 546.0	    11.0	 34.6
Editar en GitHub El enlace se abre en una ventana o una pestaña nuevas
R Exercism

¿Todo listo para empezar Árboles y tibble-aciones?

Regístrate en Exercism para aprender y dominar R con 21 conceptos111 ejercicios y mentoría humana real, todo gratis.