13.2 Základní operace

Nejdříve se podíváme na základní operace s řetězci: na to, jak řetězce spojovat, třídit, replikovat, nahrazovat, srovnávat a zjišťovat jejich délku. Ve zbytku kapitoly se pak budeme věnovat pokročilejší práci s řetězci pomocí regulárních výrazů.

13.2.1 Zjištění délky řetězce

Často potřebujeme zjistit délku jednotlivých řetězců. To nemůžeme udělat pomocí funkce length() protože ta nevrací délku řetězce, ale délku vektoru řetězců. Nebude to fungovat ani v případě jediného řetězce, protože i to je vektor o délce 1. (Pamatujte, že R nemá skalár: to, co vypadá jako jeden řetězec, je ve skutečnosti vektor řetězců o délce 1.)

Ke zjištění délky řetězce slouží funkce str_length(), která vrací vektor délek jednotlivých řetězců ve vektoru:

s1 <- "Ahoj!"
s2 <- c("A", "Bb", "Ccc", NA)
length(s1)  # délka (= počet prvků) vektoru s1
## [1] 1
length(s2)  # délka (= počet prvků) vektoru s2
## [1] 4
str_length(s1)  # vektor délek (= počtu znaků) řetězce s1
## [1] 5
str_length(s2)  # vektor délek (= počtu znaků) řetězců ve vektoru s2
## [1]  1  2  3 NA

Pozor: Technicky vzato vrací funkce str_length() počet tzv. “code points.” Ty většinou odpovídají jednotlivým znakům, ne však vždy. Např. znak á může být v paměti počítače reprezentován jako jeden znak nebo dva znaky (a a akcent). Ve druhém případě vrátí funkce str_length() délku 2. V takovém případě je bezpečnější počítat počet znaků pomocí funkce str_count(), viz dále. Příklad najdete v dokumentaci funkce. Pokud jsou však anglické i české znaky zadané obvyklým způsobem, pak bude funkce str_length() vracet počet znaků v jednotlivých řetězcích.

13.2.2 Spojení řetězců

Ke spojení více řetězců do jednoho slouží funkce str_c(..., sep = "", collapse = NULL). Funkce bere libovolný počet řetězců, vypustí z nich prázdné vektory řetězců a NULL (ale ne prázdné řetězce "") a zbylé řetězce spojí do jednoho řetězce. Funkce str_c() implicitně odděluje jednotlivé řetězce prázdným řetězcem, tj. spojí jednotlivé řetězce těsně za sebe. Oddělovací řetězec je možné nastavit pomocí pojmenovaného parametru sep. (Všimněte si, že prázdný řetězec "" funkce nevypustila.)

str_c("Jednou", "", "budem", character(0), "možná", NULL, "dál!")
## [1] "Jednoubudemmožnádál!"
str_c("Jednou", "", "budem", character(0), "možná", NULL, "dál!", sep = " ")
## [1] "Jednou  budem možná dál!"
str_c("Jednou", "", "budem", character(0), "možná", NULL, "dál!", sep = "-")
## [1] "Jednou--budem-možná-dál!"

Někdy potřebujeme spojit vektory řetězců. Funkce str_c() spojí odpovídající prvky jednotlivých vektorů (s obvyklou recyklací kratších vektorů, při které funkce vypíše varování) a vrátí vektor (rozumnější použití uvidíte dále):

s1 <- c("a", "b", "c", "d")
s2 <- 1:3  # automatická koerze převede čísla na řetězce
str_c(s1, s2)
## Warning in stri_c(..., sep = sep, collapse = collapse, ignore_null = TRUE):
## longer object length is not a multiple of shorter object length
## [1] "a1" "b2" "c3" "d1"

Pokud navíc chceme výsledný vektor spojit do jednoho řetězce, můžeme použít parametr collapse, kterým se nastavuje řetězec oddělující jednotlivé dílčí vektory (funguje i prázdný řetězec ""):

str_c(s1, s2, collapse = "-")
## Warning in stri_c(..., sep = sep, collapse = collapse, ignore_null = TRUE):
## longer object length is not a multiple of shorter object length
## [1] "a1-b2-c3-d1"
str_c(s1, s2, collapse = "")
## Warning in stri_c(..., sep = sep, collapse = collapse, ignore_null = TRUE):
## longer object length is not a multiple of shorter object length
## [1] "a1b2c3d1"

Celkove funguje funkce str_c() takto- sve parametry chape jako vektory retezcu. Tyto vektory sestavi do matice, kde kazdy vstupni vektor tvori jeden sloupec teto matice (pritom prodlouzi kratsi vektory recyklaci jeho prvku). Pak vlozi mezi jednotlive sloupce retezec sep a spoji kazdy radek do jednoho retezce, takze vysledkem je jeden vektor retezcu. Pokud je navic zadan retezec collapse (tj. neni NULL), funkce vlozi tento retezec mezi jednotlive prvky tohoto vektoru a spoji je do jednoho retezce. Tabulka 13.2 ukazuje, co udělá výraz str_c(s1, s2, sep = "-").

Tabulka 13.2: Ukázka toho, jak funguje výraz str_c(s1, s2, sep = "-").
s1 (sep) s2 výsledek
a - 1 a-1
b - 2 b-2
c - 3 c-3
d - 1 d-1

Pokud má kterýkoli řetězec ve spojovaných vektorech hodnotu NA, pak je výsledné spojení také NA:

s1 <- c("a", "b", NA)
s2 <- 1:3
str_c(s1, s2)
## [1] "a1" "b2" NA
str_c(s1, s2, collapse = "-")
## [1] NA

Některé zajímavé příklady použití funkce str_c() jsme vybrali z dokumentace funkce:

str_c("Letter", letters, sep = ": ")
##  [1] "Letter: a" "Letter: b" "Letter: c" "Letter: d" "Letter: e" "Letter: f"
##  [7] "Letter: g" "Letter: h" "Letter: i" "Letter: j" "Letter: k" "Letter: l"
## [13] "Letter: m" "Letter: n" "Letter: o" "Letter: p" "Letter: q" "Letter: r"
## [19] "Letter: s" "Letter: t" "Letter: u" "Letter: v" "Letter: w" "Letter: x"
## [25] "Letter: y" "Letter: z"
str_c(letters[-26], " comes before ", letters[-1])
##  [1] "a comes before b" "b comes before c" "c comes before d" "d comes before e"
##  [5] "e comes before f" "f comes before g" "g comes before h" "h comes before i"
##  [9] "i comes before j" "j comes before k" "k comes before l" "l comes before m"
## [13] "m comes before n" "n comes before o" "o comes before p" "p comes before q"
## [17] "q comes before r" "r comes before s" "s comes before t" "t comes before u"
## [21] "u comes before v" "v comes before w" "w comes before x" "x comes before y"
## [25] "y comes before z"
str_c(letters, collapse = ", ")
## [1] "a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z"

13.2.3 Doplnění hodnot do řetězců

Někdy potřebujeme spojit několik řetězců tak, že na vybraná místa do textu “vlepíme” hodnoty z vybraných proměnných – např. když chceme sestavit výpis z účtu:

cislo_uctu <- 854397
zustatek <- 12365.30
str_c("Na účtu číslo ", cislo_uctu, " je aktuální zůstatek ",
      format(zustatek, big.mark = ".", decimal.mark = ",", nsmall = 2),
      " Kč.")
## [1] "Na účtu číslo 854397 je aktuální zůstatek 12.365,30 Kč."

Využít ke “vlepení” hodnot do řetězce funkci str_c(), jak to ukazuje výše uvedený příklad, je možné, ale výsledek není právě přehledný. Místo toho můžeme použít speciální funkci glue() z balíku glue:

library(glue)
hezky_zustatek <- format(zustatek, big.mark = '.',
                                decimal.mark = ',', nsmall = 2)
glue("Na účtu číslo {cislo_uctu} je aktualni zustatek {hezky_zustatek} Kč.")
## Na účtu číslo 854397 je aktualni zustatek 12.365,30 Kč.

Funkce glue() vezme jeden nebo více řetězců, slepí je dohromady a na místa ve složených závorkách vloží hodnoty proměnných z aktuálního prostředí R, přesněji výsledek výpočtu, takže předchozí kód bychom mohli napsat i takto:

glue("Na účtu číslo {cislo_uctu} je aktualni zustatek ",
     "{format(zustatek, big.mark = '.', decimal.mark = ',', nsmall = 2)} Kč.")
## Na účtu číslo 854397 je aktualni zustatek 12.365,30 Kč.

Hodnoty vložených proměnných můžeme zadat přímo jako parametry funkce glue():

glue("Na účtu číslo {cislo_uctu} je aktualni zustatek {zustatek} Kč.",
     cislo_uctu = 24683, zustatek = 123)
## Na účtu číslo 24683 je aktualni zustatek 123 Kč.

Funkce glue() má několik zajímavých vlastností: zahazuje počáteční a koncová bílá místa (mezery apod.) včetně prvního a posledního zalomení řádku, ostatní zalomení a mezery však respektuje:

glue("
   Toto je jakýsi text.
        A zde pokračuje...
     ")
## Toto je jakýsi text.
##      A zde pokračuje...
glue("

   Toto je jakýsi text.
        A zde pokračuje...

        ")
## 
## Toto je jakýsi text.
##      A zde pokračuje...

Zalomení řádků je možné zabránit pomocí (zdvojeného) lomítka na jeho konci:

glue("Zde nějaký text začíná, \\
     a zde pokračuje.")
## Zde nějaký text začíná, a zde pokračuje.

Pokud z nějakého důvodu potřebujeme v textu použít složené závorky, máme dvě možnosti: buď je zdvojit, nebo pomocí parametrů .open a .close nastavit jiný počátek a konec nahrazované oblasti na jiný znak:

glue("Zde je {{jméno}}.")
## Zde je {jméno}.
glue("Jméno výherce soutěže je {\\bf <<name>>}.", .open = "<<", .close =">>",
     name = "Joe") # vhodné např. při exportu do LaTeXu
## Jméno výherce soutěže je {\bf Joe}.

Balík stringr nabízí podobnou funkcionalitu ve funkcích str_glue() a str_interp(). Funkce str_glue() je vpodstatě jen wrapper nad funkcí glue() a používá se stejně:

str_glue("Jmenuji se {name} a příští rok mi bude {age + 1} let.",
         name = "Jana", age = 40)
## Jmenuji se Jana a příští rok mi bude 41 let.

Naproti tomu funkce str_interp() má poněkud jiné použití. V jejím případě musí mít nahrazovaný text buď podobu ${}, kde ve složených závorkách je nějaký výraz, nebo $[]{}, kde v hranatých závorkách je formát a v složených výraz. Formát určuje typicky formátování čísel – např. .2f znamená, že se vytisknou dvě desetinná místa (detaily formátu viz nápověda k funkci sprintf()). Pokud chceme zadat vkládané hodnoty přímo v této funkci, je třeba je zabalit do seznamu.

str_interp("Na účtu číslo ${cislo_uctu} je aktualni zustatek ${zustatek} Kč.")
## [1] "Na účtu číslo 854397 je aktualni zustatek 12365.3 Kč."
str_interp("Na účtu číslo $[d]{cislo_uctu} je aktualni zustatek $[.2f]{zustatek} Kč.")
## [1] "Na účtu číslo 854397 je aktualni zustatek 12365.30 Kč."
str_interp("Na účtu číslo $[d]{cislo_uctu} je aktualni zustatek $[.2f]{zustatek} Kč.",
           list(cislo_uctu = 51349, zustatek = 17))
## [1] "Na účtu číslo 51349 je aktualni zustatek 17.00 Kč."

13.2.4 Řazení řetězců

Pro setřídění řetězců většinou stačí základní funkce sort() a order(). Pro složitější případy, kdy je např. třeba třídit v cizím locale, nabízí balík stringr dvě funkce:

  • str_order(x, decreasing = FALSE, na_last = TRUE, locale = "en", numeric = FALSE, ...) vrací celé číslo, které odpovídá pořadí daného řetězce ve vektoru x (podobně jako funkce order())

  • str_sort(x, decreasing = FALSE, na_last = TRUE, locale = "en", numeric = FALSE, ...) setřídí vektor řetězců x

Přitom x je vektor řetězců, který má být setříděn. decreasing je logická hodnota (implicitní hodnota je FALSE; pak třídí od nejnižšího k nejvyššímu; TRUE třídí od nejvyššího k nejnižšímu). na_last je logická hodnota (implicitní hodnota je TRUE, při které funkce umístí hodnoty NA na konec vektoru; FALSE je umístí na začátek na začátek vektoru; NA je vyhodí). locale označuje v jakém locale se má třídit (implicitně v systémovém). Pokud je logická hodnota numeric nastavena na TRUE, pak číslice řadí jako čísla, ne jako řetězce; implicitní hodnota je FALSE. ... označují další parametry přidané do stri_opts_collator.

str_order(letters, locale = "en")
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
## [26] 26
str_sort(letters, locale = "en")
##  [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s"
## [20] "t" "u" "v" "w" "x" "y" "z"

13.2.5 Výběr a náhrada pomocí indexů

Někdy je třeba z řetězce vybrat jeho část. Pokud známe pozici prvního a posledního znaku, který chceme vybrat, můžeme použít funkce str_sub(string, start = 1L, end = -1L), kde string je řetězec, ze kterého vybíráme, start je pozice znaku začátku výběru a end je pozice konce výběru (včetně). Implicitní hodnota start je 1 (tj. od začátku vektoru), implicitní hodnota end je \(-1\) (tj. do konce vektoru). Pozice znaků mohou být zadány i jako záporná čísla – ta se počítají od konce vektoru, takže např. \(-1\) je poslední znak vektoru.

s1 <- "Sol 6: Katastrofa na Marsu."
str_sub(s1, start = 8, end = str_length(s1))
## [1] "Katastrofa na Marsu."
str_sub(s1, start = 8, end = -1)  # totéž
## [1] "Katastrofa na Marsu."
str_sub(s1, 8)  # totéž
## [1] "Katastrofa na Marsu."
str_sub(s1, end = 5)
## [1] "Sol 6"

Funkce str_sub() recykluje všechny své parametry. Pokud chceme např. vybrat stejné pozice z každého vektoru v řetězci:

s2 <- c(s1, "Sol 7: Nová naděje.")
str_sub(s2, start = c(8, 8))
## [1] "Katastrofa na Marsu." "Nová naděje."
str_sub(s2, 8)  # totéž
## [1] "Katastrofa na Marsu." "Nová naděje."

Stejně tak je však možné recyklovat i řetězec a vybrat z něj naráz dva pod-řetězce:

str_sub(s1, start = c(8, 22), end = c(17, 26))
## [1] "Katastrofa" "Marsu"

Pokud je řetěz kratší než výběr znaků, vrátí funkce str_sub() tolik znaků, kolik může (někdy i prázdný řetězec):

str_sub(s1, start = 25, end = 50)
## [1] "su."
str_sub(s1, start = 40, end = 50)
## [1] ""

Funkci str_sub() je možné použít i k náhradě části řetězce:

str_sub(s1, 22, 26) <- "rudé planetě"
s1
## [1] "Sol 6: Katastrofa na rudé planetě."

13.2.6 Replikace řetězců

Někdy je potřeba nějaký řetězec “zmnožit.” K tomu slouží funkce str_dup(string, times), který vezme vektor string, zopakuje jej times-krát a výsledek spojí. To se hodí např. při načítání mnoha textových sloupců pomocí balíku readr. Při tom je někdy užitečné říct funkci read_csv(), že všechny sloupce tabulky mají typ character. K tomu slouží řetězec mnoha “c”:

str_dup("c", 28)
## [1] "cccccccccccccccccccccccccccc"

Funkce str_dup() také recykluje všechny své parametry:

str_dup(c("a", "b", "c"), 1:3)
## [1] "a"   "bb"  "ccc"

13.2.7 Odstranění okrajových mezer

Někdy dostaneme řetězec, který začíná nebo končí “bílými znaky” (mezerami, tabelátory, novými řádky “\n” apod.). Tato situace vznikne např. tehdy, když řetězec vznikl rozdělením delšího řetězce na části. Tyto bílé znaky je často vhodné odstranit. K tomu slouží funkce str_trim(string, side), kde string je řetězec a side označuje stranu, ze které se mají bílé znaky odstranit:

s1 <- c("Ahoj,", " lidi, ", "jak ", "se", " máte?")
str_trim(s1, "left")   # odstraní mezery zleva
## [1] "Ahoj,"  "lidi, " "jak "   "se"     "máte?"
str_trim(s1, "right")  # odstraní mezery zprava
## [1] "Ahoj,"  " lidi," "jak"    "se"     " máte?"
str_trim(s1, "both")   # odstraní mezery z obou stran
## [1] "Ahoj," "lidi," "jak"   "se"    "máte?"
str_trim(s1)   # totéž -- "both" je implicitní hodnota side
## [1] "Ahoj," "lidi," "jak"   "se"    "máte?"

Funkce str_squish(string) funguje podobně, ale odstraní navíc i všechny přebytečné prázdné znaky – na začátku řetězce string, na jeho konci i opakované znaky uvnitř:

str_squish("  Toto  je   koktavý  text.   ")
## [1] "Toto je koktavý text."

13.2.8 Zarovnání a ořež řetězců na stejnou délku a do odstavce

Někdy je užitečné zarovnat řetězce na stejnou délku přidáním bílých (nebo jiných) znaků. K tomu slouží funkce str_pad(string, width, side, pad = " "), kde string je vektor zarovnávaných řetězců, width je minimální délka výsledného řetězce, side je strana, na kterou se mají výplňové znaky přidat (implicitně je to left) a pad je výplňový řetězec (implicitně mezera).

str_pad(c("Ahoj", "lidi"), 7)
## [1] "   Ahoj" "   lidi"
str_pad(c("Ahoj", "lidi"), 9, side = "both", pad = "-")
## [1] "--Ahoj---" "--lidi---"

Delší řetězce funkce nemění:

str_pad(c("Ahoj", "malé zelené bytosti z Viltvodlu VI"), width = 7)
## [1] "   Ahoj"                            "malé zelené bytosti z Viltvodlu VI"

V některých případech potřebujeme delší řetězec zkrátit na určitou délku tak, že se jeho část vynechá. K tomu slouží funkce str_trunc(string, width, side = c("right", "left", "center"), ellipsis = "..."), která zkrátí řetězec string na délku width znaků. Přitom přebytečnou část textu na straně side vynechá a nahradí znaky ellipsis:

s <- "Toto je přehnaně dlouhý text, který toho mnoho neříká."
str_trunc(s, 20, "right")
## [1] "Toto je přehnaně ..."
str_trunc(s, 20, "left")
## [1] "...oho mnoho neříká."
str_trunc(s, 20, "center")
## [1] "Toto je p... neříká."

Zarovnat řetězec do odstavce umožňuje funkce str_wrap(string, width = 80, indent = 0, exdent = 0), kde strings je zarovnávaný řetězec, width je cílová šířka sloupce (implicitně 80 znaků), indent je odsazení prvního řádku a exdent je odsazení následujících řádků (oboje implicitně 0 znaků):

s1 <- "Na počátku bylo Slovo, to Slovo bylo u Boha, to Slovo byl Bůh. To bylo na počátku u Boha. Všechno povstalo skrze ně a bez něho nepovstalo nic, co jest. V něm byl život a život byl světlo lidí. To světlo ve tmě svítí a tma je nepohltila."
cat(str_wrap(s1, 60))
## Na počátku bylo Slovo, to Slovo bylo u Boha, to Slovo byl
## Bůh. To bylo na počátku u Boha. Všechno povstalo skrze ně
## a bez něho nepovstalo nic, co jest. V něm byl život a život
## byl světlo lidí. To světlo ve tmě svítí a tma je nepohltila.

13.2.9 Konverze malých a velkých písmen

Ke konverzi malých písmen na velká slouží funkce str_to_upper(string, locale = "en"), ke konverzi na malá písmena funkce str_to_lower(string, locale = "en"), kde string je převáděný řetězec. Parametr locale umožňuje zadat popis jazykových pravidel pro převod znaků. Parametr je nepovinný a pokud není zadán, použije se aktuální locale počítače.

s <- "Nějaký malý textík..."
str_to_lower(s, locale = "cz")
## [1] "nějaký malý textík..."
str_to_upper(s, locale = "cz")
## [1] "NĚJAKÝ MALÝ TEXTÍK..."

13.2.10 Nahrazení chybějících hodnot řetězcem

Někdy máme vektor řetězců, ve kterém některé hodnoty chybí, tj. mají hodnotu NA, a my potřebujeme tyto hodnoty nahradit nějakým textem. K tomu slouží funkce str_replace_na(string, replacement = "NA"), kde string je vektor řetězců a replacement je řetězec, který má v řetězci string nahradit chybějící hodnoty. Pokud chceme např. nahradit chybějící hodnoty slovem “neznámé,” můžeme to udělat takto:

poloha <- c("nahoře", "dole", NA, "vlevo")
str_replace_na(poloha, replacement = "neznámá")
## [1] "nahoře"  "dole"    "neznámá" "vlevo"

13.2.11 Formátování čísel

Někdy potřebujeme vytisknout čísla v “pěkném formátu.” To znamená, že musíme převést dané číslo na zformátovaný řetězec, který dané číslo prezentuje v požadovaném tvaru. K tomu slouží funkce format() ze základního balíku base. Funkce format() je generická a umí formátovat mnoho různých objektů. Pro čísla má následující parametry:

format(x, trim = FALSE, digits = NULL, nsmall = 0L,
       justify = c("left", "right", "centre", "none"),
       width = NULL, na.encode = TRUE, scientific = NA,
       big.mark   = "",   big.interval = 3L,
       small.mark = "", small.interval = 5L,
       decimal.mark = getOption("OutDec"),
       zero.print = NULL, drop0trailing = FALSE, ...)

kde x je numerický vektor. Další užitečné parametry jsou zejména nsmall (minimální počet desetinných míst), big.mark a decimal.mark (znak oddělující tisíce a desetinná místa), scientific (pokud je TRUE, pak vypisuje čísla ve vědecké notaci), width (minimimální počet znaků) a justify (zarovnání). Další parametry jsou popsané v dokumentaci.

x <- 123456.3
print(x)
## [1] 123456.3
print(format(x, big.mark = ".", decimal.mark = ",", nsmall = 2))
## [1] "123.456,30"
print(format(x, scientific = TRUE))
## [1] "1.234563e+05"
print(format(x, width = 10, justify = "left"))
## [1] "  123456.3"