7 Работа со строками

7.1 Работа со строками в R

Для работы со строками можно использовать:

  • базовый R
  • пакет stringr (часть tidyverse)
  • пакет stringi – отдельный пакет, так что не забудьте его установить:
install.packages("stringi")
library(tidyverse)
library(stringi)

Мы будем пользоваться в основном пакетами stingr и stringi, так как они в большинстве случаях удобнее. К счастью функции этих пакетов легко отличить от остальных: функции пакет stringr всегда начинаются с str_, а функции пакета stringi — c stri_.

Существует cheat sheet по stringr.

7.2 Как получить строку?

  • следите за кавычками
"the quick brown fox jumps over the lazy dog"
## [1] "the quick brown fox jumps over the lazy dog"
'the quick brown fox jumps over the lazy dog'
## [1] "the quick brown fox jumps over the lazy dog"
"the quick 'brown' fox jumps over the lazy dog"
## [1] "the quick 'brown' fox jumps over the lazy dog"
'the quick "brown" fox jumps over the lazy dog'
## [1] "the quick \"brown\" fox jumps over the lazy dog"
  • пустая строка
""
## [1] ""
''
## [1] ""
character(3)
## [1] "" "" ""
  • преобразование
typeof(4:7)
## [1] "integer"
as.character(4:7)
## [1] "4" "5" "6" "7"
  • встроенные векторы
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.name
##  [1] "January"   "February"  "March"     "April"     "May"       "June"     
##  [7] "July"      "August"    "September" "October"   "November"  "December"
  • Создание рандомных строк
set.seed(42)
stri_rand_strings(n = 10, length = 5:14)
##  [1] "uwHpd"          "Wj8ehS"         "ivFSwy7"        "TYu8zw5V"      
##  [5] "OuRpjoOg0"      "p0CubNR2yQ"     "xtdycKLOm2k"    "fAGVfylZqBGp"  
##  [9] "gE28DTCi0NV0a"  "9MemYE55If0Cvv"
  • Перемешивает символы внутри строки
stri_rand_shuffle("любя, съешь щипцы, — вздохнёт мэр, — кайф жгуч")
## [1] ",цо м,пюзгу   сл аиъ—в кжряд,ыщьчебэн х—штё фй"
stri_rand_shuffle(month.name)
##  [1] "aJayunr"   "eyrbraFu"  "achMr"     "Aplri"     "ayM"       "Jnue"     
##  [7] "uJly"      "usuAgt"    "tpebermSe" "tOecrbo"   "oeNembvr"  "Dmceerbe"
  • Генерирует псевдорандомный текст22
stri_rand_lipsum(nparagraphs = 2)
## [1] "Lorem ipsum dolor sit amet, donec sit nunc urna sed ultricies ac pharetra orci luctus iaculis, ac tincidunt cum. Neque eu semper at sociosqu hendrerit. Eu aliquet lacus, eu hendrerit donec aliquam eros. Risus nibh, quam in sit facilisi ipsum. Amet sem sed donec sed molestie scelerisque tincidunt. Nisl donec et facilisis interdum non sed dolor purus. In ipsum dignissim torquent velit nec aliquam pellentesque. Ac, adipiscing, neque et at torquent, vestibulum ullamcorper. Ad dictumst enim velit non nulla felis habitant. Egestas placerat consectetur, dictum nostra sed nec. Erat phasellus dolor libero aliquam viverra. Vestibulum leo et. Suscipit egestas in in montes, sapien gravida? Conubia purus varius ut nec feugiat."
## [2] "Risus eleifend magnis neque diam, suspendisse ullamcorper nulla adipiscing malesuada massa, nisi sociosqu velit id et. Aliquam facilisis et aenean. Parturient vel ac in convallis, massa diam nibh. Nulla interdum cursus et. Natoque amet, ut praesent. Tortor ultrices a consectetur, augue natoque class faucibus? Ut sed arcu elementum magna. Dignissim ac facilisi quis ut nisl eu, massa."

7.3 Соединение и разделение строк

Соединенить строки можно используя функцию str_c(), в которую, как и в функции с(), можно перечислять элементы через запятую:

tibble(upper = rev(LETTERS), smaller = letters) %>% 
  mutate(merge = str_c(upper, smaller))
ABCDEFGHIJ0123456789
upper
<chr>
smaller
<chr>
merge
<chr>
ZaZa
YbYb
XcXc
WdWd
VeVe
UfUf
TgTg
ShSh
RiRi
QjQj

Кроме того, если хочется, можно использовать особенный разделитель, указав его в аргументе sep:

tibble(upper = rev(LETTERS), smaller = letters) %>% 
  mutate(merge = str_c(upper, smaller, sep = "_"))
ABCDEFGHIJ0123456789
upper
<chr>
smaller
<chr>
merge
<chr>
ZaZ_a
YbY_b
XcX_c
WdW_d
VeV_e
UfU_f
TgT_g
ShS_h
RiR_i
QjQ_j

Аналогичным образом, для разделение строки на подстроки можно использовать функцию separate(). Это функция разносит разделенные элементы строки в соответствующие столбцы. У функции три обязательных аргумента: col — колонка, которую следует разделить, into — вектор названий новых столбец, sep — разделитель.

tibble(upper = rev(LETTERS), smaller = letters) %>% 
  mutate(merge = str_c(upper, smaller, sep = "_")) %>% 
  separate(col = merge, into = c("column_1", "column_2"), sep = "_")
ABCDEFGHIJ0123456789
upper
<chr>
smaller
<chr>
column_1
<chr>
column_2
<chr>
ZaZa
YbYb
XcXc
WdWd
VeVe
UfUf
TgTg
ShSh
RiRi
QjQj

Кроме того, есть инструмент str_split(), которая позволяет разбивать строки на подстроки, но возвращает список.

str_split(month.name, "r")
## [[1]]
## [1] "Janua" "y"    
## 
## [[2]]
## [1] "Feb" "ua"  "y"  
## 
## [[3]]
## [1] "Ma" "ch"
## 
## [[4]]
## [1] "Ap" "il"
## 
## [[5]]
## [1] "May"
## 
## [[6]]
## [1] "June"
## 
## [[7]]
## [1] "July"
## 
## [[8]]
## [1] "August"
## 
## [[9]]
## [1] "Septembe" ""        
## 
## [[10]]
## [1] "Octobe" ""      
## 
## [[11]]
## [1] "Novembe" ""       
## 
## [[12]]
## [1] "Decembe" ""

7.4 Количество символов

7.4.1 Подсчет количества символов

tibble(mn = month.name) %>% 
  mutate(n_charactars = str_count(mn))
ABCDEFGHIJ0123456789
mn
<chr>
n_charactars
<int>
January7
February8
March5
April5
May3
June4
July4
August6
September9
October7

7.4.2 Подгонка количества символов

Можно обрезать строки, используя функцию str_trunc():

tibble(mn = month.name) %>% 
  mutate(mn_new = str_trunc(mn, 6))
ABCDEFGHIJ0123456789
mn
<chr>
mn_new
<chr>
JanuaryJan...
FebruaryFeb...
MarchMarch
AprilApril
MayMay
JuneJune
JulyJuly
AugustAugust
SeptemberSep...
OctoberOct...

Можно решить с какой стороны обрезать, используя аргумент side:

tibble(mn = month.name) %>% 
  mutate(mn_new = str_trunc(mn, 6, side = "left"))
ABCDEFGHIJ0123456789
mn
<chr>
mn_new
<chr>
January...ary
February...ary
MarchMarch
AprilApril
MayMay
JuneJune
JulyJuly
AugustAugust
September...ber
October...ber
tibble(mn = month.name) %>% 
  mutate(mn_new = str_trunc(mn, 6, side = "center"))
ABCDEFGHIJ0123456789
mn
<chr>
mn_new
<chr>
JanuaryJa...y
FebruaryFe...y
MarchMarch
AprilApril
MayMay
JuneJune
JulyJuly
AugustAugust
SeptemberSe...r
OctoberOc...r

Можно заменить многоточие, используя аргумент ellipsis:

tibble(mn = month.name) %>% 
  mutate(mn_new = str_trunc(mn, 3, ellipsis = ""))
ABCDEFGHIJ0123456789
mn
<chr>
mn_new
<chr>
JanuaryJan
FebruaryFeb
MarchMar
AprilApr
MayMay
JuneJun
JulyJul
AugustAug
SeptemberSep
OctoberOct

Можно наоборот “раздуть” строку:

tibble(mn = month.name) %>% 
  mutate(mn_new = str_pad(mn, 10))
ABCDEFGHIJ0123456789
mn
<chr>
mn_new
<chr>
JanuaryJanuary
FebruaryFebruary
MarchMarch
AprilApril
MayMay
JuneJune
JulyJuly
AugustAugust
SeptemberSeptember
OctoberOctober

Опять же есть аргумент side:

tibble(mn = month.name) %>% 
  mutate(mn_new = str_pad(mn, 10,  side = "right"))
ABCDEFGHIJ0123456789
mn
<chr>
mn_new
<chr>
JanuaryJanuary
FebruaryFebruary
MarchMarch
AprilApril
MayMay
JuneJune
JulyJuly
AugustAugust
SeptemberSeptember
OctoberOctober

Также можно выбрать, чем “раздувать строку”:

tibble(mn = month.name) %>% 
  mutate(mn_new = str_pad(mn, 10,  pad = "."))
ABCDEFGHIJ0123456789
mn
<chr>
mn_new
<chr>
January...January
February..February
March.....March
April.....April
May.......May
June......June
July......July
August....August
September.September
October...October

На Pudding вышла статья про английские пабы. Здесь лежит немного обработанный датасет, которые они использовали. Визуализируйте 40 самых частотоных названий пабов в Великобритании, отложив по оси x количество символов, а по оси y – количество баров с таким названием.

📋 список подсказок ➡
👁 Датасет скачался, что дальше? ➡ Перво-наперво следует создать переменную, в которой бы хранилось количество каждого из баров.
👁 А как посчитать количество баров? ➡ Это можно сделать при помощи функции count().
👁 Бары пересчитали, что дальше? ➡ Теперь нужно создать новую переменную, где бы хранилась информация о количестве символов.
👁 Все переменные есть, теперь рисуем? ➡ Не совсем. Перед тем как рисовать нужно отфильтровать 50 самых популярных.
👁 Так, все готово, а какие geom_()? ➡ На графике geom_point() и geom_text_repel() из пакета ggrepel.
👁 А-а-а-а! could not find function "geom_text_repel" А вы включили библиотеку ggrepel? Если не включили, то функция, естественно будет недоступна.
👁 А-а-а-а! geom_text_repel requires the following missing aesthetics: label" Все, как написала программа: чтобы писать какой-то текст в функции aes() нужно добавить аргумент label = pub_name. Иначе откуда он узнает, что ему писать?
👁 Фуф! Все готово! ➡ А оси подписаны? А заголовок? А подпись про источник данных?

7.5 Сортировка

Для сортировки существует базовая функция sort() и функция из stringr str_sort():

unsorted_latin <- c("I", "♥", "N", "Y")
sort(unsorted_latin)
## [1] "♥" "I" "N" "Y"
str_sort(unsorted_latin)
## [1] "♥" "I" "N" "Y"
str_sort(unsorted_latin, locale = "lt")
## [1] "♥" "I" "Y" "N"
unsorted_cyrillic <- c("я", "i", "ж")
str_sort(unsorted_cyrillic)
## [1] "i" "ж" "я"
str_sort(unsorted_cyrillic, locale = "ru_UA")
## [1] "ж" "я" "i"

Список локалей на копмьютере можно посмотреть командой stringi::stri_locale_list(). Список всех локалей вообще приведен на этой странице. Еще полезные команды: stringi::stri_locale_info и stringi::stri_locale_set.

Не углубляясь в разнообразие алгоритмов сортировки, отмечу, что алгоритм по-умолчанию хуже работает с большими данными:

set.seed(42)
huge <- sample(letters, 1e7, replace = TRUE)
head(huge)
## [1] "q" "e" "a" "y" "j" "d"
system.time(
  sort(huge)
)
##    user  system elapsed 
##   6.502   0.029   6.531
system.time(
  sort(huge, method = "radix")
)
##    user  system elapsed 
##   0.270   0.028   0.298
system.time(
  str_sort(huge)
)
##    user  system elapsed 
##   5.658   0.056   5.715
huge_tbl <- tibble(huge)
system.time(
  huge_tbl %>% 
    arrange(huge)
)
##    user  system elapsed 
##  30.557   0.057  30.615

Предварительный вывод: для больших данных – sort(..., method = "radix").

7.6 Поиск подстроки

Можно использовать функцию str_detect():

tibble(mn = month.name) %>% 
  mutate(has_r = str_detect(mn, "r"))
ABCDEFGHIJ0123456789
mn
<chr>
has_r
<lgl>
JanuaryTRUE
FebruaryTRUE
MarchTRUE
AprilTRUE
MayFALSE
JuneFALSE
JulyFALSE
AugustFALSE
SeptemberTRUE
OctoberTRUE

Кроме того, существует функция, которая возвращает индексы, а не значения TRUE/FALSE:

tibble(mn = month.name) %>% 
  slice(str_which(mn, "r"))
ABCDEFGHIJ0123456789
mn
<chr>
January
February
March
April
September
October
November
December

Также можно посчитать количество вхождений какой-то подстроки:

tibble(mn = month.name) %>% 
  mutate(has_r = str_count(mn, "r"))
ABCDEFGHIJ0123456789
mn
<chr>
has_r
<int>
January1
February2
March1
April1
May0
June0
July0
August0
September1
October1

7.7 Изменение строк

7.7.1 Изменение регистра

latin <- "tHe QuIcK BrOwN fOx JuMpS OvEr ThE lAzY dOg"
cyrillic <- "лЮбЯ, сЪеШь ЩиПцЫ, — вЗдОхНёТ мЭр, — кАйФ жГуЧ"
str_to_upper(latin)
## [1] "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG"
str_to_lower(cyrillic)
## [1] "любя, съешь щипцы, — вздохнёт мэр, — кайф жгуч"
str_to_title(latin)
## [1] "The Quick Brown Fox Jumps Over The Lazy Dog"

7.7.2 Выделение подстроки

Подстроку в строке можно выделить двумя способами: по индексам функцией str_sub(), и по подстроке функцией str_png().

extract(images/5.07_str_sub.png)

tibble(mn = month.name) %>% 
  mutate(mutate = str_sub(mn, start = 1, end = 2))
ABCDEFGHIJ0123456789
mn
<chr>
mutate
<chr>
JanuaryJa
FebruaryFe
MarchMa
AprilAp
MayMa
JuneJu
JulyJu
AugustAu
SeptemberSe
OctoberOc

tibble(mn = month.name) %>% 
  mutate(mutate = str_extract(mn, "r"))
ABCDEFGHIJ0123456789
mn
<chr>
mutate
<chr>
Januaryr
Februaryr
Marchr
Aprilr
MayNA
JuneNA
JulyNA
AugustNA
Septemberr
Octoberr

По умолчанию функция str_extract() возвращает первое вхождение подстроки, соответствующей шаблону. Также существует функция str_extract_all(), которая возвращает все вхождения подстрок, соответствующих шаблону, однако возвращает объект типа список.

str_extract_all(month.name, "r")
## [[1]]
## [1] "r"
## 
## [[2]]
## [1] "r" "r"
## 
## [[3]]
## [1] "r"
## 
## [[4]]
## [1] "r"
## 
## [[5]]
## character(0)
## 
## [[6]]
## character(0)
## 
## [[7]]
## character(0)
## 
## [[8]]
## character(0)
## 
## [[9]]
## [1] "r"
## 
## [[10]]
## [1] "r"
## 
## [[11]]
## [1] "r"
## 
## [[12]]
## [1] "r"

7.7.3 Замена подстроки

Существует функция str_replace(), которая позволяет заменить одну подстроку в строке на другую:

tibble(mn = month.name) %>% 
  mutate(mutate = str_replace(mn, "r", "R"))
ABCDEFGHIJ0123456789
mn
<chr>
mutate
<chr>
JanuaryJanuaRy
FebruaryFebRuary
MarchMaRch
AprilApRil
MayMay
JuneJune
JulyJuly
AugustAugust
SeptemberSeptembeR
OctoberOctobeR

Как и другие функции str_replace() делает лишь одну замену, чтобы заменить все вхождения подстроки следует использовать функцию str_replace_all():

tibble(mn = month.name) %>% 
  mutate(mutate = str_replace_all(mn, "r", "R"))
ABCDEFGHIJ0123456789
mn
<chr>
mutate
<chr>
JanuaryJanuaRy
FebruaryFebRuaRy
MarchMaRch
AprilApRil
MayMay
JuneJune
JulyJuly
AugustAugust
SeptemberSeptembeR
OctoberOctobeR

7.7.4 Удаление подстроки

Для удаления подстроки на основе шаблона, используется функция str_remove() и str_remove_all()

tibble(month.name) %>% 
  mutate(mutate = str_remove(month.name, "r"))
ABCDEFGHIJ0123456789
month.name
<chr>
mutate
<chr>
JanuaryJanuay
FebruaryFebuary
MarchMach
AprilApil
MayMay
JuneJune
JulyJuly
AugustAugust
SeptemberSeptembe
OctoberOctobe
tibble(month.name) %>% 
  mutate(mutate = str_remove_all(month.name, "r"))
ABCDEFGHIJ0123456789
month.name
<chr>
mutate
<chr>
JanuaryJanuay
FebruaryFebuay
MarchMach
AprilApil
MayMay
JuneJune
JulyJuly
AugustAugust
SeptemberSeptembe
OctoberOctobe

7.7.5 Транслитерация строк

В пакете stringi сууществует достаточно много методов транслитераций строк, которые можно вывести командой stri_trans_list(). Вот пример использования некоторых из них:

stri_trans_general("stringi", "latin-cyrillic")
## [1] "стринги"
stri_trans_general("сырники", "cyrillic-latin")
## [1] "syrniki"
stri_trans_general("stringi", "latin-greek")
## [1] "στριγγι"
stri_trans_general("stringi", "latin-armenian")
## [1] "ստրինգի"

Вот два датасета:

Определите сколько городов называется обычным словом русского языка (например, город Орёл)? Не забудьте поменять ё на е.

📋 список подсказок ➡
👁 Датасеты скачались, что дальше? ➡ Надо их преобразовать к нужному виду и объединить.
👁 А как их соединить? Что у них общего? ➡ В одном датасете есть переменная city, в другом – переменная lemma. Все города начинаются с большой буквы, все леммы с маленькой буквы. Я бы уменьшил букву в датасете с городами, сделал бы новый столбец в датасете с городами (например, town), соединил бы датасеты и посчитал бы сколько в результирующем датасете значений town.
👁 А как соеднить? ➡ Я бы использовал dict %>% ... %>% inner_join(cities). Если в датасетах разные названия столбцов, то следует указывать какие столбцы, каким соответствуют:dict %>% ... %>% inner_join(cities, by = c("lemma" = "city"))
👁 Соединилось вроде… А как посчитать? ➡ Я бы, как обычно, использовал функцию count().

7.8 Регулярные выражения

Большинство функций из раздела об операциях над векторами (str_detect(), str_extract(), str_remove() и т. п.) имеют следующую структуру:

  • строка, с которой работает функция
  • образец (pattern)

Дальше мы будем использовать функцию str_view_all(), которая позволяет показывать, выделенное образцом в исходной строке.

str_view_all("Я всегда путаю с и c", "c") # я ищу латинскую c
  • Я всегда путаю с и c

7.8.1 Экранирование метасимволов

a <- "Всем известно, что 4$\\2 + 3$ * 5 = 17$? Да? Ну хорошо (а то я не был уверен). [|}^{|]"
str_view_all(a, "$")
  • Всем известно, что 4$\2 + 3$ * 5 = 17$? Да? Ну хорошо (а то я не был уверен). [|}^{|]
str_view_all(a, "\\$")
  • Всем известно, что 4$\2 + 3$ * 5 = 17$? Да? Ну хорошо (а то я не был уверен). [|}^{|]
str_view_all(a, "\\.")
  • Всем известно, что 4$\2 + 3$ * 5 = 17$? Да? Ну хорошо (а то я не был уверен). [|}^{|]
str_view_all(a, "\\*")
  • Всем известно, что 4$\2 + 3$ * 5 = 17$? Да? Ну хорошо (а то я не был уверен). [|}^{|]
str_view_all(a, "\\+")
  • Всем известно, что 4$\2 + 3$ * 5 = 17$? Да? Ну хорошо (а то я не был уверен). [|}^{|]
str_view_all(a, "\\?")
  • Всем известно, что 4$\2 + 3$ * 5 = 17$? Да? Ну хорошо (а то я не был уверен). [|}^{|]
str_view_all(a, "\\(")
  • Всем известно, что 4$\2 + 3$ * 5 = 17$? Да? Ну хорошо (а то я не был уверен). [|}^{|]
str_view_all(a, "\\)")
  • Всем известно, что 4$\2 + 3$ * 5 = 17$? Да? Ну хорошо (а то я не был уверен). [|}^{|]
str_view_all(a, "\\|")
  • Всем известно, что 4$\2 + 3$ * 5 = 17$? Да? Ну хорошо (а то я не был уверен). [|}^{|]
str_view_all(a, "\\^")
  • Всем известно, что 4$\2 + 3$ * 5 = 17$? Да? Ну хорошо (а то я не был уверен). [|}^{|]
str_view_all(a, "\\[")
  • Всем известно, что 4$\2 + 3$ * 5 = 17$? Да? Ну хорошо (а то я не был уверен). [|}^{|]
str_view_all(a, "\\]")
  • Всем известно, что 4$\2 + 3$ * 5 = 17$? Да? Ну хорошо (а то я не был уверен). [|}^{|]
str_view_all(a, "\\{")
  • Всем известно, что 4$\2 + 3$ * 5 = 17$? Да? Ну хорошо (а то я не был уверен). [|}^{|]
str_view_all(a, "\\}")
  • Всем известно, что 4$\2 + 3$ * 5 = 17$? Да? Ну хорошо (а то я не был уверен). [|}^{|]
str_view_all(a, "\\\\")
  • Всем известно, что 4$\2 + 3$ * 5 = 17$? Да? Ну хорошо (а то я не был уверен). [|}^{|]

7.8.2 Классы знаков

  • \\d – цифры. \\D – не цифры.
str_view_all("два 15 42. 42 15. 37 08 5. 20 20 20!", "\\d")
  • два 15 42. 42 15. 37 08 5. 20 20 20!
str_view_all("два 15 42. 42 15. 37 08 5. 20 20 20!", "\\D")
  • два 15 42. 42 15. 37 08 5. 20 20 20!
  • \\s – пробелы. \\S – не пробелы.
str_view_all("два 15 42. 42 15. 37 08 5. 20 20 20!", "\\s")
  • два 15 42. 42 15. 37 08 5. 20 20 20!
str_view_all("два 15 42. 42 15. 37 08 5. 20 20 20!", "\\S")
  • два 15 42. 42 15. 37 08 5. 20 20 20!
  • \\w – не пробелы и не знаки препинания. \\W – пробелы и знаки препинания.
str_view_all("два 15 42. 42 15. 37 08 5. 20 20 20!", "\\w")
  • два 15 42. 42 15. 37 08 5. 20 20 20!
str_view_all("два 15 42. 42 15. 37 08 5. 20 20 20!", "\\W")
  • два 15 42. 42 15. 37 08 5. 20 20 20!
  • произвольная группа символов и обратная к ней
str_view_all("Умей мечтать, не став рабом мечтанья", "[оауиыэёеяю]")
  • Умей мечтать, не став рабом мечтанья
str_view_all("И мыслить, мысли не обожествив", "[^оауиыэёеяю]")
  • И мыслить, мысли не обожествив
  • встроенные группы символов
str_view_all("два 15 42. 42 15. 37 08 5. 20 20 20!", "[0-9]")
  • два 15 42. 42 15. 37 08 5. 20 20 20!
str_view_all("Карл у Клары украл кораллы, а Клара у Карла украла кларнет", "[а-я]")
  • Карл у Клары украл кораллы, а Клара у Карла украла кларнет
str_view_all("Карл у Клары украл кораллы, а Клара у Карла украла кларнет", "[А-Я]")
  • Карл у Клары украл кораллы, а Клара у Карла украла кларнет
str_view_all("Карл у Клары украл кораллы, а Клара у Карла украла кларнет", "[А-я]")
  • Карл у Клары украл кораллы, а Клара у Карла украла кларнет
str_view_all("The quick brown Fox jumps over the lazy Dog", "[a-z]")
  • The quick brown Fox jumps over the lazy Dog
str_view_all("два 15 42. 42 15. 37 08 5. 20 20 20!", "[^0-9]")
  • два 15 42. 42 15. 37 08 5. 20 20 20!

  • выбор из нескольких групп
str_view_all("Карл у Клары украл кораллы, а Клара у Карла украла кларнет", "лар|рал|арл")
  • Карл у Клары украл кораллы, а Клара у Карла украла кларнет
  • произвольный символ
str_view_all("Везет Сенька Саньку с Сонькой на санках. Санки скок, Сеньку с ног, Соньку в лоб, все — в сугроб", "[Сс].н")
  • Везет Сенька Саньку с Сонькой на санках. Санки скок, Сеньку с ног, Соньку в лоб, все — в сугроб
  • знак начала и конца строки
str_view_all("от топота копыт пыль по полю летит.", "^о")
  • от топота копыт пыль по полю летит.
str_view_all("У ежа — ежата, у ужа — ужата", "жата$")
  • У ежа — ежата, у ужа — ужата
  • есть еще другие группы и другие обозначения уже приведенных групп, см. ?regex

7.8.3 Квантификация

  • ? – ноль или один раз
str_view_all("хорошее длинношеее животное", "еее?")
  • хорошее длинношеее животное
  • * – ноль и более раз
str_view_all("хорошее длинношеее животное", "ее*")
  • хорошее длинношеее животное
  • + – один и более раз
str_view_all("хорошее длинношеее животное", "е+")
  • хорошее длинношеее животное
  • {n}n раз
str_view_all("хорошее длинношеее животное", "е{2}")
  • хорошее длинношеее животное
  • {n,}n раз и более
str_view_all("хорошее длинношеее животное", "е{1,}")
  • хорошее длинношеее животное
  • {n,m} – от n до m. Отсутствие пробела важно: {1,2} – правильно, {1,␣2} – неправильно.
str_view_all("хорошее длинношеее животное", "е{2,3}")
  • хорошее длинношеее животное
  • группировка символов
str_view_all("Пушкиновед, Лермонтовед, Лермонтововед", "(ов)+")
  • Пушкиновед, Лермонтовед, Лермонтововед
str_view_all("беловатый, розоватый, розововатый", "(ов)+")
  • беловатый, розоватый, розововатый
  • жадный vs. нежадный алоритмы
str_view_all("Пушкиновед, Лермонтовед, Лермонтововед", "в.*ед")
  • Пушкиновед, Лермонтовед, Лермонтововед
str_view_all("Пушкиновед, Лермонтовед, Лермонтововед", "в.*?ед")
  • Пушкиновед, Лермонтовед, Лермонтововед

7.8.4 Позиционная проверка (look arounds)

Позиционная проверка – выглядит достаточно непоследовательно даже в свете остальных регулярных выражений.

Давайте найдем все а перед р:

str_view_all("Карл у Клары украл кораллы, а Клара у Карла украла кларнет", "а(?=р)")
  • Карл у Клары украл кораллы, а Клара у Карла украла кларнет

А теперь все а перед р или л:

str_view_all("Карл у Клары украл кораллы, а Клара у Карла украла кларнет", "а(?=[рл])")
  • Карл у Клары украл кораллы, а Клара у Карла украла кларнет

Давайте найдем все а после р

str_view_all("Карл у Клары украл кораллы, а Клара у Карла украла кларнет", "(?<=р)а")
  • Карл у Клары украл кораллы, а Клара у Карла украла кларнет

А теперь все а после р или л:

str_view_all("Карл у Клары украл кораллы, а Клара у Карла украла кларнет", "(?<=[рл])а")
  • Карл у Клары украл кораллы, а Клара у Карла украла кларнет

Также у этих выражений есть формы с отрицанием. Давайте найдем все р не перед а:

str_view_all("Карл у Клары украл кораллы, а Клара у Карла украла кларнет", "р(?!а)")
  • Карл у Клары украл кораллы, а Клара у Карла украла кларнет

А теперь все р не после а:

str_view_all("Карл у Клары украл кораллы, а Клара у Карла украла кларнет", "(?<!а)р")
  • Карл у Клары украл кораллы, а Клара у Карла украла кларнет

Запомнить с ходу это достаточно сложно, так что подсматривайте сюда:

Вот отсюда можно скачать файл с текстом стихотворения Н. Заболоцкого “Меркнут знаки задиака.” Посчитайте долю женских (ударение падает на предпоследний слог рифмующихся слов) и мужских (ударение падает на последний слог рифмующихся слов) рифм в стихотворении.

📋 список подсказок ➡
👁 Датасеты скачивается с ошибкой, почему? ➡ Дело в том, что исходный файл в формате .txt, а не .csv. Его нужно скачивать, например, командой read_lines()
👁 Ошибка: ...applied to an object of class "character" Скачав файл Вы получили вектор со строками, где каждая элимент вектора – строка стихотворения. Создайте tibble(), тогда можно будет применять стандартные инструменты tidyverse.
👁 Хорошо, tibble() создан, что дальше? ➡ Дальше нужно создать переменную, из которой будет понятно, мужская в каждой строке рифма, или женская.
👁 А как определить, какая рифма? Нужно с словарем сравнивать? ➡ Формально говоря, определять рифму можно по косвенным признакам. Все стихотворение написано четырехстопным хореем, значит в нем либо 7, либо 8 слогов. Значит, посчитав количество слогов, мы поймем, какая перед нами рифма.
👁 А как посчитать гласные? ➡ Нужно написать регулярное выражение… вроде бы это тема нашего занятия…
👁 Гласные посчитаны. А что дальше? ➡ Ну теперь нужно посчитать, сколько каких длин (в количестве слогов) бывает в стихотворении. Это можно сделать при помощи функции count().
👁 А почему у меня есть строки длины 0 слогов ➡ Ну, видимо, в стихотворении были пустые строки. Они использовались для разделения строф.
👁 А почему у меня есть строки длины 6 слогов ➡ Ну, видимо, Вы написали регулярное выражение, которое не учитывает, что гласные буквы могут быть еще и в начале строки, а значит написаны с большой буквы.

В ходе анализа данных чаще всего бороться со строками и регулярными выражениями приходится в процессе обработки неаккуратнособранных анкет. Предлагаю обработать переменные sex и age такой вот неудачно собранной анкеты и построить следующий график:

📋 список подсказок ➡
👁 А что это за geom_...()? ➡ Это geom_dotplot() с аргументом method = "histodot" и с удаленной осью y при помощи команды scale_y_continuous(NULL, breaks = NULL)
👁 Почему на графике рисутеся каждое значение возраста? ➡ Если Вы все правильно преобразовали, должно помочь преобразование строковой переменной age в числовую при помощи функции as.integer().

7.9 Определение языка

Для определения языка существует два пакета cld2 (вероятностный) и cld3 (нейросеть).

udhr_24 <- read_csv("https://raw.githubusercontent.com/agricolamz/DS_for_DH/master/data/article_24_from_UDHR.csv")
## 
## ── Column specification ────────────────────────────────────────────────────────
## cols(
##   article_text = col_character()
## )
udhr_24
ABCDEFGHIJ0123456789
article_text
<chr>
Каждый человек имеет право на отдых и досуг, включая право на разумное ограничение рабочего дня и на оплачиваемый периодический отпуск.
Everyone has the right to rest and leisure, including reasonable limitation of working hours and periodic holidays with pay.
Toute personne a droit au repos et aux loisirs et notamment à une limitation raisonnable de la durée du travail et à des congés payés périodiques.
Toda persona tiene derecho al descanso, al disfrute del tiempo libre, a una limitación razonable de la duración del trabajo y a vacaciones periódicas pagadas.
لكلِّ شخص حقٌّ في الراحة وأوقات الفراغ، وخصوصًا في تحديد معقول لساعات العمل وفي إجازات دورية مأجورة.
人人有享有休息和闲暇的权利,包括工作时间有合理限制和定期给薪休假的权利。
cld2::detect_language(udhr_24$article_text)
## [1] "ru" "en" "fr" "es" "ar" "zh"
cld2::detect_language(udhr_24$article_text, lang_code = FALSE)
## [1] "RUSSIAN" "ENGLISH" "FRENCH"  "SPANISH" "ARABIC"  "CHINESE"
cld3::detect_language(udhr_24$article_text)
## [1] "ru" "en" "fr" "es" "ar" "zh"
cld2::detect_language("Ты женат? Говорите ли по-английски?")
## [1] "bg"
cld3::detect_language("Ты женат? Говорите ли по-английски?")
## [1] NA
cld2::detect_language("Варкалось. Хливкие шорьки пырялись по наве, и хрюкотали зелюки, как мюмзики в мове.")
## [1] "ru"
cld3::detect_language("Варкалось. Хливкие шорьки пырялись по наве, и хрюкотали зелюки, как мюмзики в мове.")
## [1] "ru"
cld2::detect_language("Варчилось… Хлив'язкі тхурки викрули, свербчись навкрузі, жасумновілі худоки гривіли зехряки в чузі.")
## [1] "uk"
cld3::detect_language("Варчилось… Хлив'язкі тхурки викрули, свербчись навкрузі, жасумновілі худоки гривіли зехряки в чузі.")
## [1] "uk"
cld2::detect_language_mixed("Многие в нашей команде OpenDataScience занимаются state-of-the-art технологиями машинного обучения: DL-фреймворками, байесовскими методами машинного обучения, вероятностным программированием и не только.")
## $classification
##   language code latin proportion
## 1  RUSSIAN   ru FALSE       0.87
## 2  ENGLISH   en  TRUE       0.11
## 3  UNKNOWN   un  TRUE       0.00
## 
## $bytes
## [1] 353
## 
## $reliabale
## [1] TRUE
cld3::detect_language_mixed("Многие в нашей команде OpenDataScience занимаются state-of-the-art технологиями машинного обучения: DL-фреймворками, байесовскими методами машинного обучения, вероятностным программированием и не только.")
ABCDEFGHIJ0123456789
language
<chr>
probability
<dbl>
reliable
<lgl>
proportion
<dbl>
ru0.9983915TRUE0.88951844
en0.9992564TRUE0.05099150
sr0.4266235FALSE0.04815864

7.10 Расстояния между строками

Существует много разных метрик для измерения расстояния между строками (см. ?`stringdist-metrics`), в примерах используется расстояние Дамерау — Левенштейна. Данное расстояние получается при подсчете количества операций, которые нужно сделать, чтобы перевести одну строку в другую.

  • вставка ab → aNb
  • удаление aOb → ab
  • замена символа aOb → aNb
  • перестановка символов ab → ba
library(stringdist)
## 
## Attaching package: 'stringdist'
## The following object is masked from 'package:tidyr':
## 
##     extract
stringdist("корова","корова")
## [1] 0
stringdist("коровы", c("курица", "бык", "утка", "корова", "осел"))
## [1] 4 6 6 1 5
amatch(c("быки", "коровы"), c("курица", "бык", "утка", "корова", "осел"), maxDist = 2)
## [1] 2 4

7.11 Дополнительные задания:

В датасет записаны твиты Донольда Трампа взятые с kaggle. Постройте график рассеяния, которые показывает связь количества ретвитов и лайков. Чтобы убрать научную запись больших чисел используйте команду options(scipen = 999).

Постройте гистограмму, которая показывает распределения длины твитов в символах. Какой метод определения размера ячейки использован на приведенном графике? [Sturgers 1926], [Scott 1979] или [Freedman, Diaconis 1981]?

Постройте график рассеивания, который бы показывал связь с длиной твита во времени. Используя geom_hline(), наложите две линии: 140 символов и 280. Сделайте прозрачность 0.1.

Постройте график рассеивания, который бы показывал связь с длиной твита во времени. Разбейте и раскрасьте твиты на основании наличия в них интернет ссылок.

Можно ли утверждать, что твиты со ссылками длиннее? Постройте вайолинплот, которые показывает распределение значений длины твитов в зависимости от наличия в них интернет ссылок.

Найдите твиты которые содержат корень america, которые встречаются больше одного раза, и фасетизируйте по таким словам.


  1. Lorem ipsum — классический текст-заполнитель на основе трактата Марка Туллия Цицерона “О пределах добра и зла.” Его используют, чтобы посмотреть, как страница смотриться, когда заполнена текстом↩︎