Tracce
/
R
R
/
Programma
/
Dataframe
Da

Dataframe in R

1 esercizio

Informazioni su Dataframe

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

  • I vettori atomici sono monodimensionali e omogenei nel tipo.
  • Le liste sono monodimensionali e i loro elementi possono essere di tipi diversi.
  • Le matrici e gli array sono multidimensionali e omogenei.

In questo concetto vedremo come memorizzare dati multidimensionali ed eterogenei. In pratica, la maggior parte dei dati del mondo reale è così: 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 dati tabellari.

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

Il data.table

I tibble (descritti più avanti) e i data.table sono entrambi tentativi apprezzati di migliorare il tradizionale data.frame del Base R. C'è inevitabilmente molta discussione su quale sia «il migliore». Forse c'è un certo grado di consenso sui punti seguenti (anche se saranno criticati come semplicistici).

  • tibble è ottimizzato soprattutto per la facilità d'uso e per l'integrazione con l'ecosistema Tidyverse.
  • data.table è ottimizzato soprattutto per la potenza grezza e la scalabilità, specialmente quando si lavora con dataset molto grandi.

In ogni caso, data.table non è disponibile su Exercism, quindi lo citiamo qui solo per completezza.

Il data.frame

Nel Base R, un data.frame è una 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

# look at the structure
str(df)
#> 'data.frame':	4 obs. of  3 variables:
 #> $ languages   : chr  "Fortran" "R" "Python" "Julia"
 #> $ created     : num  1957 1993 1991 2012
 #> $ has.syllabus: logi  FALSE TRUE TRUE TRUE

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

Il tibble

Il design del data.frame è vecchio.

Decenni di esperienza, più il cambiamento delle modalità d'uso di R, hanno portato a una riprogettazione, creando un'alternativa modernizzata nel Tidyverse: i tibble.

Rispetto al Base R, i tibble hanno:

  • Valori predefiniti diversi, per ridurre i problemi più comuni.
  • Meno propensione a convertire i tipi di dati durante l'inserimento.
  • Messaggi di errore più numerosi e più chiari.
  • Formati di visualizzazione diversi, di solito migliori.

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

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

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

Per il lavoro nuovo, usare i tibble probabilmente ti aiuterà a scrivere codice più robusto. Tuttavia, nel mondo di R il codice e i dati di vecchia data sono molto abbondanti, quindi è probabile che il data.frame resti comune 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      
  
str(tbl)
#> tibble [4 × 3] (S3: tbl_df/tbl/data.frame)
#>  $ languages   : chr [1:4] "Fortran" "R" "Python" "Julia"
#>  $ created     : num [1:4] 1957 1993 1991 2012
#>  $ has.syllabus: logi [1:4] FALSE TRUE TRUE 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, quindi aggiungili con library(tibble) oppure library(tidyverse).

La documentazione è piuttosto ampia, nello stile del 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 per riga, la funzione corrispondente è tribble().

tbl_r <- tribble(
  # column names marked with tilde prefix
  ~languages, ~created, ~has.syllabus,
  "Fortran", 1957, FALSE,
  "R", 1993, TRUE,
  "Python", 1991, TRUE,
  "Julia", 2012, TRUE
)

tbl_r
# 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    

In pratica, ci sono decine di modi per creare tibble, perché sono il formato di output predefinito di un'ampia gamma di funzioni del Tidyverse. Ci torneremo in un concetto futuro.

Manipolare un tibble

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

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

Abbiamo già introdotto dplyr, nel concetto Switch. Lì abbiamo usato solo alcune funzioni di utilità, ma ora possiamo iniziare a esplorare il resto di questa grande libreria.

Selezione di sottoinsiemi

I dataframe, tibble inclusi, possono essere trattati come liste di vettori colonna, quindi l'indicizzazione di una lista estrae 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 un'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

Per ottenere una singola colonna, usa pull() con il nome o il numero sequenziale (i numeri negativi contano da destra verso sinistra).

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

È lo stesso risultato di tbl$created, ma ottenuto con una funzione che si presta all'uso in una pipeline.

Per ottenere più colonne, la funzione appropriata è select(), che è estremamente 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

# Exclude a column
tbl |> select(!created)
  # A tibble: 4 × 2
#>   languages has.syllabus
#>   <chr>     <lgl>       
#> 1 Fortran   FALSE       
#> 2 R         TRUE        
#> 3 Python    TRUE        
#> 4 Julia     TRUE        

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

Sono ammessi più criteri, usando gli operatori booleani &, | e ! (e, o, non).

I nomi di colonna che sono identificatori R validi non hanno bisogno di virgolette dentro una select(). I nomi non validi (ad esempio quelli che contengono spazi) possono essere racchiusi tra backtick, anche se rinominarli potrebbe essere meglio.

La funzione select() può lavorare con una serie di funzioni ausiliarie per scegliere i nomi delle colonne: starts_with, contains, num_range e varie altre. matches consente la corrispondenza completa con RegEx. Consulta la documentazione per i dettagli.

Tutta questa potenza sembra piuttosto eccessiva con il nostro dataframe giocattolo di linguaggi. Per fortuna, il tibble starwars è incluso in dplyr e ci dà qualcosa di più grande con cui esercitarci.

# limit display to top 3 rows of non-list columns
starwars |> 
  select(!where(is.list)) |> 
  head(3)
  # A tibble: 3 × 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  

# 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

Note

Chiaramente, dplyr offre modi potenti per selezionare le colonne per nome.

Possiamo fare qualcosa di simile con i nomi delle righe?

No! I dataframe tradizionali di R possono avere nomi di riga, ma (dopo una storia di bug e problemi di prestazioni) i nomi di riga non sono ammessi nei tibbles.

Se vuoi dei nomi, mettili in una colonna <chr> (di solito la colonna 1), usata come qualsiasi altra colonna. Le funzioni di importazione come as_tibble() la creeranno automaticamente quando importi dati con righe con nome.

Se questa limitazione sui nomi di riga sembra stranamente restrittiva, ricorda che la maggior parte dei grandi sistemi di database gestisce le tabelle allo stesso modo: Oracle, SQL Server, PostgreSQL, MySQL...

Ottieni le righe che soddisfano certi criteri 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 arbitrariamente complessi, ma sono sempre basati sul contenuto delle righe.

Se si conoscono i numeri delle righe, possiamo usare una varietà di 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   

# random sample of rows
starwars |> 
  select(name | homeworld) |> 
  slice_sample(n = 4)
  # A tibble: 4 × 2
#>   name            homeworld
#>   <chr>           <chr>    
#> 1 Shaak Ti        Shili    
#> 2 Luminara Unduli Mirial   
#> 3 Grievous        Kalee    
#> 4 Palpatine       Naboo    

Per rimuovere le righe duplicate, usa distinct().

Modificare un tibble

Prima avvertenza: il comportamento predefinito copy-on-modify fa sì che il tibble originale di solito rimanga invariato.

La maggior parte delle modifiche viene applicata per colonna.

I nomi delle colonne possono essere cambiati con rename(newname = oldname), oppure con rename_with() per applicare una funzione. Un uso tipico è ripulire i nomi importati per renderli più facili da usare in R, rimuovendo gli spazi e imponendo un formato coerente per i nomi correlati.

Nota la sintassi dentro rename(). Il contenuto della colonna oldname viene associato al nome newname, da cui l'ordine.

L'ordine delle colonne può essere cambiato con relocate(). Le colonne specificate vengono spostate di default nelle posizioni più a sinistra, ma si può usare un argomento .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 confondente, 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

Quando vuoi operare su un sottoinsieme delle colonne con funzioni come mutate(), la sequenza select() |> mutate() dell'esempio precedente è una possibilità. Nel risultato ci saranno solo le colonne selezionate.

In alternativa, può essere comodo usare pick() dentro la chiamata a mutate():

starwars |> mutate(pick(c(name, species, height, mass)), BMI = mass / (height / 100)^2) |> head(4)
When you want to operate on a subset of the columns with functions such as `mutate()`, the `select() |> mutate()` sequence in the above example is one option.
Only the selected columns will be in the result.

Alternatively, it can be convenient to use `pick()` _within_ the `mutate()` call:

```R
starwars |> mutate(pick(c(name, species, height, mass)), BMI = mass / (height / 100)^2) |> head(4)
    # A tibble: 4 × 15
#>   name         height  mass hair_color skin_color eye_color birth_year sex   gender homeworld species films vehicles
#>   <chr>         <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> <chr>  <chr>     <chr>   <lis> <list>  
#> 1 Luke Skywal…    172    77 blond      fair       blue            19   male  mascu… Tatooine  Human   <chr> <chr>   
#> 2 C-3PO           167    75 NA         gold       yellow         112   none  mascu… Tatooine  Droid   <chr> <chr>   
#> 3 R2-D2            96    32 NA         white, bl… red             33   none  mascu… Naboo     Droid   <chr> <chr>   
#> 4 Darth Vader     202   136 none       white      yellow          41.9 male  mascu… Tatooine  Human   <chr> <chr>   
   # ℹ 2 more variables: starships <list>, BMI <dbl>

Nella mutazione vengono usate solo le colonne passate a pick, ma vengono restituite tutte le colonne.

Le operazioni per riga sono meno comuni quando si modifica un singolo tibble (l'unione di più tibble sarà discussa 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     

I dataframe, tradizionali o tibble, sono centrali nel modo in cui il R moderno viene tipicamente usato.

La maggior parte delle funzioni del Tidyverse (non solo dplyr) accetta tibble in input e/o li crea come output.

Questo concetto ha fornito solo una breve introduzione, scalfendo appena la superficie di ciò che è possibile.

Concetti successivi tratteranno diversi altri aspetti dei dataframe (entro i vincoli tecnici di Exercism).

Modifica tramite GitHub Il collegamento si apre in una nuova finestra o scheda

Impara Dataframe