Da

Dataframes σε R

1 άσκηση

Σχετικά με την έννοια Dataframes

Σε άλλα σημεία της ύλης έχουμε δει διάφορους τύπους δεδομένων με διαφορετικά χαρακτηριστικά.

  • Τα ατομικά διανύσματα είναι μονοδιάστατα και ομοιογενή ως προς τον τύπο.
  • Οι λίστες είναι μονοδιάστατες και τα στοιχεία τους μπορούν να είναι διαφορετικών τύπων.
  • Οι πίνακες και οι διατάξεις είναι πολυδιάστατα και ομοιογενή.

Αυτή η Έννοια θα εξετάσει τρόπους αποθήκευσης πολυδιάστατων, ετερογενών δεδομένων. Στην πράξη, τα περισσότερα δεδομένα του πραγματικού κόσμου είναι έτσι, οπότε φτάνουμε τώρα στην καρδιά του πώς χρησιμοποιείται (κατά κύριο λόγο) η R στην πράξη.

Παραλλαγές των dataframe

Με την πάροδο των δεκαετιών, η R έχει προσθέσει πολλούς τύπους δεδομένων για τη διαχείριση πινακοποιημένων δεδομένων.

Αυτή η ύλη θα επικεντρωθεί κυρίως στα tibbles, αλλά είναι χρήσιμο να γνωρίζεις μερικές εναλλακτικές.

Το data.table

Τα tibbles (περιγράφονται παρακάτω) και τα data.tables είναι και τα δύο αξιοσέβαστες προσπάθειες βελτίωσης του παραδοσιακού data.frame της Base R. Αναπόφευκτα υπάρχει μεγάλη διαμάχη για το ποιο είναι "καλύτερο". Ίσως υπάρχει κάποιος βαθμός συναίνεσης γύρω από τα παρακάτω σημεία (ακόμα κι αν κάποιοι τα χαρακτηρίσουν απλοϊκά).

  • Το tibble είναι βελτιστοποιημένο κυρίως για ευκολία χρήσης και ενσωμάτωση με το οικοσύστημα του Tidyverse.
  • Το data.table είναι βελτιστοποιημένο κυρίως για ακατέργαστη ισχύ και κλιμάκωση, ειδικά όταν δουλεύεις με πολύ μεγάλα σύνολα δεδομένων.

Σε κάθε περίπτωση, το data.table δεν είναι διαθέσιμο στο Exercism, οπότε αναφέρεται εδώ μόνο για λόγους πληρότητας.

Το data.frame

Στη Base R, ένα data.frame είναι μια list από vectors ίσου μήκους. Αυτό μπορεί να ιδωθεί ως ένας ορθογώνιος πίνακας δεδομένων, όπου κάθε στήλη είναι ομοιογενής, αλλά κάθε γραμμή μπορεί (και συνήθως συμβαίνει) να περιέχει διαφορετικούς τύπους δεδομένων.

Ένα παράδειγμα που το δείχνει αυτό:

# 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

Έχουμε μια στήλη με συμβολοσειρές χαρακτήρων, μια στήλη με αριθμούς και μια στήλη με τιμές Boolean (λογικές τιμές). Σε μεγαλύτερη κλίμακα, αυτός είναι ένας διαισθητικός τρόπος αναπαράστασης πολλών συλλογών δεδομένων του πραγματικού κόσμου.

Το tibble

Ο σχεδιασμός του data.frame είναι παλιός.

Η εμπειρία πολλών δεκαετιών, μαζί με τους μεταβαλλόμενους τρόπους χρήσης της R, οδήγησε σε έναν επανασχεδιασμό, δημιουργώντας μια εκσυγχρονισμένη εναλλακτική στο Tidyverse: τα tibbles.

Σε σύγκριση με τη Base R, τα tibbles έχουν:

  • Διαφορετικές προεπιλογές, για να μειώνονται τα συνηθισμένα προβλήματα.
  • Μικρότερη προθυμία να μετατρέψουν τύπους δεδομένων κατά την εισαγωγή.
  • Περισσότερα και πιο σαφή μηνύματα σφάλματος.
  • Διαφορετικές, συνήθως καλύτερες, μορφές εμφάνισης.

Με δυο λόγια, ένα tibble στοχεύει να "κάνει λιγότερα και να παραπονιέται περισσότερο", κάτι που περιγράφεται και ως "τεμπέλικο και γκρινιάρικο".

Οι τύποι είναι συνήθως εναλλάξιμοι: οποιαδήποτε συνάρτηση δέχεται ένα data.frame θα δεχτεί και ένα tibble, και αντίστροφα.

Οι μετατροπές μεταξύ των τύπων είναι εύκολες, με τις as_tibble(df) και as.data.frame(tbl).

Για νέα δουλειά, η χρήση tibbles πιθανότατα θα σε βοηθήσει να γράψεις πιο εύρωστο κώδικα. Ωστόσο, ο παλιός κώδικας και τα παλιά δεδομένα αφθονούν στον κόσμο της R, οπότε το data.frame είναι πιθανό να παραμείνει συνηθισμένο για πολύ καιρό.

# 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

Πρόσεξε την προεπιλεγμένη μορφή εκτύπωσης: η γραμμή σχολίου με τις διαστάσεις εκτυπώνεται αυτόματα και εμφανίζονται επίσης οι τύποι των στηλών.

Εργασία με tibbles

Τα tibbles είναι βασικό κομμάτι του Tidyverse, οπότε πρόσθεσέ τα είτε με library(tibble) είτε με library(tidyverse).

Η τεκμηρίωση είναι αρκετά εκτενής, στο στυλ του Tidyverse:

Δημιουργία ενός tibble

Πιο απλά, μπορούμε να χρησιμοποιήσουμε τη συνάρτηση tibble() για να ενώσουμε διανύσματα στηλών, όπως με τη data.frame(). Ένα παράδειγμα αυτού δόθηκε σε προηγούμενη ενότητα.

Αν είναι πιο βολικό να εισάγεις τιμές ανά γραμμή, η αντίστοιχη συνάρτηση είναι η 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    

Στην πράξη, υπάρχουν δεκάδες τρόποι να δημιουργήσεις tibbles, καθώς αποτελούν την προεπιλεγμένη μορφή εξόδου μιας μεγάλης ποικιλίας συναρτήσεων του Tidyverse. Θα επανέλθουμε σε αυτό σε μια μελλοντική Έννοια.

Χειρισμός ενός tibble

Η Έννοια του Συναρτησιακού Προγραμματισμού συζήτησε τη βιβλιοθήκη purrr για τον χειρισμό διανυσμάτων και λιστών (μονοδιάστατων δομών δεδομένων).

Για τα dataframes (είτε παραδοσιακά είτε tibbles), η αντίστοιχη βιβλιοθήκη που θα χρησιμοποιήσεις είναι η dplyr.

Παρουσιάσαμε τη dplyr προηγουμένως, στην Έννοια Switch. Εκείνη χρησιμοποίησε μόνο μερικές βοηθητικές συναρτήσεις, αλλά τώρα μπορούμε να αρχίσουμε να εξερευνούμε την υπόλοιπη μεγάλη βιβλιοθήκη.

Δημιουργία υποσυνόλων

Τα dataframes, συμπεριλαμβανομένων των tibbles, μπορούν να αντιμετωπιστούν ως λίστες από διανύσματα στηλών, οπότε με ευρετηρίαση της λίστας ανακτάς μια συγκεκριμένη στήλη.

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

Ένα dataframe μπορεί επίσης να δεχτεί ευρετηρίαση τύπου πίνακα.

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

Στη σύγχρονη R με το οικοσύστημα του Tidyverse, οι συναρτήσεις της dplyr είναι γενικά πιο ευέλικτες και βολικές, και θα είναι το επίκεντρο για το υπόλοιπο αυτής της Έννοιας.

Πράξεις ανά στήλη

Πάρε μια μεμονωμένη στήλη με τη pull(), δίνοντας το όνομα ή τον αύξοντα αριθμό (αρνητικοί αριθμοί για μέτρηση από τα δεξιά προς τα αριστερά).

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

Αυτό δίνει το ίδιο αποτέλεσμα με το tbl$created, αλλά χρησιμοποιεί μια συνάρτηση φιλική προς τα pipelines.

Για να πάρεις πολλές στήλες, η κατάλληλη συνάρτηση είναι η select(), η οποία είναι εξαιρετικά ευέλικτη. Πάρε (ή απόρριψε) στήλες με βάση ιδιότητες του ονόματος ή του τύπου τους.

# 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

Επιτρέπονται πολλαπλά κριτήρια, με χρήση των τελεστών Boolean &, | και ! (and, or, not).

Τα ονόματα στηλών που είναι έγκυρα αναγνωριστικά της R δεν χρειάζονται εισαγωγικά μέσα σε μια select(). Τα μη έγκυρα ονόματα (π.χ. όσα περιέχουν κενά) μπορούν να περικλειστούν σε backticks (βαρείες), αν και ίσως είναι καλύτερο να τα μετονομάσεις.

Η συνάρτηση select() μπορεί να συνεργαστεί με μια σειρά βοηθητικών συναρτήσεων για την επιλογή ονομάτων στηλών: starts_with, contains, num_range και διάφορες άλλες. Η matches επιτρέπει πλήρη αντιστοίχιση με κανονικές εκφράσεις. Δες την τεκμηρίωση για λεπτομέρειες.

Τέτοια ισχύς φαίνεται αρκετά υπερβολική με το δοκιμαστικό dataframe των γλωσσών μας. Ευτυχώς, το tibble starwars περιλαμβάνεται στη dplyr, δίνοντάς μας κάτι μεγαλύτερο για εξάσκηση.

# 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    

Πράξεις ανά γραμμή

Note

Προφανώς, η dplyr παρέχει ισχυρούς τρόπους επιλογής στηλών με βάση το όνομα.

Μπορούμε να κάνουμε παρόμοια πράγματα με τα ονόματα των γραμμών;

Όχι! Τα παραδοσιακά dataframes της R μπορούν να έχουν ονόματα γραμμών, αλλά (μετά από ένα ιστορικό από bugs και προβλήματα απόδοσης) τα ονόματα γραμμών δεν επιτρέπονται στα tibbles.

Αν θέλεις ονόματα, βάλ' τα σε μια στήλη <chr> (συνήθως τη στήλη 1), που χρησιμοποιείται όπως κάθε άλλη στήλη. Συναρτήσεις εισαγωγής όπως η as_tibble() θα το δημιουργήσουν αυτόματα όταν εισάγεις δεδομένα με ονομασμένες γραμμές.

Αν αυτός ο περιορισμός στα ονόματα γραμμών φαίνεται παράξενα περιοριστικός, θυμήσου ότι τα περισσότερα μεγάλα συστήματα βάσεων δεδομένων χειρίζονται τους πίνακες με τον ίδιο τρόπο: Oracle, SQL Server, PostgreSQL, MySQL...

Πάρε τις γραμμές που ταιριάζουν με κάποια κριτήρια με τη filter(), ή εξαίρεσέ τες με τη 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

Τα κριτήρια φιλτραρίσματος μπορούν να είναι αυθαίρετα πολύπλοκα, αλλά βασίζονται πάντα στο περιεχόμενο της γραμμής.

Αν οι αριθμοί των γραμμών είναι γνωστοί, μπορούμε να χρησιμοποιήσουμε διάφορες συναρτήσεις 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    

Για να αφαιρέσεις διπλότυπες γραμμές, χρησιμοποίησε τη distinct().

Τροποποίηση ενός tibble

Πρώτη επιφύλαξη: η προεπιλογή copy-on-modify σημαίνει ότι το αρχικό tibble συνήθως παραμένει αμετάβλητο.

Οι περισσότερες τροποποιήσεις εφαρμόζονται ανά στήλη.

Τα ονόματα στηλών μπορούν να αλλάξουν με τη rename(newname = oldname), ή με τη rename_with() για να εφαρμόσεις μια συνάρτηση. Μια τυπική χρήση θα ήταν ο καθαρισμός εισαγόμενων ονομάτων ώστε να είναι ευκολότερα στη διαχείριση στην R, αφαιρώντας κενά διαστήματα και επιβάλλοντας μια συνεπή μορφή για συσχετισμένα ονόματα.

Πρόσεξε τη σύνταξη μέσα στη rename(). Το περιεχόμενο της στήλης oldname δεσμεύεται στο όνομα newname, εξ ου και η σειρά.

Η σειρά των στηλών μπορεί να αλλάξει με τη relocate(). Οι καθορισμένες στήλες μετακινούνται στις πιο αριστερές θέσεις από προεπιλογή, αλλά μπορείς να χρησιμοποιήσεις ένα όρισμα .before ή .after για ακριβέστερη τοποθέτηση.

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

Για μεγαλύτερες αλλαγές, η mutate() σου επιτρέπει να:

  • Δημιουργήσεις νέες στήλες που είναι συναρτήσεις υπαρχουσών στηλών.
  • Αντικαταστήσεις μια υπάρχουσα στήλη, δημιουργώντας μια νέα στήλη με το ίδιο όνομα.
  • Διαγράψεις μια στήλη, ορίζοντας την τιμή της σε NULL

Προφανώς, η mutate() είναι ισχυρή, δυνητικά μπερδεμένη, και ένας λόγος να είμαστε πολύ ευγνώμονες για το copy-on-modify.

Δεν υπάρχει προφανής λόγος να σε νοιάζει ο Δείκτης Μάζας Σώματος των χαρακτήρων του Star Wars, αλλά για κάθε ενδεχόμενο:

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

Όταν θέλεις να επεξεργαστείς ένα υποσύνολο των στηλών με συναρτήσεις όπως η mutate(), η ακολουθία select() |> mutate() στο παραπάνω παράδειγμα είναι μια επιλογή. Μόνο οι επιλεγμένες στήλες θα υπάρχουν στο αποτέλεσμα.

Εναλλακτικά, μπορεί να είναι βολικό να χρησιμοποιήσεις τη pick() μέσα στην κλήση της 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>

Μόνο οι στήλες που επιλέγονται με τη pick χρησιμοποιούνται στην τροποποίηση, αλλά επιστρέφονται όλες οι στήλες.

Οι πράξεις ανά γραμμή είναι λιγότερο συνηθισμένες για την τροποποίηση μεμονωμένων tibbles (η συγχώνευση πολλών tibbles θα συζητηθεί σε μεταγενέστερη έννοια).

Μια εξαίρεση: η arrange() ταξινομεί τις γραμμές με βάση τις τιμές σε μία ή περισσότερες στήλες.

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     

Σύνοψη

Τα dataframes, είτε παραδοσιακά είτε tibbles, είναι κεντρικά στον τρόπο που χρησιμοποιείται συνήθως η σύγχρονη R.

Οι περισσότερες συναρτήσεις του Tidyverse (όχι μόνο της dplyr) δέχονται tibbles ως είσοδο ή/και τα δημιουργούν ως έξοδο.

Αυτή η έννοια παρείχε μόνο μια σύντομη εισαγωγή, μόλις που ξύνοντας την επιφάνεια του τι είναι δυνατό.

Μεταγενέστερες έννοιες θα συζητήσουν αρκετές άλλες πτυχές των dataframes (μέσα στους τεχνικούς περιορισμούς του Exercism).

Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα

Μάθε την έννοια Dataframes