27  Ένας οδηγός για το βασικό σύνολο λειτουργιών της R

27.1 Εισαγωγή

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

Σε αυτό το σημείο είναι καλό να σας υπενθυμίσουμε ότι το tidyverse δεν υπάρχει μόνο για να αντιμετωπίζει προβλήματα της επιστήμης δεδομένων. Δείχνουμε το tidyverse σε αυτό το βιβλίο επειδή τα αντίστοιχα πακέτα του μοιράζονται μία κοινή φιλοσοφία, αυξάνοντας τη συνοχή μεταξύ των συναρτήσεων και καθιστώντας κάθε νέα συνάρτηση ή πακέτο λίγο πιο εύκολα στην εκμάθηση και χρήση. Δεν είναι δυνατό να χρησιμοποιήσετε το tidyverse χωρίς τη χρήση των βασικών συναρτήσεων της R. Σας έχουμε ήδη μάθει πολλές βασικές συναρτήσεις R: από την library() για την φόρτωση πακέτων, τη sum() και τη mean() για αριθμητικές περιλήψεις, για τους τύπους δεδομένων με παράγοντες (factors), ημερομηνίες και POSIXct και φυσικά όλους τους βασικούς τελεστές όπως +, -, /, *, |, &, και !. Αυτό στο οποίο δεν έχουμε επικεντρωθεί μέχρι στιγμής είναι οι βασικές ροές εργασίας της R, επομένως θα επισημάνουμε μερικές από αυτές σε αυτό το κεφάλαιο.

Αφού διαβάσετε αυτό το βιβλίο, θα μάθετε και άλλες προσεγγίσεις πάνω στα ίδια προβλήματα χρησιμοποιώντας το βασικό σύνολο λειτουργιών της R, το data.table και άλλα πακέτα. Θα συναντήσετε αυτές τις άλλες προσεγγίσεις χωρίς αμφιβολία όταν αρχίσετε να διαβάζετε κώδικα R που γράφτηκε από άλλους, ιδιαίτερα εάν χρησιμοποιείτε το StackOverflow. Είναι 100% εντάξει να γράφετε κώδικα που χρησιμοποιεί έναν συνδυασμό προσεγγίσεων και μην αφήνετε κανέναν να σας πει το αντίθετο!

Σε αυτό το κεφάλαιο, θα εστιάσουμε σε τέσσερις μεγάλες θεματολογίες: δημιουργία υποσυνόλων με [, δημιουργία υποσυνόλων με τα [[ και $, την οικογένεια συναρτήσεων apply και βρόγχους for. Τέλος, θα συζητήσουμε εν συντομία δύο συναρτήσεις δημιουργίας διαγραμμάτων.

27.1.1 Προαπαιτούμενα

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

27.2 Επιλογή πολλαπλών στοιχείων με [

Το [ χρησιμοποιείται για την εξαγωγή υποσυνόλων από διανύσματα και πλαίσια δεδομένων και καλείται ως x[i] ή x[i, j]. Σε αυτήν την ενότητα, θα σας παρουσιάσουμε τις ικανότητες του [, δείχνοντάς σας πρώτα πώς μπορείτε να το χρησιμοποιήσετε με διανύσματα και στη συνέχεια πως οι ίδιες αρχές επεκτείνονται με απλό τρόπο στις δύο διαστάσεις όπως στα πλαίσια δεδομένων. Στη συνέχεια, θα σας βοηθήσουμε να το εμπεδώσετε δείχνοντας πώς διάφορες συναρτήσεις της dplyr είναι ειδικές περιπτώσεις του [.

27.2.1 Υποσύνολα από διανύσματα

Υπάρχουν πέντε κύριοι τύποι κατηγοριών με τις οποίες μπορείτε να εξάγετε ένα υποσύνολο από ένα διάνυσμα, δηλαδή πέντε διαφορετικές κατηγορίες που αντιστοιχούν στο i από το x[i]:

  1. Διάνυσμα με θετικούς ακέραιους αριθμούς. Η δημιουργία υποσυνόλων με θετικούς ακέραιους αριθμούς επιστρέφει τα στοιχεία των αντίστοιχων θέσεων:

    x <- c("one", "two", "three", "four", "five")
    x[c(3, 2, 5)]
    #> [1] "three" "two"   "five"

    Επαναλαμβάνοντας μία θέση μπορείτε να δημιουργήσετε μία μεγαλύτερη έξοδο, κάνοντας τον όρο “υποσύνολο” ελαφρώς λανθασμένο.

    x[c(1, 1, 5, 5, 5, 2)]
    #> [1] "one"  "one"  "five" "five" "five" "two"
  2. Διάνυσμα με αρνητικούς ακέραιους αριθμούς. Αρνητικές τιμές αφαιρούν τα στοιχεία στις αντίστοιχες θέσεις:

    x[c(-1, -3, -5)]
    #> [1] "two"  "four"
  3. Λογικό διάνυσμα. Δημιουργώντας ένα υποσύνολο χρησιμοποιώντας λογικές τιμές επιστρέφει όλες τις τιμές όπου είναι TRUE. Συχνά, αυτό είναι πιο χρήσιμο σε συνδυασμό με συναρτήσεις σύγκρισης.

    x <- c(10, 3, NA, 5, 8, 1, NA)
    
    # Όλες οι μη κενές τιμές του x
    x[!is.na(x)]
    #> [1] 10  3  5  8  1
    
    # Όλες οι ζυγές (ή κενές!) τιμές του x
    x[x %% 2 == 0]
    #> [1] 10 NA  8 NA

    Σε αντίθεση με την filter(), οι θέσεις που περιέχουν NA θα συμπεριληφθούν στην έξοδο ως NA.

  4. Διάνυσμα χαρακτήρα. Εάν έχετε ένα διάνυσμα με ονόματα για κάθε στοιχείο του, μπορείτε να δημιουργήσετε ένα υποσύνολο με ένα διάνυσμα χαρακτήρα:

    x <- c(abc = 1, def = 2, xyz = 5)
    x[c("xyz", "def")]
    #> xyz def 
    #>   5   2

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

  5. Κενό. Ο τελευταίος τύπος δημιουργίας υποσύνολου είναι το κενό, x[], το οποίο επιστρέφει ολόκληρο το x. Δεν είναι χρήσιμο όμως για υποσύνολα διανυσμάτων, αλλά όπως θα δούμε σύντομα, είναι χρήσιμο για υποσύνολα δισδιάστατων δομών, όπως τα tibbles.

27.2.2 Υποσύνολα πλαισίων δεδομένων

Υπάρχουν αρκετοί διαφορετικοί τρόποι1 με τους οποίους μπορείτε να χρησιμοποιήσετε το [ με ένα πλαίσιο δεδομένων, αλλά ο πιο σημαντικός είναι να επιλέξετε γραμμές και στήλες ξεχωριστά χρησιμοποιώντας το df[γραμμές, στήλες]. Εδώ οι γραμμές και οι στήλες είναι διανύσματα όπως περιγράφονται παραπάνω. Για παράδειγμα, τα df[γραμμές, ] και df[, στήλες] επιλέγουν μόνο γραμμές ή μόνο στήλες, χρησιμοποιώντας το κενό υποσύνολο για να διατηρήσουν την άλλη διάσταση.

Μερικά παραδείγματα:

df <- tibble(
  x = 1:3, 
  y = c("a", "e", "f"), 
  z = runif(3)
)

# Επιλέξτε την πρώτη γραμμή και τη δεύτερη στήλη
df[1, 2]
#> # A tibble: 1 × 1
#>   y    
#>   <chr>
#> 1 a

# Επιλέξτε όλες τις γραμμές και τις στήλες x και y
df[, c("x" , "y")]
#> # A tibble: 3 × 2
#>       x y    
#>   <int> <chr>
#> 1     1 a    
#> 2     2 e    
#> 3     3 f

# Επιλέξτε γραμμλες όπου το `x` είναι μεγαλύτερο από 1 και όλες οι στήλες
df[df$x > 1, ]
#> # A tibble: 2 × 3
#>       x y         z
#>   <int> <chr> <dbl>
#> 1     2 e     0.834
#> 2     3 f     0.601

Θα επανέλθουμε σύντομα στο $, θα πρέπει όμως να μπορείτε να μαντέψετε ήδη τι κάνει το df$x από τα συμφραζόμενα: εξάγει τη μεταβλητή x από το df. Εδώ, πρέπει να το χρησιμοποιήσουμε επειδή το [ δεν χρησιμοποιεί αξιολόγηση tidy, επομένως πρέπει να είστε ξεκάθαροι σχετικά με την πηγή της μεταβλητής x.

Υπάρχει μία σημαντική διαφορά μεταξύ των tibbles και των πλαισίων δεδομένων όσον αφορά το [. Σε αυτό το βιβλίο, χρησιμοποιήσαμε κυρίως τα tibbles, τα οποία είναι πλαίσια δεδομένων, αλλά τροποποιούν ορισμένες συμπεριφορές για να κάνουν τη ζωή σας λίγο πιο εύκολη. Στα περισσότερα σημεία, μπορείτε να χρησιμοποιήσετε είτε το “tibble” είτε το “data frame” (πλαίσιο δεδομένων), οπότε όταν θέλουμε να δώσουμε έμφαση στο ενσωματωμένο πλαίσιο δεδομένων της R, θα γράφουμε data.frame. Εάν το df είναι ένα data.frame, τότε το df[, cols] θα επιστρέψει ένα διάνυσμα εάν το col επιλέγει μία στήλη, και ένα πλαίσιο δεδομένων εάν επιλέγει περισσότερες από μία στήλες. Εάν το df είναι tibble, τότε η χρήση του [ θα επιστρέφει πάντα ένα tibble.

df1 <- data.frame(x = 1:3)
df1[, "x"]
#> [1] 1 2 3

df2 <- tibble(x = 1:3)
df2[, "x"]
#> # A tibble: 3 × 1
#>       x
#>   <int>
#> 1     1
#> 2     2
#> 3     3

Ένας τρόπος για να αποφύγετε αυτή την ασάφεια με τα data.frame είναι να θέτετε το όρισμα drop = FALSE:

df1[, "x" , drop = FALSE]
#>   x
#> 1 1
#> 2 2
#> 3 3

27.2.3 Ισοδύναμα της dplyr

Αρκετές συναρτήσεις της dplyr είναι ειδικές περιπτώσεις του [:

  • Η filter() ισοδυναμεί με το να δημιουργείτε υποσύνολα γραμμών χρησιμοποιώντας ένα λογικό διάνυσμα ενώ ταυτόχρονα εξαιρείτε τις κενές τιμές:

    df <- tibble(
      x = c(2, 3, 1, 1, NA), 
      y = letters[1:5], 
      z = runif(5)
    )
    df |> filter(x > 1)
    
    # ίδιο με το
    df[!is.na(df$x) & df$x > 1, ]

    Μία άλλη γνωστή τεχνική είναι η χρήση της which(), εξαιτίας της ιδιότητάς της να εξαιρεί κενές τιμές: df[which(df$x > 1), ].

  • Η arrange() ισοδυναμεί με την αναδιοργάνωση των γραμμών χρησιμοποιώντας ένα διάνυσμα ακέραιου αριθμού. Συνήθως γίνεται με την order():

    df |> arrange(x, y)
    
    # ίδιο με το
    df[order(df$x, df$y), ]

    Μπορείτε να χρησιμοποιήσετε το order(decreasing = TRUE) για να ταξινομήσετε όλες τις στήλες με φθίνουσα σειρά ή το -rank(col) για να ταξινομήσετε τις στήλες με φθίνουσα μία προς μία.

  • Τόσο η select() όσο και η relocate() είναι παρόμοιες με το να δημιουργείτε υποσύνολα στηλών χρησιμοποιώντας ένα διάνυσμα χαρακτήρων:

    df |> select(x, z)
    
    # ίδιο με το
    df[, c("x", "z")]

Το βασικό σύνολο της R προσφέρει μία συνάρτηση η οποία συνδυάζει τα χαρακτηριστικά της filter() και της select()2 και ονομάζεται subset():

df |> 
  filter(x > 1) |> 
  select(y, z)
#> # A tibble: 2 × 2
#>   y           z
#>   <chr>   <dbl>
#> 1 a     0.157  
#> 2 b     0.00740
# ίδιο με το
df |> subset(x > 1, c(y, z))

Η συνάρτηση αυτή ήταν η έμπνευση για ένα αρκετά μεγάλο μέρος του συντακτικού της dplyr.

27.2.4 Ασκήσεις

  1. Δημιουργήστε συναρτήσεις οι οποίες παίρνουν ως είσοδο ένα διάνυσμα και επιστρέφουν:

    α.  Τα στοιχεία που βρίσκονται σε ζυγές θέσεις.
    β.  Όλα τα στοιχεία εκτός του τελευταίου.
    γ.  Μόνο ζυγές τιμές (και όχι κενές τιμές).
  2. Γιατί το x[-which(x > 0)] δεν είναι το ίδιο με το x[x <= 0]; Διαβάστε τις οδηγίες για την which() και πειραματιστείτε για να το βρείτε.

27.3 Επιλέγοντας μόνο ένα στοιχείο με το $ και το [[

Το [, το οποίο επιλέγει πολλαπλά στοιχεία, συνδυάζεται με τα [[ και $, τα οποία εξάγουν ένα μεμονωμένο στοιχείο. Σε αυτήν την ενότητα, θα σας δείξουμε πώς να χρησιμοποιείτε τα [[ και $ για να εξάγετε στήλες από τα πλαίσια δεδομένων, θα συζητήσουμε μερικές ακόμη διαφορές μεταξύ των data.frame και των tibbles και θα τονίσουμε μερικές σημαντικές διαφορές μεταξύ του [ και του [[ όταν χρησιμοποιούνται με λίστες.

27.3.1 Πλαίσια δεδομένων

Τα [[ και $ μπορούν να χρησιμοποιηθούν για την εξαγωγή στηλών από ένα πλαίσιο δεδομένων. Το [[ μπορεί να έχει πρόσβαση σε δομές δεδομένων με βάση τη θέση ή το όνομα, και το $ είναι ειδικά για πρόσβαση με βάση το όνομα:

tb <- tibble(
  x = 1:4,
  y = c(10, 4, 1, 21)
)

# ανά θέση
tb[[1]]
#> [1] 1 2 3 4

# ανά όνομα
tb[["x"]]
#> [1] 1 2 3 4
tb$x
#> [1] 1 2 3 4

Μπορούν να χρησιμοποιηθούν και για τη δημιουργία νέων στηλών. Το αντίστοιχο της mutate() στο βασικό σύνολο της R είναι:

tb$z <- tb$x + tb$y
tb
#> # A tibble: 4 × 3
#>       x     y     z
#>   <int> <dbl> <dbl>
#> 1     1    10    11
#> 2     2     4     6
#> 3     3     1     4
#> 4     4    21    25

Υπάρχουν πολλές άλλες προσεγγίσεις του βασικού συνόλου της R για τη δημιουργία νέων στηλών, συμπεριλαμβανομένων των transform(), with() και within(). Ο Hadley συγκέντρωσε μερικά παραδείγματα στη διεύθυνση https://gist.github.com/hadley/1986a273e384fb2d4d752c18ed71bedf.

Η απευθείας χρήση του $ είναι βολική για την κατασκευή γρήγορων περιλήψεων. Για παράδειγμα, εάν θέλετε απλώς να βρείτε το μέγεθος του μεγαλύτερου διαμαντιού ή τις πιθανές τιμές της cut, δεν χρειάζεται να χρησιμοποιήσετε την summarize():

max(diamonds$carat)
#> [1] 5.01

levels(diamonds$cut)
#> [1] "Fair"      "Good"      "Very Good" "Premium"   "Ideal"

Η dplyr παρέχει επίσης ένα ισοδύναμο με το [[/$ που δεν αναφέραμε στο Κεφάλαιο 3: την pull(). Η pull() παίρνει είτε ένα όνομα μεταβλητής είτε μία θέση μεταβλητής και επιστρέφει ακριβώς αυτήν τη στήλη. Αυτό σημαίνει ότι θα μπορούσαμε να ξαναγράψουμε τον παραπάνω κώδικα για να χρησιμοποιήσουμε το pipe:

diamonds |> pull(carat) |> max()
#> [1] 5.01

diamonds |> pull(cut) |> levels()
#> [1] "Fair"      "Good"      "Very Good" "Premium"   "Ideal"

27.3.2 Tibbles

Υπάρχουν μερικές σημαντικές διαφορές μεταξύ των tibbles και του βασικού data.frame όσον αφορά το $. Στα πλαίσια δεδομένων αρκεί να ταιριάξει το πρόθεμα οποιουδήποτε ονόματος μεταβλητής (η λεγόμενη μερική αντιστοίχιση) και δεν παραπονιέται εάν η στήλη δεν υπάρχει:

df <- data.frame(x1 = 1)
df$x
#> [1] 1
df$z
#> NULL

Τα tibbles είναι πιο αυστηρά: τα ονόματα των μεταβλητών πρέπει να ταιριάζουν ακριβώς και θα επιστρέψουν μία προειδοποίηση εάν η στήλη στην οποία προσπαθείτε να αποκτήσετε πρόσβαση δεν υπάρχει:

tb <- tibble(x1 = 1)

tb$x
#> Warning: Unknown or uninitialised column: `x`.
#> NULL
tb$z
#> Warning: Unknown or uninitialised column: `z`.
#> NULL

Για αυτόν τον λόγο, μερικές φορές αστειευόμαστε ότι τα tibbles είναι τεμπέλικα και κακόκεφα: κάνουν λίγα και παραπονιούνται περισσότερο.

27.3.3 Λίστες

Τα [[ και $ είναι επίσης πολύ σημαντικά για εργασίες με λίστες και είναι σημαντικό να κατανοήσουμε πώς διαφέρουν από το [. Ας δείξουμε τις διαφορές με μία λίστα με όνομα l:

l <- list(
  a = 1:3, 
  b = "a string", 
  c = pi, 
  d = list(-1, -5)
)
  • Το [ εξάγει μία υπο-λίστα. Δεν έχει σημασία πόσα στοιχεία θα εξάγετε, το αποτέλεσμα θα είναι πάντα λίστα.

    str(l[1:2])
    #> List of 2
    #>  $ a: int [1:3] 1 2 3
    #>  $ b: chr "a string"
    
    str(l[1])
    #> List of 1
    #>  $ a: int [1:3] 1 2 3
    
    str(l[4])
    #> List of 1
    #>  $ d:List of 2
    #>   ..$ : num -1
    #>   ..$ : num -5

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

  • Τα [[ και $ εξάγουν ένα μόνο στοιχείο από μία λίστα. Αφαιρούν ένα επίπεδο από τη λίστα.

    str(l[[1]])
    #>  int [1:3] 1 2 3
    
    str(l[[4]])
    #> List of 2
    #>  $ : num -1
    #>  $ : num -5
    
    str(l$a)
    #>  int [1:3] 1 2 3

Η διαφορά μεταξύ [ και [[ είναι ιδιαίτερα σημαντική για τις λίστες, επειδή το [[ εμβαθύνει στη λίστα ενώ το [ επιστρέφει μία νέα, μικρότερη λίστα. Για να σας βοηθήσουμε να θυμάστε τη διαφορά, ρίξτε μία ματιά στην ασυνήθιστη πιπεριέρα στο Σχήμα 27.1. Εάν η πιπεριέρα είναι η λίστα σας με όνομα pepper, τότε, το pepper[1] είναι μία πιπεριέρα που περιέχει ένα μόνο φακελάκι πιπεριού. Το pepper[2] θα ήταν το ίδιο, αλλά θα περιέχει το δεύτερο φακελάκι. Το pepper[1:2] θα ήταν μία πιπεριέρα με δύο φακελάκια πιπεριού. Το pepper[[1]] θα εξάγει το ίδιο το φακελάκι πιπεριού.

Τρεις φωτογραφίες. Στα αριστερά είναι μια φωτογραφία μίας γυάλινης πιπεριέρας. Η πιπεριέρα αντί για πιπέρι, περιέχει μία συσκευασία με πιπέρι. Στη μέση είναι μια φωτογραφία μίας συσκευασίας με πιπέρι. Στα δεξιά είναι μια φωτογραφία του περιεχομένου μίας συσκευασίας με πιπέρι.
Σχήμα 27.1: (Αριστερά) Μία πιπεριέρα που βρήκε κάποτε ο Χάντλεϊ στο δωμάτιο του ξενοδοχείου του. (Μέση) pepper[1]. (Δεξιά) pepper[[1]]

Η ίδια αρχή ισχύει όταν χρησιμοποιείτε το μονοδιάστατο [ σε ένα πλαίσιο δεδομένων: το df["x"] επιστρέφει ένα πλαίσιο δεδομένων μίας στήλης και το df[["x"]] επιστρέφει ένα διάνυσμα.

27.3.4 Ασκήσεις

  1. Τι συμβαίνει όταν χρησιμοποιείτε το [[ με έναν θετικό ακέραιο που είναι μεγαλύτερος από το μήκος του διανύσματος; Τι συμβαίνει όταν δημιουργείτε ένα υποσυνόλου χρησιμοποιώντας ένα όνομα που δεν υπάρχει;

  2. Τι θα μπορούσε να είναι το pepper[[1]][1]; Το pepper[[1]][[1]];

27.4 Οικογένεια συναρτήσεων apply

Στο Κεφάλαιο 26, μάθατε τεχνικές σχετικές με το tidyverse για επανάληψη, όπως την dplyr::across() και την οικογένεια συναρτήσεων map. Σε αυτήν την ενότητα, θα μάθετε για τα βασικά τους ισοδύναμα, την οικογένεια συναρτήσεων apply. Σε αυτό το πλαίσιο, η apply και η map είναι συνώνυμες επειδή ένας άλλος τρόπος να πούμε “αντιστοίχιση (map) μιας συνάρτησης σε κάθε στοιχείο ενός διανύσματος” είναι “εφαρμογή (apply) μιας συνάρτησης σε κάθε στοιχείο ενός διανύσματος”. Εδώ θα σας δώσουμε μία γρήγορη επισκόπηση αυτής της οικογένειας συναρτήσεων, ώστε να μπορείτε να την αναγνωρίσετε εκεί έξω.

Το πιο σημαντικό μέλος αυτής της οικογένειας είναι η lapply(), η οποία μοιάζει πολύ με τη purrr::map()3. Στην πραγματικότητα, επειδή δεν έχουμε χρησιμοποιήσει καμία από τις πιο προχωρημένες δυνατότητες της map(), μπορείτε να αντικαταστήσετε κάθε κλήση της map() στο Κεφάλαιο 26 με την lapply().

Δεν υπάρχει ακριβής αντιστοίχιση του βασικού συνόλου λειτουργιών της R με την across(), αλλά μπορείτε να έρθετε αρκετά κοντά χρησιμοποιώντας το [ με την lapply(). Αυτό λειτουργεί επειδή στο παρασκήνιο, τα πλαίσια δεδομένων είναι λίστες στηλών, επομένως η κλήση της lapply() σε ένα πλαίσιο δεδομένων εφαρμόζει τη συνάρτηση σε κάθε στήλη.

df <- tibble(a = 1, b = 2, c = "a", d = "b", e = 4)

# Πρώτα βρείτε τις αριθμητικές στήλες
num_cols <- sapply(df, is.numeric)
num_cols
#>     a     b     c     d     e 
#>  TRUE  TRUE FALSE FALSE  TRUE

# Στη συνέχεια, αλλάξτε κάθε στήλη με την lapply() και αντικαταστήστε τις αρχικές τιμές
df[, num_cols] <- lapply(df[, num_cols, drop = FALSE], \(x) x * 2)
df
#> # A tibble: 1 × 5
#>       a     b c     d         e
#>   <dbl> <dbl> <chr> <chr> <dbl>
#> 1     2     4 a     b         8

Ο παραπάνω κώδικας χρησιμοποιεί μία νέα συνάρτηση, την sapply(). Είναι παρόμοια με την lapply(), αλλά προσπαθεί πάντα να απλοποιήσει το αποτέλεσμα, εξ ου και το s στο όνομά της. Εδώ παράγει ένα λογικό διάνυσμα αντί για μία λίστα. Δεν συνιστούμε να τη χρησιμοποιήσετε για τη δημιουργία προγραμμάτων, επειδή η απλοποίηση μπορεί να αποτύχει και να σας δώσει έναν απροσδόκητο τύπο δεδομένων. Συνήθως όμως είναι καλή για διαδραστική χρήση. Η purrr έχει μία παρόμοια συνάρτηση που ονομάζεται map_vec() που δεν αναφέραμε στο Κεφάλαιο 26.

Το βασικό σύνολο της R παρέχει μία πιο αυστηρή έκδοση της sapply() που ονομάζεται vapply(), συντομογραφία για το vector apply. Παίρνει ένα πρόσθετο όρισμα που καθορίζει τον αναμενόμενο τύπο, διασφαλίζοντας ότι η απλοποίηση γίνεται με τον ίδιο τρόπο ανεξάρτητα από την είσοδο. Για παράδειγμα, θα μπορούσαμε να αντικαταστήσουμε την κλήση της sapply() παραπάνω με αυτήν της vapply() όπου προσδιορίζουμε ότι αναμένουμε από την is.numeric() να επιστρέψει ένα λογικό διάνυσμα μήκους 1:

vapply(df, is.numeric, logical(1))
#>     a     b     c     d     e 
#>  TRUE  TRUE FALSE FALSE  TRUE

Η διαφορά μεταξύ των sapply() και vapply() είναι πολύ σημαντική όταν βρίσκονται μέσα σε μία συνάρτηση (καθώς επηρεάζεται η ανθεκτικότητα της συνάρτησης σε ασυνήθιστες εισόδους), αλλά συνήθως δεν έχει σημασία στην ανάλυση δεδομένων.

Ένα άλλο σημαντικό μέλος της οικογένειας συναρτήσεων apply είναι η tapply() η οποία υπολογίζει μία ενιαία ομαδοποιημένη σύνοψη:

diamonds |> 
  group_by(cut) |> 
  summarize(price = mean(price))
#> # A tibble: 5 × 2
#>   cut       price
#>   <ord>     <dbl>
#> 1 Fair      4359.
#> 2 Good      3929.
#> 3 Very Good 3982.
#> 4 Premium   4584.
#> 5 Ideal     3458.

tapply(diamonds$price, diamonds$cut, mean)
#>      Fair      Good Very Good   Premium     Ideal 
#>  4358.758  3928.864  3981.760  4584.258  3457.542

Δυστυχώς, η tapply() επιστρέφει τα αποτελέσματά της σε ένα διάνυσμα με όνομα και απαιτεί κάποια προσπάθεια εάν θέλετε να συλλέξετε πολλές περιλήψεις και μεταβλητές ομαδοποίησης σε ένα πλαίσιο δεδομένων (είναι σίγουρα εφικτό το να μην το κάνετε αυτό και απλώς να εργαστείτε με ξεχωριστά διανύσματα, αλλά κατά την εμπειρία μας αυτό απλώς καθυστερεί την δουλειά που πρέπει να γίνει). Αν θέλετε να δείτε πώς μπορείτε να χρησιμοποιήσετε την tapply() ή άλλες βασικές τεχνικές για να εκτελέσετε άλλες ομαδοποιημένες περιλήψεις, ο Hadley έχει συγκεντρώσει μερικές τεχνικές σε ένα gist.

Το τελευταίο μέλος της οικογένειας apply είναι η apply(), η οποία λειτουργεί με μητρώα και arrays. Πιο αναλυτικά, προσέξτε την apply(df, 2, something), που είναι ένας αργός και δυνητικά επικίνδυνος τρόπος για να εφαρμόσετε την lapply(df, something). Αυτό σπάνια εμφανίζεται στην επιστήμη δεδομένων επειδή συνήθως εργαζόμαστε με πλαίσια δεδομένων και όχι με μητρώα.

27.5 Βρόγχοι for

Οι βρόγχοι for είναι το θεμελιώδες δομικό στοιχείο της επανάληψης που χρησιμοποιούν και οι οικογένειες apply και map στο παρασκήνιο Οι βρόγχοι for είναι ισχυρά και γενικά εργαλεία που είναι σημαντικό να μάθετε καθώς γίνεστε πιο έμπειρος προγραμματιστής στην R. Η βασική δομή ενός βρόγχου for μοιάζει με το εξής:

for (element in vector) {
  # εφαρμόστε οτιδήποτε με την element
}

Η πιο απλή χρήση των βρόγχων for είναι είναι για την επίτευξη του ίδιου αποτελέσματος με την walk(): η κλήση κάποιας συνάρτησης με κάποιο αποτέλεσμα σε κάθε στοιχείο μιας λίστας. Για παράδειγμα, στην Ενότητα 26.4.1 αντί να χρησιμοποιήσετε τη walk():

paths |> walk(append_file)

Θα μπορούσαμε να χρησιμοποιήσουμε έναν βρόγχο for:

for (path in paths) {
  append_file(path)
}

Τα πράγματα γίνονται λίγο πιο δύσκολα αν θέλετε να αποθηκεύσετε την έξοδο του βρόγχου for, για παράδειγμα η ανάγνωση όλων των αρχείων excel σε έναν κατάλογο όπως κάναμε στο Κεφάλαιο 26:

paths <- dir("data/gapminder", pattern = "\\.xlsx$", full.names = TRUE)
files <- map(paths, readxl::read_excel)

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

files <- vector("list", length(paths))

Στη συνέχεια, αντί να επαναλαμβάνουμε τις διαδικασίες μας στα στοιχεία του paths, θα τις επαναλαμβάνουμε στους δείκτες τους, χρησιμοποιώντας τη seq_along() για να δημιουργήσουμε ένα ευρετήριο για κάθε στοιχείο του paths:

seq_along(paths)
#>  [1]  1  2  3  4  5  6  7  8  9 10 11 12

Η χρήση των δεικτών είναι σημαντική γιατί μας επιτρέπει να συνδέσουμε κάθε θέση στην είσοδο με την αντίστοιχη θέση στην έξοδο:

for (i in seq_along(paths)) {
  files[[i]] <- readxl::read_excel(paths[[i]])
}

Για να συνδυάσετε τη λίστα των tibbles σε ένα μόνο tibble, μπορείτε να χρησιμοποιήσετε τις do.call() + rbind():

do.call(rbind, files)
#> # A tibble: 1,704 × 5
#>   country     continent lifeExp      pop gdpPercap
#>   <chr>       <chr>       <dbl>    <dbl>     <dbl>
#> 1 Afghanistan Asia         28.8  8425333      779.
#> 2 Albania     Europe       55.2  1282697     1601.
#> 3 Algeria     Africa       43.1  9279525     2449.
#> 4 Angola      Africa       30.0  4232095     3521.
#> 5 Argentina   Americas     62.5 17876956     5911.
#> 6 Australia   Oceania      69.1  8691212    10040.
#> # ℹ 1,698 more rows

Αντί να δημιουργήσουμε μία λίστα και να αποθηκεύουμε εκεί τα αποτελέσματα καθώς προχωράμε, μία απλούστερη προσέγγιση είναι να δημιουργήσουμε το πλαίσιο δεδομένων κομμάτι-κομμάτι:

out <- NULL
for (path in paths) {
  out <- rbind(out, readxl::read_excel(path))
}

Συνιστούμε να αποφύγετε αυτό το μοτίβο γιατί μπορεί να γίνει αρκετά αργό όταν το διάνυσμα είναι πολύ μεγάλο. Αυτή είναι και η πηγή του μύθου ότι οι βρόγχοι for είναι αργοί: δεν είναι, αλλά το να αυξάνεται προοδευτικά ένα διάνυσμα, είναι.

27.6 Διαγράμματα

Πολλοί χρήστες της R, που δεν χρησιμοποιούν υπό άλλες συνθήκες το tidyverse, προτιμούν το πακέτο ggplot2 για σχεδίαση, λόγω των χρήσιμων χαρακτηριστικών της, όπως οι λογικές προεπιλογές, τα αυτοματοποιημένα υπομνήματα και η μοντέρνα εμφάνιση. Ωστόσο, οι βασικές συναρτήσεις σχεδίασης της R μπορούν να είναι ακόμα χρήσιμες μόνο και μόνο επειδή επειδή είναι τόσο συνοπτικές — χρειάζεται πολύ λίγη πληκτρολόγηση για να κάνετε ένα απλό εξερευνητικό διάγραμμα.

Υπάρχουν δύο κύριοι τύποι απλών διαγραμμάτων που θα δείτε εκεί έξω: διαγράμματα διασποράς και ιστογράμματα, που παράγονται με τις plot() και hist() αντίστοιχα. Ακολουθεί ένα γρήγορο παράδειγμα από το σύνολο δεδομένων diamonds:

# Αριστερά
hist(diamonds$carat)

# Δεξιά
plot(diamonds$carat, diamonds$price)

Στα αριστερά, ένα ιστόγραμμα καρατιών διαμαντιών, που κυμαίνεται από 0 έως 5 καράτια. Η κατανομή έχει μία κορυφή και είναι λοξή προς τα δεξιά. Στα δεξιά, ένα διάγραμμα διασποράς των τιμών έναντι των καρατιών διαμαντιών, δείχνοντας μια θετική σχέση στην οποία φαίνεται να αυξάνονται τόσο η τιμή όσο και τα καράτια. Το διάγραμμα διασποράς δείχνει πολύ λίγα διαμάντια μεγαλύτερα από 3 καράτια σε σύγκριση με διαμάντια μεταξύ 0 και 3 καρατιών.

Στα αριστερά, ένα ιστόγραμμα καρατιών διαμαντιών, που κυμαίνεται από 0 έως 5 καράτια. Η κατανομή έχει μία κορυφή και είναι λοξή προς τα δεξιά. Στα δεξιά, ένα διάγραμμα διασποράς των τιμών έναντι των καρατιών διαμαντιών, δείχνοντας μια θετική σχέση στην οποία φαίνεται να αυξάνονται τόσο η τιμή όσο και τα καράτια. Το διάγραμμα διασποράς δείχνει πολύ λίγα διαμάντια μεγαλύτερα από 3 καράτια σε σύγκριση με διαμάντια μεταξύ 0 και 3 καρατιών.

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

27.7 Σύνοψη

Σε αυτό το κεφάλαιο, σας δείξαμε μία επιλογή από βασικές συναρτήσεις της R που είναι χρήσιμες για την δημιουργία υποσυνόλων και για την επανάληψη. Σε σύγκριση με προσεγγίσεις που συζητούνται αλλού στο βιβλίο, αυτές οι συναρτήσεις τείνουν να έχουν περισσότερο άρωμα “διανύσματος” παρά “πλαισίου δεδομένων”, επειδή οι βασικές συναρτήσεις της R τείνουν να λαμβάνουν μεμονωμένα διανύσματα ως είσοδο, παρά ένα πλαίσιο δεδομένων μαζί με προδιαγραφές στηλών. Αυτό συχνά, στον προγραμματισμό, κάνει τη ζωή πιο εύκολη και γι’ αυτό γίνεται πιο σημαντικό καθώς γράφετε περισσότερες συναρτήσεις και αρχίζετε να γράφετε τα δικά σας πακέτα.

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


  1. Διαβάστε το https://adv-r.hadley.nz/subsetting.html#subset-multiple για να δείτε πώς μπορείτε επίσης να ορίσετε ένα υποσύνολο σε ένα πλαίσιο δεδομένων σαν να είναι αντικείμενο μίας διάστασης και πώς μπορείτε να δημιουργήσετε ένα υποσύνολο με ένα μητρώο.↩︎

  2. Ωστόσο, δεν χειρίζεται ομαδοποιημένα πλαίσια δεδομένων με διαφορετικό τρόπο και δεν υποστηρίζει βοηθητικές συναρτήσεις επιλογής όπως η starts_with().↩︎

  3. Απλώς δεν διαθέτει βολικές λειτουργίες, όπως καταστάσεις προόδου και αναφορές για το ποιο στοιχείο προκάλεσε το πρόβλημα εάν υπάρχει σφάλμα.↩︎