Stringi


Materiały pomocnicze:

https://en.wikipedia.org/wiki/Regular_expression

https://r4ds.had.co.nz/strings.html

https://www.regular-expressions.info/

https://www.amazon.com/Mastering-Regular-Expressions-Jeffrey-Friedl/dp/0596528124



Dorzućmy sobie kilka narzędzi (pakietów) do pracy…

# installed.packages("stringr")
library(stringr)

# install.packages("dplyr")
library(dplyr)

# install.packages("babynames")
library(babynames)

# install.packages("rebus")
library(rebus)




Przypomnienie z rozruchowych zajęć (1 i 2). Do tworzenia string’a, czy też ciągu znaków używamy: "..." (cudzysłów), bądź '...' (apostrof). Możemy je również łączyć i mieszać, jeśli ma to merytoryczne uzasadnienie, albo istnieje taka konieczność…

# Przykład
print("Cześć")
## [1] "Cześć"
print("Hello \U1F30D")  # <= Unicode characters
## [1] "Hello 🌍"



""Cześć" - powiedziałem jak tylko ją zobaczyłem" nie zadziała, ponieważ jedne nawiasy wyłączają znaczenie drugich. Rozwiązaniem może być …albo miks pojedynczych i podwójnych…

# Przykład
print('"Cześć" - powiedziałem jak tylko ją zobaczyłem') # wbił nam się ukośnik (an escape sequence) - nie przejmujemy się
## [1] "\"Cześć\" - powiedziałem jak tylko ją zobaczyłem"
cat(  '"Cześć" - powiedziałem jak tylko ją zobaczyłem') # gdyż tak to się będzie printować w finalnych raportach, dashboard'ach, etc.
## "Cześć" - powiedziałem jak tylko ją zobaczyłem



…albo wyłączenie znaczenia znaku specjalnego poprzez jawne użycie ukośnika (an escape sequence)

# Przykład
print("\"Cześć\" - powiedziałem jak tylko ją zobaczyłem")
## [1] "\"Cześć\" - powiedziałem jak tylko ją zobaczyłem"
cat(  "\"Cześć\" - powiedziałem jak tylko ją zobaczyłem")
## "Cześć" - powiedziałem jak tylko ją zobaczyłem



Reguła (dobra praktyka) jest taka, że używamy:

Co do zasady podwójnego

print("Cześć!")
## [1] "Cześć!"
cat(  "Cześć!")
## Cześć!



Gdy cytujemy wewnątrz string’a, to w środku podwójny, na zewnątrz pojedynczy

print('"Cześć!" - rzuciłem bez większego zaangażowania')
## [1] "\"Cześć!\" - rzuciłem bez większego zaangażowania"
cat(  '"Cześć!" - rzuciłem bez większego zaangażowania')
## "Cześć!" - rzuciłem bez większego zaangażowania



Gdy musimy w środku string’a użyć i jednych i drugi, wówczas wyłączajmy znaczenie specjalne nawiasów.

print("\"Cześć!\" - rzuciłem bez większego zaangażowania. \"Słuchasz rock\'n\'roll\'a?\"")
## [1] "\"Cześć!\" - rzuciłem bez większego zaangażowania. \"Słuchasz rock'n'roll'a?\""
cat(  "\"Cześć!\" - rzuciłem bez większego zaangażowania. \"Słuchasz rock\'n\'roll\'a?\"")
## "Cześć!" - rzuciłem bez większego zaangażowania. "Słuchasz rock'n'roll'a?"




print()

  • Wyświetla obiekt w „standardowy”, sformatowany sposób używany przez R.

  • Nadaje się do debugowania i automatycznego wypisywania wyników (np. w konsoli, funkcjach).

  • Zwraca obiekt, który został wydrukowany.

x <- 6
print(x)
## [1] 6

cat()

  • Łączy i wypisuje tekst bez dodatkowego formatowania, zwykle w jednej linii.

  • Idealne do tworzenia komunikatów tekstowych, napisów, własnego formatowania.

  • Nie zwraca obiektu — wypisuje tylko tekst.

x <- 6
cat("Wartość x wynosi:", x, "!")
## Wartość x wynosi: 6 !


cat("┌───────────────┐\n│ R is awesome! │\n└───────────────┘\n")
## ┌───────────────┐
## │ R is awesome! │
## └───────────────┘
cat("✔ Sukces\n")
## ✔ Sukces
cat("✘ Błąd\n")
## ✘ Błąd
cat("➜ Dalej...\n")
## ➜ Dalej...
cat("R mówi: 😺📊📈\n")
## R mówi: 😺📊📈
print("R mówi: 😺📊📈\n") # zrobić w konsoli
## [1] "R mówi: 😺📊📈\n"


Podsumowanie:

print() do czytelnego wyświetlania obiektów R, zaś cat() do wypisywania tekstów/komunikatów.




format()

Zwraca obiekt (np. liczby, daty) jako łańcuch znaków w estetycznej formie. Służy do ustawiania szerokości, wyrównania, liczby miejsc po przecinku itp. – głównie do ładnego wyświetlania lub tworzenia tabel tekstowych.

x <- 3.1415926
format(x, digits = 4)      # "3.142"
## [1] "3.142"
format(x, nsmall = 4)      # "3.1416"
## [1] "3.141593"


formatC()

To niskopoziomowa, szybka funkcja do formatowania liczb w stylu języka C. Użyteczna gdy chcemy dokładnie kontrolować:

  • liczbę miejsc po przecinku,

  • szerokość pola,

  • wyrównanie,

  • notację (np. naukową).

x <- 3.1415926
formatC(x, 
        format = "f", # tzw. fixed format
        digits = 2)   # "3.14" <- zwykły ułamek dziesiętny
## [1] "3.14"
formatC(x, 
        format = "e", # tzw. scientific format
        digits = 2)   # "3.14e+00" <- notacja naukowa; mantysa × 10^(wykładnik)
## [1] "3.14e+00"


prettyNum()

Zmienia liczby na ładniejsze teksty, np. dodając separatory tysięcy albo kontrolując format naukowy. Służy do prezentacji liczb przyjemnie dla oka odbiorcy, np. w raportach:

prettyNum(1234567.89, 
          big.mark = " ", 
          decimal.mark = ",") # "1 234 567,89"
## [1] "1 234 568"


Po więcej info i bajerów: ?prettyNum(), bądź ?format(), albo też ?formatC()




Jak łączyć stringi? Użyjemy funkcji paste() lub paste0(), które w międzyczasie używaliśmy kilkukrotnie w różnych przykładach na poprzednich zajęciach.

paste()

Łączy (konkatenuje) elementy w jeden łańcuch znaków, wstawiając separator między nimi (domyślnie spacja " ").

paste("W", "I", "T", "A", "M")
## [1] "W I T A M"
# (domyślny sep = " ")
paste("W", "I", "T", "A", "M", 
      sep = " * ")
## [1] "W * I * T * A * M"
paste("lot: ", 1:3)  # z poprzednich zajęć, przykład tzw. recyclingu
## [1] "lot:  1" "lot:  2" "lot:  3"
x <- 1:12
paste(c("Zajęcia", x), collapse = ", ")
## [1] "Zajęcia, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12"
paste("Zajęcia", x, collapse = ", ")
## [1] "Zajęcia 1, Zajęcia 2, Zajęcia 3, Zajęcia 4, Zajęcia 5, Zajęcia 6, Zajęcia 7, Zajęcia 8, Zajęcia 9, Zajęcia 10, Zajęcia 11, Zajęcia 12"


paste0()

To skrót od paste(..., sep = "") – czyli łączenie bez żadnego separatora. Gdy chcemy skleić coś „na styk”: nazwy plików, identyfikatory, prefiksy, sufiksy itp.

paste0("ID_", 1:3) # "ID_1" "ID_2" "ID_3"
## [1] "ID_1" "ID_2" "ID_3"
folder <- "wyniki"
plik <- "dane.csv"
paste0(folder, "/", plik) # "wyniki/dane.csv"
## [1] "wyniki/dane.csv"


paste( "a", "b")      # a b
## [1] "a b"
paste0("a", "b")      # ab
## [1] "ab"


W raporcie możemy chcieć dodać jednostkę do wartości

# przykład z dolarem
wartosc = 354
paste("$", wartosc, sep = "")
## [1] "$354"
# przykład z procentem
wartosc = 7.8
paste(wartosc, "%", sep = "")
## [1] "7.8%"
# przykład ze złotówkami
wartosci = c(234, 456, 756, 423, 456, 345)
paste(wartosci, "PLN", sep = "")
## [1] "234PLN" "456PLN" "756PLN" "423PLN" "456PLN" "345PLN"


Przykład mundialowego “pejsta”

polska = "Polska!"
cat(paste(c("Kto wygra mecz? ", "Kto? ", " Ktoooo?"), polska, 
      collapse = ", " ), rep("Do booojuuuu...", 3))
## Kto wygra mecz?  Polska!, Kto?  Polska!,  Ktoooo? Polska! Do booojuuuu... Do booojuuuu... Do booojuuuu...



Wtręt, przedsmak pisania funkcji, poniżej przykład funkcji z Data.Camp.com, do śpiewania przyśpiewki..

old_mac <- function(animal, animal_goes){
  
  eieio <- paste("E", "I", "E", "I", "O", sep = "-")
  
  old_mac <- "Old MacDonald had a farm"
  
  writeLines(c(old_mac,
               eieio, 
               paste("And on his farm he had a", 
                     animal),
               eieio, 
               paste(c("Here", "There", "Everywhere"), "a", c(animal_goes, 
                                                              animal_goes,
                                                              paste(rep(animal_goes, 2),
                                                                    collapse = "-")),
                     collapse = ", "),
               old_mac,
               eieio)) # ?writeLines()
  }

# dwa argumenty, nazwa zwierzątka i wytwarzany przez nie dźwięk
old_mac("ratel miodożerny", "WrrrAgrrrr")
## Old MacDonald had a farm
## E-I-E-I-O
## And on his farm he had a ratel miodożerny
## E-I-E-I-O
## Here a WrrrAgrrrr, There a WrrrAgrrrr, Everywhere a WrrrAgrrrr-WrrrAgrrrr
## Old MacDonald had a farm
## E-I-E-I-O
“Ratel: Kuloodporny, Nieśmiertelny i Nieustraszony”
“Ratel: Kuloodporny, Nieśmiertelny i Nieustraszony”


Połączenie dotychczas poznanych funkcji format() i paste() w celu stworzenia schludnej tabelki

(nazwy <- c("Rok 0", "Rok 1", "Rok 2", "Razem..."))
## [1] "Rok 0"    "Rok 1"    "Rok 2"    "Razem..."
(nazwy <- format(nazwy, justify = "right"))
## [1] "   Rok 0" "   Rok 1" "   Rok 2" "Razem..."


(dochod <- c(12.34, 2345.6701, 34567.807, 875432.5))
## [1]     12.34   2345.67  34567.81 875432.50
(dochod <- format(dochod, digits = 2, big.mark=","))
## [1] "     12" "  2,346" " 34,568" "875,433"
(dochod <- paste("$ kolumbijskie  ", dochod, sep = ""))
## [1] "$ kolumbijskie       12" "$ kolumbijskie    2,346"
## [3] "$ kolumbijskie   34,568" "$ kolumbijskie  875,433"


wiersze <- paste(nazwy, dochod, sep="   ")
writeLines(wiersze)
##    Rok 0   $ kolumbijskie       12
##    Rok 1   $ kolumbijskie    2,346
##    Rok 2   $ kolumbijskie   34,568
## Razem...   $ kolumbijskie  875,433



for (i in 1:10) {
  cat("\r", strrep("█", i), strrep("·", 10 - i), i * 10, "%")
  flush.console()
  Sys.sleep(0.1)
}
cat("\n")



{stringr}

Pakiet stringr w R jest częścią ekosystemu tidyverse i służy do manipulacji tekstem. Oferuje prosty i spójny zestaw funkcji do operacji na ciągach znaków


str_c()


Funkcja str_c() w pakiecie {stringr} służy do łączenia (konkatenacji) ciągów znaków. Jest to odpowiednik funkcji paste() w bazowym R, ale z bardziej spójną składnią i dodatkowymi możliwościami.

nazwiska <- c("Kowalski", "Nowak", "Szymkiewicz", "Dziurda") # wektor nazwisk

paste(c("Pan", "Pani", rep("Pan", 2)), nazwiska, sep = " ")
## [1] "Pan Kowalski"    "Pani Nowak"      "Pan Szymkiewicz" "Pan Dziurda"
str_c(c("Pan", "Pani", rep("Pan", 2)), nazwiska, sep = " ") # bliźniacza funkcja z pakietu {stringr}
## [1] "Pan Kowalski"    "Pani Nowak"      "Pan Szymkiewicz" "Pan Dziurda"



Różnice (radzenie sobie z brakami danych)

(nazwiska <- c(NA, "Kowalski", "Nowak", NA, "Szymkiewicz", "Dziurda")) #
## [1] NA            "Kowalski"    "Nowak"       NA            "Szymkiewicz"
## [6] "Dziurda"
paste(c("Pan"), nazwiska, sep = " ") # "Pan NA"
## [1] "Pan NA"          "Pan Kowalski"    "Pan Nowak"       "Pan NA"         
## [5] "Pan Szymkiewicz" "Pan Dziurda"
str_c(c("Pan"), nazwiska, sep = " ") # a tu zwykły missing values, czują Państwo przewagę?
## [1] NA                "Pan Kowalski"    "Pan Nowak"       NA               
## [5] "Pan Szymkiewicz" "Pan Dziurda"

str_replace_na()

str_replace_na(string = nazwiska, 
               replacement = "Brak danych")
## [1] "Brak danych" "Kowalski"    "Nowak"       "Brak danych" "Szymkiewicz"
## [6] "Dziurda"



str_length()


Funkcja str_length() w pakiecie {stringr} służy do zwracania długości ciągów znaków. Jest to bardzo przydatne, gdy chcesz szybko sprawdzić, ile znaków zawiera dany ciąg.

nazwiska <- c("Kowalski", "Nowak", "Szymkiewicz", "Dziurda")
str_length(nazwiska) # zwrócił długość każdego elementu wektora
## [1]  8  5 11  7


babynames data set.

# install.packages("babynames")
# library(babynames)
data(babynames)
dplyr::glimpse(babynames)
## Rows: 1,924,665
## Columns: 5
## $ year <dbl> 1880, 1880, 1880, 1880, 1880, 1880, 1880, 1880, 1880, 1880, 1880,…
## $ sex  <chr> "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", …
## $ name <chr> "Mary", "Anna", "Emma", "Elizabeth", "Minnie", "Margaret", "Ida",…
## $ n    <int> 7065, 2604, 2003, 1939, 1746, 1578, 1472, 1414, 1320, 1288, 1258,…
## $ prop <dbl> 0.07238359, 0.02667896, 0.02052149, 0.01986579, 0.01788843, 0.016…
# ?babynames


babynames %>% 
  filter(year == 2014 & sex == "M") %>% 
  select(name) -> boy_names
boy_names <- as.vector(boy_names$name)

babynames %>% 
  filter(year == 2014 & sex == "F") %>% 
  select(name) -> girl_names
girl_names <- as.vector(girl_names$name)

# średnie długości imion w zbiorze przyciętym do roku 2014
# dla kobiet i mężczyzn -> różnica średnich
boy_length <- str_length(boy_names)
girl_length <- str_length(girl_names)
(mean(girl_length)- mean(boy_length))
## [1] 0.3374758

Przykładowy dashboard z sieci bazujący na danych (dla inspiracji)

https://staff.math.su.se/hoehle/blog/2017/03/01/morebabynames.html


str_sub()


Funkcja str_sub() w pakiecie {stringr} służy do wyodrębniania podciągów z ciągów znaków na podstawie pozycji początkowej i końcowej. Można również użyć tej funkcji do modyfikacji części ciągu znaków.

boy_first_letter <- str_sub(string = boy_names,
                            start =  1,
                            end =  1) # pierwsze litery imiony chłopców
(janitor::tabyl(boy_first_letter))
##  boy_first_letter    n     percent
##                 A 1454 0.103509646
##                 B  651 0.046344415
##                 C  770 0.054815975
##                 D  998 0.071047199
##                 E  549 0.039083078
##                 F  185 0.013170072
##                 G  334 0.023777319
##                 H  403 0.028689400
##                 I  235 0.016729551
##                 J 1390 0.098953513
##                 K 1291 0.091905745
##                 L  537 0.038228803
##                 M  914 0.065067274
##                 N  424 0.030184381
##                 O  207 0.014736243
##                 P  230 0.016373603
##                 Q   56 0.003986616
##                 R  778 0.055385492
##                 S  806 0.057378800
##                 T  771 0.054887165
##                 U   43 0.003061152
##                 V  160 0.011390332
##                 W  174 0.012386987
##                 X   56 0.003986616
##                 Y  252 0.017939774
##                 Z  379 0.026980850
boy_last_letter <- str_sub(boy_names,
                           start =  -1,
                           end =  -1) # ostatnie litery imion chłopców (numerowanie ze znakiem ujemnym)
(janitor::tabyl(boy_last_letter))
##  boy_last_letter    n     percent
##                a  421 0.029970812
##                b  104 0.007403716
##                c   92 0.006549441
##                d  436 0.031038656
##                e 1148 0.081725635
##                f   66 0.004698512
##                g   82 0.005837545
##                h  583 0.041503524
##                i  705 0.050188652
##                j   57 0.004057806
##                k  349 0.024845163
##                l  945 0.067274151
##                m  389 0.027692746
##                n 4672 0.332597708
##                o  730 0.051968392
##                p   32 0.002278066
##                q   19 0.001352602
##                r 1011 0.071972663
##                s  826 0.058802591
##                t  292 0.020787357
##                u   81 0.005766356
##                v   71 0.005054460
##                w   34 0.002420446
##                x   86 0.006122304
##                y  697 0.049619136
##                z  119 0.008471560
girl_first_letter <- str_sub(girl_names,
                             start =  1
                             ,end =  1)
janitor::tabyl(girl_first_letter)
##  girl_first_letter    n     percent
##                  A 3101 0.161670403
##                  B  699 0.036442313
##                  C  946 0.049319639
##                  D  810 0.042229289
##                  E  933 0.048641885
##                  F  209 0.010896199
##                  G  345 0.017986549
##                  H  469 0.024451280
##                  I  373 0.019446327
##                  J 1430 0.074552943
##                  K 1694 0.088316563
##                  L 1122 0.058495386
##                  M 1746 0.091027579
##                  N  752 0.039205464
##                  O  143 0.007455294
##                  P  303 0.015796882
##                  Q   38 0.001981127
##                  R  831 0.043324123
##                  S 1369 0.071372713
##                  T  683 0.035608154
##                  U   28 0.001459778
##                  V  214 0.011156874
##                  W   85 0.004431469
##                  X   62 0.003232365
##                  Y  294 0.015327668
##                  Z  502 0.026171732
girl_last_letter <- str_sub(girl_names, 
                            start = -1,
                            end = -1)
janitor::tabyl(girl_last_letter)
##  girl_last_letter    n      percent
##                 a 6632 0.3457588238
##                 b   20 0.0010426985
##                 c   13 0.0006777540
##                 d   81 0.0042229289
##                 e 3114 0.1623481570
##                 f    8 0.0004170794
##                 g   21 0.0010948334
##                 h 1942 0.1012460247
##                 i 1581 0.0824253167
##                 j   12 0.0006256191
##                 k   31 0.0016161827
##                 l  450 0.0234607163
##                 m  115 0.0059955164
##                 n 2608 0.1359678849
##                 o  105 0.0054741671
##                 p    3 0.0001564048
##                 q    2 0.0001042699
##                 r  291 0.0151712632
##                 s  326 0.0169959856
##                 t  208 0.0108440644
##                 u   59 0.0030759606
##                 v    6 0.0003128096
##                 w   17 0.0008862937
##                 x   50 0.0026067463
##                 y 1435 0.0748136176
##                 z   51 0.0026588812
# ekwiwalent substr()

# więcej o tabyl() choćby tu:
# https://cran.r-project.org/web/packages/janitor/vignettes/tabyls.html

Wartości ujemne - koniec string’u.

# Wyodrębnianie ostatnich 6 znaków
str_sub("Hello, world!", -6, -1)
## [1] "world!"


Modyfikacja “podciągów”.

# Zmiana pierwszych 5 znaków
x <- "Hello, world!"
str_sub(x, 1, 5) <- "Hi"


str_detect()


Funkcja str_detect() w pakiecie {stringr} służy do sprawdzania, czy dany wzorzec występuje w ciągu znaków. Zwraca wartość logiczną (TRUE lub FALSE) dla każdego elementu wektora, w zależności od tego, czy wzorzec został znaleziony.


Które z postaci z poniższego wektora były ninja?

PowerRangers <- c("Power Rangers Time Force", 
                  "Mighty Morphin Power Rangers 1",
                  "Mighty Morphin Super Alien Rangers",
                  "power Rangers Turbo",
                  "Power Rangerrs Ninja Storm",
                  "Power Rrangerrs Super Ninja Steel Ninja",
                  "power Rangers Wild Force") # https://pl.wikipedia.org/wiki/Power_Rangers

# Które ninja?
str_detect(string = PowerRangers, pattern = "Ninja")
## [1] FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE
ninja <- str_detect(string = PowerRangers, pattern = "Ninja")
PowerRangers[ninja] # indeksowanie wektorem ninja
## [1] "Power Rangerrs Ninja Storm"             
## [2] "Power Rrangerrs Super Ninja Steel Ninja"
# Które z postaci miały "moc"? 
force <- str_detect(PowerRangers, "Force")
PowerRangers[force]
## [1] "Power Rangers Time Force" "power Rangers Wild Force"
# zwraca wektor logiczny, TRUE jest w miejscu wektora (na elemencie), 
# gdzie znaleziona została szukana fraza


str_subset()


Funkcja str_subset() w pakiecie {stringr} służy do filtrowania wektora ciągów znaków, zwracając tylko te elementy, które pasują do określonego wzorca. Jest to bardzo przydatne, gdy chcemy wyodrębnić podzbiór danych tekstowych na podstawie określonego kryterium.


Zwraca te elementy wektora, które zawierają szukaną frazę

str_subset(string = PowerRangers, pattern = "Super")
## [1] "Mighty Morphin Super Alien Rangers"     
## [2] "Power Rrangerrs Super Ninja Steel Ninja"
str_subset(string = PowerRangers, pattern = "Force")
## [1] "Power Rangers Time Force" "power Rangers Wild Force"


Filtrowanie ciągów zawierających cyfrę

str_subset(PowerRangers,
           "\\d" # W wyrażeniach regularnych, \\d oznacza dowolną cyfrę od 0 do 9. Jest to skrót, który pozwala na łatwe wyszukiwanie cyfr w ciągach znaków
           )
## [1] "Mighty Morphin Power Rangers 1"


str_count()


Funkcja str_count() z pakietu {stringr} w R służy do liczenia liczby wystąpień wzorca w ciągu znaków.

# liczy wystąpienia wzoru, w każdym elemencie wektora
str_count(string = PowerRangers, pattern = "Ninja")
## [1] 0 0 0 0 1 2 0
str_count(string = PowerRangers, pattern = "Force")
## [1] 1 0 0 0 0 0 1


Funkcja str_count() jest czuła na wielkość liter, więc np. ‘a’ i ‘A’ będą traktowane jako różne znaki.

str_count(string = PowerRangers, pattern = "ninja")
## [1] 0 0 0 0 0 0 0


str_extract()


Funkcja str_extract() z pakietu {stringr} w R służy do wyodrębniania pierwszego pełnego dopasowania wzorca z każdego ciągu znaków.

# zwraca wektor tej samej długości, ale tylko elementy, które pasują do wzoru
str_extract(string = PowerRangers, pattern = "Super")
## [1] NA      NA      "Super" NA      NA      "Super" NA


Wyodrębniamy pierwszą cyfrę z każdego elementu wektora

str_extract(PowerRangers, "\\d")
## [1] NA  "1" NA  NA  NA  NA  NA


Wyodrębniamy pierwsze słowo z każdego elementu wektora

str_extract(PowerRangers, "[a-z]+")
## [1] "ower"  "ighty" "ighty" "power" "ower"  "ower"  "power"

Lepiej…

str_extract(PowerRangers, "[a-zA-Z]+")
## [1] "Power"  "Mighty" "Mighty" "power"  "Power"  "Power"  "power"

Chociaż te zakres też obejmie duże i małe litery.

str_extract(PowerRangers, "[A-z]+")
## [1] "Power"  "Mighty" "Mighty" "power"  "Power"  "Power"  "power"


str_split()


Funkcja str_split() z pakietu {stringr} w R służy do dzielenia ciągu znaków na części według określonego wzorca.

# rozbija element
str_split(string = c("Matematyka i Kognitywistyka"), 
          pattern = " i ") # czy rozpoznają Państwo jaki typ obiektu został zwrócony?
## [[1]]
## [1] "Matematyka"     "Kognitywistyka"
# może tak będzie łatwiej?
str_split(string = c("Matematyka i Kognitywistyka", "Kognistywistyka i Informatyka"),
          pattern = " i ")
## [[1]]
## [1] "Matematyka"     "Kognitywistyka"
## 
## [[2]]
## [1] "Kognistywistyka" "Informatyka"
# chcąc mieć obiekt "prostszy" w obsłudze, możemy użyć dodatkowego argumentu funkcji
str_split(string = c("Matematyka i Kognitywistyka"), 
          pattern = " i ", simplify = TRUE) # wówczas mamy do czynienia nie z listą ale z matrycą
##      [,1]         [,2]            
## [1,] "Matematyka" "Kognitywistyka"
str_split(string = c("Matematyka i Kognitywistyka", "Kognistywistyka i Informatyka"), 
          pattern = " i ", simplify = TRUE)
##      [,1]              [,2]            
## [1,] "Matematyka"      "Kognitywistyka"
## [2,] "Kognistywistyka" "Informatyka"
str_split(string = c("Matematyka i Kognitywistyka i Informatyka"),
          pattern = " i ")
## [[1]]
## [1] "Matematyka"     "Kognitywistyka" "Informatyka"
str_split(string = c("Matematyka i Kognitywistyka i Informatyka"), 
          pattern = " i ", n = 2) # na dwie części, kolejne wystąpienie wzoru zostanie zignorowane
## [[1]]
## [1] "Matematyka"                   "Kognitywistyka i Informatyka"


str_replace()


Funkcjastr_replace() z pakietu {stringr} w R służy do zastępowania pierwszego dopasowania wzorca w ciągu znaków.

# Zamienia wzór na inny wzór
str_replace(string = "i czasopisma", 
            pattern = "i ", # https://pl.wikipedia.org/wiki/Ustawa_o_radiofonii_i_telewizji
            replacement = " lub ")# https://pl.wikipedia.org/wiki/(%E2%80%A6)_lub_czasopisma
## [1] " lub czasopisma"
str_replace(string = "Matematyka i Kognitywistyka i Informatyka", 
            pattern = " i ", 
            replacement = " lub ")
## [1] "Matematyka lub Kognitywistyka i Informatyka"


A jeśli wszystko wówczas użyjemy funkcji str_replace_all()

str_replace_all(string = "Matematyka i Kognitywistyka i Informatyka", # wektor jednoelementowy
                pattern = " i ", 
                replacement = " lub ")
## [1] "Matematyka lub Kognitywistyka lub Informatyka"
str_replace_all(string = c("Matematyka i Kognitywistyka", 
                           "Kognistywistyka i Informatyka"), 
                pattern = " i ", 
                replacement = " lub ") # działa na wektorze wieloelementowym
## [1] "Matematyka lub Kognitywistyka"   "Kognistywistyka lub Informatyka"




Wyrażenia regularne {base}


grepl()


grepl(pattern = <regex>, x = <string>)

# ?regex

animals <- c("cat","moose","impala","ant","kiwi")# ćwiczeniowy wektor

grepl(pattern = "a", x = animals) # pierwsze wystąpienie "a"
## [1]  TRUE FALSE  TRUE  TRUE FALSE
grepl(pattern = "^a", x = animals) # "a" na początku wyrazu
## [1] FALSE FALSE FALSE  TRUE FALSE
grepl(pattern = "a$", x = animals) # "a" na końcu wyrazu
## [1] FALSE FALSE  TRUE FALSE FALSE


##grep()


grep(pattern = <regex>, x = <string>)

(animals <- c("cat","moose","impala","ant","kiwi"))# ćwiczeniowy wektor
## [1] "cat"    "moose"  "impala" "ant"    "kiwi"
grepl(pattern = "a", x = animals)
## [1]  TRUE FALSE  TRUE  TRUE FALSE
# vs
grep(pattern = "a", x = animals)
## [1] 1 3 4
which(
  grepl(pattern = "a", 
        x = animals))
## [1] 1 3 4
grep(pattern = "^a", # ^ na początku 
     x = animals)
## [1] 4


Czyli..

grepl() w R jest funkcją służącą do wyszukiwania wzorców w ciągach znaków. Zwraca wektor wartości logicznych, gdzie TRUE oznacza, że wzorzec został znaleziony, a FALSE oznacza, że nie został znaleziony.

grep() służy również do wyszukiwania wzorców w ciągach znaków, ale zwraca indeksy, na których wzorzec został znaleziony.


sub(), gsub()


sub(pattern = <regex>, replacement = <str>, x = <str>)

animals <- c("cat","moose","impala","ant","kiwi")# ćwiczeniowy wektor

sub(pattern = "a", 
    replacement = "o", 
    x = animals) # pierwsze wystąpienie
## [1] "cot"    "moose"  "impola" "ont"    "kiwi"
# vs

gsub(pattern = "a", 
     replacement = "o",
     x = animals) # wszystkie wystąpienia
## [1] "cot"    "moose"  "impolo" "ont"    "kiwi"


Czyli…

sub() (od “substitute”) służy do zastępowania pierwszego wystąpienia danego wzorca w każdym elemencie wektora tekstu.

gsub() (od “global substitute”) zastępuje wszystkie wystąpienia danego wzorca w każdym elemencie wektora tekstu.


gsub(pattern = "a|i",  # a lub i
     replacement = "_", 
     x = animals)
## [1] "c_t"    "moose"  "_mp_l_" "_nt"    "k_w_"
gsub(pattern = "a|i|o", # a lub i lub o
     replacement = "_", 
     x = animals)
## [1] "c_t"    "m__se"  "_mp_l_" "_nt"    "k_w_"



Mamy przygotowany wektor z adresami e-mail. Wydrukujmy wynik.

(emails <- c("john.doe@ivyleague.edu", 
             "education@world.gov", 
             "dalai.lama@peace.org",
            "invalid.edu", 
            "quant@bigdatacollege.edu", 
            "cookie.monster@sesame.tv"))
## [1] "john.doe@ivyleague.edu"   "education@world.gov"     
## [3] "dalai.lama@peace.org"     "invalid.edu"             
## [5] "quant@bigdatacollege.edu" "cookie.monster@sesame.tv"


Chcielibyśmy wyciągnąć tylko e-mail’e, które są prawidłowe i dotyczą kont akademickich

# czy adres zawiera frazę .edu ?
czyZawiera <- grepl(".edu", emails)
emails[czyZawiera]
## [1] "john.doe@ivyleague.edu"   "invalid.edu"             
## [3] "quant@bigdatacollege.edu"
# ale widzimy, że jest jakiś problem z drugi adresem (to nie adres), zatem...


# czy adres zawiera 'małpę", czyli jest adresem e-mail, między małpą a domeną jest jeden lub wiele znaków, oraz jest to zdres akademicki, zapisana do wektora hits
(hits <- grep("@.*\\.edu$", emails))
## [1] 1 5
# Podzbiór szukanych mejli z użycie wektora hits (daje nam indeksy, gdzie szukana fraza została znaleziona)
emails[hits]
## [1] "john.doe@ivyleague.edu"   "quant@bigdatacollege.edu"


  • @, ponieważ prawidłowy adres e-mail musi zawierać znak at-sign.

  • .*,który dopasowuje dowolny znak (.) zero lub więcej razy (*). Zarówno kropka, jak i gwiazdka są metaznakami. Możesz ich użyć do dopasowania dowolnego znaku znajdującego się pomiędzy znakiem at a częścią „.edu” adresu e-mail.

  • \\.edu$, aby dopasować część „.edu” adresu e-mail na końcu ciągu. Część \\ zamienia znaczenie kropce: mówi R, że chcesz użyć . jako jej rzeczywistą postać.



Podczas gdy grep() i grepl() służyły po prostu do sprawdzania, czy wyrażenie regularne może być dopasowane do wektora znaków, sub()i gsub() idą o krok dalej: możesz podać argument zamiany. Jeśli wewnątrz wektora znaków x, zostanie znaleziony wzór wyrażenia regularnego, to pasujący element (elementy) zostanie zastąpiony zamiennikiem. sub() zastępuje tylko pierwsze dopasowanie, podczas gdy gsub() zastępuje wszystkie dopasowania.

Załóżmy, że wektor e-maili, z którym pracujesz, jest fragmentem bazy e-maili DataCamp. Dlaczego by nie zaoferować właścicielom adresów e-mail z .edu nowego adresu e-mail w domenie datacamp.edu? Edukacja online przejmuje tradycyjne instytucje edukacyjne i może pobudzać wrażenie, że się należy do takiej międzynarodowej grupy subskrybentów/ studentów.

Używając zaawansowanego wyrażenia regularnego “@.edu$”, użyjmy sub(), aby zastąpić dopasowanie przez "@datacamp.edu". Ponieważ będzie tylko jedno dopasowanie na łańcuch znaków, gsub() nie jest tutaj konieczne. Sprawdź wyniki.

# sub() do zamiany domen na datacamp.edu
sub("@.*\\.edu$", "@datacamp.edu", emails)
## [1] "john.doe@datacamp.edu"    "education@world.gov"     
## [3] "dalai.lama@peace.org"     "invalid.edu"             
## [5] "quant@datacamp.edu"       "cookie.monster@sesame.tv"


Wyrażenia regularne to typowy obszar programowania, którego się nie nauczysz na pamięć, a bardziej wykonując, popełniając błędy, sprawdzając i oglądając przykłady. Przyjrzyjmy się kilku nowym rzeczom, które zostaną za moment użyte:

  • .*: Można go odczytać jako „dowolny znak, który jest dopasowany zero lub więcej razy”.

  • \\s: spacja, niezbędny “znak ucieczki” (\\)

  • [0-9]+: Dopasuj cyfry od 0 do 9 przynajmniej raz (+).

  • ([0-9]+): Nawiasy służą do udostępniania części pasującego ciągu w celu zdefiniowania zamiany. \\1 w argumencie replacement funkcji sub() zostaje ustawione na ciąg znaków przechwycony przez wyrażenie regularne [0-9]+.




Powiedzmy, że chcemy w ciągu znaków w poniższym wektorze wyciągnąć informacje o liczbie nominacji. Niedoskonale, ale zawsze lepiej niż ręcznie (pomyślmy o takim wektorze o tysiącu elementach).

awards <- c("Won 1 Oscar.",
  "Won 1 Oscar. Another 9 wins & 24 nominations.",
  "1 win and 2 nominations.",
  "2 wins & 3 nominations.",
  "Nominated for 2 Golden Globes. 1 more win & 2 nominations.",
  "4 wins & 1 nomination.")

# sub(".*\\s[0-9]+\\snomination.*$", "\\1", awards)
sub(".*\\s([0-9]+)\\snomination.*$", "\\1", awards)
## [1] "Won 1 Oscar." "24"           "2"            "3"            "2"           
## [6] "1"

Napisaliśmy w skrócie “dopasuj dowolny ciąg znaków, następnie pojedynczy znak odstępu, jedną lub więcej cyfr, kolejny znak odstępu, słowo ‘nomination’ i dowolny ciąg znaków aż do końca linii”. Czy to naprawdę musi być takie skomplikowane i wymagać znajomości meta znaków RegEx? Otóż, nie!



Wyrażenia regularne {rebus}


Motto pakietu: ‘Build Regular Expressions in a Human Readable Way’
https://cran.r-project.org/web/packages/rebus/index.html


str_view() Przydatna funkcja do testowania własnego wyrażenia regularnego (https://www.rdocumentation.org/packages/stringr/versions/1.5.0/topics/str_view)


# shortcuts to specify regular expressions that match the start and end of the string

START # <- {rebus}  regex -> ^
## <regex> ^
END #  <- {rebus}  regex -> $
## <regex> $
# a wildcard to match a single character
ANY_CHAR # <- {rebus}  regex -> .
## <regex> .

Puśćmy w konsoli poniższe linijki..

str_view(string = PowerRangers, 
         pattern = START %R% 
           "Pow")
## [1] │ <Pow>er Rangers Time Force
## [5] │ <Pow>er Rangerrs Ninja Storm
## [6] │ <Pow>er Rrangerrs Super Ninja Steel Ninja
#  When you are reading rebus code, think of %R% as "then"
# "the start of string then a Pow"
# or in other words: strings that start with Pow. 

# A zatem koniec stringa jak napiszemy?
str_view(string = PowerRangers, pattern = "Rangers" %R% END)
## [3] │ Mighty Morphin Super Alien <Rangers>


pattern’u zawierającego wyrażenie regularne możemy używać do wszystkich funkcji z rodziny str_...().


A co w sytuacji, gdy mamy taki wektor, gdzie znak specjalny jest istotnym elementem naszego wektora?

hajsy <- c("500$", "haj$", "hajs")

# no no no
(pattern_hajsy = "$" %R% END)
## <regex> $$
str_view(string = hajsy, pattern = pattern_hajsy)
## [1] │ 500$<>
## [2] │ haj$<>
## [3] │ hajs<>
# yee yes yes # https://www.youtube.com/watch?v=R5wTQPlHHL4
(pattern_hajsy = DOLLAR %R% END)
## <regex> \$$
str_view(string = hajsy,
         pattern = pattern_hajsy)
## [1] │ 500<$>
## [2] │ haj<$>


A jeśli nie wiemy czego szukamy (samo życie), albo szukamy kilku rzeczy w tym samym czasie? Zobaczmy…

(pattern_fn = or("Force","Ninja"))
## <regex> (?:Force|Ninja)
str_view(string = PowerRangers, pattern = pattern_fn)
## [1] │ Power Rangers Time <Force>
## [5] │ Power Rangerrs <Ninja> Storm
## [6] │ Power Rrangerrs Super <Ninja> Steel <Ninja>
## [7] │ power Rangers Wild <Force>


A co w sytuacji, gdy szukamy wystąpienia pojedynczego znaku i dodatkowo jesteśmy wrażliwi na wielkość liter (R też w końcu jest case sensitive)

# char_class -> a way of specifying "match one (and only one) of the following characters"
(pattern_Pp = char_class("Pp"))
## <regex> [Pp]
str_view(string = PowerRangers, pattern = pattern_Pp)
## [1] │ <P>ower Rangers Time Force
## [2] │ Mighty Mor<p>hin <P>ower Rangers 1
## [3] │ Mighty Mor<p>hin Su<p>er Alien Rangers
## [4] │ <p>ower Rangers Turbo
## [5] │ <P>ower Rangerrs Ninja Storm
## [6] │ <P>ower Rrangerrs Su<p>er Ninja Steel Ninja
## [7] │ <p>ower Rangers Wild Force
(pattern_p = char_class("P"))
## <regex> [P]
str_view(string = PowerRangers, pattern = pattern_p)
## [1] │ <P>ower Rangers Time Force
## [2] │ Mighty Morphin <P>ower Rangers 1
## [5] │ <P>ower Rangerrs Ninja Storm
## [6] │ <P>ower Rrangerrs Super Ninja Steel Ninja
# nie zastanawia nas czemu tylko pierwsze wystąpienie pokazuje?
# żeby zobaczyć wszystkie musimy użyć funkcji
(pattern_M = char_class("M"))
## <regex> [M]
str_view(string = PowerRangers, pattern = pattern_M)
## [2] │ <M>ighty <M>orphin Power Rangers 1
## [3] │ <M>ighty <M>orphin Super Alien Rangers
str_view_all(string = PowerRangers, pattern = pattern_M)
## Warning: `str_view_all()` was deprecated in stringr 1.5.0.
## ℹ Please use `str_view()` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## [1] │ Power Rangers Time Force
## [2] │ <M>ighty <M>orphin Power Rangers 1
## [3] │ <M>ighty <M>orphin Super Alien Rangers
## [4] │ power Rangers Turbo
## [5] │ Power Rangerrs Ninja Storm
## [6] │ Power Rrangerrs Super Ninja Steel Ninja
## [7] │ power Rangers Wild Force
# Tylko niezaczynające się na literę "p" lub "P" na początku string'a? Wówczas
(pattern_Pp = negated_char_class("Pp"))
## <regex> [^Pp]
str_view(string = PowerRangers, pattern = pattern_M)
## [2] │ <M>ighty <M>orphin Power Rangers 1
## [3] │ <M>ighty <M>orphin Super Alien Rangers


Powtarzalność wzoru:

  • optional() (regex -> ?)

  • zero_or_more() (regex -> * )

  • one_or_more() (regex -> +)

  • repeated() (regex -> {m, n}) # pomiędzy m i n



kolory <- c("niebieski", "różowy", "magenta", "zielony")
str_view(kolory, pattern = or("różowy", "magenta"))
## [2] │ <różowy>
## [3] │ <magenta>


imiona <- c("Angelika", "Patrycja", "Andżelika", "Piotr")
str_view(imiona, pattern = or("Angelika", "Andżelika"))
## [1] │ <Angelika>
## [3] │ <Andżelika>
#alternatywnie, skoro kwestia jednej, dwóch liter
str_view(imiona, pattern ="An" %R% or("g", "dż") %R% "elika", match = TRUE) # ostatni argument spowoduje, że wyświetlą się nam jedynie szukane frazy, przy dużych zbiorach oszczędza przestrzeń
## [1] │ <Angelika>
## [3] │ <Andżelika>
imiona2 <- c("Jeffrey","Jeffery","Geoffrey","Jeffry","Jefferey", "Piotr")
po_elementach <- or("Je", "Geo") %R% "ff" %R% or("ry", "ery", "rey", "erey")
str_view(imiona2, pattern = po_elementach)
## [1] │ <Jeffrey>
## [2] │ <Jeffery>
## [3] │ <Geoffrey>
## [4] │ <Jeffry>
## [5] │ <Jefferey>


vowels <- char_class("aeiouAEIOU")
x <- c("ow", "ooh", "yeeeah!", "shh")
str_view(x, pattern = one_or_more(vowels)) # jedna, dwie, albo wszystkie samogłoski
## [1] │ <o>w
## [2] │ <oo>h
## [3] │ y<eeea>h!
str_view(x, pattern = zero_or_more(vowels)) # zero, albo pierwsze, patrząc od poczatku string'a wystąpienie
## [1] │ <o><>w<>
## [2] │ <oo><>h<>
## [3] │ <>y<eeea><>h<>!<>
## [4] │ <>s<>h<>h<>



Skróty

# używany przed momentem DOLLAR
DOLLAR %R% char_class("0123456789")
## <regex> \$[0123456789]
char_class("0-9") # zakres, zamiast c("0123456789")
## <regex> [0-9]
# jeszcze szybciej, a to samo co powyżej
DGT # == char_class("0-9")
## <regex> \d
# albo chcemy wszystkie małe litery alfabetu łacińskiego
char_class("a-z") #  zakres, zamiast c("a", "b", "c", ....) albo letters
## <regex> [a-z]
char_class("A-Z") #  zakres, zamiast c("A", "B", "C", ....) albo LETTERS
## <regex> [A-Z]
# jeszcze szybciej, a to samo co powyżej zusammen
WRD # == char_class(a-zA-Z0-9_)
## <regex> \w
SPC # spacja, whitespace character
## <regex> \s


Dygresja, użyta została funkcja charClass().


(liczbyC <- c("0123456789"))
## [1] "0123456789"
# versus
(liczbyCharClass <- char_class("0123456789"))
## <regex> [0123456789]
class(liczbyC)
## [1] "character"
class(liczbyCharClass)
## [1] "regex"     "character"

Jest przydatna, gdy potrzebujemy np. szybko i efektywnie sprawdzić, do jakiej klasy należy dany znak lub ciąg znaków. Może to być szczególnie użyteczne w analizie tekstu, walidacji danych, czy przetwarzaniu wejściowych danych użytkownika.

x <- "12345"
if (all(charClass(x, "digit"))) {
  print("Wszystkie znaki są cyframi")
} else {
  print("Nie wszystkie znaki są cyframi")
}
## [1] "Wszystkie znaki są cyframi"

Więcej w dokumentacji.


Wracając…


Jak to “ugryźć” w praktyce powyższe? Załóżmy, że chcemy rozpracować taki wektor

(contact <- c("Call me at 555-555-0191",
              "123 Main St",
              "(555) 555 0191",
              "Phone: 555.555.0191 Mobile: 555.555.0192"))
## [1] "Call me at 555-555-0191"                 
## [2] "123 Main St"                             
## [3] "(555) 555 0191"                          
## [4] "Phone: 555.555.0191 Mobile: 555.555.0192"


Numery mają długość 10 znaków i składają się z cyfr, rzut oka na wektor sugeruje wyciąganie elementów po trzy cyfry

str_view_all(contact, pattern = DGT %R% DGT %R% DGT)
## [1] │ Call me at <555>-<555>-<019>1
## [2] │ <123> Main St
## [3] │ (<555>) <555> <019>1
## [4] │ Phone: <555>.<555>.<019>1 Mobile: <555>.<555>.<019>2

Przeszkadzają nam różne separatory w numerach

str_view_all(contact, pattern =  char_class("-.() "))
## [1] │ Call< >me< >at< >555<->555<->0191
## [2] │ 123< >Main< >St
## [3] │ <(>555<)>< >555< >0191
## [4] │ Phone:< >555<.>555<.>0191< >Mobile:< >555<.>555<.>0192

Razem:

# elementy wyrażenia
trzy_cyfry <- DGT %R% DGT %R% DGT
cztery_cyfry <- trzy_cyfry %R% DGT
separator <- char_class("-.() ")

# Wyrażenie
tel_pattern <- optional(OPEN_PAREN) %R% 
  trzy_cyfry %R% 
  one_or_more(separator) %R% 
  trzy_cyfry %R% 
  zero_or_more(separator) %R%
  cztery_cyfry
# testowańsko
str_view_all(contact, pattern = tel_pattern)
## [1] │ Call me at <555-555-0191>
## [2] │ 123 Main St
## [3] │ <(555) 555 0191>
## [4] │ Phone: <555.555.0191> Mobile: <555.555.0192>
str_extract(contact, tel_pattern) # wektor
## [1] "555-555-0191"   NA               "(555) 555 0191" "555.555.0191"
str_extract_all(contact, tel_pattern) # lista
## [[1]]
## [1] "555-555-0191"
## 
## [[2]]
## character(0)
## 
## [[3]]
## [1] "(555) 555 0191"
## 
## [[4]]
## [1] "555.555.0191" "555.555.0192"


Fragment bazy zawierający opisy przypadków incydentów nagłych zaraportowany np. przez zespół ZRM. Co możemy wyciągnąć z tych “narracyjnych” zmiennych, które mają pewną logikę i standard, ale jeszcze nie są poczyszczone

# # dla starszych wersji R (można instalować wsteczne wersje pakietów i samego R)
# install.packages("neiss")
# library(neiss)
# data(accidents)
# ?accidents
 narratives <- c("19YOM-SHOULDER STRAIN-WAS TACKLED WHILE PLAYING FOOTBALL W/ FRIENDS",
                 "31 YOF FELL FROM TOILET HITITNG HEAD SUSTAINING A CHI",
                 "ANKLE STR. 82 YOM STRAINED ANKLE GETTING OUT OF BED",
                 "TRIPPED OVER CAT AND LANDED ON HARDWOOD FLOOR. LACERATION ELBOW, LEFT. 33 YOF*",
                 "10YOM CUT THUMB ON METAL TRASH CAN DX AVULSION OF SKIN OF THUMB",
                 "53 YO F TRIPPED ON CARPET AT HOME. DX HIP CONTUSION",
                 "13 MOF TRYING TO STAND UP HOLDING ONTO BED FELL AND HIT FOREHEAD ON RADIATOR DX LACERATION",
                 "14YR M PLAYING FOOTBALL; DX KNEE SPRAIN",
                 "55YOM RIDER OF A BICYCLE AND FELL OFF SUSTAINED A CONTUSION TO KNEE",
                 "5 YOM ROLLING ON FLOOR DOING A SOMERSAULT AND SUSTAINED A CERVICAL STRA IN")

(age <- DGT %R% optional(DGT)) # dgt(1, 2)
## <regex> \d[\d]?
(unit <- optional(SPC) %R% or("YO", "YR", "MO"))
## <regex> [\s]?(?:YO|YR|MO)
str_view(narratives,
         pattern = age %R% unit) # mamy wiek pacjenta
##  [1] │ <19YO>M-SHOULDER STRAIN-WAS TACKLED WHILE PLAYING FOOTBALL W/ FRIENDS
##  [2] │ <31 YO>F FELL FROM TOILET HITITNG HEAD SUSTAINING A CHI
##  [3] │ ANKLE STR. <82 YO>M STRAINED ANKLE GETTING OUT OF BED
##  [4] │ TRIPPED OVER CAT AND LANDED ON HARDWOOD FLOOR. LACERATION ELBOW, LEFT. <33 YO>F*
##  [5] │ <10YO>M CUT THUMB ON METAL TRASH CAN DX AVULSION OF SKIN OF THUMB
##  [6] │ <53 YO> F TRIPPED ON CARPET AT HOME. DX HIP CONTUSION
##  [7] │ <13 MO>F TRYING TO STAND UP HOLDING ONTO BED FELL AND HIT FOREHEAD ON RADIATOR DX LACERATION
##  [8] │ <14YR> M PLAYING FOOTBALL; DX KNEE SPRAIN
##  [9] │ <55YO>M RIDER OF A BICYCLE AND FELL OFF SUSTAINED A CONTUSION TO KNEE
## [10] │ <5 YO>M ROLLING ON FLOOR DOING A SOMERSAULT AND SUSTAINED A CERVICAL STRA IN
(gender <- optional(SPC) %R% or("M", "F"))
## <regex> [\s]?(?:M|F)
str_view(narratives, 
         pattern = age %R% unit %R% gender) # uzupełniliśmy płcią
##  [1] │ <19YOM>-SHOULDER STRAIN-WAS TACKLED WHILE PLAYING FOOTBALL W/ FRIENDS
##  [2] │ <31 YOF> FELL FROM TOILET HITITNG HEAD SUSTAINING A CHI
##  [3] │ ANKLE STR. <82 YOM> STRAINED ANKLE GETTING OUT OF BED
##  [4] │ TRIPPED OVER CAT AND LANDED ON HARDWOOD FLOOR. LACERATION ELBOW, LEFT. <33 YOF>*
##  [5] │ <10YOM> CUT THUMB ON METAL TRASH CAN DX AVULSION OF SKIN OF THUMB
##  [6] │ <53 YO F> TRIPPED ON CARPET AT HOME. DX HIP CONTUSION
##  [7] │ <13 MOF> TRYING TO STAND UP HOLDING ONTO BED FELL AND HIT FOREHEAD ON RADIATOR DX LACERATION
##  [8] │ <14YR M> PLAYING FOOTBALL; DX KNEE SPRAIN
##  [9] │ <55YOM> RIDER OF A BICYCLE AND FELL OFF SUSTAINED A CONTUSION TO KNEE
## [10] │ <5 YOM> ROLLING ON FLOOR DOING A SOMERSAULT AND SUSTAINED A CERVICAL STRA IN
# wciąż brzydki wektor
(age_gender <- str_extract(narratives,  age %R% unit %R% gender))
##  [1] "19YOM"   "31 YOF"  "82 YOM"  "33 YOF"  "10YOM"   "53 YO F" "13 MOF" 
##  [8] "14YR M"  "55YOM"   "5 YOM"
# zatem
(ages_numeric <- as.numeric(
  str_extract(age_gender, age)
  )) # wyciągnięcie wieku i nadanie mu właściwego typu
##  [1] 19 31 82 33 10 53 13 14 55  5
(genders <- str_remove(age_gender, 
                       pattern = age %R% unit)) # wyciągnięcie płci
##  [1] "M"  "F"  "M"  "F"  "M"  " F" "F"  " M" "M"  "M"
(genders <- str_remove_all(genders, 
                           pattern = one_or_more(SPC))) # czyszczenie ze zbędnych złogów białych znaków
##  [1] "M" "F" "M" "F" "M" "F" "F" "M" "M" "M"
(time_units <- str_extract(age_gender, 
                           pattern =  unit)) # jednostki wieku jeszcze "brudne"
##  [1] "YO"  " YO" " YO" " YO" "YO"  " YO" " MO" "YR"  "YO"  " YO"
(time_units_clean <- str_extract(time_units, 
                                 pattern =  WRD))# jednostki wieku "czyste", pierwsza litera jednostki (rok, miesiąc)
##  [1] "Y" "Y" "Y" "Y" "Y" "Y" "M" "Y" "Y" "Y"
ifelse(time_units_clean == "Y", ages_numeric, ages_numeric / 12) # zamiana na miesiące, gdybyśmy chcieli ujednolicić jednostkę wieku
##  [1] 19.000000 31.000000 82.000000 33.000000 10.000000 53.000000  1.083333
##  [8] 14.000000 55.000000  5.000000
data.frame(Wiek = ages_numeric, 
           Jednostka.wieku = time_units_clean, 
           Plec = genders)
##    Wiek Jednostka.wieku Plec
## 1    19               Y    M
## 2    31               Y    F
## 3    82               Y    M
## 4    33               Y    F
## 5    10               Y    M
## 6    53               Y    F
## 7    13               M    F
## 8    14               Y    M
## 9    55               Y    M
## 10    5               Y    M



capture() & str_match()
“A powerful way for extracting pieces of text”
Zobaczmy jak działa funkcja na przykładzie…

hero_contacts <- c("wolverine@xmen.com", 
                   "wonderwoman@justiceleague.org",
                   "thor@avengers.com")

# można ją wrzucić w wyrażenie regularne...
email <- capture(one_or_more(WRD)) %R% 
  "@" %R% 
  capture(one_or_more(WRD)) %R% 
  DOT %R% 
  capture(one_or_more(WRD))

# ...które nie psuje wyrażenia,
str_view(hero_contacts, email) # podejrzenie poprawności wyrażenia regularnego
## [1] │ <wolverine@xmen.com>
## [2] │ <wonderwoman@justiceleague.org>
## [3] │ <thor@avengers.com>
# ale daje możliwość wyciągania elementów, które chcemy "przechwycić"
(hero_data <- str_match(hero_contacts, email)) # wyciągnięcie matrycy z poszczególnymi elementami
##      [,1]                            [,2]          [,3]            [,4] 
## [1,] "wolverine@xmen.com"            "wolverine"   "xmen"          "com"
## [2,] "wonderwoman@justiceleague.org" "wonderwoman" "justiceleague" "org"
## [3,] "thor@avengers.com"             "thor"        "avengers"      "com"
# bohater
hero_data[,2]
## [1] "wolverine"   "wonderwoman" "thor"
# domena
hero_data[,4]
## [1] "com" "org" "com"
# host
hero_data[,3]
## [1] "xmen"          "justiceleague" "avengers"

Różnica między one_or_more(WRD) a capture(one_or_more(WRD)) polega na tym, że:

  • one_or_more(WRD): To wyrażenie regularne, które dopasowuje jeden lub więcej wystąpień słowa (WRD). Używane jest do wyszukiwania ciągów znaków, które zawierają co najmniej jedno słowo.

  • capture(one_or_more(WRD)): To wyrażenie regularne, które nie tylko dopasowuje jeden lub więcej wystąpień słowa (WRD), ale także przechwytuje (zapamiętuje) te dopasowania. Dzięki temu można później odwoływać się do tych przechwyconych wartości.



Wróćmy do przykładu z wyłuskiwaniem numerów telefonów

# przypomnienie jak wyglądał wektor
(contact <- c("Call me at 555-555-0191",
              "123 Main St",
              "(555) 555 0191",
              "Phone: 555.555.0191 Mobile: 555.555.0192"))
## [1] "Call me at 555-555-0191"                 
## [2] "123 Main St"                             
## [3] "(555) 555 0191"                          
## [4] "Phone: 555.555.0191 Mobile: 555.555.0192"
# Wyrażenie
tel_pattern <- optional(OPEN_PAREN) %R% 
  capture(trzy_cyfry) %R% 
  one_or_more(separator) %R% 
  capture(trzy_cyfry) %R% 
  zero_or_more(separator) %R%
  capture(cztery_cyfry)

# Pull out the parts with str_match()
(phone_numbers <- str_match(contact, tel_pattern))
##      [,1]             [,2]  [,3]  [,4]  
## [1,] "555-555-0191"   "555" "555" "0191"
## [2,] NA               NA    NA    NA    
## [3,] "(555) 555 0191" "555" "555" "0191"
## [4,] "555.555.0191"   "555" "555" "0191"
# wyciąganie (sklejenie) numerów w formacie (XXX) XXX-XXXX
str_c("(",  phone_numbers[,2],  ")",  phone_numbers[,3],  "-",  phone_numbers[,4])
## [1] "(555)555-0191" NA              "(555)555-0191" "(555)555-0191"
# sięgając po wszystkie numery musielibyśmy użyć str_match_all(), otrzymując listę
str_match_all(contact, tel_pattern)
## [[1]]
##      [,1]           [,2]  [,3]  [,4]  
## [1,] "555-555-0191" "555" "555" "0191"
## 
## [[2]]
##      [,1] [,2] [,3] [,4]
## 
## [[3]]
##      [,1]             [,2]  [,3]  [,4]  
## [1,] "(555) 555 0191" "555" "555" "0191"
## 
## [[4]]
##      [,1]           [,2]  [,3]  [,4]  
## [1,] "555.555.0191" "555" "555" "0191"
## [2,] "555.555.0192" "555" "555" "0192"
# ?Unicode
# ?unicode_property
# ?unicode_general_category


"\1F600" # -> :)
## [1] "\001F600"
"\u03BC" # -> μ
## [1] "μ"
as.hexmode(utf8ToInt("μ"))
## [1] "3bc"
(formula <- "Normal(\u03BC = 0, \u03C3 = 1)")
## [1] "Normal(μ = 0, σ = 1)"



“datacamp.com”

Przypomnienie:

str_detect() -> zwraca prawdę lub fałsz gdy napotka na wzór odpowiadający wyrażeniu

str_match() -> jeśli wzór wyrażenia pasuje, zwróci nam je

Sprawdźmy działanie specjalnych symboli.

str_match("Michał", "^.")
##      [,1]
## [1,] "M"
str_match("Michał", ".$")
##      [,1]
## [1,] "ł"
str_match("Michał", "\\.")
##      [,1]
## [1,] NA
str_match("Michał.", "\\.") # dodana kropka w
##      [,1]
## [1,] "."



Times & Dates


Pakiety: {lubridate}, {zoo}, {xts}


W R, daty są reprezentowane przez obiekty Date, podczas gdy godziny są reprezentowane przez obiekty POSIXct. Jednak pod maską, te daty i godziny są prostymi wartościami numerycznymi. Obiekty Date przechowują liczbę dni od 1 stycznia 1970 roku. Z drugiej strony, obiekty POSIXct przechowują liczbę sekund od 1 stycznia 1970 roku.

1 stycznia 1970 roku jest powszechnym źródłem reprezentacji czasu i daty w wielu językach programowania. Nie ma ku temu szczególnego powodu; jest to prosta konwencja. Oczywiście możliwe jest również tworzenie dat i godzin przed 1970 rokiem; odpowiednie wartości liczbowe są w tym przypadku po prostu ujemne.


(today <- Sys.Date())
## [1] "2025-12-01"
class(today) # under the hood
## [1] "Date"
(now <- Sys.time())
## [1] "2025-12-01 15:27:36 CET"
class(now) # under the hood
## [1] "POSIXct" "POSIXt"


(my_birth <- as.Date("1985-14-02", format = "%Y-%d-%m"))
## [1] "1985-02-14"
today - my_birth
## Time difference of 14900 days



Aby utworzyć obiekt Date z prostego ciągu znaków w R, można użyć funkcji as.Date(). Ciąg znaków musi być zgodny z formatem, który można zdefiniować za pomocą zestawu symboli (przykłady odpowiadają 13 stycznia 1982 r.):


  • %Y: 4-digit year (1982)
  • %y: 2-digit year (82)
  • %m: 2-digit month (01)
  • %d: 2-digit day of the month (13)
  • %A: weekday (Wednesday)
  • %a: abbreviated weekday (Wed)
  • %B: month (January)
  • %b: abbreviated month (Jan)


Poniższe polecenia R utworzą ten sam obiekt Date dla 13 dnia stycznia 1982 roku:


as.Date("1982-01-13")
as.Date("Jan-13-82", format = "%b-%d-%y")
as.Date("13 January, 1982", format = "%d %B, %Y")

Zauważmy, że pierwsza linia tutaj nie wymagała argumentu formatu, ponieważ domyślnie R dopasowuje ciąg znaków do formatów "%Y-%m-%d" lub "%Y/%m/%d".


Oprócz tworzenia dat można je także konwertować na ciągi znaków korzystające z innego zapisu daty. W tym celu użyj funkcji format(). Wypróbuj następujące linie kodu:

today <- Sys.Date()
format(Sys.Date(), format = "%d %B, %Y")
format(Sys.Date(), format = "Today is a %A!")

# Stringi reprezentujące daty
str1 <- "May 23, '96"
str2 <- "2012-03-15"
str3 <- "30/January/2006"

# Konwersja na daty sensu stricte
date1 <- as.Date(str1, format = "%b %d, '%y")
date2 <- as.Date(str2)
date3 <- as.Date(str3, format = "%d/%B/%Y")

# Wyciąganie z dat interesujących informacji
format(date1, "%A")
## [1] "Thursday"
format(date2, "%d")
## [1] "15"
format(date3, "%b %Y")
## [1] "Jan 2006"


Podobnie jak w przypadku dat, można użyć metody as.POSIXct() do konwersji ciągu znaków na obiekt POSIXct oraz metody format() do konwersji obiektu POSIXct na ciąg znaków. Ponownie mamy szeroką gamę symboli:

  • %H: hours as a decimal number (00-23)
  • %I: hours as a decimal number (01-12)
  • %M: minutes as a decimal number
  • %S: seconds as a decimal number
  • %T: shorthand notation for the typical format %H:%M:%S
  • %p: AM/PM indicator


Pełną listę symboli konwersji znajdziesz w dokumentacji strptime w konsoli:

?strptime

as.POSIXct() używa domyślnego formatu do dopasowywania ciągów znaków. W tym przypadku jest to %Y-%m-%d %H:%M:%S.

# Definition of character strings representing times
str1 <- "May 23, '96 hours:23 minutes:01 seconds:45"
str2 <- "2012-3-12 14:23:08"

# Convert the strings to POSIXct objects: time1, time2
time1 <- as.POSIXct(str1, format = "%B %d, '%y hours:%H minutes:%M seconds:%S")
time2 <- as.POSIXct(str2)

# Convert times to formatted strings
format(time1, "%M")
## [1] "01"
format(time2, "%I:%M %p")
## [1] "02:23 PM"


(today <- Sys.Date())
## [1] "2025-12-01"


today + 1
## [1] "2025-12-02"


today - 1
## [1] "2025-11-30"


as.Date("2015-03-12") - as.Date("2015-02-27")
## Time difference of 13 days






Źródła i inspiracje pomocne w przygotowaniu powyższej prezentacji:




Wektor potrzebny w trakcie ćwiczeń…

movie_titles <- c("Karate Kid","The Twilight Saga: Eclispe","Knight & Day",
                  "Shrek Forever After 3D", "Marmaduke.","Street Dance",
                  "Predators","StreetDance 3D", "Robin Hood",
                  "Micmacs A Tire-Larigot", "50 Shades of Grey",
                  "Sex And the City 2", "Inception","The Dark Knight",
                  "300","Toy Story 3 In Disney Digital 3D",
                  "50 Shades of Gray","Italien, Le",
                  "Tournee","The A-Team", "El Secreto De Sus Ojos","Kiss & Kill",
                  "The Road","Cosa Voglio Di Piu",
                  "Nur für dich","Prince Of Persia: The Sands Of Time",
                  "Saw 4","Saw 5", "Saw 6","21 Grams" )