A reguláris kifejezések rendkívül sokoldalú eszközt kínálnak a stringek mintaillesztésére, egy erre a célra tervezett domain-specifikus nyelven (DSL) keresztül.
A regex számos képessége be van építve a Base R-be, és régebbi kódokban láthatsz is példákat rá.
Új kód esetén erősen ajánlott a stringr könyvtár használata.
Ahogy a Strings fogalom is megjegyzi, a stringr dokumentációja jó, és rendszerint a csomag szerzője (szerzői) írja.
Ez az R tanterv feltételezi, hogy már ismered a regexek alapvető szintaxisát. Kizárólag az R-specifikus jellemzőkre összpontosítunk.
Az alábbiakban néhány forrást találsz, amivel felfrissítheted a reguláris kifejezésekkel kapcsolatos ismereteidet.
A Strings fogalomban már tárgyalt függvények közül sok alapértelmezés szerint regexes keresésre készült.
A Strings fogalmat viszonylag egyszerűen tartottuk, csak stringliterálokra illesztettünk, de bármilyen regexminta használható helyette.
Formálisan egy regexet R-ben a regex() meghívásával hozunk létre.
A kényelem kedvéért a stringr függvényei minden mintaként átadott stringet regexként kezelnek.
Az alábbi példában az első str_replace_all() szintaktikai cukor a második változathoz:
input <- "Some input string"
# "\\s" matches any whitespace character
input |> str_replace_all("\\s", "_")
[1] "Some_input_string"
input |> str_replace_all(regex("\\s"), "_")
[1] "Some_input_string"
A puskalap hasznos útmutató: minden pattern argumentummal rendelkező függvény (pirossal szedve) elfogad regexet, és a 2. oldal összefoglalja a regex szintaxisát.
Vedd figyelembe, hogy a regex() függvény továbbra is fontos lehet, ha felül kell bírálnod az alapértelmezéseket, például a kis- és nagybetűk megkülönböztetését vagy a locale-t.
# default is case sensitive
"A Mixed Assortment" |>
str_replace_all(regex("[aeiou]", ignore_case = TRUE), "*")
#> [1] "* M*x*d *ss*rtm*nt"
Sok regexben szerepel backslash, például a fenti \s is.
A szokásos R stringekben a \ karaktert meg kell duplázni, ezért lesz belőle "\\s".
A backslashek elszaporodhatnak és zavaróvá válhatnak, ezért bonyolult esetekben segíthet a raw stringek használata.
Alapértelmezés szerint a raw string definiálásának szintaxisa r"( )", de más határolójelek is megengedettek.
Mivel a regex szintaxisában a kerek zárójelek ( ) és a szögletes zárójelek [ ] egyaránt gyakoriak (rögzítő csoportokra és opciócsoportokra), a kapcsos zárójelek { } jó választás lehet.
input |> str_replace_all(r"{\s\w}", "__")
#> [1] "Some__nput__tring"
Íme néhány kérdés, és néhány függvény, amelyek segíthetnek megválaszolni őket.
Néhány példa a fruit vektort használja, amely 80 gyümölcsnevet tartalmaz, és a stringr-rel együtt jár gyakorlási célból.
Tartalmaz egy string találatot?
str_detect(), vagy str_starts(), illetve str_ends(), hogy ne kelljen a ^ vagy $ horgonyt használnod.
# berries begining with b
fruit |> str_detect("^b.*berry") |> head(15)
#> [1] FALSE FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE TRUE TRUE FALSE FALSE FALSE FALSE
# alternative syntax
fruit |> str_starts("b.*berry") |> head(15)
#> [1] FALSE FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE TRUE TRUE FALSE FALSE FALSE FALSE
Csak a találatot tartalmazó stringeket szeretnéd visszakapni?
A str_subset() függvénnyel megkapod a stringeket, a str_which() függvénnyel pedig az indexeket.
fruit |> str_subset("a...e")
[1] "apple" "blackberry" "mandarine" "nectarine" "pineapple" "pomegranate" "raspberry" "salal berry"
> fruit |> str_which("a...e")
[1] 1 7 48 51 62 64 70 73
Hány találat van az egyes stringekben?
str_count()
# count vowels
fruit |> str_count("[aeiou]") |> head(10)
#> [1] 2 3 4 3 3 2 2 3 5 3
Hol van a találat?
Interaktív használathoz a str_view(), általánosabban a str_locate() vagy a str_locate_all().
# str_view only shows matching elements, with match in < > delimiters
fruit |> str_view("a...e")
[1] │ <apple>
[7] │ bl<ackbe>rry
[48] │ mand<arine>
[51] │ nect<arine>
[62] │ pine<apple>
[64] │ pomegr<anate>
[70] │ r<aspbe>rry
[73] │ sal<al be>rry
# str_locate returns all elements
fruit |> str_locate("a...e") |> head(10)
start end
[1,] 1 5
[2,] NA NA
[3,] NA NA
[4,] NA NA
[5,] NA NA
[6,] NA NA
[7,] 3 7
[8,] NA NA
[9,] NA NA
[10,] NA NA
Gyakran szeretnénk tudni, hogy mi illeszkedik. Ezt úgy érhetjük el, hogy rögzítő csoportokat teszünk zárójelbe a regexen belül.
Lehetőség van arra, hogy ugyanazon a regexen belül visszahivatkozz a rögzítésekre a \1, \2 stb. segítségével.
Például ismétlődő betűpárok megkeresése:
fruit |> str_subset("(..)\\1")
[1] "banana" "coconut" "cucumber" "jujube" "papaya" "salal berry"
Hasonló szintaxissal a rögzítések a helyettesítő stringekben is használhatók:
# double to triple
fruit |>
str_subset("(..)\\1") |>
str_replace("(..)\\1", "\\1\\1\\1")
[1] "bananana" "cococonut" "cucucumber" "jujujube" "papapaya" "salalal berry"
A találatokat későbbi feldolgozáshoz a str_match() függvénnyel gyűjtheted össze, ha csak az első találatra vagy kíváncsi, vagy a str_match_all() függvénnyel, ha az összesre.
A rögzítő csoportok minden nyelvben hajlamosak bonyolult kimenetet adni.
Az R megközelítése (amely ráadásul vektorizált, ami plusz kihívást jelent) az, hogy a str_match() egy mátrixot hoz létre: minden bemeneti stringhez egy sor tartozik, az 1. oszlop a teljes illeszkedő stringet tartalmazza, a többi oszlop pedig a különféle rögzítő csoportokat.
A nevesített csoportok használata előnyt jelenthet, mert azokból lesznek az eredményül kapott mátrix oszlopnevei.
recipe <- "dissolve 25g sugar in 200ml water"
recipe |> str_match("(\\d+g) .* (\\d+ml)")
[,1] [,2] [,3]
[1,] "25g sugar in 200ml" "25g" "200ml"
# use named capture groups
recipe |> str_match("(?<wt>\\d+g) .* (?<vol>\\d+ml)")
wt vol
[1,] "25g sugar in 200ml" "25g" "200ml"
Ebben a tantervben nem sokat beszéltünk a mátrixokról, de van egy fogalmi dokumentum, amelyet megnézhetsz. A mátrixok nem illenek jól a Tidyverse ökoszisztémájába, pedig már régóta a Base R részei.
Röviden összefoglalva: a mátrixokat olyan vektorként kezeld, amelyeknek 2 indexük van, [rows, cols] sorrendben.
Ha valamelyik indexet üresen hagyod, megkapod a teljes sort/oszlopot.
Amikor egyetlen r sort kérsz le a m[r,] kifejezéssel, vagy egyetlen c oszlopot a m[, c] kifejezéssel, a visszaadott érték alapértelmezés szerint vektorrá lapul.
> amounts <- recipe |> str_match("(?<wt>\\d+g) .* (?<vol>\\d+ml)")
# get all of row 1, as a named vector
> amt1 <- amounts[1,]
> amt1
wt vol
"25g sugar in 200ml" "25g" "200ml"
# get a vector element
amt1["wt"] |> unname()
[1] "25g"
Elkerülhetetlenül a str_match_all() még bonyolultabb szerkezetű kimenetet ad:
[[1]] indexeléssel bontsd ki a listából, ahogy a Lists fogalom leírja.match() esetében.mix <- "Mix 200g of sugar, 180g of flour and 150g of butter"
# raw output
> str_match_all(mix, "(\\d+)g")
[[1]]
[,1] [,2]
[1,] "200g" "200"
[2,] "180g" "180"
[3,] "150g" "150"
# we just want the first list element
> str_match_all(mix, "(\\d+)g")[[1]]
[,1] [,2]
[1,] "200g" "200"
[2,] "180g" "180"
[3,] "150g" "150"
# captures are in column 2, so get that as a vector
> str_match_all(mix, "(\\d+)g")[[1]][,2]
[1] "200" "180" "150"
Persze a találatok kudarcot vallhatnak.
Ilyenkor az eredmény a különleges NA érték lesz.
Készülj fel ennek ellenőrzésére, ahogy a Nothingness fogalom leírja.
# failed match
recipe |> str_match("(not here)")
[,1] [,2]
[1,] NA NA
Az R for Data science megjegyzi:
A
str_match()mátrixot ad vissza, így nem különösebben könnyű vele dolgozni.
A lábjegyzettel:
Leginkább azért, mert ebben a könyvben soha nem tárgyaljuk a mátrixokat!
A könyv egyik szerzője írta a stringr könyvtárat is, és implicit módon a saját munkáját kritizálja.
Érdekes lesz látni, hogyan fejlődik ennek a függvénynek a megvalósítása a jövőbeli kiadásokban.