5 Строки

Мы уже знакомились со строковым типом данных character. Но мы обозначили, что многие операции над этим типом невозможны (например, нельзя сложить две строки). Возникает вопрос: что делать?

Для начала научимся ставить дополнительные пакеты.

5.1 Установка дополнительных пакетов

В самом начале мы упоминали, что R имеет большие возможности расширения функционала с помощью специальных пакетов. Для работы со строками нам будут нужны два — stringi («стринг-ай») и stringr («стринг-ар»). На их примере мы и разберем механизм установки и подключения дополнительных пакетов.

Пакет — это набор функций, не входящих в «базовую комплектацию R», которые, как правило, специализированы под те или иные задачи. В нашем случае — под работу со строками. Поэтому сначала пакет необходимо скачать на комп:

install.packages(c("stringi", "stringr"))

В функцию install.packages() необходимо передать строковый вектор, содержащий названия пакетов, которые мы бы хотели установить.

Часто одни пакеты ссылаются на функции других, поэтому можно указать в аргументе dependencies TRUE, чтобы попутно установились зависимые пакеты.

install.packages(c("stringi", "stringr"), dependencies = TRUE)

По умолчанию пакеты скачиваются с репозитория CRAN — основное хранилище, где лежит подавляющее большинство пакетов и всякого разного. Но может быть такое, что установить соединение с этим репозиторием по каким-либо причинам не получается. Чтобы пофиксить сей баг, нужно указать в настройках какое-либо из зеркал. Options → Packages → Primary CRAN repository → Change…, и выбрать что-то. Если не помогло — попробуйте ещё.

Но мало просто скачать пакет. Надо его ещё подключить к текущей сессии, в которой мы работаем. Для этого используется функция

library(stringi)
library(stringr)

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

Тут запутываются термины «пакет» и «библиотека», но опустим эти детали и примем сей момент как есть…

После того, как мы подключили пакет к данной сессии, мы можем использовать функции из него.

Кайф, поехали!

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

Можно сделать строку руками (наблюдайте за кавычками):

s1 <- "сложившаяся структура организации влечет за собой процесс внедрения и модернизации новых предложений"
s1
## [1] "сложившаяся структура организации влечет за собой процесс внедрения и модернизации новых предложений"
s2 <- 'С другой стороны постоянный количественный "рост" и сфера нашей активности позволяет выполнять важные задания по разработке соответствующий условий активизации'
s2
## [1] "С другой стороны постоянный количественный \"рост\" и сфера нашей активности позволяет выполнять важные задания по разработке соответствующий условий активизации"
s3 <- "С другой стороны постоянный количественный "рост" и сфера нашей активности позволяет выполнять важные задания по разработке соответствующий условий активизации"
s3
## Error: <text>:1:52: unexpected symbol
## 1: s3 <- "С другой стороны постоянный количественный "рост
##                                                        ^
s4 <- "С другой стороны постоянный количественный 'рост' и сфера нашей активности позволяет выполнять важные задания по разработке соответствующий условий активизации"
s4
## [1] "С другой стороны постоянный количественный 'рост' и сфера нашей активности позволяет выполнять важные задания по разработке соответствующий условий активизации"
s5 <- "С другой стороны постоянный количественный «рост» и сфера нашей активности позволяет выполнять важные задания по разработке соответствующий условий активизации"
s5
## [1] "С другой стороны постоянный количественный «рост» и сфера нашей активности позволяет выполнять важные задания по разработке соответствующий условий активизации"
s6 <- "" # это пустая строка
s6
## [1] ""

Также можно использовать функцию character()1 для создания вектора из пустых строк:

character(5)
## [1] "" "" "" "" ""

А также функции для приведения типов, которые мы уже обсуждали:

as.character(1:30)
##  [1] "1"  "2"  "3"  "4"  "5"  "6"  "7"  "8"  "9"  "10" "11" "12" "13" "14" "15"
## [16] "16" "17" "18" "19" "20" "21" "22" "23" "24" "25" "26" "27" "28" "29" "30"

Кроме того, существуют встроенные текстовые векторы:

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"
<img class=“taskimg” src="img/question.png>

Есть ещё встроенные векторы, которые содержат названия месяцев и аббревиатуры названий месяцев. Отыщите их.

Мы умеем генерировать числавые последовательности. Можно также генерировать и строки. Например, когда вы хотите сгенерировать ID для испытуемых не просто как числа, а как уникальные текстовые значения:

stri_rand_strings(n = 10, length = 5)
##  [1] "xxGbr" "XhWvN" "0Cona" "IEQRr" "tKgdM" "Iq5IX" "zwVPm" "fEsWA" "vYAA7"
## [10] "d4b1I"

Фнукции из пакета stringi начинаются с префикса stri, а функции пакета stringr — с префикса str. Их легко можно опознать.

Средствами stringi можно генерировать не только случайные строки, но и «рыбу»-текст. Один из вариант такого текста — широко известный «Lorem ipsum». Найдите функцию, которая генерирует такой текст и сгенерируйте четыре абзаца.

## [1] "Lorem ipsum dolor sit amet, ultricies in enim laoreet montes eros sagittis consectetur purus. Sed eros porta vitae dictumst elit. Nullam quis dapibus commodo taciti. Class aptent tincidunt magnis in justo ut mus, eu tincidunt orci. At eleifend sagittis et eu vel tincidunt sed. Sed, sit primis magna pellentesque lacus, nullam fringilla non. Volutpat erat mi vitae mauris mauris eu sit, dictumst suspendisse. Sed aliquet aliquam maximus sed integer rutrum ligula neque lacinia tortor. Urna id sed dis ac quis nulla amet."                                                                            
## [2] "Enim dignissim convallis sed dictumst at. Dapibus a quis cursus nunc risus! Dui eu donec nec litora integer, non euismod ex. Elementum ante nisl in interdum suspendisse eros sed ut euismod mauris. Velit et, et mi dolor sed! Nunc donec ac nunc enim in nunc mi aliquet. Suspendisse pretium, erat sodales neque erat sed metus enim elementum quam et. Ex mi ac fermentum diam ut, tristique eu sed amet a. Enim vitae, bibendum vitae velit. Ultrices sed accumsan, enim quis diam. Senectus fames, cubilia risus donec at mus, adipiscing. Enim lorem ut eros sed class risus vestibulum sed."                 
## [3] "Litora quis pharetra. Ad libero augue cursus finibus maximus montes sit velit. Nulla donec odio tincidunt lectus, libero urna nisl et? Praesent montes, urna posuere turpis sed sed ex massa quam. Purus ac in facilisis sed iaculis quis. Vitae aliquam platea tincidunt fames integer phasellus. Aptent ac amet scelerisque facilisi eu. Nunc at, magna ac hac himenaeos quis amet facilisis non. Eget ornare, ut leo, nec augue vel imperdiet sit et vehicula aliquet ipsum. Gravida, arcu pulvinar enim proin tortor erat quisque. Sit blandit sed pellentesque et sociosqu sed elementum, ridiculus, himenaeos."
## [4] "Et sed, risus magnis porttitor commodo etiam, praesent lorem, neque dolor himenaeos. Non bibendum tellus aptent montes porttitor diam sociis suscipit at. Non mollis elementum nunc nulla facilisis. Netus ipsum, consectetur tortor penatibus commodo non nam tincidunt pellentesque. Mi tempor dapibus proin diam malesuada tristique. Amet mauris vestibulum sed nec dignissim, etiam amet laoreet arcu. Magna vestibulum orci interdum eget, nulla sed diam ultricies habitasse. Luctus maecenas nunc dapibus lorem nostra in sit ut quis sed. Varius morbi sapien magna est aliquam euismod eu senectus lorem."

5.3 Конкатенация строк

Строки можно соединять воедино:

paste('first', 'second', 'third') # базовый R
## [1] "first second third"
paste('first', 'second', 'third', sep = "_")
## [1] "first_second_third"
paste0('first', 'second', 'third')
## [1] "firstsecondthird"
stri_c('first', 'second', 'third') # пакет stringi
## [1] "firstsecondthird"
stri_c('first', 'second', 'third', sep = " & ") # пакет stringi
## [1] "first & second & third"

Сгенерируйте вектор, который будет описывать различные сочетания условий вашего гипотетического эксперимента, который вы сможете добавить в гипотетический датафрейм. Пусть у вас будет десять испытуемых, для каждого из которого в датафрейме предусмотрено четыре строки: две под условие A1 и две под условие A2. В свою очередь каждое из условий A может сочетаться с вариантами условия B (B1 и B2). В итоге у вас должен получиться вектор, каждое значение которого структурно выглядит так: ID_условиеA_условиеB.

##  [1] "1_A1_B1"  "1_A1_B2"  "1_A2_B1"  "1_A2_B2"  "2_A1_B1"  "2_A1_B2" 
##  [7] "2_A2_B1"  "2_A2_B2"  "3_A1_B1"  "3_A1_B2"  "3_A2_B1"  "3_A2_B2" 
## [13] "4_A1_B1"  "4_A1_B2"  "4_A2_B1"  "4_A2_B2"  "5_A1_B1"  "5_A1_B2" 
## [19] "5_A2_B1"  "5_A2_B2"  "6_A1_B1"  "6_A1_B2"  "6_A2_B1"  "6_A2_B2" 
## [25] "7_A1_B1"  "7_A1_B2"  "7_A2_B1"  "7_A2_B2"  "8_A1_B1"  "8_A1_B2" 
## [31] "8_A2_B1"  "8_A2_B2"  "9_A1_B1"  "9_A1_B2"  "9_A2_B1"  "9_A2_B2" 
## [37] "10_A1_B1" "10_A1_B2" "10_A2_B1" "10_A2_B2"

5.4 Разделение строк

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

str_split(s_exp, pattern = "_")
## [[1]]
## [1] "1"  "A1" "B1"
## 
## [[2]]
## [1] "1"  "A1" "B2"
## 
## [[3]]
## [1] "1"  "A2" "B1"
## 
## [[4]]
## [1] "1"  "A2" "B2"
## 
## [[5]]
## [1] "2"  "A1" "B1"
## 
## [[6]]
## [1] "2"  "A1" "B2"
## 
## [[7]]
## [1] "2"  "A2" "B1"
## 
## [[8]]
## [1] "2"  "A2" "B2"
## 
## [[9]]
## [1] "3"  "A1" "B1"
## 
## [[10]]
## [1] "3"  "A1" "B2"
## 
## [[11]]
## [1] "3"  "A2" "B1"
## 
## [[12]]
## [1] "3"  "A2" "B2"
## 
## [[13]]
## [1] "4"  "A1" "B1"
## 
## [[14]]
## [1] "4"  "A1" "B2"
## 
## [[15]]
## [1] "4"  "A2" "B1"
## 
## [[16]]
## [1] "4"  "A2" "B2"
## 
## [[17]]
## [1] "5"  "A1" "B1"
## 
## [[18]]
## [1] "5"  "A1" "B2"
## 
## [[19]]
## [1] "5"  "A2" "B1"
## 
## [[20]]
## [1] "5"  "A2" "B2"
## 
## [[21]]
## [1] "6"  "A1" "B1"
## 
## [[22]]
## [1] "6"  "A1" "B2"
## 
## [[23]]
## [1] "6"  "A2" "B1"
## 
## [[24]]
## [1] "6"  "A2" "B2"
## 
## [[25]]
## [1] "7"  "A1" "B1"
## 
## [[26]]
## [1] "7"  "A1" "B2"
## 
## [[27]]
## [1] "7"  "A2" "B1"
## 
## [[28]]
## [1] "7"  "A2" "B2"
## 
## [[29]]
## [1] "8"  "A1" "B1"
## 
## [[30]]
## [1] "8"  "A1" "B2"
## 
## [[31]]
## [1] "8"  "A2" "B1"
## 
## [[32]]
## [1] "8"  "A2" "B2"
## 
## [[33]]
## [1] "9"  "A1" "B1"
## 
## [[34]]
## [1] "9"  "A1" "B2"
## 
## [[35]]
## [1] "9"  "A2" "B1"
## 
## [[36]]
## [1] "9"  "A2" "B2"
## 
## [[37]]
## [1] "10" "A1" "B1"
## 
## [[38]]
## [1] "10" "A1" "B2"
## 
## [[39]]
## [1] "10" "A2" "B1"
## 
## [[40]]
## [1] "10" "A2" "B2"

Нам вернулся список. Шо с ним делать — вопрос хороший. Можно развернуть список в вектор и индексацией вытащить нужные нам части.

Возьмите список, которые вернула функция str_split(), разверните его в вектор с помощью функции unlist() и вытащите из него по отдельности ID, условия A и условия B.

##  [1] "1"  "1"  "1"  "1"  "2"  "2"  "2"  "2"  "3"  "3"  "3"  "3"  "4"  "4"  "4" 
## [16] "4"  "5"  "5"  "5"  "5"  "6"  "6"  "6"  "6"  "7"  "7"  "7"  "7"  "8"  "8" 
## [31] "8"  "8"  "9"  "9"  "9"  "9"  "10" "10" "10" "10"
##  [1] "A1" "A1" "A2" "A2" "A1" "A1" "A2" "A2" "A1" "A1" "A2" "A2" "A1" "A1" "A2"
## [16] "A2" "A1" "A1" "A2" "A2" "A1" "A1" "A2" "A2" "A1" "A1" "A2" "A2" "A1" "A1"
## [31] "A2" "A2" "A1" "A1" "A2" "A2" "A1" "A1" "A2" "A2"
##  [1] "B1" "B2" "B1" "B2" "B1" "B2" "B1" "B2" "B1" "B2" "B1" "B2" "B1" "B2" "B1"
## [16] "B2" "B1" "B2" "B1" "B2" "B1" "B2" "B1" "B2" "B1" "B2" "B1" "B2" "B1" "B2"
## [31] "B1" "B2" "B1" "B2" "B1" "B2" "B1" "B2" "B1" "B2"

Но есть более удобная функция separate(), которая разбивает субстроки сразу по колонкам. Её мы вспомним, когда будет говорить о предобработке данных.

5.5 Сортировка строк

Строки можно сортировать:

unsorted_s <- paste0(
  sample(LETTERS, size = length(LETTERS), replace = TRUE),
  sample(letters, size = length(letters)),
  sample(letters, size = length(letters))
) # делаем несортированный вектор из трёхбуквенных «слов»
unsorted_s
##  [1] "Cie" "Hza" "Eci" "Loy" "Vmt" "Thk" "Wvd" "Anm" "Qdg" "Ofb" "Ykq" "Iav"
## [13] "Buw" "Wbr" "Gqs" "Ayx" "Ylo" "Qjf" "Jgj" "Twc" "Rpl" "Mrh" "Msz" "Zxu"
## [25] "Ntp" "Yen"

Сортируем:

sort(unsorted_s) # можно базовой функцией
##  [1] "Anm" "Ayx" "Buw" "Cie" "Eci" "Gqs" "Hza" "Iav" "Jgj" "Loy" "Mrh" "Msz"
## [13] "Ntp" "Ofb" "Qdg" "Qjf" "Rpl" "Thk" "Twc" "Vmt" "Wbr" "Wvd" "Yen" "Ykq"
## [25] "Ylo" "Zxu"
str_sort(unsorted_s) # можно функцией из пакета stringr
##  [1] "Anm" "Ayx" "Buw" "Cie" "Eci" "Gqs" "Hza" "Iav" "Jgj" "Loy" "Mrh" "Msz"
## [13] "Ntp" "Ofb" "Qdg" "Qjf" "Rpl" "Thk" "Twc" "Vmt" "Wbr" "Wvd" "Yen" "Ykq"
## [25] "Ylo" "Zxu"

Строки сортируются в лексикографическом порядке. Обратите внимание, что порядок сортировки зависит от того, в какой локали мы работаем, то есть от того, алфавит какого языка используется для определения лексикографического порядка:

str_sort(c("э", "а", "у", "i"), locale = 'en') # по умолчанию
## [1] "i" "а" "у" "э"
str_sort(c("э", "а", "у", "i"), locale = 'ru') # русский
## [1] "а" "у" "э" "i"

Сортировок существует много разных. Но чем больше у вас данные, тем медленнее будет работать базовый sort(). Поэтому используйте str_sort().

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

Допусти вы собирали данные онлайн, и у вас было поле ступень образования, в которой респонденты должны быть указать бакалавриат/магистратура/аспирантура. Но по каким-то причинам, разработчик онлайн-формы не подумал, что хорошо бы сделать это поле списком, из котрого можно выбирать, и оставил его как обычное текстовое поле. Теперь у вас в данных есть «бакалавриат», «Бакалавриат» и какой-нибудь «БАкалавриат», то вообще-то одно и то же. Напасти подобного рода можно победить, если привести строки к единому регистру:

str_to_lower(c("Бакалавриат", "БАКАЛАВРИАТ", "АСпирантура", "магистратура"))
## [1] "бакалавриат"  "бакалавриат"  "аспирантура"  "магистратура"
str_to_upper(c("Бакалавриат", "БАКАЛАВРИАТ", "АСпирантура", "магистратура"))
## [1] "БАКАЛАВРИАТ"  "БАКАЛАВРИАТ"  "АСПИРАНТУРА"  "МАГИСТРАТУРА"

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

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

Если нам надо найти какую-то подстроку, то мы можем использоваться функцию str_detect():

str_detect(unsorted_s, 'b') # ищем все элементы, в которых есть маленькая «b»
##  [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE
## [13] FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [25] FALSE FALSE
unsorted_s[str_detect(unsorted_s, 'b')] # а вот и сами элементы
## [1] "Ofb" "Wbr"

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

str_count(unsorted_s, 'a')
##  [1] 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0

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

Раз мы умеем искать подстроку, то, наверное, её можно и как-то изменять.

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

str_sub(unsorted_s, start = 1, end = 2) # по индексам
##  [1] "Ci" "Hz" "Ec" "Lo" "Vm" "Th" "Wv" "An" "Qd" "Of" "Yk" "Ia" "Bu" "Wb" "Gq"
## [16] "Ay" "Yl" "Qj" "Jg" "Tw" "Rp" "Mr" "Ms" "Zx" "Nt" "Ye"

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

str_replace(unsorted_s, pattern = "b", replacement = "Ц")
##  [1] "Cie" "Hza" "Eci" "Loy" "Vmt" "Thk" "Wvd" "Anm" "Qdg" "OfЦ" "Ykq" "Iav"
## [13] "Buw" "WЦr" "Gqs" "Ayx" "Ylo" "Qjf" "Jgj" "Twc" "Rpl" "Mrh" "Msz" "Zxu"
## [25] "Ntp" "Yen"

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

str_remove(unsorted_s, 'b')
##  [1] "Cie" "Hza" "Eci" "Loy" "Vmt" "Thk" "Wvd" "Anm" "Qdg" "Of"  "Ykq" "Iav"
## [13] "Buw" "Wr"  "Gqs" "Ayx" "Ylo" "Qjf" "Jgj" "Twc" "Rpl" "Mrh" "Msz" "Zxu"
## [25] "Ntp" "Yen"

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

stri_trans_general("русский текст, который должен быть написан латиницей",
                   'cyrillic-latin')
## [1] "russkij tekst, kotoryj dolžen bytʹ napisan latinicej"

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

Но часто нас интересует не конкретная подстрока, и все варианты подстрок, которые имеют определенную структуру. Например, мы хотим найти корректно введенные даты рождения в нашем датасете. Пусть по условию дата должна иметь формат DD.MM.YYYY. И у нас есть вот такой вектор:

dates <- c('21.92.2001', '01.04.1994', '5-3-2011', '6/04/1999')

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

Чтобы задать структуру паттерна, который мы хотим найти, используются регулярные выражения.

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

5.9.1 Метасимволы

Если мы попробуем поискать точки в наших датах, то просто так мы их не найдем:

str_view_all(dates, pattern = ".")

Подсветились все символы, так как точка в регулярных выражениях — специальный символ. Поэтому чтобы указать, что нас интересуют собственно точки, нужно их экранировать.

str_view_all(dates, pattern = '\\.')

Так лучше. Еще к специальным знакам (метасимфолам) относятся $, *, +, ?, ^, [, ], и другие.

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

В датах нас интересуют цифры, и для них есть специальное обозначение:

str_view_all(dates, pattern = '\\d') # ищем цифры
str_view_all(dates, pattern = '\\D') # ищем не-цифры

Другие классы символов можно найти в следующих примерах:

str_view_all('успешный балбес', '\\s') # пробелы
str_view_all('успешный балбес', '\\S') # не-пробелы
str_view_all('верно ведь, что здесь что-то есть', '\\w') # не пробелы и не знаки препинания
str_view_all('верно ведь, что здесь что-то есть', '\\W') # пробелы и знаки препинания

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

Можно указать, сколько раз должен встречаться тот или иной символ:

  • ? — ноль или один раз
  • * — ноль или более раз
  • + — один или более раз
  • {n} — n раз

Например, найдем все группировки по два числа:

str_view_all(dates, '\\d{2}')

Итак, теперь мы можем вытащить все корректные даты, которые есть в нашем векторе:

str_view_all(dates, '\\d{2}\\.[01]\\d{1}\\.\\d{4}')

Она получилась всего одна. Но в целом в нашим набором условий это справедливо.

Как мы отмечали выше, в нашем векторе в датами есть ещё две даты, который выглядят адекватно, но немного отличаются форматом, в котором они записаны. Подправьте их формат с помощью функций, изученных выше, и измените регулярное выражение так, чтобы оно обнаруживало и их тоже.

Подсказка: возможно, вам понадобятся некоторые варианты выше упомянутых функций.


  1. Ещё есть numeric() и logical(), например.↩︎