Una stringa è un pezzo di testo racchiuso tra virgolette.
mystring <- "some text"
mystring
#> [1] "some text"
typeof(mystring)
#> [1] "character"
Purtroppo la terminologia può confondere chi ha familiarità con altri linguaggi.
Anche se il type è character, R non fa distinzione tra una singola lettera (chiamata char in diversi altri linguaggi, ma non in R) e una stringa lunga.
Entrambe sono un unico elemento e possono essere racchiuse indifferentemente tra virgolette singole ' ' o doppie " ".
La guida di stile consiglia di usare le virgolette doppie, tranne quando vuoi usare virgolette doppie all'interno della stringa senza eseguirne l'escape.
str2 <- 'String with "quotes"' # convenience format
str2
#> [1] "String with \"quotes\"" # standard format
Un character vector è quindi un vettore di stringhe, in cui ogni stringa è un singolo elemento.
s <- c("some", "strings")
s
#> [1] "some" "strings"
length(s)
#> [1] 2
length(s[1]) # the number of vector elements, not the string length
#> [1] 1
nchar(s[1]) # the number of characters in the string "some"
#> [1] 4
Nell'esempio qui sopra, length(s[1]) equivale a length("some"): il numero di elementi del vettore, non la lunghezza della stringa.
Manipolare una singola stringa è ovviamente possibile, come con nchar() nell'esempio qui sopra, ma richiede funzioni specifiche che vedremo più avanti.
Come abbiamo visto sopra, alcuni caratteri di punteggiatura devono essere «escaped», facendoli precedere da una barra rovesciata \.
L'elenco non sorprenderà la maggior parte di chi programma da tempo, perché questo aspetto è ormai piuttosto standardizzato tra i vari linguaggi.
Oltre alle virgolette (come mostrato sopra), l'elenco comprende i caratteri di nuova riga \n, le tabulazioni \t, la barra rovesciata \\ e i simboli Unicode che iniziano con \U o \u.
Per evitare di usare molti caratteri di escape, è possibile definire stringhe raw all'interno di r"( )":
r"(raw "string")"
#> [1] "raw \"string\""
Per le stringhe raw che contengono parentesi ( ), esistono molte altre opzioni di sintassi.
Qualsiasi versione di R attualmente supportata codifica le stringhe in UTF-8, quindi si può gestire la maggior parte delle lingue del mondo.
"आधुनिक मानक हिन्दी"
#> [1] "आधुनिक मानक हिन्दी"
Inoltre, molte funzioni di R tengono conto del locale e cercano di seguire le convenzioni locali per l'ordinamento delle stringhe, la conversione tra maiuscole e minuscole, le date e gli orari, e così via.
Questo percorso mostrerà esempi principalmente per l'inglese americano: sia perché è un valore predefinito comune, sia perché chi ha scritto il percorso risiede negli Stati Uniti.
I locale sono un argomento complesso, quindi cerca la documentazione adatta alla tua situazione.
Con il base R quasi tutto è possibile, ma molti trovano il supporto per la manipolazione delle stringhe piuttosto limitato e confuso.
Per fortuna esistono i pacchetti tidyverse, molto più intuitivi, flessibili e coerenti al loro interno.
Esiste ancora molto codice legacy che usa le vecchie funzioni per le stringhe. È il caso della maggior parte delle soluzioni della community su Exercism.
Inoltre, per i compiti piccoli può essere utile usare le funzioni integrate invece di importare una libreria grande. Chi mantiene il percorso R tende a seguire questo approccio quando scrive soluzioni di esempio per convalidare gli esercizi sul nostro repository GitHub.
Quindi vale comunque la pena conoscere un po' i vecchi approcci.
Le funzioni seguenti sono presentate nella loro forma più semplice, ma quasi tutte hanno anche argomenti opzionali. Consulta la documentazione per i dettagli.
ncharRestituisce il numero di caratteri in una stringa.
nchar("string")
#> [1] 6
nchar("Hrōðgār") # a person in Beowulf, written in Old English
#> [1] 7
L'argomento (complicato!) di che cosa significhi «carattere» va oltre lo scopo di questa pagina. Nota solo un paio di punti negli esempi qui sopra:
\0 (il che aggiungerebbe 1 alla lunghezza).Fai attenzione a ciò che passi a nchar.
I risultati possono comunque essere imprevedibili per valori che non sono di tipo character, anche se la situazione è migliorata nelle versioni recenti di R.
grepCerca un pattern in un vettore di stringhe e restituisce gli indici in cui trova una corrispondenza.
grep("Jav", c("Java", "Python", "Javascript"))
#> [1] 1 3
paste e paste0
Combina un numero arbitrario di stringhe in un'unica stringa.
Per paste si può specificare facoltativamente un separatore: il valore predefinito è uno spazio.
paste0(v) è equivalente a paste(v, sep = "").
paste("bits", "of", "a", "string")
#> [1] "bits of a string"
paste0("bits", "of", "a", "string")
#> [1] "bitsofastring"
sprintfPer un controllo più fine sulla composizione delle stringhe, R riprende la funzione sprintf dal C (e da diversi linguaggi successivi: Wikipedia ne elenca circa 30).
r <- 5.3
pi <- 3.14159
sprintf("A circle of radius %.1f has area %.2f", r, pi * r^2)
#> [1] "A circle of radius 5.3 has area 88.25"
trimwsGli spazi bianchi iniziali e/o finali si possono rimuovere con trimws():
s <- " messy string "
trimws(s)
#> [1] "messy string"
Esiste un parametro which = "left" (o "right") per evitare di tagliare entrambe le estremità.
substrOpera su una singola stringa e restituisce una sotto-stringa compresa tra due limiti (inclusi).
substr("Exercism", 5, 7)
#> [1] "cis"
strsplitDivide una stringa in una lista di R, in base a una stringa separatore. Il separatore è obbligatorio (non ha un valore predefinito), ma può essere una stringa vuota.
strsplit("R, Python, Julia", ", ")
[[1]]
#> [1] "R" "Python" "Julia"
strsplit("Exercism", "")
[[1]]
#> [1] "E" "x" "e" "r" "c" "i" "s" "m"
Le liste saranno trattate in un concetto a parte.
Se non hai ancora raggiunto quella parte del percorso, per ora sappi solo che la funzione unlist() (in questo caso) converte una lista in un vettore.
unlist(strsplit("Exercism", ""))
#> [1] "E" "x" "e" "r" "c" "i" "s" "m"
regexpr e regexpr
Abbreviazione di «espressione regolare», queste funzioni cercano le occorrenze di un pattern in una stringa e restituiscono una lista di dati (potenzialmente) utili. Le citiamo solo per completezza: ci sono opzioni migliori!
regexpr restituisce solo la prima occorrenza, gregexpr le restituisce tutte («g» sta per ricerca «global»).
Come suggeriscono i nomi, il pattern può essere un'espressione regolare, che sarà trattata in un concetto successivo.
Perché queste vecchie funzioni per le stringhe assomigliano ai comandi della shell Unix?
Anche se R è stato creato nel 1993, è stato progettato per essere in gran parte retrocompatibile con il linguaggio S.
S è stato progettato ai Bell Labs, dove è apparso per la prima volta nel 1976. I Bell Labs sono stati anche responsabili di Unix (dal 1969) e del linguaggio C (1972).
Che squadra, quella di un'epoca così lontana! Possiamo solo supporre che parlassero tra loro e si scambiassero idee.
Volendo essere generosi, potremmo dire che le vecchie funzioni per le stringhe non sono il meglio che R abbia da offrire (esistono opinioni meno generose).
Per migliorare la situazione, la libreria stringr è in sviluppo dal 2009 e oggi è una parte fondamentale della raccolta Tidyverse.
Importala nel tuo namespace con library(stringr) o library(tidyverse).
Alcune funzionalità principali:
str_ e cercano di rendere chiaro lo scopo.Come tutti i pacchetti del Tidyverse, stringr ha una buona documentazione, di solito scritta da chi ha creato il pacchetto.
In stringr ci sono decine di funzioni, spesso con diversi argomenti opzionali.
All'inizio può essere un po' scoraggiante: la documentazione è la tua migliore alleata.
Pensa a cosa vuoi inserire (una singola stringa? un vettore? qualcosa di più complesso?) e a cosa vuoi ottenere.
Probabilmente esiste un modo semplice per fare ciò che vuoi, se riesci a trovare la funzione giusta e gli argomenti giusti.
L'uso delle pipe |> per collegare le funzioni sarà trattato più in dettaglio in altre parti del percorso.
Per ora, è utile sapere che:
L'esempio seguente illustra la sintassi. Le singole funzioni saranno trattate più avanti.
library(stringr)
"Monday-25-February" |>
str_split("-") |>
unlist() |> # [1] "Monday" "25" "February"
str_sub(1, 3) |> # [1] "Mon" "25" "Feb"
str_to_upper() |> # [1] "MON" "25" "FEB"
str_flatten(collapse = ", ")
#> [1] "MON, 25, FEB"
str_length conta i code point (simile a nchars).
Varie altre funzioni manipolano gli spazi bianchi, tra cui str_trim() e str_squish().
Spesso è un'operazione di pulizia essenziale all'inizio di qualsiasi progetto di data science.
# The first 3 kernels in JuPyteR notebooks:
str_length(c("Julia", "Python", "R")) #> [1] 1 6 5
#> [1] 5 6 1
str_pad(c("Julia", "Python", "R"), 8, "right") # add spaces if necessary
#> [1] "Julia " "Python " "R "
str_trim("Julia Python R ") # remove leading & trailing whitespace
#> [1] "Julia Python R"
str_squish("Julia Python R ") # trim, collapse multiple spaces
#> [1] "Julia Python R"
La funzione str_sub(string, start, end) restituisce la sotto-stringa da start a end (estremi inclusi), mentre str_sub_all() può operare su più stringhe.
C'è molta flessibilità.
start e end hanno come valore predefinito rispettivamente il primo e l'ultimo carattere.
Possono essere vettori di indici.
In piacevole contrasto con l'indicizzazione dei vettori, i valori negativi si contano dalla fine della stringa, in stile Python.
s <- "abcdefgh"
str_sub(s, 2, 4)
#> [1] "bcd"
str_sub(s, 5)
#> [1] "efgh"
str_sub(s, -3, -2)
#> [1] "fg"
str_sub(s, c(1, 4), c(2, 5))
#> [1] "ab" "de"
str_sub(s, c(1, 4, 6), c(2, 5, -1))
#> [1] "ab" "de" "fgh"
s |> str_sub(2, 4) # these functions are designed for pipes
#> [1] "bcd"
# str_sub_all takes vector input, gives list output
str_sub_all(c(s, "ijklmnop"), c(1, 4, 6), c(2, 5, -1))
[[1]]
#> [1] "ab" "de" "fgh"
[[2]]
#> [1] "ij" "lm" "nop"
str_flatten converte un vettore di caratteri in un'unica stringa.
L'argomento collapse viene inserito tra un elemento e l'altro e ha come valore predefinito "".
c("R", "Julia", "Python") |> str_flatten()
#> [1] "RJuliaPython"
c("R", "Julia", "Python") |> str_flatten(collapse = " - ")
#> [1] "R - Julia - Python"
str_c converte più vettori di caratteri in un unico vettore di caratteri, con riciclo dove necessario.
str_c(LETTERS[1:8], 1:8, sep = ":")
#> [1] "A:1" "B:2" "C:3" "D:4" "E:5" "F:6" "G:7" "H:8"
str_glue porta in R una semplice interpolazione di stringhe.
Le espressioni racchiuse tra parentesi graffe { } vengono interpretate e al loro posto viene sostituita la loro rappresentazione testuale.
Se vengono fornite più stringhe di espressioni, anche queste vengono unite come in str_flatten.
Le stringhe possono anche essere ripulite facoltativamente dagli spazi bianchi iniziali e finali.
r <- 5.3
pi <- 3.14159
str_glue("A circle of radius {r} has area {pi * r^2}")
A circle of radius 5.3 has area 88.2472631
Anche se str_glue è flessibile (e piuttosto complicata), la libreria glue sottostante offre una gamma di possibilità ancora più ampia.
Un uso molto comune di R è riordinare dati disordinati in un formato coerente per l'analisi. Per aiutarti in questo, ci sono diversi modi per dividere il testo in sotto-stringhe.
str_split_1 è la più semplice: converte una singola stringa in un vettore di parti, divise in corrispondenza di un pattern specificato (non esiste un pattern predefinito).
s <- "R Julia Python"
str_split_1(s, " ")
#> [1] "R" "Julia" "Python"
str_split_i prende un vettore di caratteri e un indice n e restituisce un vettore con l'n-esima sotto-stringa di ogni stringa di input.
s2 <- c(s, "C Kotlin, F#") #> [1] "R Julia Python" "C Kotlin, F#"
str_split_i(s2, " ", 2) # 2nd element of each
#> [1] "Julia" "Kotlin,"
str_split prende un vettore di caratteri e restituisce una lista di vettori.
Nel nostro percorso c'è un concetto sulle liste che spiega l'output.
str_split(s2, " ")
[[1]]
#> [1] "R" "Julia" "Python"
[[2]]
#> [1] "C" "Kotlin," "F#"
Esiste anche str_split_fixed, che restituisce una matrice.
Le matrici saranno trattate in un concetto successivo.
Per confrontare l'intera stringa, str_equal() può usare varie definizioni di «uguaglianza».
Nel caso più semplice, il confronto predefinito distingue maiuscole e minuscole, ma si può cambiare.
I locale e Unicode aprono molte altre possibilità.
str_equal("Odin", "odin")
#> [1] FALSE
str_equal("Odin", "odin", ignore_case = TRUE)
#> [1] TRUE
Ci sono molte altre funzioni, che saranno trattate più in dettaglio nel concetto sulle espressioni regolari.
Alcune lingue (soprattutto europee) distinguono tra lettere MAIUSCOLE e minuscole.
Esiste una serie di funzioni di stringr per convertirle tra loro, nel modo appropriato al locale:
str_to_lower()str_to_upper()str_to_title() : prima lettera di ogni parola in maiuscolostr_to_sentence() : prima lettera della frase in maiuscolostr_to_upper("elixir") # defaults to locale = "en"
#> [1] "ELIXIR"
str_to_upper("ελληνικά") # infers Greek
#> [1] "ΕΛΛΗΝΙΚΆ"
str_to_title("the cat sat on the mat")
#> [1] "The Cat Sat On The Mat"
str_to_sentence("the cat sat on the mat")
#> [1] "The cat sat on the mat"
Per la sostituzione di sotto-stringhe basata sulla corrispondenza di un pattern, str_replace() modifica la prima occorrenza del pattern, mentre str_replace_all() le modifica tutte.
Per la sostituzione di sotto-stringhe basata sull'indice dei caratteri, esiste una forma di assegnazione di str_sub().
s <- "abcde"
str_sub(s, 3, 4) <- "yz"
s
#> [1] "abyze"
str_dup crea duplicati e str_unique li rimuove.
Per impostazione predefinita str_dup non ha separatore, ma se ne può specificare uno.
str_dup("xyz", 4, sep = ", ")
#> [1] "xyz, xyz, xyz, xyz"
str_unique è un'estensione di unique().
Entrambe rimuovono le voci duplicate da un vettore, ma la funzione di stringr sa gestire anche la distinzione tra maiuscole e minuscole e vari confronti specifici del locale.
inp <- c("some", "strings", "Some", "Strings")
str_unique(inp)
#> [1] "some" "strings" "Some" "Strings"
str_unique(inp, ignore_case = TRUE)
#> [1] "some" "strings"
La libreria stringr si basa sulla libreria stringi di funzioni di livello più basso.
Se hai esigenze specifiche che non abbiamo già trattato sopra, allora: