Σε άλλα σημεία της ύλης έχουμε δει διάφορους τύπους δεδομένων με διαφορετικά χαρακτηριστικά.
Αυτή η Έννοια θα εξετάσει τρόπους αποθήκευσης πολυδιάστατων, ετερογενών δεδομένων. Στην πράξη, τα περισσότερα δεδομένα του πραγματικού κόσμου είναι έτσι, οπότε φτάνουμε τώρα στην καρδιά του πώς χρησιμοποιείται (κατά κύριο λόγο) η R στην πράξη.
Με την πάροδο των δεκαετιών, η 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 είναι βασικό κομμάτι του Tidyverse, οπότε πρόσθεσέ τα είτε με library(tibble) είτε με library(tidyverse).
Η τεκμηρίωση είναι αρκετά εκτενής, στο στυλ του Tidyverse:
Πιο απλά, μπορούμε να χρησιμοποιήσουμε τη συνάρτηση 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. Θα επανέλθουμε σε αυτό σε μια μελλοντική Έννοια.
Η Έννοια του Συναρτησιακού Προγραμματισμού συζήτησε τη βιβλιοθήκη 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
Προφανώς, η 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().
Πρώτη επιφύλαξη: η προεπιλογή 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).