Сейчас легко найти данные. Но вот найти высококачественные оказывается весьма проблематично. Одна из характерных черт низкопробных данных в том, что они запутаны и редко точны. Сколько бы мы, профессионалы в этой сфере, не говорили об алгоритмах и проверке моделей, большую часть времени занимает именно очистка данных.
В этом смысле работа со строками требует несколько иного набора навыков, чем работа с теми же списками или data.frame
. В текущей статье, как вы уже поняли, мы будем учиться максимально эффективно управлять строками. Начнем!
Вставка и разделение
Вставка и разделение частей строк — это две из наиболее типичных задач, с которыми мы встречаемся.
Для них у нас есть две простые функции: paste()
и strsplit()
.
paste('Ugurcan' , 'Demir' , sep = " ") ## [1] "Ugurcan Demir" ## [[1]] ## [1] "Ugurcan" "Demir" strsplit("Ugurcan Demir" , split = " ") paste("The","United" ,"States" ,"of" ,"America" , sep = " ") ## [1] "The United States of America" unlist(strsplit("The United States of America" , split = " ")) ## [1] "The" "United" "States" "of" "America"
Общее число символов и разделение
У R и Python есть немало общего. Оба этих языка легки в освоении и за счет своих гибких библиотек становятся типичным выбором среди статистов, практиков машинного обучения, да и любых интересующихся наукой о данных людей. Если же вы пришли в R из Python, то примеры, которые я планирую показать, могут показаться вам несколько странными.
Например, для нахождения общего количества символов интуитивным выбором будет lengh()
. Но в R это происходит не так, как в Python.
length("The United States of America") ## [1] 1 nchar("The United States of America") ## [1] 28
Разделение здесь тоже отличается. Для этого у нас есть две функции: substr()
и substring()
. Они работают абсолютно одинаково, если указать параметры начала и завершения. Тем не менее у substring()
есть предустановленное стоп-значение, а у substr()
нет.
substr("The United States of America" , start = 10 , stop = 20) ## [1] "d States of" substring("The United States of America" , first = 10 , last = 20) ## [1] "d States of"
Вот что происходит, если не передать аргумент в параметр stop
:
substring("The United States of America" , first = 10 ) ## [1] "d States of America" substr("The United States of America" , start = 10 ) ## Error in substr("The United States of America", start = 10): argument "stop" is missing, with no default
regexec() , gregexpr() и grep()
Я уже слышу ваш вопрос: “А откуда нам знать индексы для передачи аргументов?”. Что ж, когда у вас всего один фрагмент строки, то это несложно подсчитать на пальцах, но в случае миллионов, да даже десятков, строк данных такой подход не сгодится. К счастью, у нас есть для этого две прекрасные функции.
Первая из них, regexec()
, используется для поиска первого вхождения подстроки внутри большой строки.
regexec(pattern = 'United' , text = "The United States of America" ) ## [[1]] ## [1] 5 ## attr(,"match.length") ## [1] 6 ## attr(,"index.type") ## [1] "chars" ## attr(,"useBytes") ## [1] TRUE regexec(pattern = 'U' , text = "The United States of America" ) ## [[1]] ## [1] 5 ## attr(,"match.length") ## [1] 1 ## attr(,"index.type") ## [1] "chars" ## attr(,"useBytes") ## [1] TRUE
При этом gregexpr()
находит все вхождения подстроки.
gregexpr(pattern = 'e' , text = "The United States of America" ) ## [[1]] ## [1] 3 9 16 24 ## attr(,"match.length") ## [1] 1 1 1 1 ## attr(,"index.type") ## [1] "chars" ## attr(,"useBytes") ## [1] TRUE
Функция grep()
получает второй аргумент, который отражает уже не фрагмент строки, а вектор строк. В ответ же она возвращает индексы элементов, которые содержат искомую подстроку. Если дополнительно установить значение параметра на T
(TRUE), функция вернет сами элементы.
grep(pattern = 'wigh' , x = c('Michael' , "Jim" , "Dwight" , "Pam") ) ## [1] 3 grep(pattern = 'm' , x = c('Michael' , "Jim" , "Dwight" , "Pam") ) ## [1] 2 4 grep(pattern = 'm' , x = c('Michael' , "Jim" , "Dwight" , "Pam") , value = T) ## [1] "Jim" "Pam"
sub() и gsub()
sub()
и gsub()
идут еще дальше и заменяют часть большой строки, соответствующую заданной подстроке, строкой, переданной в качестве аргумента.
sub(pattern = 'm' , replacement = "n" , x = c('Michael',"Jim","Dwight","Pam")) ## [1] "Michael" "Jin" "Dwight" "Pan" sub(pattern = "i" , replacement = "a" , x = "The United States of America") ## [1] "The Unated States of America"
Если вы заметили, что слово “America” осталось прежним, то дело в том, что sub()
заменяет только первое вхождение подстроки. Для замены всех мы используем gsub()
.
gsub(pattern = "i" , replacement = "a" , x = "The United States of America") ## [1] "The Unated States of Ameraca"
Регулярные выражения (REGEX)
До этого момента мы искали простые фрагменты подстрок внутри других строк. Но искомая подстрока может быть не такой простой, как в примерах. Мы даже можем не знать, какую конкретно подстроку нужно найти, и искать такую, которая будет вписываться в определенную модель. В подобных случаях мы задействуем регулярные выражения, или regex.
Эти выражения встречаются во многих языках программирования и несколько отличаются в реализации. Их основная задача — искать паттерн строки в заданной строке большего размера.
Регулярные выражения не получают точную подстроку, которую нужно найти. Вместо этого они ищут такие подстроки, которые вписываются в переданный им паттерн. Для этого у них есть собственный мини-язык и метасимволы. Я подробно объясню все метасимволы и правила, которые определяют работу регулярного выражения.
Метасимволы
Первым делом нужно сказать, что символы мини-языка регулярных выражений называются метасимволами и представляют собой их каркас.
- “$”
- “*”
- “+”
- “.”
- “?”
- “[ ]”
- “^”
- “{ }”
- “|”
- “( )”
- “\ ”
Далее я объясню действие каждого из этих метасимволов.
Квантификаторы
Среди метасимволов знаки “?” , “*” , “+” и “{ }” называются квантификаторами, потому что указывают, сколько раз мы хотим увидеть заданный паттерн.
- “*”: предыдущий элемент должен встречаться 0 или более раз.
- “+”: предыдущий элемент должен встречаться 1 или более раз.
- “?”: предыдущий элемент должен встречаться 0 или 1 раз.
- “{ ,m}”: предыдущий элемент должен встречаться m или меньше раз.
- “{n, }”: предыдущий элемент должен встречаться n или более раз.
- “{n , m}”: предыдущий элемент должен встречаться от n до m раз.
- “{m}”: предыдущий элемент должен встречаться ровно m раз.
letter_vector <- c( "AACACA","BBCCBC","CCABBB","ABABAA","ACBCAA","BCACBC", "BABABA","CACABA","BBABAB","BCCBAB","CAABCC","BCCBCA", "CAAABA","BAABCB","CCABBC","ABABBA","CABAAC","CAABCC", "CABCAC","AABCAA","CAAACB","BBACCA","BCAAAB","BBACBC", "CCCCBC","ACABCA","BCBBBC","AABBCC","CCBBBB","BBABBA","BBCAAC" ) grep(pattern = "ABC" , x = letter_vector , value = T) ## [1] "CAABCC" "BAABCB" "CAABCC" "CABCAC" "AABCAA" "ACABCA" grep(pattern = "AB*C" , x = letter_vector , value = T) ## [1] "AACACA" "ACBCAA" "BCACBC" "CACABA" "CAABCC" "BAABCB" "CCABBC" "CABAAC" ## [9] "CAABCC" "CABCAC" "AABCAA" "CAAACB" "BBACCA" "BBACBC" "ACABCA" "AABBCC" ## [17] "BBCAAC" grep(pattern = "AB+C" , x = letter_vector , value = T) ## [1] "CAABCC" "BAABCB" "CCABBC" "CAABCC" "CABCAC" "AABCAA" "ACABCA" "AABBCC" grep(pattern = "AB?C" , x = letter_vector , value = T) ## [1] "AACACA" "ACBCAA" "BCACBC" "CACABA" "CAABCC" "BAABCB" "CABAAC" "CAABCC" ## [9] "CABCAC" "AABCAA" "CAAACB" "BBACCA" "BBACBC" "ACABCA" "BBCAAC" grep(pattern = "AB{,2}C" , x = letter_vector , value = T) ## [1] "AACACA" "ACBCAA" "BCACBC" "CACABA" "CAABCC" "BAABCB" "CCABBC" "CABAAC" ## [9] "CAABCC" "CABCAC" "AABCAA" "CAAACB" "BBACCA" "BBACBC" "ACABCA" "AABBCC" ## [17] "BBCAAC" grep(pattern = "AB{2,}C" , x = letter_vector , value = T) ## [1] "CCABBC" "AABBCC" grep(pattern = "AB{1,2}C" , x = letter_vector , value = T) ## [1] "CAABCC" "BAABCB" "CCABBC" "CAABCC" "CABCAC" "AABCAA" "ACABCA" "AABBCC" grep(pattern = "AB{2}C" , x = letter_vector , value = T) ## [1] "CCABBC" "AABBCC"
Метасимволы начала и завершения
Символы “^” и “$” представляют начало и конец строки соответственно. Иногда их еще называют якорями. При этом они никаким символам не соответствуют.
grep(pattern = "^A" , x = letter_vector , value = T) ## [1] "AACACA" "ABABAA" "ACBCAA" "ABABBA" "AABCAA" "ACABCA" "AABBCC" grep(pattern = "C$" , x = letter_vector , value = T) ## [1] "BBCCBC" "BCACBC" "CAABCC" "CCABBC" "CABAAC" "CAABCC" "CABCAC" "BBACBC" ## [9] "CCCCBC" "BCBBBC" "AABBCC" "BBCAAC"
Плейсхолдер
Следующий метасимвол — это “.”, который соответствует любому символу в том месте, где используется. В примере ниже ищется любой паттерн, начинающийся с “C”, заканчивающийся на “A” и имеющий между этими символами любые два символа.
grep(pattern = "C..A" , x = letter_vector , value = T) ## [1] "AACACA" "ACBCAA" "CACABA" "BCCBAB" "BCCBCA" "CAAABA" "CABAAC" "CAAACB" ## [9] "BCAAAB"
Последовательности
Метасимвол “\” при использовании с набором ключевых букв служит для определения конкретной последовательности символов в строке и сопоставляется с этой последовательностью при использовании в функциях для строк. Ниже приводится список ключевых букв, которые часто используются с этим метасимволом:
- “\d” = цифра;
- “\D” = не цифра;
- “\w” = словесный символ (a-z, A-Z, 0???9);
- “\W” = не словесный символ;
- “\s” = пробельный символ;
- “\S” = не пробельный символ;
- “\b” = граница слова;
- “\B” = не граница слова.
Вот примеры:
string1 <- 'My name is Ugurcan and I am 25.' gsub(pattern = "\\d" , replacement = "-" , x = string1) ## [1] "My name is Ugurcan and I am --." gsub(pattern = "\\s" , replacement = "-" , x = string1) ## [1] "My-name-is-Ugurcan-and-I-am-25." gsub(pattern = "\\w" , replacement = "-" , x = string1) ## [1] "-- ---- -- ------- --- - -- --." gsub(pattern = "\\b" , replacement = "-" , x = string1) ## [1] "-M-y- -n-a-m-e- -i-s- -U-g-u-r-c-a-n- -a-n-d- -I- -a-m- -2-5-.-"
Символьные классы
Еще один метасимвол — это “[ ]”, который зачастую служит для формирования комплексных паттернов при анализе сложных и неструктурированных текстовых данных. В эти квадратные скобки можно передать несколько символов, в результате чего будут найдены только они, но не вместе, а по-отдельности, порядок при этом значения не имеет. Также можно указывать диапазон искомых символов с помощью дефиса.
grep(pattern = "[zp]" , x = state.name , value = T) ## [1] "Arizona" "Mississippi" "New Hampshire" grep(pattern = "[b-d]" , x = state.name , value = T) ## [1] "Alabama" "Colorado" "Connecticut" "Florida" ## [5] "Idaho" "Indiana" "Kentucky" "Maryland" ## [9] "Massachusetts" "Michigan" "Nebraska" "Nevada" ## [13] "New Mexico" "Rhode Island" "Wisconsin" grep(pattern = "[od]$" , x = state.name , value = T) ## [1] "Colorado" "Idaho" "Maryland" "New Mexico" "Ohio" ## [6] "Rhode Island"
В определенных случаях можно заключать в квадратные скобки встроенные имена классов. Ниже приводится полный список таких имен.
[:alnum:]
= буквенно-цифровые символы: [:alpha:] и [:digit:].[:alpha:]
= буквенные символы: [:lower:] и [:upper:].[:blank:]
= пустые символы: пробелы и табуляции, а также определяемые языковым стандартом, например неразрывный пробел.[:cntrl:]
= управляющие символы в ASCII. Эти символы имеют восьмеричные коды от 000 до 037 и 177 (DEL). В других наборах символов они являются равнозначными, если присутствуют.[:digit:]
= цифры: 0 1 2 3 4 5 6 7 8 9.[:graph:]
= графические символы: [:alnum:] и [:punct:].[:lower:]
= буквы нижнего регистра в текущем языковом стандарте.[:print:]
= печатные символы: [:alnum:], [:punct:] и пробел.[:punct:]
= знаки препинания: ! “ # $ % & ’ ( ) * + , — / : ; < = > ? @ [ ] ^ _ ` { | } ~. “.[:space:]
= пробельные символы: табуляция, новая строка, вертикальная табуляция, возврат каретки, перевод страницы, а также другие символы, определяемые различными языковыми стандартами.[:upper:]
= буквы верхнего регистра в текущем языковом стандарте.[:xdigit:]
= шестнадцатеричные цифры: 0 1 2 3 4 5 6 7 8 9 A B C D E F a b c d e f.
Группировка и оператор ИЛИ
Последними метасимволами у нас идут “()” и “|”, которые обычно используются вместе. С помощью скобок мы обособляем нужные наборы символов, а оператор ИЛИ позволяет указывать на возможность выбора между ними. Вот примеры:
grep(pattern = "(th|la)" , x = state.name , value = T) ## [1] "Alabama" "Alaska" "Delaware" "Maryland" ## [5] "North Carolina" "North Dakota" "Oklahoma" "Rhode Island" ## [9] "South Carolina" "South Dakota" grep(pattern = "^New (Y|J)" , x = state.name , value = T) ## [1] "New Jersey" "New York"
Экранирование
Мы рассмотрели все метасимволы, но что, если искомый паттерн содержит один из них?
Это очевидный и ожидаемый вопрос. В таком случае нужно сообщить R, что этот символ нужно рассматривать не как метасимвол, а как обычный знак.
Для этого мы добавляем тот же обратный слэш “\” перед нужным метасимволом, экранируя его. А поскольку обратный слэш сам является метасимволом, мы добавляем к нему еще один слэш для экранирования. Вот примеры:
string2 <- c("Lionel Messi\ PSG" , 'file_name$' , "{2022}") grep(pattern = "\$" , x = string2 , value = T) ## [1] "file_name$" grep(pattern = "\\{" , x = string2 , value = T) ## [1] "{2022}"
В этой статье мы разобрали все функции, которые используем в работе со строками. После этого мы рассмотрели одиннадцать метасимволов, с помощью которых можем создавать сложные паттерны для поиска.
Читайте также:
- В каком возрасте вы получите Нобелевскую премию — визуализация на языке R.
- R - язык для статистической обработки данных. Часть 3/3
- Введение в R: линейная алгебра
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Uğurcan Demir: A Concise Guide for Strings and Regular Expressions in R