14  Συμβολοσειρές

14.1 Εισαγωγή

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

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

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

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

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

Μπορείτε να καταλάβετε γρήγορα πότε χρησιμοποιείται μία συνάρτηση του πακέτου stringr επειδή όλες οι συναρτήσεις της ξεκινούν με str_. Αυτό είναι ιδιαίτερα χρήσιμο εάν χρησιμοποιείτε το RStudio, επειδή πληκτρολογώντας str_ θα ενεργοποιηθεί η αυτόματη συμπλήρωση, δίνοντάς σας την ευκαιρία να θυμηθείτε τις διαθέσιμες συναρτήσεις.

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

14.2 Δημιουργώντας μία συμβολοσειρά

Έχουμε δημιουργήσει συμβολοσειρές νωρίτερα στο βιβλίο, αλλά δεν συζητήσαμε για λεπτομέρειες. Αρχικά, μπορείτε να δημιουργήσετε μία συμβολοσειρά χρησιμοποιώντας είτε μονά εισαγωγικά (') είτε διπλά ("). Δεν υπάρχει διαφορά στη συμπεριφορά μεταξύ των δύο, επομένως, για λόγους συνέπειας, ο οδηγός τρόπου γραφής του tidyverse συνιστά τη χρήση του ", εκτός εάν η συμβολοσειρά περιέχει πολλαπλά ".

string1 <- "This is a string"
string2 <- 'If I want to include a "quote" inside a string, I use single quotes'

Εάν ξεχάσετε να κλείσετε κάποιο εισαγωγικό, θα δείτε το +, την εντολή επέκτασης:

> "Αυτή είναι μία συμβολοσειρά χωρίς δεύτερο εισαγωγικό στο τέλος της
+ 
+ 
+ ΛΙΓΗ ΒΟΗΘΕΙΑ ΠΑΡΑΚΑΛΩ ΕΧΩ ΚΟΛΛΗΣΕΙ ΣΕ ΜΙΑ ΣΥΜΒΟΛΟΔΕΙΡΑ

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

14.2.1 Χαρακτήρες διαφυγής

Για να συμπεριλάβετε ένα κυριολεκτικό μονό ή διπλό εισαγωγικό σε μία συμβολοσειρά, μπορείτε να χρησιμοποιήσετε το \ για να το “φυγαδεύσετε”:

double_quote <- "\"" # or '"'
single_quote <- '\'' # or "'"

Επομένως, αν θέλετε να συμπεριλάβετε μία κυριολεκτική ανάποδη κάθετο στη συμβολοσειρά σας, θα πρέπει να την αποφύγετε: "\\":

backslash <- "\\"

Προσέξτε ότι η εκτυπωμένη αναπαράσταση μιας συμβολοσειράς δεν είναι ίδια με την ίδια τη συμβολοσειρά, επειδή η εκτυπωμένη αναπαράσταση δείχνει τις διαφυγές (με άλλα λόγια, όταν εκτυπώνετε μία συμβολοσειρά, μπορείτε να αντιγράψετε και να επικολλήσετε την έξοδο για να δημιουργήσετε εκ νέου αυτήν τη συμβολοσειρά). Για να δείτε τα ακατέργαστα περιεχόμενα της συμβολοσειράς, χρησιμοποιήστε την str_view()1:

x <- c(single_quote, double_quote, backslash)
x
#> [1] "'"  "\"" "\\"

str_view(x)
#> [1] │ '
#> [2] │ "
#> [3] │ \

14.2.2 Ακατέργαστες συμβολοσειρές

Η δημιουργία μιας συμβολοσειράς με πολλαπλά εισαγωγικά ή ανάποδες καθέτους γίνεται γρήγορα πολύπλοκη. Για να δείξουμε το πρόβλημα, ας δημιουργήσουμε μία συμβολοσειρά που περιέχει τα περιεχόμενα του κώδικα όπου ορίζουμε τις μεταβλητές double_quote και single_quote:

tricky <- "double_quote <- \"\\\"\" # or '\"'
single_quote <- '\\'' # or \"'\""
str_view(tricky)
#> [1] │ double_quote <- "\"" # or '"'
#>     │ single_quote <- '\'' # or "'"

Έχουμε πολλές ανάποδες καθέτους! (Μερικές φορές αυτό ονομάζεται σύνδρομο κεκλιμένης οδοντογλυφίδας (leaning toothpick syndrome).) Για να αποφύγετε τη διαφυγή, μπορείτε να χρησιμοποιήσετε μία ακατέργαστη συμβολοσειρά2:

tricky <- r"(double_quote <- "\"" # or '"'
single_quote <- '\'' # or "'")"
str_view(tricky)
#> [1] │ double_quote <- "\"" # or '"'
#>     │ single_quote <- '\'' # or "'"

Μία ακατέργαστη συμβολοσειρά αρχίζει συνήθως με r"( και τελειώνει με )". Αλλά αν η συμβολοσειρά σας περιέχει )" μπορείτε αντ ’αυτού να χρησιμοποιήσετε το r"[]" ή το r"{}", και αν ακόμα κι αυτό δεν είναι αρκετό, μπορείτε να εισάγετε οποιονδήποτε αριθμό από παύλες για να κάνετε τα ζεύγη ανοίγματος και κλεισίματος μοναδικά, π.χ. r"--()--", r"---()---", κ.λπ. Οι ακατέργαστες συμβολοσειρές είναι αρκετά ευέλικτες ώστε να χειρίζονται οποιοδήποτε κείμενο.

14.2.3 Λοιποί ειδικοί χαρακτήρες

Εκτός από τους \", \' και \\, υπάρχουν πολλοί άλλοι ειδικοί χαρακτήρες που μπορεί να σας φανούν χρήσιμοι. Οι πιο συνηθισμένοι είναι ο \n, για τον ορισμό μιας νέας γραμμής και o \t, για στηλοθέτες. Επίσης, μερικές φορές θα δείτε συμβολοσειρές που περιέχουν διαφυγές Unicode που ξεκινούν με \u ή \U. Αυτός είναι ένας τρόπος γραφής μη αγγλικών χαρακτήρων οι οποίοι λειτουργούν σε όλα τα συστήματα. Μπορείτε να δείτε την πλήρη λίστα των ειδικοί χαρακτήρες εκτελώντας το ?Quotes.

x <- c("one\ntwo", "one\ttwo", "\u00b5", "\U0001f604")
x
#> [1] "one\ntwo" "one\ttwo" "µ"        "😄"
str_view(x)
#> [1] │ one
#>     │ two
#> [2] │ one{\t}two
#> [3] │ µ
#> [4] │ 😄

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

14.2.4 Ασκήσεις

  1. Δημιουργήστε συμβολοσειρές που περιέχουν τις ακόλουθες τιμές:

    1. He said "That's amazing!"

    2. \a\b\c\d

    3. \\\\\\

  2. Δημιουργήστε την ακόλουθη συμβολοσειρά στην R και εκτυπώστε την. Τι συμβαίνει με το ειδικό “\u00a0”;/ Πώς τον εμφανίζει η str_view();
    Μπορείτε να κάνετε λίγο γκουγκλάρισμα για να καταλάβετε ποιος είναι αυτός ο ιδιαίτερος χαρακτήρας;

    x <- "This\u00a0is\u00a0tricky"

14.3 Δημιουργία πολλαπλών συμβολοσειρών από δεδομένα

Τώρα που έχετε μάθει τα βασικά για τη δημιουργία μιας συμβολοσειράς “με το χέρι”, θα μεταφερθούμε στις λεπτομέρειες της δημιουργίας συμβολοσειρών από άλλες συμβολοσειρές. Αυτό θα σας βοηθήσει να λύσετε το συχνό πρόβλημα στο οποίο έχετε κάποιο κείμενο που γράψατε και θέλετε να το συνδυάσετε με συμβολοσειρές που βρίσκονται σε ένα πλαίσιο δεδομένων. Για παράδειγμα, μπορείτε να συνδυάσετε το “Hello” με μία μεταβλητή που προσδιορίζει ένα όνομα name, για να δημιουργήσετε έναν χαιρετισμό. Θα σας δείξουμε πώς να το κάνετε με τις str_c() και str_glue(), και πώς μπορείτε να τις χρησιμοποιήσετε με την mutate(). Αυτό εγείρει φυσικά το ερώτημα ποιες συναρτήσεις του πακέτου stringr μπορείτε να χρησιμοποιήσετε με την summarize(), οπότε θα ολοκληρώσουμε αυτήν την ενότητα με μία συζήτηση για την str_flatten(), μία συνάρτηση σύνοψης για συμβολοσειρές.

14.3.1 str_c()

Η str_c() παίρνει οποιονδήποτε αριθμό διανυσμάτων ως όρισμα και επιστρέφει ένα διάνυσμα χαρακτήρων:

str_c("x", "y")
#> [1] "xy"
str_c("x", "y", "z")
#> [1] "xyz"
str_c("Hello ", c("John", "Susan"))
#> [1] "Hello John"  "Hello Susan"

Η str_c() είναι παρόμοια με την βασική συνάρτηση paste0(), αλλά έχει σχεδιαστεί για να χρησιμοποιείται μαζί με την mutate(), υπακούοντας στους συνήθεις κανόνες τακτοποίησης για την ανακύκλωση και τη διαχείριση κενών τιμών:

df <- tibble(name = c("Flora", "David", "Terra", NA))
df |> mutate(greeting = str_c("Hi ", name, "!"))
#> # A tibble: 4 × 2
#>   name  greeting 
#>   <chr> <chr>    
#> 1 Flora Hi Flora!
#> 2 David Hi David!
#> 3 Terra Hi Terra!
#> 4 <NA>  <NA>

Εάν θέλετε οι κενές τιμές να εμφανίζονται με άλλο τρόπο, χρησιμοποιήστε την coalesce() για να τις αντικαταστήσετε. Ανάλογα με το τι θέλετε κάθε φορά, μπορείτε να την χρησιμοποιήσετε είτε εντός, είτε εκτός της str_c():

df |> 
  mutate(
    greeting1 = str_c("Hi ", coalesce(name, "you"), "!"),
    greeting2 = coalesce(str_c("Hi ", name, "!"), "Hi!")
  )
#> # A tibble: 4 × 3
#>   name  greeting1 greeting2
#>   <chr> <chr>     <chr>    
#> 1 Flora Hi Flora! Hi Flora!
#> 2 David Hi David! Hi David!
#> 3 Terra Hi Terra! Hi Terra!
#> 4 <NA>  Hi you!   Hi!

14.3.2 str_glue()

Εάν βάζετε μαζί πολλές σταθερές και μεταβλητές συμβολοσειρές με την str_c(), θα παρατηρήσετε ότι πληκτρολογείτε πολλά ", κάνοντας εύκολο τον αποπροσανατολισμό σας από τον τελικό στόχο του κώδικα. Μία εναλλακτική προσέγγιση παρέχεται από το πακέτο glue μέσω της str_glue()4. Εσείς δίνετε μόνο μία συμβολοσειρά με ένα ειδικό χαρακτηριστικό: οτιδήποτε βρίσκεται μέσα στα {} θα αξιολογηθεί σαν να είναι έξω από τα εισαγωγικά:

df |> mutate(greeting = str_glue("Hi {name}!"))
#> # A tibble: 4 × 2
#>   name  greeting 
#>   <chr> <glue>   
#> 1 Flora Hi Flora!
#> 2 David Hi David!
#> 3 Terra Hi Terra!
#> 4 <NA>  Hi NA!

Όπως μπορείτε να δείτε, η str_glue() μετατρέπει αυτήν τη στιγμή τις τιμές που λείπουν στη συμβολοσειρά "NA", καθιστώντας το, δυστυχώς, ασυνεπές με το str_c().

Μπορεί επίσης να αναρωτιέστε τι συμβαίνει εάν χρειαστεί να συμπεριλάβετε ένα κανονικό { ή } στη συμβολοσειρά σας. Είστε σε καλό δρόμο αν μαντέψετε ότι θα χρειαστεί με κάποιο τρόπο να το διαφύγετε. Το κόλπο είναι ότι το πακέτο glue χρησιμοποιεί μία ελαφρώς διαφορετική τεχνική διαφυγής: αντί να προσθέτει κάποιο πρόθεμα ειδικού χαρακτήρα όπως το \, διπλασιάζει τους ειδικούς χαρακτήρες:

df |> mutate(greeting = str_glue("{{Hi {name}!}}"))
#> # A tibble: 4 × 2
#>   name  greeting   
#>   <chr> <glue>     
#> 1 Flora {Hi Flora!}
#> 2 David {Hi David!}
#> 3 Terra {Hi Terra!}
#> 4 <NA>  {Hi NA!}

14.3.3 str_flatten()

Η str_c() και η str_glue() λειτουργούν καλά με την mutate() επειδή η έξοδος τους έχει το ίδιο μήκος με τις εισόδους τους. Τι γίνεται αν θέλετε μία συνάρτηση που λειτουργεί καλά με την summarize(), δηλαδή κάτι που επιστρέφει πάντα μία συμβολοσειρά;
Αυτή είναι η δουλειά της str_flatten()5: παίρνει ένα διάνυσμα χαρακτήρων και συνδυάζει κάθε στοιχείο του διανύσματος σε μία συμβολοσειρά:

str_flatten(c("x", "y", "z"))
#> [1] "xyz"
str_flatten(c("x", "y", "z"), ", ")
#> [1] "x, y, z"
str_flatten(c("x", "y", "z"), ", ", last = ", and ")
#> [1] "x, y, and z"

Αυτό την κάνει να λειτουργεί καλά με την summarize():

df <- tribble(
  ~ name, ~ fruit,
  "Carmen", "banana",
  "Carmen", "apple",
  "Marvin", "nectarine",
  "Terence", "cantaloupe",
  "Terence", "papaya",
  "Terence", "mandarin"
)
df |>
  group_by(name) |> 
  summarize(fruits = str_flatten(fruit, ", "))
#> # A tibble: 3 × 2
#>   name    fruits                      
#>   <chr>   <chr>                       
#> 1 Carmen  banana, apple               
#> 2 Marvin  nectarine                   
#> 3 Terence cantaloupe, papaya, mandarin

14.3.4 Ασκήσεις

  1. Συγκρίνετε και αντιπαραβάλλετε τα αποτελέσματα της paste0() με αυτά της str_c() για τις ακόλουθες εισόδους:

    str_c("hi ", NA)
    str_c(letters[1:2], letters[1:3])
  2. Ποια είναι η διαφορά μεταξύ της paste() και της paste0();
    Πώς μπορείτε να δημιουργήσετε ξανά το ισοδύναμο της paste() με την str_c();

  3. Μετατρέψτε τις παρακάτω εκφράσεις από str_c() σε str_glue() ή αντίστροφα:

    1. str_c("The price of ", food, " is ", price)
    2. str_glue("I'm {age} years old and live in {country}")
    3. str_c("\\section{", title, "}")

14.4 Εξάγοντας δεδομένα απο σειμβολοδειρές

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

  • df |> separate_longer_delim(col, delim)
  • df |> separate_longer_position(col, width)
  • df |> separate_wider_delim(col, delim, names)
  • df |> separate_wider_position(col, widths)

Αν κοιτάξετε προσεκτικά, μπορείτε να δείτε ότι υπάρχει ένα κοινό μοτίβο: separate_, ακολουθούμενο από longer ή wider, ακολουθούμενο από _, ακολουθούμενο από delim ή position. Αυτό συμβαίνει επειδή αυτές οι τέσσερις συναρτήσεις αποτελούνται από δύο πιο απλές:

  • Ακριβώς όπως με τις pivot_longer() και pivot_wider(), οι συναρτήσεις _longer κάνουν το πλαίσιο δεδομένων εισόδου μακρύτερο δημιουργώντας νέες γραμμές ενώ οι συναρτήσεις _wider κάνουν το πλαίσιο δεδομένων εισόδου πλατύτερο με την δημιουργία νέων στηλών.
  • Το delim διαχωρίζει μία συμβολοσειρά με έναν οριοθέτη, όπως ", " ή " ". Το position τη διαχωρίζει σε καθορισμένα πλάτη, όπως c(3, 5, 2).

Θα επιστρέψουμε στο τελευταίο μέλος αυτής της οικογένειας, την separate_wider_regex(), στο Κεφάλαιο 15. Είναι η πιο ευέλικτη από τις wider συναρτήσεις, αλλά για να μπορέσετε να τη χρησιμοποιήσετε θα πρέπει να γνωρίζετε λίγα πράγματα για τις κανονικές εκφράσεις.

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

14.4.1 Διαχωρίζοντας σε γραμμές

Ο διαχωρισμός μιας συμβολοσειράς σε γραμμές τείνει να είναι πιο χρήσιμη όταν ο αριθμός των στοιχείων ποικίλλει από γραμμή σε γραμμή. Η πιο συνηθισμένη περίπτωση είναι η λειτουργία διαχωρισμού της separate_longer_delim() με βάση έναν οριοθέτη:

df1 <- tibble(x = c("a,b,c", "d,e", "f"))
df1 |> 
  separate_longer_delim(x, delim = ",")
#> # A tibble: 6 × 1
#>   x    
#>   <chr>
#> 1 a    
#> 2 b    
#> 3 c    
#> 4 d    
#> 5 e    
#> 6 f

Είναι πιο σπάνιο να δείτε την separate_longer_position() εκεί έξω, αλλά ορισμένα παλαιότερα σύνολα δεδομένων χρησιμοποιούν μία αρκετά συμπαγή μορφή, όπου κάθε χαρακτήρας χρησιμοποιείται για την καταγραφή μιας τιμής:

df2 <- tibble(x = c("1211", "131", "21"))
df2 |> 
  separate_longer_position(x, width = 1)
#> # A tibble: 9 × 1
#>   x    
#>   <chr>
#> 1 1    
#> 2 2    
#> 3 1    
#> 4 1    
#> 5 1    
#> 6 3    
#> # ℹ 3 more rows

14.4.2 Διαχωρίζωντας σε στήλες

Ο διαχωρισμός μιας συμβολοσειράς σε στήλες τείνει να είναι πιο χρήσιμος όταν υπάρχει ένας σταθερός αριθμός στοιχείων σε κάθε συμβολοσειρά και θέλετε να τα κατανείμετε σε στήλες. Είναι ελαφρώς πιο περίπλοκα από τα longer ισοδύναμά τους, επειδή πρέπει να ονομάσετε τις στήλες. Για παράδειγμα, στο ακόλουθο σύνολο δεδομένων, το x αποτελείται από έναν κωδικό, έναν αριθμό έκδοσης και ένα έτος, χωρισμένα με ".". Για να χρησιμοποιήσουμε την separate_wider_delim(), παρέχουμε τον οριοθέτη και τα ονόματα σε δύο ορίσματα:

df3 <- tibble(x = c("a10.1.2022", "b10.2.2011", "e15.1.2015"))
df3 |> 
  separate_wider_delim(
    x,
    delim = ".",
    names = c("code", "edition", "year")
  )
#> # A tibble: 3 × 3
#>   code  edition year 
#>   <chr> <chr>   <chr>
#> 1 a10   1       2022 
#> 2 b10   2       2011 
#> 3 e15   1       2015

Εάν ένα συγκεκριμένο κομμάτι δεν είναι χρήσιμο, μπορείτε να χρησιμοποιήσετε ένα όνομα NA για να το παραλείψετε από τα αποτελέσματα:

df3 |> 
  separate_wider_delim(
    x,
    delim = ".",
    names = c("code", NA, "year")
  )
#> # A tibble: 3 × 2
#>   code  year 
#>   <chr> <chr>
#> 1 a10   2022 
#> 2 b10   2011 
#> 3 e15   2015

H separate_wider_position() λειτουργεί λίγο διαφορετικά, επειδή συνήθως θέλετε να καθορίσετε το πλάτος κάθε στήλης. Έτσι, του δίνετε ένα αριθμητικό διάνυσμα με όνομα, όπου το όνομα δίνει το όνομα της νέας στήλης και η τιμή είναι ο αριθμός των χαρακτήρων που καταλαμβάνει. Μπορείτε να παραλείψετε τιμές από την έξοδο, χωρίς να τις ονομάσετε:

df4 <- tibble(x = c("202215TX", "202122LA", "202325CA")) 
df4 |> 
  separate_wider_position(
    x,
    widths = c(year = 4, age = 2, state = 2)
  )
#> # A tibble: 3 × 3
#>   year  age   state
#>   <chr> <chr> <chr>
#> 1 2022  15    TX   
#> 2 2021  22    LA   
#> 3 2023  25    CA

14.4.3 Διάγνωση προβλημάτων διεύρυνσης

Η separate_wider_delim()6 απαιτεί ένα σταθερό και γνωστό σύνολο στηλών. Τι συμβαίνει εάν ορισμένες από τις γραμμές δεν έχουν τον αναμενόμενο αριθμό τμημάτων;
Υπάρχουν δύο πιθανά προβλήματα, πολύ λίγα ή πάρα πολλά τμήματα, επομένως η separate_wider_delim() παρέχει δύο ορίσματα για να βοηθήσει: το too_few και το too_many. Ας δούμε πρώτα την περίπτωση του too_few με το ακόλουθο δείγμα δεδομένων:

df <- tibble(x = c("1-1-1", "1-1-2", "1-3", "1-3-2", "1"))

df |> 
  separate_wider_delim(
    x,
    delim = "-",
    names = c("x", "y", "z")
  )
#> Error in `separate_wider_delim()`:
#> ! Expected 3 pieces in each element of `x`.
#> ! 2 values were too short.
#> ℹ Use `too_few = "debug"` to diagnose the problem.
#> ℹ Use `too_few = "align_start"/"align_end"` to silence this message.

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

debug <- df |> 
  separate_wider_delim(
    x,
    delim = "-",
    names = c("x", "y", "z"),
    too_few = "debug"
  )
#> Warning: Debug mode activated: adding variables `x_ok`, `x_pieces`, and
#> `x_remainder`.
debug
#> # A tibble: 5 × 6
#>   x     y     z     x_ok  x_pieces x_remainder
#>   <chr> <chr> <chr> <lgl>    <int> <chr>      
#> 1 1-1-1 1     1     TRUE         3 ""         
#> 2 1-1-2 1     2     TRUE         3 ""         
#> 3 1-3   3     <NA>  FALSE        2 ""         
#> 4 1-3-2 3     2     TRUE         3 ""         
#> 5 1     <NA>  <NA>  FALSE        1 ""

Όταν χρησιμοποιείτε τη λειτουργία εντοπισμού σφαλμάτων, λαμβάνετε τρεις επιπλέον στήλες που προστίθενται στην έξοδο: x_ok, x_pieces και x_remainder (αν διαχωρίσετε μία μεταβλητή με διαφορετικό όνομα, θα λάβετε διαφορετικό πρόθεμα). Εδώ, το x_ok σας επιτρέπει να βρείτε γρήγορα τις εισόδους που απέτυχαν:

debug |> filter(!x_ok)
#> # A tibble: 2 × 6
#>   x     y     z     x_ok  x_pieces x_remainder
#>   <chr> <chr> <chr> <lgl>    <int> <chr>      
#> 1 1-3   3     <NA>  FALSE        2 ""         
#> 2 1     <NA>  <NA>  FALSE        1 ""

Το x_pieces μας λέει πόσα τμήματα βρέθηκαν, σε σύγκριση με τα αναμενόμενα 3 (το μήκος του names). Το x_remainder δεν είναι χρήσιμο όταν υπάρχουν πολύ λίγα τμήματα, αλλά θα το δούμε ξανά σύντομα.

Μερικές φορές η εξέταση αυτών των πληροφοριών για τον εντοπισμό σφαλμάτων θα αποκαλύψει ένα πρόβλημα με τη στρατηγική οριοθέτησης, ή θα υποδείξει ότι πρέπει να κάνετε περισσότερη προεπεξεργασία πριν τον διαχωρισμό. Σε αυτήν την περίπτωση, διορθώστε το πρόβλημα στην αρχή και φροντίστε να αφαιρέσετε το too_few = "debug", για να διασφαλίσετε ότι τα νέα προβλήματα θα μετατραπούν σε σφάλματα.

Σε άλλες περιπτώσεις, μπορεί να θέλετε να συμπληρώσετε τα κομμάτια που λείπουν με NA και να προχωρήσετε. Αυτή είναι η δουλειά των too_few = "align_start" και too_few = "align_end", που σας επιτρέπουν να ελέγχετε πού πρέπει να πηγαίνουν τα NA:

df |> 
  separate_wider_delim(
    x,
    delim = "-",
    names = c("x", "y", "z"),
    too_few = "align_start"
  )
#> # A tibble: 5 × 3
#>   x     y     z    
#>   <chr> <chr> <chr>
#> 1 1     1     1    
#> 2 1     1     2    
#> 3 1     3     <NA> 
#> 4 1     3     2    
#> 5 1     <NA>  <NA>

Οι ίδιες αρχές ισχύουν και στην περίπτωση που έχετε πάρα πολλά τμήματα:

df <- tibble(x = c("1-1-1", "1-1-2", "1-3-5-6", "1-3-2", "1-3-5-7-9"))

df |> 
  separate_wider_delim(
    x,
    delim = "-",
    names = c("x", "y", "z")
  )
#> Error in `separate_wider_delim()`:
#> ! Expected 3 pieces in each element of `x`.
#> ! 2 values were too long.
#> ℹ Use `too_many = "debug"` to diagnose the problem.
#> ℹ Use `too_many = "drop"/"merge"` to silence this message.

Τώρα όμως, όταν αντιμετωπίζουμε σφάλματα στο αποτέλεσμα, μπορείτε να καταλάβετε τον σκοπό του x_remainder:

debug <- df |> 
  separate_wider_delim(
    x,
    delim = "-",
    names = c("x", "y", "z"),
    too_many = "debug"
  )
#> Warning: Debug mode activated: adding variables `x_ok`, `x_pieces`, and
#> `x_remainder`.
debug |> filter(!x_ok)
#> # A tibble: 2 × 6
#>   x         y     z     x_ok  x_pieces x_remainder
#>   <chr>     <chr> <chr> <lgl>    <int> <chr>      
#> 1 1-3-5-6   3     5     FALSE        4 -6         
#> 2 1-3-5-7-9 3     5     FALSE        5 -7-9

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

df |> 
  separate_wider_delim(
    x,
    delim = "-",
    names = c("x", "y", "z"),
    too_many = "drop"
  )
#> # A tibble: 5 × 3
#>   x     y     z    
#>   <chr> <chr> <chr>
#> 1 1     1     1    
#> 2 1     1     2    
#> 3 1     3     5    
#> 4 1     3     2    
#> 5 1     3     5


df |> 
  separate_wider_delim(
    x,
    delim = "-",
    names = c("x", "y", "z"),
    too_many = "merge"
  )
#> # A tibble: 5 × 3
#>   x     y     z    
#>   <chr> <chr> <chr>
#> 1 1     1     1    
#> 2 1     1     2    
#> 3 1     3     5-6  
#> 4 1     3     2    
#> 5 1     3     5-7-9

14.5 Γράμματα

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

14.5.1 Μήκος

Η str_length() σας λέει τον αριθμό των γραμμάτων σε μία συμβολοσειρά:

str_length(c("a", "R for data science", NA))
#> [1]  1 18 NA

Θα μπορούσατε να τη χρησιμοποιήσετε με την count(), για να βρείτε την κατανομή των μεγεθών των ονομάτων που δίνονται σε μωρά στις ΗΠΑ, και στη συνέχεια με την filter(), για να δείτε τα μεγαλύτερα ονόματα, τα οποία τυχαίνει να έχουν 15 γράμματα7:

babynames |>
  count(length = str_length(name), wt = n)
#> # A tibble: 14 × 2
#>   length        n
#>    <int>    <int>
#> 1      2   338150
#> 2      3  8589596
#> 3      4 48506739
#> 4      5 87011607
#> 5      6 90749404
#> 6      7 72120767
#> # ℹ 8 more rows

babynames |> 
  filter(str_length(name) == 15) |> 
  count(name, wt = n, sort = TRUE)
#> # A tibble: 34 × 2
#>   name                n
#>   <chr>           <int>
#> 1 Franciscojavier   123
#> 2 Christopherjohn   118
#> 3 Johnchristopher   118
#> 4 Christopherjame   108
#> 5 Christophermich    52
#> 6 Ryanchristopher    45
#> # ℹ 28 more rows

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

Μπορείτε να εξάγετε μέρη μιας συμβολοσειράς χρησιμοποιώντας την str_sub(string, start, end), όπου start και end είναι οι θέσεις στις οποίες πρέπει να ξεκινά και να τελειώνει η υποσυμβολοσειρά. Τα ορίσματα start και end περιλαμβάνονται στο αποτέλεσμα, επομένως το μήκος της συμβολοσειράς που επιστρέφεται θα είναι end - start + 1:

x <- c("Apple", "Banana", "Pear")
str_sub(x, 1, 3)
#> [1] "App" "Ban" "Pea"

Μπορείτε να χρησιμοποιήσετε αρνητικές τιμές για να μετρήσετε αντίστροφα από το τέλος της συμβολοσειράς: -1 είναι ο τελευταίος χαρακτήρας, -2 είναι ο ο δεύτερος απο το τέλος κ.λπ.

str_sub(x, -3, -1)
#> [1] "ple" "ana" "ear"

Σημειώστε ότι η str_sub() δεν θα αποτύχει εάν η συμβολοσειρά είναι πολύ μικρή: απλώς θα επιστρέψει όσο το δυνατόν περισσότερα:

str_sub("a", 1, 5)
#> [1] "a"

Θα μπορούσαμε να χρησιμοποιήσουμε την str_sub() με την mutate() για να βρούμε το πρώτο και το τελευταίο γράμμα κάθε ονόματος:

babynames |> 
  mutate(
    first = str_sub(name, 1, 1),
    last = str_sub(name, -1, -1)
  )
#> # A tibble: 1,924,665 × 7
#>    year sex   name          n   prop first last 
#>   <dbl> <chr> <chr>     <int>  <dbl> <chr> <chr>
#> 1  1880 F     Mary       7065 0.0724 M     y    
#> 2  1880 F     Anna       2604 0.0267 A     a    
#> 3  1880 F     Emma       2003 0.0205 E     a    
#> 4  1880 F     Elizabeth  1939 0.0199 E     h    
#> 5  1880 F     Minnie     1746 0.0179 M     e    
#> 6  1880 F     Margaret   1578 0.0162 M     t    
#> # ℹ 1,924,659 more rows

14.5.3 Ασκήσεις

  1. Γιατί χρησιμοποιήσαμε το wt = n, κατά τον υπολογισμό της κατανομής του μήκους των ονομάτων μωρών;
  2. Χρησιμοποιήστε τις str_length() και str_sub() για να εξάγετε το μεσαίο γράμμα από κάθε όνομα μωρού. Τι θα κάνετε εάν η συμβολοσειρά έχει ζυγό αριθμό χαρακτήρων;
  3. Υπάρχουν σημαντικές τάσεις στο μήκος των ονομάτων μωρών με την πάροδο του χρόνου;/ Τι γίνεται με τη δημοτικότητα των πρώτων και τελευταίων γραμμάτων;

14.6 Μη αγγλικό κείμενο

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

14.6.1 Κωδικοποίηση

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

charToRaw("Hadley")
#> [1] 48 61 64 6c 65 79

Καθένας από αυτούς τους έξι δεκαεξαδικούς αριθμούς αντιπροσωπεύει ένα γράμμα: το 48 είναι το H, το 61 είναι το a και ούτω καθεξής. Η αντιστοίχιση από δεκαεξαδικό αριθμό σε χαρακτήρα ονομάζεται κωδικοποίηση και σε αυτή την περίπτωση η κωδικοποίηση ονομάζεται ASCII. Η ASCII κάνει εξαιρετική δουλειά στην αναπαράσταση αγγλικών χαρακτήρων, επειδή είναι ο Αμερικανικός Πρότυπος Κώδικας για Ανταλλαγή Πληροφοριών (American Standard Code for Information Interchange).

Τα πράγματα δεν είναι τόσο εύκολα για άλλες γλώσσες. Στις πρώτες μέρες των υπολογιστών, υπήρχαν πολλά ανταγωνιστικά πρότυπα για την κωδικοποίηση μη αγγλικών χαρακτήρων. Για παράδειγμα, υπήρχαν δύο διαφορετικές κωδικοποιήσεις για την Ευρώπη: η Latin1 (γνωστή και ως ISO-8859-1) χρησιμοποιήθηκε για τις δυτικοευρωπαϊκές γλώσσες και η Latin2 (γνωστή και ως ISO-8859-2) για τις γλώσσες της Κεντρικής Ευρώπης. Στη Latin1, το byte b1 είναι “±”, αλλά στη Latin2, είναι “ą”! Ευτυχώς, σήμερα υπάρχει ένα πρότυπο που υποστηρίζεται σχεδόν παντού: το UTF-8. Το UTF-8 μπορεί να κωδικοποιήσει σχεδόν κάθε χαρακτήρα που χρησιμοποιείται από τους ανθρώπους σήμερα, αλλά και πολλά επιπλέον σύμβολα όπως emojis.

Η readr χρησιμοποιεί UTF-8 παντού. Αυτή είναι μία καλή προεπιλογή, αλλά θα αποτύχει για δεδομένα που παράγονται από παλαιότερα συστήματα που δεν χρησιμοποιούν UTF-8. Εάν συμβεί αυτό, οι συμβολοσειρές σας θα φαίνονται περίεργα όταν τις εκτυπώνετε. Μερικές φορές μόνο ένας ή δύο χαρακτήρες μπορεί να είναι μπερδεμένοι. Αλλες φορές, υπάρχει πλήρης ασυναρτησία. Για παράδειγμα, ακολουθούν δύο ένθετα CSV με ασυνήθιστες κωδικοποιήσεις8:

x1 <- "text\nEl Ni\xf1o was particularly bad this year"
read_csv(x1)$text
#> [1] "El Ni\xf1o was particularly bad this year"

x2 <- "text\n\x82\xb1\x82\xf1\x82\xc9\x82\xbf\x82\xcd"
read_csv(x2)$text
#> [1] "\x82\xb1\x82\xf1\x82ɂ\xbf\x82\xcd"

Για να τα διαβάσετε σωστά, καθορίζετε την κωδικοποίηση μέσω του ορίσματος locale:

read_csv(x1, locale = locale(encoding = "Latin1"))$text
#> [1] "El Niño was particularly bad this year"

read_csv(x2, locale = locale(encoding = "Shift-JIS"))$text
#> [1] "こんにちは"

Πώς βρίσκετε τη σωστή κωδικοποίηση;/ Εάν είστε τυχεροί, θα συμπεριληφθεί κάπου στις οδηγίες των δεδομένων. Δυστυχώς, αυτό συμβαίνει σπάνια, οπότε το πακέτο readr παρέχει την guess_encoding() για να σας βοηθήσει να την βρείτε. Δεν είναι αλάνθαστο και λειτουργεί καλύτερα όταν έχετε πολύ κείμενο (σε αντίθεση με εδώ), αλλά είναι ένα λογικό μέρος για να ξεκινήσετε. Να περιμένετε ότι θα δοκιμάσετε μερικές διαφορετικές κωδικοποιήσεις μέχρι να βρείτε τη σωστή.

Οι κωδικοποιήσεις είναι ένα πλούσιο και πολύπλοκο θέμα. Μεχρι ώρας, έχουμε εξετάσει μόνο την επιφάνεια. Εάν θέλετε να μάθετε περισσότερα, συνιστούμε να διαβάσετε τις λεπτομέρειες στη διεύθυνση http://kunststube.net/encoding/.

14.6.2 Παραλλαγές γραμμάτων

Η εργασία σε γλώσσες που χρησιμοποιούν τόνους αποτελεί σημαντική πρόκληση κατά τον προσδιορισμό της θέσης των γραμμάτων (π.χ. με την str_length() και την str_sub()), καθώς τα τονισμένα γράμματα μπορεί να κωδικοποιηθούν ως ένας μεμονωμένος χαρακτήρας (π.χ. το ü) ή ως δύο χαρακτήρες συνδυάζοντας ένα γράμμα χωρίς τόνο (π.χ., u) με ένα διακριτικό σημάδι (π.χ., ¨). Για παράδειγμα, αυτός ο κώδικας δείχνει δύο τρόπους αναπαράστασης του ü, που φαίνονται πανομοιότυποι:

u <- c("\u00fc", "u\u0308")
str_view(u)
#> [1] │ ü
#> [2] │ ü

Αλλά και οι δύο συμβολοσειρές διαφέρουν σε μήκος και οι πρώτοι χαρακτήρες τους είναι διαφορετικοί:

str_length(u)
#> [1] 1 2
str_sub(u, 1, 1)
#> [1] "ü" "u"

Τέλος, σημειώστε ότι μία σύγκριση αυτών των συμβολοσειρών με το == ερμηνεύει αυτές τις συμβολοσειρές ως διαφορετικές, ενώ η εύχρηστη συνάρτηση str_equal() στο πακέτο stringr αναγνωρίζει ότι και οι δύο έχουν την ίδια εμφάνιση:

u[[1]] == u[[2]]
#> [1] FALSE

str_equal(u[[1]], u[[2]])
#> [1] TRUE

14.6.3 Συναρτήσεις που εξαρτώνται από την γεωγραφική τοποθεσία

Τέλος, υπάρχει ένας αριθμός από συναρτήσεις του πακέτου stringr, των οποίων η συμπεριφορά εξαρτάται από την τοποθεσία σας. Μία ρύθμιση τοποθεσίας είναι παρόμοια με αυτή της γλώσσας, αλλά περιλαμβάνει έναν προαιρετικό προσδιοριστή περιοχής για τον χειρισμό τοπικών παραλλαγών σε μία γλώσσα. Μία ρύθμιση τοποθεσίας καθορίζεται από μία συντομογραφία γλώσσας με πεζά γράμματα, προαιρετικά ακολουθούμενη από ένα _ και ένα αναγνωριστικό περιοχής με κεφαλαία. Για παράδειγμα, το “en” είναι αγγλικά, το “en_GB” είναι τα βρετανικά αγγλικά και το “en_US” είναι τα αμερικανικά αγγλικά. Εάν δεν γνωρίζετε ήδη τον κώδικα για τη γλώσσα σας, η Wikipedia έχει μία καλή λίστα και μπορείτε να δείτε ποιοι κώδικες υποστηρίζονται στο πακέτο stringr εκτελώντας το stringi::stri_locale_list().

Οι βασικές συναρτήσεις συμβολοσειράς της R χρησιμοποιούν αυτόματα τις τοπικές ρυθμίσεις που έχουν οριστεί από το λειτουργικό σας σύστημα. Αυτό σημαίνει ότι οι βασικές συναρτήσεις συμβολοσειράς της R κάνουν ό,τι περιμένετε για τη γλώσσα σας, αλλά ο κώδικάς σας μπορεί να λειτουργήσει διαφορετικά εάν τον μοιραστείτε με κάποιον που ζει σε διαφορετική χώρα. Για να αποφευχθεί αυτό το πρόβλημα, το πακέτο stringr ορίζει από προεπιλογή τους αγγλικούς κανόνες χρησιμοποιώντας την ρύθμιση “en” και για να το παρακάμψετε, απαιτεί από εσάς να καθορίσετε το όρισμα locale. Ευτυχώς, υπάρχουν δύο σύνολα συναρτήσεων όπου η ρύθμιση τοποθεσίας έχει πραγματικά σημασία: στην αλλαγή πεζών-κεφαλαίων και στην ταξινόμηση.

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

str_to_upper(c("i", "ı"))
#> [1] "I" "I"
str_to_upper(c("i", "ı"), locale = "tr")
#> [1] "İ" "I"

Η ταξινόμηση των συμβολοσειρών εξαρτάται από τη σειρά του αλφαβήτου και η σειρά του αλφαβήτου δεν είναι ίδια σε κάθε γλώσσα9! Ακολουθεί ένα παράδειγμα: στα τσέχικα, το “ch” είναι ένα σύνθετο γράμμα που εμφανίζεται μετά το “h” στο αλφάβητο.

str_sort(c("a", "c", "ch", "h", "z"))
#> [1] "a"  "c"  "ch" "h"  "z"
str_sort(c("a", "c", "ch", "h", "z"), locale = "cs")
#> [1] "a"  "c"  "h"  "ch" "z"

Αυτό εμφανίζεται επίσης στην ταξινόμηση συμβολοσειρών με την dplyr::arrange(), γι’ αυτό και περιέχει κι αυτή ένα όρισμα locale.

14.7 Σύνοψη

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


  1. Ή χρησιμοποιήστε τη βασική συνάρτηση της R writeLines().↩︎

  2. Διατίθεται στην R 4.0.0 και νεότερες εκδόσεις.↩︎

  3. Η str_view() χρησιμοποιεί επίσης χρώμα για να φέρει στην προσοχή σας στηλοθέτες, κενά, αντιστοιχίσεις κ.λπ. Τα χρώματα δεν εμφανίζονται προς το παρόν στο βιβλίο, αλλά θα τα παρατηρήσετε όταν εκτελείτε τον κώδικα.↩︎

  4. Ακόμα και εάν δεν χρησιμοποιείτε το πακέτο stringr, μπορείτε να το χρησιμοποιήσετε απευθείας με την glue::glue().↩︎

  5. Το ισοδύναμο του βασικού συνόλου λειτουργιών της R είναι η paste() η οποία χρησιμοποιείται με το όρισμα collapse.↩︎

  6. Οι ίδιες αρχές ισχύουν για τις separate_wider_position() και separate_wider_regex().↩︎

  7. Εξετάζοντας τις εγγραφές, θα λέγαμε ότι τα δεδομένα ονομάτων μωρών αφαιρούν κενά ή παύλες και περικόπτονται μετά από 15 γράμματα.↩︎

  8. Εδώ χρησιμοποιώ το ειδικό \x για να κωδικοποιήσω δυαδικά δεδομένα απευθείας σε μία συμβολοσειρά.↩︎

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