16  Baziniai veiksmai su simbolių sekomis

Teorinėje informatikoje apibrėžiamas specialus duomenų tipas — string. Tokio duomenų tipo reikšmės yra baigtinį elementų skaičių turinčios sekos, sudarytos iš bet kokia tvarka išdėstytų simbolių iš tam tikros aibės, vadinamos alfabetu. Duomenų tipas tuo pačiu apibrėžia ir veiksmus su string tipo kintamaisiais.

Simbolių seka rašoma tarp viengubų ar dvigubų kabučių. Simbolių sekas, kaip ir kitus vienodo tipo elementus, galima apjungti į vektorius. R turi keletą tokių tekstinių vektorių – konstantų. Tai didžiosios ir mažosios raidės bei mėnesių pavadinimai.

Kodas
letters
#>  [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"
LETTERS
#>  [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"

month.abb
#>  [1] "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"
month.name
#>  [1] "January"   "February"  "March"     "April"     "May"       "June"     
#>  [7] "July"      "August"    "September" "October"   "November"  "December"

16.1 Simbolių sekoje pakeitimas

Simbolių skaičių sekoje vadinkime sekos ilgiu. Jam apskaičiuoti naudojama f-ją nchar().

Argumentas Reikšmė
x simbolių seka arba keletos sekų vektorius
type sekos dydžio matavimo vienetai: bytes, chars arba width

Funkcijos nchar() rezultatas yra skaičių vektorius. Jo elementų skaičius sutampa su vektoriaus x elementų skaičiumi. Pavyzdžiui, nustatysime mėnesių pavadinimų ilgius.

Kodas
nchar(month.name)
#>  [1] 7 8 5 5 3 4 4 6 9 7 8 8

Tokį ilgių vektorių galima panaudoti ilgiausios sekos nustatymui ir išrinkimui. Pavyzdžiui, nustatysime, kurio mėnesio pavadinimas ilgiausias.

Kodas
month.name[which.max(nchar(month.name))]
#> [1] "September"

Atskiru atveju sekų vektorius gali būti ir viena seka. Pavyzdžiui, nustatysime ilgiausio daniško žodžio ilgį.

Kodas
zodis <- "Kindercarnavalsoptochtvoorbereidingswerkzaamhedenplan"
nchar(zodis)
#> [1] 53

Atliekant natūralios kalbos tekstų statistinę analizę, didžiosios bei mažosios raidės dažniausiai taip pat neskiriamos, o kartais tiesiog reikia suvienodinti raidžių registrą. Tam naudojamos funkcijos toupper() ir tolower().

Kodas
RAIDES <- c("A", "Ą", "B", "C", "Č", "D", "E", "Ę", 
            "Ė", "F", "G", "H", "I", "Į", "Y", "J", 
            "K", "L", "M", "N", "O", "P", "R", "S", 
            "Š", "T", "U", "Ų", "Ū", "V", "Z", "Ž")

raidės <- tolower(RAIDES)
raidės
#>  [1] "a" "ą" "b" "c" "č" "d" "e" "ę" "ė" "f" "g" "h" "i" "į" "y" "j" "k" "l" "m"
#> [20] "n" "o" "p" "r" "s" "š" "t" "u" "ų" "ū" "v" "z" "ž"

Jei vienus simbolius sekoje reikia pakeisti į kitus, naudojama funkcija chartr().

Argumentas Reikšmė
old keičiamų simbolių seka
new pakeistų simbolių seka
x seka, kurioje atliekamas simbolių pakeitimas

Pavyzdžiui, šią funkciją galima panaudoti tuo atveju, kai visame tekste reikia pakeisti vieną kokį nors neteisingai naudojamą simbolį į kitą simbolį. Tarkime, žinome, kad citatoje vietoje simbolio “?” turi būti raidė “i”.

Kodas
citata <- "Arš?aus? g?nča? kyla tada, ka? nė v?ena šal?s netur? svar?ų įrodymų."

chartr("?", "i", citata)
#> [1] "Aršiausi ginčai kyla tada, kai nė viena šalis neturi svarių įrodymų."

Galima keisti ne tik vieną simbolį, bet visą simbolių seką. Tokiu atveju parametrai old ir new turi būti vienodo ilgio viena simbolių seka. Pavyzdžiui, tekste pašalinsime visus diakretinius ženklus.

Kodas
x <- c("šeštadienis", "žiema ir šiluma")

old_vec <- "ąčęėįšųūž"
new_vec <- "aceeisuuz"

chartr(old_vec, new_vec, x)
#> [1] "sestadienis"     "ziema ir siluma"

16.2 Subsekos išskyrimas

Jeigu turime n simbolių seką, tai subseka (substring) vadinsime tam tikrą dalį tos sekos. Jeigu sekoje yra tarpų, tai tarpai taip pat gali patekti į subseką, tačiau subseka niekada negali būti tuščia ("").

R kalboje subsekos išskyrimui arba jos keitimui naudojama funkcija substr().

Argumentas Reikšmė
x pradinė simbolių seka arba sekų vektorius
start išskiriamos subsekos pirmojo simbolio eilės numeris
stop išskiriamos subsekos paskutinio simbolio eilės numeris

Išskirkime iš žodio “tekstas” galunę.

Kodas
substr("tekstas", 6, 7)
#> [1] "as"

Parametrų start ir stop reikšmės nebūtinai turi būti vienas skaičius – galima priskirti ir skaičių vektorių. Tada iš sekos bus išskirtos kelios jos subsekos.

Pvz., išnagrinėsime Vinco Kudirkos eilėraštį “Mįslės”. Eilėraščio tekstas padalintas į atskiras eilutes. Iš kiekvienos eilutės išskirsime pirmąją jos raidę.

Kodas
eilerastis <- c("Dievas visad ant lūpų, o širdyje velnias;",
                "Akis tuojau užmerkia išvydusi kelnes;",
                "Vaikščioja atsiplėšus – įžadai mat toki;",
                "Atmintyje tik laiko, kur atlaidai koki",
                "Tur liežuvį bjauresnį už gyvatės gylį;",
                "Kasdien tupi bažnyčioj, nes tinginį myli.",
                "Atminki, kas tai būtų, jei mįslius mint moki?")

substr(eilerastis, 1, 1)
#> [1] "D" "A" "V" "A" "T" "K" "A"

Funkcija substr() gali būti naudojama ir vienos subsekos pakeitimui į kitą tokio paties ilgio subseką. Pavyzdžiui, turime failų pavadinimų vektorių. Visų failų pavadinimai standartiniai ir vienodo ilgio. Pakeisime failo išplėtimą iš txt į rez.

Kodas
failai <- c("failas_01.txt", "failas_02.txt", "failas_03.txt", "failas_04.txt")

substr(failai, 11, 13) <- "rez"
failai
#> [1] "failas_01.rez" "failas_02.rez" "failas_03.rez" "failas_04.rez"

žinoma, tokiam tikslui naudojama funkcija substr() turi labai ribotas pritaikymo galimybes. Pavyzdžiui, šiuo atveju, jei failų pavadinimai būtų skirtingų ilgių, kiekvienam pavadinimui reikėtų nustatyti vis kitokias indeksų reikšmes. žymiai didesnes galimybes turi reguliarios išraiškos (regular expressions).

Užduotis
  1. Užrašykite komandą, kuri sudarytų absoliučiai visų sekos “susisuko” subsekų vektorių ir nustatykite jų skaičių.

  2. Sukurkite komandą, kuri visus skaitmeni tekste pakeičia simboliu • (simbolio koduotė “022”). Pritaikykite tai šiam tekstui: c("Užsakymas #A-1205 sumokėta 35,60 €", "2025-11-06 14:05:09", "Tel. +370 612 34 567")

  3. Turimi failų pavadinimai, kuriuose pradžioje yra data formatu “YYYYMMDD”: c("20251106_ataskaita.txt", "20191231_duomenys.csv", "20010203_log.json"). Naudojant substr(), išskirkite metus, mėnesį ir dieną ir suformuokite datą formatu “YYYY-MM-DD”.

16.3 Sekos išskaidymas į atskiras dalis

Vienas iš tipinių ir dažnai atliekamų veiksmų — sekos suskaidymas į atskiras dalis. Pavyzdžiui, sakinį galima suskaidyti į atskirus žodžius ar net atskirus simbolius. Tekstiniu formatu užrašytą datą galima išskaidyti į metus, mėnesius ir dienas ir t. t. šis suskaidymas primena subsekos išskyrimą, bet šiuo atveju laikoma, kad sekos dalys atskirtos tam tikru simboliu ar jų kombinacija. Pvz., sakinyje žodžiai vienas nuo kito atskirti tarpo simboliu, atskiros datos dalys atskiriamos arba brūkšneliu, arba tašku.

R kalboje sekos skaidymui į atskiras dalis naudojama f-ja strsplit().

Argumentas Reikšmė
x seka arba sekų vektorius
split simbolis arba jų seka, pagal kurią skaidoma pradinė seka
fixed loginis, FALSE nurodo, kad split yra ne reguliari išraiška

Pavyzdžiui, duota kableliais vienas nuo kito atskirtų olimpiadinių žodžių seka. šią seką reikia išskaidyti į atskirus žodžius.

Kodas
zodziai <- "bąla, gęsta, gvęra, kręšta, sąla, šąla, šąšta, tęžta, tręšta"

Galima pastebėti, kad šioje sekoje žodžiai atskirti ne tik kableliais, bet dar ir tarpais, todėl parametrui split nurodome simbolių seką ,.

Kodas
strsplit(zodziai, split = ", ")
#> [[1]]
#> [1] "bąla"   "gęsta"  "gvęra"  "kręšta" "sąla"   "šąla"   "šąšta"  "tęžta" 
#> [9] "tręšta"

Funkcijos strsplit() rezultatas yra list tipo sąrašas. Tai nelabai patogu, jeigu skaidome vieną seką, bet labai patogu, jei į atskiras dalis skaidomos iš karto kelios sekos. Tada gauname sąrašą, kuris turi tiek elementų, kiek yra pradinių sekų. Sąrašo elementai yra vektoriai, kurių elementai yra atskiros sekų dalys. Pavyzdžiui, duotas Vilniaus greitųjų autobusų maršrutų vektorius. Išskaidysime šiuos maršrutus į atskiras dalis, kurios viena nuo kitos atskirtos brūkšneliu.

Kodas
marsrutai <- c("Stotis-Kalvarijų g.-Santariškės",
               "Santariškės-Laisvės pr.-Stotis",
               "Fabijoniškės-Centras-Oro uostas",
               "Pilaitė-Konstitucijos pr.-Saulėtekis",
               "Pašilaičiai-Ozo g.-Saulėtekis",
               "Parko-Olandų g.-žaliasis tiltas")

strsplit(marsrutai, split = "-")
#> [[1]]
#> [1] "Stotis"       "Kalvarijų g." "Santariškės" 
#> 
#> [[2]]
#> [1] "Santariškės" "Laisvės pr." "Stotis"     
#> 
#> [[3]]
#> [1] "Fabijoniškės" "Centras"      "Oro uostas"  
#> 
#> [[4]]
#> [1] "Pilaitė"           "Konstitucijos pr." "Saulėtekis"       
#> 
#> [[5]]
#> [1] "Pašilaičiai" "Ozo g."      "Saulėtekis" 
#> 
#> [[6]]
#> [1] "Parko"           "Olandų g."       "žaliasis tiltas"

Parametro split reikšmė yra reguliari išraiška - tai tokia simbolių seka, kuri leidžia užrašyti tam tikrą sekos šabloną. Reguliarios išraiškos naudoja dviejų tipų simbolius: simbolius, kurie interpretuojami tiesiogiai, ir metasimbolius, kurie turi specialią prasmę. Kokie simboliai yra metasimboliai - priklauso nuo reguliarių išraiškų standarto. R naudojamas išplėstas reg. išraiškų standartas ERE, kuriame metasimboliai yra

  • . \ | ( ) [ { ^ $ * + ?

Jei metasimbolį reikia naudoti kaip simbolį, prieš jį rašomas valdymo simbolis. Pavyzdžiui, R reguliarioje išraiškoje skliaustai () užrašomi \\( \\).

Plačiau apie reguliariųjų išraiškų naudojimą baziniame R rasite šiose nuorodose:

Tuo atveju, kada parametrui split priskiriama seka turi būti interpetuojama ne kaip reguliari išraiška, parametrui fixed priskiriama loginė reikšmė TRUE. Tai nurodo, kad visi metasimboliai bus suprantami, kaip paprasti simboliai.

Pavyzdžiui, reikia išskaidyti tekstiniu formatu užrašytas datas. Datoje metai, mėnuo ir diena vienas nuo kito atskirti tašku.

Kodas
#          Saulės        Durbės        Žalgirio      Oršos         Salaspilio
#         ------------  ------------  ------------  ------------  ------------ 
data <- c("1236.09.22", "1260.07.13", "1410.07.15", "1514.09.08", "1605.09.27")

Reguliariose išraiškose taškas yra metasimbolis, kuris nurodo bet kokį simbolį, tarp jų ir patį tašką, todėl datos bus išskaidytos pagal skaičius ir taškus, o rezultatas bus tuščios sekos.

Kodas
strsplit(data, ".")
#> [[1]]
#>  [1] "" "" "" "" "" "" "" "" "" ""
#> 
#> [[2]]
#>  [1] "" "" "" "" "" "" "" "" "" ""
#> 
#> [[3]]
#>  [1] "" "" "" "" "" "" "" "" "" ""
#> 
#> [[4]]
#>  [1] "" "" "" "" "" "" "" "" "" ""
#> 
#> [[5]]
#>  [1] "" "" "" "" "" "" "" "" "" ""

Kad “.” būtų interpretuojamas kaip taškas, pakeičiame parametro fixed reikšmę.

Kodas
strsplit(data, ".", fixed = TRUE)
#> [[1]]
#> [1] "1236" "09"   "22"  
#> 
#> [[2]]
#> [1] "1260" "07"   "13"  
#> 
#> [[3]]
#> [1] "1410" "07"   "15"  
#> 
#> [[4]]
#> [1] "1514" "09"   "08"  
#> 
#> [[5]]
#> [1] "1605" "09"   "27"

Naudojant valdymo simbolį, tašką galima užrašyti ir kaip reguliarią išraišką.

Kodas
strsplit(data, "\\.")
#> [[1]]
#> [1] "1236" "09"   "22"  
#> 
#> [[2]]
#> [1] "1260" "07"   "13"  
#> 
#> [[3]]
#> [1] "1410" "07"   "15"  
#> 
#> [[4]]
#> [1] "1514" "09"   "08"  
#> 
#> [[5]]
#> [1] "1605" "09"   "27"
Užduotis
  1. Užrašykite komandą, kuri sudarytų simbolių pasirodymo tekste dažnių lentelę.

  2. Užrašykite komandą, kuri sakinį išskaidytų į atskirus žodžius bei nustatytų kiekvieno žodžio ilgį.

  3. Tarkime, kad visų sakinių pabaigą tam tikrame tekste žymi taškas, šauktukas arba klaustukas, o po šių ženklų visada prasideda naujas sakinys (realybėje, žinoma, taip yra nevisada). Užrašykite komandą, kuri nustatytų, kiek žodžių yra kiekviename duoto teksto sakinyje.

  4. Sugalvokite tokią komandą, kuri nuskaitytų kabliataškiais atskirtų vektorių elementus, kurie vienas nuo kito atskirti kableliais. Pvz., tokia seka turi 4 vektorius: “1.2, 4.9, 3.1; 8.6, 7.4; 2.5, 1.2, 8.2, 1.8; 0.5”. Rezultatas turi būti sąrašas iš 4 elementų, kurių elementai yra vektorių reikšmės.

16.4 Kitos teksto apdorojimo funkcijos

Kol kas pateiktos tik kelios specifinės paskirties teksto apdorojimo funkcijos. Neišsiplečiant, nurodysime daugiau funkcijų, kurias suskirstysime pagal atliekamų veiksmų kategorijas:

  1. Paprastas struktūros atpažinimas (be reguliariųjų išraiškų). R turi keletą funkcijų, kurios leidžia ieškoti teksto nereikalaujant reguliariųjų išraiškų žinių:
  • startsWith(x, preffix)

  • endsWith(x, suffix)

  • grepl("tekstas", x, fixed=TRUE)

  1. Teksto skaidymas, sujungimas ar formatavimas. Paprastos bazinės funkcijos, kurios dažnai naudojamos teksto jungimui ar skaidymui:
  1. Apdoroti tarpus. Dažnai pradedantieji duomenų analitikai nepastabi teksto pradžioje (leading) ar pabaigoje (trailing) paliktų tarpų, kuriuos galima pašalinti:
  • trimws()

  • gsub(" +", "", char_vektorius, fixed = FALSE) - naudojant regex pašalinami leading ir trailing tarpai iš char_vektorius elementų.

  1. Konvertavimas į didžiasias / mažasiais raides. Elementarios bazinio R funkcijos:

Paketas stringr

Daugeliu atveju žinant reguliariųjų išraiškų sudarymo taisykles, galima susikonstruoti norimą paieškos (f-ja grepl()) ar pakeitimo (f-ja gsub()) teksto komandą.

Tačiau, jeigu reguliarios išraiškos yra R naudotojui pernelyg komplikuotos, galima pasinaudoti stringr paketu. Jame rasite daugybę teksto apdorojimų funkcijų, įskaitant ir bazinio R ekvivalenčias funkcijas.

Pakete stringr funkcijos sudarytos pagal šabloną str_*, kur vietoje “*” simbolio nurodomas tam tikras atliekamas veiksmas, pvz. str_length(), str_to_lower().

Pavyzdžiui, žemiau pateiktoje lentelėje nurodytos kelios bazinio R ir stringr paketo funkcijos, atliekančios tuos pačius veiksmus.

Bazinis R Paketas stringr
nchar str_length
tolower str_to_lower
toupper str_to_upper
toTitleCase str_to_title
trimws str_trim
paste0 str_c
substr str_sub

Toliau be taikymo pavyzdžių (paliekame skaitytojui išnagrinėti f-jų veikimą), išvardysime stringr paketo funkcijas pagal atliekamus teksto apdorojimo veiksmus:

  1. Teksto ilgio ir raidžių keitimas. Paprastos funkcijos teksto ilgiui nustatyti ir raidžių formatui keisti:
  • str_length(x)

  • str_to_lower(x)

  • str_to_upper(x)

  • str_to_title(x)

  1. Tarpų ir nereikalingų simbolių tvarkymas. Funkcijos, padedančios „apkarpyti“ ir sutvarkyti tarpus:
  • str_trim(x, side = "both")

  • str_squish(x)

  • str_remove(x, pattern)

  • str_remove_all(x, pattern)

  1. Teksto jungimas ir formatavimas. Teksto dalių sujungimui į vientisą eilutę ar formatuotą tekstą:
  • str_c(...)

  • str_c(..., collapse = " ")

  • str_glue("tekstas su įterptais R objektas, pvz. {vektorius} arba {data}")

  1. Teksto paieška ir filtravimas. Funkcijos, leidžiančios patikrinti, ar tekstas atitinka tam tikrą šabloną, ir filtruoti reikšmes:
  • str_detect(x, pattern)

  • str_which(x, pattern)

  • str_subset(x, pattern)

  1. Teksto keitimas. Funkcijos šablono pakeitimui kitu tekstu:
  • str_replace(x, pattern, replacement)

  • str_replace_all(x, pattern, replacement)

  1. Teksto skaidymas ir dalių išėmimas. Funkcijos teksto padalinimui ir konkrečių pozicijų iškirpimui:
  • str_split(x, pattern)

  • str_split_fixed(x, pattern, n)

  • str_sub(x, start, end)

  1. Skaičiavimas tekste. Funkcijos, leidžiančios suskaičiuoti tam tikro šablono pasikartojimus:
  • str_count(x, pattern)
  1. Teksto „užpildymas“ iki nustatyto pločio. Naudinga kodams, identifikatoriams ar stulpelių užpildymui, kai fiksuotas teksto ilgis turi būti vienodas:
  • str_pad(x, width, side = "left")

  • str_pad(x, width, side = "right")

Užduotis
  1. Duotas vektorius su komentarais. Standartizuokite tekstą pagal šiuos veiksmus:
  1. pašalinkite perteklinius tarpus,

  2. paverskite į mažasias raides,

  3. kiekvienas sakinys (komentaras) turi prasidėti didžiąją raide,

  4. suskaičiuokite kiekvieno komentaro žodžių skaičių.

komentarai <- c(
  "   LABAS!!! Kaip sekasi??   ",
  "R PROGRAMAVIMAS   yra    *užbaikite sakinį*",
  "   šiandien   SNINGA , o ryt VISKAS IŠTIRPS"
)
  1. Duotas produktų sąrašas. Standartizuokite pavadinimus pagal šiuos veiksmus:
  1. pašalinkite apatinius brūkšnelius ir brūkšnelius,

  2. pašalinkite tarpus,

  3. konvertuokite į “Title Case”

  4. sudarykite duomenų lentelę su stulpeliais produktas ir prekes_zenklas, kuris yra gautas iš produkto pirmo žodžio.

produktai <- c(
  "   apple_iphone_15 PRO  ",
  "Samsung-Galaxy-S24ultra",
  "xiaomi    Redmi note     13"
)
  1. Duotas interneto svetainės prisijungimo vardų vektorius. Nustatykite:
  1. kurie įrašai turi skaičių prisijungimo varde,

  2. kurie yra trumpesni už 3 simbolius (pasinaudokite regex pagalba),

  3. pakeiskite a) bei b) punktus atitinkančius atvejus INVALID reikšmėmis.

vardai <- c("  ", "Jonas", "P3tra5", "AI", "!!!", "Marius1", "U2")
  1. Įsivaizduokite, kad kuriate paprastą internetinį žaidimą, kuriame kiekvienas žaidėjas gauna unikalų ID kodą. ID kodas turi būti sudarytas iš:
  • priešdėlio “PLR” (player),

  • trijų skaitmenų žaidėjo numerio (pvz. 001, 025, 300),

  • priesagos “-LTU”.

Pavyzdžiui, žaidėjo numeris 5 -> PLR005-LTU. Užduotis: sugeneruoti tokio formato ID kodus duotai žaidėjų grupei zaidejai.

Kodas
zaidejai <- sample(x = 1:999, size = 50)