Rutas
/
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 cuanto a tipo.
  • Las listas son unidimensionales y sus elementos pueden ser de tipos heterogéneos.
  • Las matrices y los arrays son multidimensionales y homogéneos.

En este concepto veremos formas de almacenar datos multidimensionales y heterogéneos. En la práctica, la mayoría de los datos del mundo real son así, de modo que ahora llegamos al núcleo de cómo se usa R (en su mayor parte) en la práctica.

Variantes de dataframe

A lo largo de las décadas, R ha añadido varios 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 el R base, un data.frame es una list de vectors de la misma longitud. Se puede concebir como una tabla rectangular de datos en la que cada columna es homogénea, pero cada fila puede contener (y normalmente contiene) tipos de datos diferentes.

Un ejemplo para ilustrarlo:

# 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, una columna de números y una columna de booleanos. Cuando se amplía la escala, 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, junto con los cambios en la forma de usar R, llevaron a un rediseño que creó una alternativa modernizada en el Tidyverse: los tibbles.

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

  • Valores predeterminados distintos, para reducir problemas habituales.
  • Menos disposición a forzar la conversión de tipos de datos durante la entrada.
  • Mensajes de error más numerosos y más claros.
  • Formatos de visualización distintos y, por lo general, mejores.

En resumen, un tibble pretende «hacer menos y quejarse más», algo que también se describe como «perezoso y gruñón».

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

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

Para el trabajo nuevo, usar tibbles probablemente te ayudará a crear código más robusto. Sin embargo, el código y los datos heredados abundan en el mundo de R, así que es probable que el data.frame siga siendo habitual 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      

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

Trabajar con tibbles

Los tibbles son una parte fundamental del Tidyverse, así que añádelos con library(tibble) o con library(tidyverse).

Crear un tibble

De la forma más sencilla, podemos usar la función tibble() para unir vectores columna, igual que con data.frame(). En una sección anterior se mostró un ejemplo de esto.

Si te resulta más cómodo introducir los valores por filas, la función correspondiente es tribble().

En la práctica, hay decenas de formas de crear tibbles, ya que son el formato de salida predeterminado de una amplia variedad de funciones del Tidyverse.

Manipular un tibble

En el concepto de Programación funcional se habló de la librería purrr para manipular vectores y listas (estructuras de datos unidimensionales).

Para los dataframes (ya sean tradicionales o tibbles), la librería que corresponde usar es dplyr.

Subconjuntos

Los dataframes, incluidos los tibbles, se pueden tratar como listas de vectores columna, así que la indexación de listas recupera una columna concreta.

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

También se puede indexar un dataframe con indexación al estilo de las matrices.

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 suelen ser más flexibles y cómodas, y serán el centro de atención durante el resto de este concepto.

Operaciones por columnas

Obtén una sola columna con pull(), indicando el nombre o el número secuencial (los números negativos cuentan 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 que encaja bien en un pipeline.

Para obtener varias columnas, la función adecuada es select(), que es muy versátil. Obtén (o descarta) columnas según las propiedades de su nombre o su 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 varios criterios, usando los operadores booleanos &, | y ! (y, o, no).

Tanto poder parece un poco absurdo con nuestro dataframe de juguete de lenguajes. Por suerte, el tibble starwars está incluido en dplyr, lo que nos da algo más grande con lo que 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 las filas que cumplen ciertos criterios 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 filtrado pueden ser arbitrariamente complejos, pero siempre se basan en el contenido de las filas.

Si se conocen los números de fila, podemos usar varias 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 comportamiento predeterminado de copiar al modificar hace que el tibble original normalmente no cambie.

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

Los nombres de las columnas se pueden cambiar con rename(newname = oldname), o con rename_with() para aplicar una función. Fíjate en 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 de forma predeterminada a la posición o posiciones más a la izquierda, 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 columnas nuevas que son funciones de columnas existentes.
  • Reemplazar una columna existente, creando una columna nueva con el mismo nombre.
  • Eliminar una columna, asignando a su valor NULL.

Está claro que mutate() es potente, puede resultar confuso y es un motivo para estar muy agradecido por el copiar al modificar.

No hay ninguna razón evidente para interesarse 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 habituales para modificar un solo tibble (la combinación de varios tibbles se tratará en un concepto posterior).

Una excepción: arrange() ordena las filas según los valores de una o varias 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 cerezal suele tener un pequeño inventario de árboles sobrantes que le gustaría ofrecer como madera. Esto ayuda a mantener el cerezal sano y, al mismo tiempo, aporta ingresos adicionales. Te han pedido que ayudes a programar cierta funcionalidad que sirva de apoyo en el proceso de selección de árboles cuando llama un posible cliente.

Note

La biblioteca datasets integrada incluye muchos conjuntos de datos de juguete, y este ejercicio usa el conjunto de datos trees. Para llamar al conjunto de datos, basta con usar 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 puede que quieras trabajar con él como tibble.

1. Renombra una columna

Por desgracia, hubo un error administrativo que nunca se ha corregido y una columna del conjunto de datos se llama Girth, cuando en realidad se trata del Diameter.

Define un data.frame/tibble llamado tree_data que corrija el nombre de la columna Girth de 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. Añade las columnas de perímetro y peso

El perímetro (también conocido como circunferencia) sigue siendo una medida útil, y el peso de la madera también. El perímetro 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 recibe 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 adecuado 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 cerezal

Para cada posible cliente, el cerezal 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, al principio y está ordenado por Weight.

Define la función orchard_copy(data, important_cols), que recibe un dataframe como entrada. Debe devolver un nuevo dataframe con Weight y Height al principio 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 basada en sus preferencias. Para ello, 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 para el cliente del dataframe con las columnas Height, Weight, Diameter y Girth y con las filas que cumplen 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 pestaña nueva
R Exercism

¿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.