Track
/
R
R
/
Esercizi
/
Alberi e Tibble-azioni
Alberi e Tibble-azioni

Alberi e Tibble-azioni

Esercizio di apprendimento

Introduzione

In altre parti del percorso abbiamo visto vari tipi di dati con caratteristiche diverse.

  • I vettori atomici sono monodimensionali e omogenei per tipo.
  • Gli array sono monodimensionali e gli elementi possono essere di tipi eterogenei.
  • Matrici e array sono multidimensionali e omogenei.

Questo concetto esaminerà i modi per memorizzare dati multidimensionali ed eterogenei. In pratica, la maggior parte dei dati del mondo reale è così, quindi adesso arriviamo al cuore di come R viene usato (per lo più) nella pratica.

Varianti di dataframe

Nel corso dei decenni, R ha aggiunto diversi tipi di dati per gestire i dati tabellari.

Questo percorso si concentrerà principalmente sui tibble, ma è utile conoscere le alternative.

Il data.frame

Nel R base, un data.frame è un list di vectors di uguale lunghezza. Lo si può immaginare come una tabella rettangolare di dati, in cui ogni colonna è omogenea, ma ogni riga può contenere (e di solito contiene) tipi di dati diversi.

Un esempio per illustrarlo:

# 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

Abbiamo una colonna di stringhe di caratteri, una colonna di numeri e una colonna di valori booleani. Su scala più ampia, questo è un modo intuitivo per rappresentare molte raccolte di dati del mondo reale.

Il tibble

Il design del data.frame è vecchio.

L'esperienza di più decenni, insieme ai cambiamenti nel modo in cui si usa R, ha portato a una riprogettazione: nel Tidyverse è nata un'alternativa modernizzata, i tibble.

Rispetto al R base, i tibble hanno:

  • Valori predefiniti diversi, per ridurre i problemi più comuni.
  • Minore tendenza a forzare la conversione dei tipi di dati in fase di input.
  • Messaggi di errore più numerosi e più chiari.
  • Formati di visualizzazione diversi, di solito migliori.

In breve, un tibble punta a «fare meno e lamentarsi di più», descritto anche come «pigro e scontroso».

I due tipi sono di solito intercambiabili: qualsiasi funzione che accetta un data.frame accetterà anche un tibble, e viceversa.

Le conversioni tra i due tipi sono semplici, con as_tibble(df) e as.data.frame(tbl).

Per il codice nuovo, usare i tibble ti aiuterà probabilmente a scrivere codice più robusto. Tuttavia, nel mondo di R il codice e i dati ereditati sono molto abbondanti, quindi è probabile che il data.frame resti comune ancora a lungo.

# 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      

Nota il formato di stampa predefinito: la riga di commento con le dimensioni viene stampata automaticamente e vengono mostrati anche i tipi delle colonne.

Lavorare con i tibble

I tibble sono una parte fondamentale del Tidyverse: aggiungili con library(tibble) o con library(tidyverse).

Creare un tibble

Nel modo più semplice, possiamo usare la funzione tibble() per unire vettori colonna, come per data.frame(). Un esempio è stato mostrato in una sezione precedente.

Se è più comodo inserire i valori riga per riga, la funzione corrispondente è tribble().

In pratica esistono decine di modi per creare tibble, perché sono il formato di output predefinito di numerose funzioni del Tidyverse.

Manipolare un tibble

Il concetto di programmazione funzionale ha presentato la libreria purrr per manipolare vettori e array (strutture di dati monodimensionali).

Per i dataframe (tradizionali o tibble), la libreria da usare è dplyr.

Sottoinsiemi

I dataframe, compresi i tibble, possono essere trattati come array di vettori colonna, quindi indicizzandoli come array si recupera una colonna specifica.

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 può anche essere indicizzato con l'indicizzazione in stile matrice.

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

Nel R moderno con l'ecosistema Tidyverse, le funzioni di dplyr sono in genere più flessibili e comode, e saranno al centro del resto di questo concetto.

Operazioni per colonna

Ottieni una singola colonna con pull(), indicando il nome o il numero progressivo (i numeri negativi contano da destra verso sinistra).

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

È lo stesso risultato di tbl$created, ma usando una funzione adatta alle pipeline.

Per ottenere più colonne, la funzione adatta è select(), molto versatile. Ottieni (o escludi) colonne in base alle proprietà del loro nome o del loro 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

Si possono usare più criteri, con gli operatori booleani &, | e ! (and, or, not).

Tutta questa potenza sembra piuttosto ridicola con il nostro dataframe di esempio sui linguaggi. Per fortuna, il tibble starwars è incluso in dplyr e ci offre qualcosa di più grande su cui esercitarci.

# 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    

Operazioni per riga

Ottieni le righe che soddisfano un criterio con filter(), oppure escludile 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

I criteri di filtro possono essere complessi quanto si vuole, ma si basano sempre sul contenuto delle righe.

Se conosciamo i numeri delle righe, possiamo usare le varie funzioni 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   

Modificare un tibble

Prima avvertenza: il comportamento predefinito di copy-on-modify significa che il tibble originale di solito resta invariato.

La maggior parte delle modifiche si applica per colonna.

I nomi delle colonne si possono cambiare con rename(newname = oldname), oppure con rename_with() per applicare una funzione. Nota la sintassi dentro rename(). Il contenuto della colonna oldname viene associato al nome newname: da qui l'ordine.

L'ordine delle colonne si può cambiare con relocate(). Per impostazione predefinita, le colonne specificate vengono spostate nelle posizioni più a sinistra, ma si possono usare gli argomenti .before o .after per un posizionamento più 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

Per modifiche più grandi, mutate() ti permette di:

  • Creare nuove colonne che sono funzioni di colonne esistenti.
  • Sostituire una colonna esistente, creando una nuova colonna con lo stesso nome.
  • Eliminare una colonna, impostando il suo valore a NULL.

Chiaramente mutate() è potente, potenzialmente confuso, e un motivo per essere molto grati al copy-on-modify.

Non c'è un motivo evidente per interessarsi all'indice di massa corporea dei personaggi di Star Wars, ma non si sa mai:

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

Le operazioni per riga sono meno comuni quando si modifica un singolo tibble (l'unione di più tibble sarà trattata in un concetto successivo).

Un'eccezione: arrange() ordina le righe in base ai valori di una o più colonne.

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     

Istruzioni

Un grande ciliegeto ha in genere una piccola scorta di alberi in eccesso che vorrebbe offrire come legname. Questo aiuta a mantenere sano il ciliegeto e allo stesso tempo a generare entrate extra. Ti hanno chiesto di aiutare a scrivere un po' di funzionalità che sia d'aiuto nel processo di selezione degli alberi quando chiama un potenziale cliente.

Note

La libreria datasets integrata contiene molti dataset di esempio, e questo esercizio usa il dataset trees. Per richiamare il dataset, basta usare il nome 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

Nota le tre colonne: Girth, Height e Volume

Il dataset è inizialmente sotto forma di data.frame, ma potresti scegliere di lavorarci come tibble.

1. Rinominare una colonna

Purtroppo c'è stato un errore di trascrizione mai corretto, e una colonna del dataset si chiama Girth, ma in realtà è il Diameter.

Definisci un data.frame/tibble chiamato tree_data che corregga il nome della colonna Girth in trees in 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. Aggiungere le colonne Girth e Weight

Il girth (ovvero la circonferenza) resta comunque una misura utile, e lo stesso vale per il peso del legname. La circonferenza si calcola facilmente con πD, dove D è il diametro. Il peso di tutto il legname si può calcolare con ρV, dove ρ = 35 è la densità del legno di ciliegio e V è il volume del legname.

Definisci la funzione girth_n_weight(data, rnd_digits), che accetta un dataframe e argomenti numerici. La funzione deve restituire un nuovo dataframe con due colonne aggiuntive: Girth e Weight, arrotondate al numero di cifre indicato da 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 dataset per il ciliegeto

Per ogni potenziale cliente, il ciliegeto conserva una copia speciale del dataset per facilitare la vendita. Questo dataset ha le colonne più rilevanti, Weight e Height, spostate in testa ed è ordinato per Weight.

Definisci la funzione orchard_copy(data, important_cols), che accetta un dataframe come input. Deve restituire un nuovo dataframe con Weight e Height spostate in testa e le righe ordinate per 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 dataset per il cliente

Ogni potenziale cliente riceverà una versione personale del dataset in base alle sue preferenze. A questo scopo, il cliente può specificare: l'altezza minima, l'altezza massima e il peso massimo di un singolo albero.

Definisci la funzione customer_copy(data, min_height, max_height, max_weight). Restituisci una copia del dataframe per il cliente con le colonne Height, Weight, Diameter e Girth e le righe che rientrano nei vincoli.

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
Modifica tramite GitHub Il link si apre in una nuova finestra o scheda
R Exercism

Vuoi iniziare Alberi e Tibble-azioni?

Iscriviti a Exercism per imparare e padroneggiare R con 21 concetti111 esercizi e il mentoring di persone reali, tutto gratis.