В этом руководстве рассматривается одна из наиболее недокументированных функций Stata: регулярные выражения, или для краткости regex. В этом руководстве мы узнаем, как реализовать функции регулярных выражений, показанные в шпаргалке Stata ниже. Это включает изучение квантификаторов, построение специфических для общих выражений снизу вверх, границ слов и изучение жадного и притяжательного сопоставления:

Regex - это основной алгоритм, который используется для поиска по тексту с использованием сопоставления с образцом. Практически ежедневно можно встретить онлайн-реализации регулярных выражений. К ним относится автоматическая проверка того, правильно ли вы заполнили свой адрес электронной почты, имеет ли ваш пароль достаточное количество символов или достаточно ли он надежен. Алгоритм регулярного выражения может быть многоуровневым с помощью множества дополнительных инструментов. Сюда входят интеллектуальный анализ текста, обработка естественного языка (NLP), анализ настроений, машинное обучение (ML), автоматическая журналистика, автозаполнение при поиске в Интернете и программирование поисковых роботов. Такие компании, как Google, вероятно, также используют какую-либо расширенную версию регулярного выражения, чтобы просматривать ваши электронные письма в поисках ключевых слов для целевой рекламы.

Где я лично использую регулярные выражения? Имея дело с данными опросов и общедоступными данными в течение почти двух десятилетий, регулярное выражение помогает очистить беспорядочные текстовые записи, такие как имена, адреса или строки синтаксического анализа, объединенные в одной переменной. Кроме того, нет ничего необычного в том, что данные оказываются в PDF-файлах. Хотя сейчас существуют достойные конвертеры PDF в Excel или csv, есть большая вероятность, что некоторые столбцы данных будут перепутаны или объединены вместе. Например, я извлек данные для этой интерактивной визуализации данных в Flourish после анализа более 4500 страниц из PDF-файлов с использованием комбинации регулярных выражений и строковых функций. На создание всех комбинаций текстового поиска ушло всего два-три дня.

Как я уже упоминал в предыдущих руководствах, очистка данных - это большая часть времени, затрачиваемого на данные. Выполнение регрессии или составление таблиц - самая простая часть. Так что также нужно научиться эффективно очищать данные. Если у кого-то нет доступа к большому количеству ресурсов или рабочей силы (что также часто используется в странах с низким уровнем дохода), чистка методом грубой силы или определение условий замены одно за другим является болезненно сложной задачей. Кроме того, если речь идет о нескольких тысячах записей, важно изучить сопоставление с образцом.

Регулярные выражения, без сомнения, очень мощный инструмент. Они также существуют в Stata и были обновлены в версии 14 до более общего Unicode из более ограниченной версии POSIX.2. Это значительно расширило возможности Stata по выполнению более сложных сопоставлений строк. Но документации по этой теме крайне мало. Помимо некоторых обсуждений на форумах Stata и нескольких старых статей в Интернете, файл справки для регулярных выражений и связанных команд настолько прост, насколько это возможно.

В этом документе я расскажу об основах регулярного выражения и некоторых приложений для реальных данных. Два момента, прежде чем мы начнем. Во-первых, отказ от ответственности, что я все еще изучаю язык регулярных выражений, который обширен и есть несколько способов определения одного и того же алгоритма. Так что есть шанс, что можно будет написать более продвинутый и более компактный синтаксис. Во-вторых, в Интернете существует множество ресурсов, включая учебные пособия и руководства по регулярным выражениям. Важно знать, что регулярные выражения реализуются по-разному на разных языках, а также используются разные стандарты. Поэтому будьте осторожны при использовании определенных веб-сайтов для обучения, поскольку не весь код можно напрямую использовать в Stata. Короче говоря, если вы используете онлайн-ресурс, чтобы узнать больше, вам нужно найти версию регулярного выражения Unicode. В этом руководстве основное внимание уделяется реализации регулярного выражения Unicode в Stata в сочетании с другими встроенными строковыми функциями.

Этот документ должен развиваться с течением времени по мере нахождения новых приложений или улучшения кода. Но в нынешнем виде это руководство охватывает большинство основ, которые вам нужно знать, чтобы начать работу.

Прежде чем мы начнем, вот широко цитируемый комикс с регулярными выражениями от XKCD, который, как мне кажется, я тоже должен добавить сюда:

Преамбула

Ожидается базовое знание Stata. Желательно иметь Stata 14 или выше, поскольку Stata 14+ имеет две версии регулярных выражений. Если вы наберете:

help regex

вы попадете на эту страницу. Эта страница дает много подсказок, но не дает подробностей:

Первый намек состоит в том, что исходный regex набор команд основан на старом стандарте POSIX.2, который ограничен в своем применении, например, как он работает с пробелами, длиной символов и т. Д. Второй намек заключается в том, что есть новая команда ustrregex на основе символов Unicode, которые являются более поздними и более продвинутыми. Если вы нажмете на это, появится другая минимально определенная справочная страница.

В этом руководстве будет явно использоваться версия ustrregex, реализация которой отличается от реализации regex, что и есть в большинстве существующих руководств Stata, которые можно использовать в Интернете. В этом руководстве я буду использовать термин регулярное выражение как сокращение регулярных выражений, а не как команду. Я также предлагаю вам изучить версию ustr, а не исходную реализацию.

В приведенном ниже руководстве я не буду предоставлять скриншоты окна браузера данных (для доступа к нему нужно ввести br в командном окне). Но, пожалуйста, оставьте его открытым, чтобы увидеть, что создается. Вы можете выполнить код в dofile и открыть сбоку окно браузера, чтобы наблюдать за изменениями данных.

Это руководство также непросто написать, поскольку в нем нет результатов в закрытой форме, таких как создание графика или таблицы. Он вводит концепцию регулярного выражения, который является открытым языком, поэтому цель этого руководства - объяснить, как «думать» на языке регулярных выражений и строить функции поиска снизу вверх.

Если вы уже знакомы с регулярным выражением или являетесь опытным пользователем, присылайте предложения или улучшения кода! Цель этого руководства - документировать этот очень полезный инструмент для более широкого сообщества Stata.

Часть I: Основы

Начнем с простого примера. Здесь мы генерируем ячейку со следующей записью:

clear
set obs 1
gen x = ""
replace x = "abcdefghiJKLMN 11123" in 1

Он содержит строчные буквы, заглавные буквы и цифры. Давайте сгенерируем простой поиск по шаблону строки:

gen t1 = ustrregexm(x, "def")

Это указывает Stata найти строку def, и, если она существует, вернуть 1 или 0 в противном случае. Здесь m в ustrregexm означает совпадение. Как только Stata находит совпадение, он также сохраняет информацию о том, что было найдено. Это можно исправить следующим образом:

gen t2 = ustrregexs(0) if ustrregexm(x, "def")

где s обозначает подвыражения, которые я буду называть токенами. Токен 0 содержит все совпавшие шаблоны. Если обнаруживается более одного вхождения шаблона, то токен 0 будет содержать все совпадения, токен 1 - первый, токен 2 - второй, и так далее. Мы вернемся к токенам позже. Приведенный выше код просто сгенерирует def, который мы и хотим найти.

Если мы введем это:

gen t3 = ustrregexra(x, "[def]", "")

где ra означает заменить, в строке x выполняется поиск [def], что означает поиск любого из d, e или f, и заменяется отсутствующим. Другими словами, мы говорим вернуть все, кроме указанного нами срока. Таким образом, приведенный выше код вернет исходную строку минус def. Дополнительно, если мы укажем:

gen t4 = ustrregexra(x, "[^def]", "")

^def означает НЕ def, а квадратная скобка также означает НЕ def. Другими словами, это двойное отрицание подразумевает, что мы удаляем все, кроме def, так что в итоге мы должны получить def.

Звучит несколько запутанный способ получить то, что мы хотим, но здесь шаблон довольно прост. Для более сложных шаблонов, например, которые удаляют все числа из строки, это очень полезная команда. Также вернемся к этому позже.

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

Например:

gen t4_1 = ustrregexra("ABDEFGHIJ", "[def]", "X", 1)

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

Если мы введем эти два:

gen t5 = ustrregexs(0) if ustrregexm(x, "[a-z]+")
gen t6 = ustrregexs(0) if ustrregexm(x, "[A-Z]+")

затем мы восстановим первый набор строчных и заглавных букв в строке двух переменных.

Для чисел мы можем сделать то же самое:

gen t7 = ustrregexs(0) if ustrregexm(x, "[0-9]+")  
gen t8 = ustrregexs(0) if ustrregexm(x, "1+")  

Первая команда находит первый набор чисел в строке, а вторая находит все вхождения всех единиц.

Приведенные выше шаблоны поиска помогают нам находить текст или числа, которые соответствуют некоторому простому шаблону поиска.

Часть II: Строковые функции

Здесь мы начинаем со случайного текста, который генерируем сами:

clear
set obs 10
gen x = ""
replace x = "The quick brown fox jumps over the lazy dog." in 1
replace x = "the sun is shining. The birds are singing." in 2
replace x = "  Pi equals 3.14159265" in 3
replace x = "TheRe arE 9 plANetS in THE solar SYstem.  eARth's mOOn is round" in 4
replace x = "I LOVE Stata 16 . " in 5
replace x = "Always correct the regressions for clustered standard errors." in 6
replace x = "I get an error code r(997.55). What do i do next?" in 7
replace x = "[email protected], Tel: +43 444 5555" in 8
replace x = " [email protected], Tel: +1 800 1337. " in 9
replace x = "Firstname  Lastname  03-06-1990" in 10

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

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

help string functions

Это меню вызывает множество опций, о большинстве из которых пользователи, вероятно, не знают. Сюда также входят другие строковые функции Unicode, которые начинаются с префикса ustr. Для получения более подробной информации о Unicode, о том, что это такое и как он работает, прочтите эти два файла справки и их записи в руководстве Stata:

help unicode locale
help set locale_functions

Не вдаваясь в подробности всех строковых функций, в Stata полезно знать следующие:

gen temp1 = upper(x)
gen temp2 = lower(x)
gen temp3 = proper(x)
gen temp4 = trim(x)
gen temp5 = proper(trim(x))

Поскольку мы имеем дело с простыми алфавитами, приведенных выше команд достаточно. Но если в вашем тексте есть различные символы с диакритическими знаками, вы можете заменить указанное выше на ustrupper, ustrlower и т. Д.

Первые три меняют регистр букв, что очевидно из их названий. upper делает все заглавными, lower - строчными, а proper делает первую букву каждого слова заглавной, разделенной пробелом. Для сопоставления строк с беспорядочным текстом преобразование всего в верхний или нижний может облегчить некоторые задачи. Команда trim избавляется от пробелов в начале или в конце строковых записей. Можно также использовать ftrim или ltrim для очистки пробелов в начале (f = первый) или в конце (l = последний) строковых переменных. Такие пробелы обычно появляются при импорте из плохо закодированных файлов Excel. Иногда ведущие нули исчезают, когда важные данные в Stata (например, 00001 будет отображаться как 1, но длина будет сохранена), поэтому общий trim не всегда может быть лучшим вариантом.

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

Еще одна полезная функция length:

gen diff = length(x) - length(temp4)

Здесь мы сравниваем длину символов в исходных данных с усеченными данными. Если diff больше нуля, это означает, что пробелы были удалены.

Еще одна очень полезная функция - wordcount:

cap drop temp*
cap drop diff
gen count = wordcount(x)

Эта команда считает слова, разделенные пробелами. Более часто используемой функцией в Stata является команда split, которая по умолчанию разделяет переменные на основе пробелов. Также можно определить другие символы для синтаксического анализа строки.

Итак, если мы разделим данные путем синтаксического анализа по пустому пространству:

split x, parse(" ") gen(test)

мы получим данные в новых столбцах переменных с префиксом имени test. Для каждой строки заполненные столбцы должны точно соответствовать числу, показанному в переменной count. Для более сложного программирования в Stata есть еще одна функция tokenize, которая также помогает анализировать локальные переменные (например, с сохраненными именами файлов или временными переменными). Если столбец данных, который необходимо очистить, имеет очень структурированный строковый шаблон, например, все даты записаны в этом формате дд – мм – гггг, тогда можно использовать split и проанализировать p("-") и получить даты, месяцы и год .

Часть III: общий текстовый поиск

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

cap drop t*
gen t1 = ustrregexs(0) if ustrregexm(x, "\w+")  // first word

Приведенная выше команда возвращает первый буквенно-цифровой символ (буквы и цифры), который определяется как \w+. Без + мы получим только первую букву. Знак + означает, что мы должны продолжать поиск, пока не найдем не буквенно-цифровой символ. Обратите внимание, что \w+ не работает с исходной командой regex.

В регулярном выражении мы также можем выбирать не буквенно-цифровые символы, что определяется как:

gen t2 = ustrregexs(0) if ustrregexm(x, "\W+")  // first non-word

или просто как капитал W. В приведенном выше тексте видимый не буквенно-цифровой символ - это знак @, а невидимые - пробелы.

Аналогично для чисел:

gen t3 = ustrregexs(0) if ustrregexm(x, "\d+")  // first number

\d+ выбирает первый набор последовательных чисел. Противоположно \D+:

gen t4 = ustrregexs(0) if ustrregexm(x, "\D+")

выбирает все, что предшествует первому набору чисел. Этот способ поиска сильно отличается от обычного поиска и замены в Stata (например, с использованием subinstr), и требуется некоторое время, чтобы привыкнуть к нему.

Кратко подведем итоги основных функций регулярного выражения:

[jkl]    = j or k or l
[^jkl]   = everything except j,k, or l
[j|l]    = j or l 
[a-z]    = all lower-case letters
[a-zA-Z] = all lower and uppercase letters
[0-9]    = find all numbers

\d       all numbers            \D        all non numbers
\w       all alphanumeric       \W        all non alphanumeric
\s       all spaces             \S        all non spaces

Давайте теперь сделаем более сложный поиск. Мы хотим найти в тексте все «The». Если начать с простого варианта:

cap drop t*
gen t1  = ustrregexm(x,"The")
gen t2 = ustrregexs(0) if ustrregexm(x,"The")

Это отметит все правильно, кроме четвертой строки, где первое слово - «Там». Переменная t2 также вернет «The» для 1-й, 2-й и 4-й строк.

Если мы введем это:

gen t3 = ustrregexs(0) if ustrregexm(x,"[t|T]he")

Затем мы получаем больше совпадений, в которых записаны как заглавная, так и строчная буква «the». Это также подберет «то», которое встречается в середине предложения. Помните, что это находит первое совпадение независимо от того, где оно находится.

Мы также можем указать квантификатор ^, который указывает, что мы находим только «то», с которого начинается предложение:

gen t4 = ustrregexs(0) if ustrregexm(x, "^[t|T]he")

Сравните строку 2 переменной t4 с t1. t1 нашел заглавную «The», которая была началом второго предложения, а t4 находит первое «the», которое начинает первое предложение.

Затем попробуйте это:

gen t5 = ustrregexs(0) if ustrregexm(x, "^[t|T]he\s")

Здесь мы говорим, что найдите «the», с которого начинается предложение, за которым следует пробел \s. Это исключает «Там» в строке 4 из поиска.

Попробуйте эти два варианта:

gen t6 = ustrregexs(0) if ustrregexm(x, "(^[t|T]\w+)") 
gen t7 = ustrregexs(0) if ustrregexm(x, "(^[t|T]\w{2}\s)")

Переменная t6 возвращает все слова, которые начинаются с t или T и имеют любое количество букв впереди. Переменная t7 возвращает слова, которые начинаются с t или T, за которыми следуют две буквы и пробел.

Обратите внимание, как мы перешли от конкретного поиска слова к более общему шаблону. Хотя все эти поиски зависят от регистра, приведенные выше примеры показывают, как можно построить выражение из простых правил, чтобы получить то, что мы хотим.

Давайте теперь сосредоточимся на предложениях:

cap drop t*
gen t1 = ustrregexs(0) if ustrregexm(x, ".*")  // returns everything

Здесь опция .* возвращает все, поскольку * является «жадным» квантификатором. Эту универсальную функцию, например, можно использовать для поиска закономерностей между определенной начальной и конечной точкой.

Следующий вариант:

gen t2 = ustrregexs(0) if ustrregexm(x, ".*\.") 

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

Если мы укажем эту опцию:

gen t3 = ustrregexs(0) if ustrregexm(x, ".*\.$")

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

И еще вариант:

gen t4 = ustrregexs(0) if ustrregexm(x, ".*\.\s")

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

Здесь я также хотел бы ввести подвыражения или токены:

cap drop t*
gen t5_0 = ustrregexs(0) if ustrregexm(x, "(.*\.)(.*\.)")  
gen t5_1 = ustrregexs(1) if ustrregexm(x, "(.*\.)(.*\.)")  
gen t5_2 = ustrregexs(2) if ustrregexm(x, "(.*\.)(.*\.)")

Здесь мы определяем две опции поиска, как показано выражениями в двух круглых скобках ()(). Здесь мы говорим, что первое выражение - это все, что заканчивается точкой. Второе выражение точно такое же. Другими словами, мы находим два предложения. Переменная t5_0 возвращает все совпадения. В то время как t5_1 и t5_2 возвращают первое и второе совпадения соответственно. Другой вариант, который можно попробовать, - это шаблон поиска (.*\.)\s?(.*\.), в котором говорится, что между двумя предложениями может быть пробел, как определено в \s?.

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

gen t6 = ustrregexra(x,"[^a-zA-Z]","")
gen t7 = ustrregexra(x,"\W","")

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

Мы также можем ввести границы слов с помощью оператора \b. Это ограничивает диапазон поиска, чтобы получить точное совпадение. Например, если мы хотим найти электронное письмо, мы можем ограничить границы чем-то вроде [email protected]. Для приведенного выше текста это можно реализовать следующим образом:

cap drop t*
gen t1 = ustrregexs(0) if ustrregexm(x, "\b([a-zA-Z]+[_|\-|\.]?[a-zA-Z0-9]+@[a-zA-Z]+\.[com|net]+)\b")

Где мы открываем и закрываем с \b. Затем мы говорим найти текст, содержащий буквы. Мы разрешаем, чтобы электронное письмо могло (?) содержать подчеркивания _, тире - и точки .. После этого идет поиск по тексту и числам, за которым следует @, а затем имя домена, которое может заканчиваться на .com, .net или что-то еще, что мы хотим указать. В последней части мы также можем просто сказать, что нам нужна трехбуквенная строка \s{3}. Опять же, все это зависит от того, где мы ищем и что ищем, но важно понимать, что можно постепенно наращивать выражение, чтобы охватить все случаи и возможности, которые мы хотим охватить. Таким образом, можно начать с очень простого выражения, такого как \w+@\w+\.\w{3}, которое так же просто, как и для адреса электронной почты, и продолжать добавлять к нему условия.

Часть IV: Типовой числовой поиск

Переходим к цифрам:

cap drop t*
gen t1 = ustrregexs(0) if ustrregexm(x, "[0-9]")  
gen t2 = ustrregexs(0) if ustrregexm(x, "[0-9]+")  
gen t3 = ustrregexs(0) if ustrregexm(x, "[0-9][0-9][0-9]") 

Первая переменная t1 возвращает первое число, t2 возвращает первый набор чисел, а t3 возвращает первый набор трехзначных чисел. Для получения более упорядоченных данных достаточно этих простых функций поиска.

Мы также можем быть немного экономными в нашем выражении, указав:

gen t4 = ustrregexs(0) if ustrregexm(x, "\d{4}") 

где \d{4} говорит: найдите 4-значное число. Здесь мы также можем указать диапазон, например d{2,4}, который находит числа от 2 до 4 цифр, в зависимости от того, какое из них соответствует критериям первым.

Если мы укажем:

gen t5 = ustrregexs(0) if ustrregexm(x, "\d+-\d+")

Затем мы хотим найти цифры, разделенные дефисом. Или, если мы хотим указать числа, начинающиеся со знака +:

gen t6 = ustrregexs(0) if ustrregexm(x, "\+\d+")

Обратите внимание, что для символа используется \+, поскольку + зарезервирован для сопоставления. Мы можем сделать наш поиск немного более расплывчатым, набрав:

gen t7 = ustrregexs(0) if ustrregexm(x, "\+?\d+")

в котором говорится, что число «может» начинаться со знака «плюс». Все, что заканчивается на ?, дает немного больше гибкости.

Эта команда:

gen t8 = ustrregexs(0) if ustrregexm(x, "(\d*\.\d+)") 

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

Чтобы найти числа в приведенных выше данных, мы можем итеративно построить выражение, начиная со следующего:

cap drop t*
gen t1 = ustrregexs(0) if ustrregexm(x, "\d+")

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

gen t2 = ustrregexs(0) if ustrregexm(x, "\+?\d+")

Далее мы можем сказать, что хотим добавить к номеру больше символов:

gen t3 = ustrregexs(0) if ustrregexm(x, "\+?\d+([\s|\.|-]?)")

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

gen t4 = ustrregexs(0) if ustrregexm(x, "\+?\d+([\s|\.|-]?)(\d+)?")

и мы можем добавить то же самое, чтобы охватить все числа:

gen t5 = ustrregexs(0) if ustrregexm(x, "\+?\d+([\s|\.|-]?)(\d+)?([\s|\.|-]?)(\d+)")

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

Фактические данные I: Имена и дата рождения

Загрузим настоящий файл данных:

import delimited using "https://raw.githubusercontent.com/asjadnaqvi/COVID19-Stata-Tutorials/master/raw/file2.csv", clear bindq(strict) varn(1)

Приведенный ниже файл представляет собой подмножество стандартизированных экзаменов для 5-го класса, на которых Комиссия по образованию Пенджаба (PEC) ежегодно тестирует около 1,4 миллиона учеников в провинции Пенджаб в Пакистане. Данные взяты за год случайного экзамена и не содержат идентификаторов и результатов экзамена:

Здесь мы делаем несколько простых упражнений. Разделите имя на имя и фамилию, а день рождения разделите на год, месяц и день. Кто из них является месяцем, а какой - датой, остается только догадываться, но нередко сталкиваются с этой проблемой. Даже преобразование CSV в Excel может испортить форматы даты.

Имена можно разделить следующим образом:

gen name1 = trim(proper(ustrregexs(1))) if ustrregexm(name, "([A-Z]\w+)\s?([A-Z]\w+)?")
gen name2 = trim(proper(ustrregexs(2))) if ustrregexm(name, "([A-Z]\w+)\s?([A-Z]\w+)?")

Здесь мы используем одно и то же выражение и просто вызываем его дважды для первого и второго токенов. Обратите внимание, что здесь мы также используем trim и right одновременно. Выражение говорит, что начинаются с заглавной буквы, за которой следует любой тип букв (заглавные или строчные). Поскольку это в скобках, это первый токен. Далее может быть или не быть пробела \s., за которым следует потенциальное второе имя. Поскольку у некоторых детей в наборе данных не указаны вторые имена, знак ? после пробела и второй токен относятся к этому случаю.

Для даты рождения (dob) мы можем сначала избавиться от 0:00:

replace dob = trim(subinstr(dob,"0:00","",.))

А затем мы можем извлечь переменную года как:

gen year = ustrregexs(0) if ustrregexm(dob, "\d+$")

Это говорит о том, что выберите число, заканчивающее переменную, поскольку у нас указан знак $.

На первое свидание можно просто сказать:

gen val1 = ustrregexs(0) if ustrregexm(dob, "(\d+)")

а для второй даты это может быть определено на основе предшествующих / или символов:

gen val2 = ustrregexs(1) if ustrregexm(dob, "[/|-](\d+)")

Здесь мы не хотим восстанавливать полное совпадение, а только выражение в скобках, поэтому обратите внимание на использование ustrregexs(1), а не ustrregexs(0), которое также вернет знак / или -.

Как только эти переменные извлечены, их можно преобразовать в числовые (destring), и можно использовать некоторую логику для объединения их в правильную дату (gen date = mdy()).

Фактические данные II: школьная перепись

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

clear
https://raw.githubusercontent.com/asjadnaqvi/COVID19-Stata-Tutorials/master/raw/file1.csv

Я использую подвыборку полных данных, в которой этот набор данных содержит около 590 строк из более чем 43 000 школ. Если просмотреть столбец с названием школы, то в строке будет храниться несколько фрагментов информации:

Названия школ пишутся полностью или сокращенно и представлены во всех возможных конфигурациях: мальчики / девочки, начальная / средняя / средняя / высшая. Кроме того, переменная также содержит название школы, название деревни и информацию о поле. Чтобы прояснить это, используются следующие сокращения: GPS = государственная начальная школа, GGPS = правительство. Начальная школа для девочек. Букву P можно заменить на E = Elementary, M = Middle, H = High. Таким образом, государственная средняя школа для девочек может быть записана как GGMS или GOVT. ДЕВУШКИ M / S. Некоторые школы также расположены в базовых медицинских центрах (BHU), которые также имеют сокращенное или полное название.

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

Мы можем генерировать различные переменные типа школы в зависимости от условий, которые мы указываем.

Первое условие, которое мы можем указать, относительно простое:

gen schooltype1 = ustrregexs(0) if ustrregexm(school, "(G?[G|B][E|P|M|H|S]S)")

Здесь мы говорим, что сокращенное имя может начинаться с буквы G (представляющей правительство), за которой следует B или G (мальчик или девочка), затем следует E, P, S, M или H (уровень школы) и, наконец, S, представляющая Школа. Это уже определяет 45% данных:

tab schooltype1, m

Далее идет немного более сложная настройка:

gen schooltype2 = ustrregexs(0) if ustrregexm(school, "(GOVT)?\.?\s?((BOY|GIRL)S?)?\s?((E|M|P|H|S)/?\\?S(CHOOL)?)?")

Если мы внимательно прочитаем это, мы говорим следующее:

Имя может начинаться с GOVT, за которым может следовать. и / или пробел. Далее идет МАЛЬЧИК или ДЕВОЧКА, которые также могут быть множественного числа (МАЛЬЧИКИ или ДЕВОЧКИ). Затем может быть пробел, за которым следует тип школы, за которым, возможно, следует косая черта /, за которой следует буква S, которая также может быть написана полностью. Если мы запустим эту команду, мы определим еще 45% школ. Если вы просмотрите эту переменную, то заметите, что некоторые ошибки все еще сохраняются, поскольку не все поля фиксируются правильно. Я оставляю это в качестве упражнений, чтобы исправить :)

Можно использовать и другую логику. Можно сгенерировать условия для выявления различных полей. Например, пол может быть в одной области, уровень школы - в другой. Все это субъективно, и человек учится использовать разные методы после того, как «прочувствует» данные.

Для данных BHU мы можем использовать эту команду:

gen schooltype3 = ustrregexs(0) if ustrregexm(school, "B(ASIC)?\.?\s?H(EALTH)?\.?\s?U(NIT)?\.?")

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

Простая проверочная переменная:

gen check = schooltype1!="" | schooltype2!=""  | schooltype3!=""
tab check

Показывает, что мы определили более 91% школ.

Информация о поле, указанная в скобках в конце имени, должным образом усреднена и может быть извлечена следующим образом:

gen gender1 = ustrregexs(0) if ustrregexm(school, "\(([A-Z]+)\)")
gen gender2 = ustrregexs(1) if ustrregexm(school, "\(([A-Z]+)\)")

Первый вариант возвращает имена в квадратных скобках, а второй возвращает только информацию в скобках.

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

replace school = subinstr(school, schooltype1, "", .)
replace school = subinstr(school, schooltype2, "", .)
replace school = subinstr(school, schooltype3, "", .)
replace school = subinstr(school, gender1, "", .)
replace school = trim(school)

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

Шпаргалка

Существует множество шпаргалок по регулярным выражениям. Вот основные из них, которые следует помнить из этого руководства:

  • Соответствие символов:
[jkl]    = j or k or l
[^jkl]   = everything except j,k, or l
[j|l]    = j or l 
[a-z]    = all lower-case letters
[a-zA-Z] = all lower and uppercase letters
[0-9]    = find all numbers
\d       all numbers           \D        all non numbers
\w       all alphanumeric      \W        all non alphanumeric 
\s       all spaces            \S        all non spaces
  • Квантификаторы:
^  matches the beginning of a string
$  matches the end of a string
.  matches any character
|  the separator for or. Same as in Stata
?  matches zero or one instance    
*  matches zero or more instances  
+  matches one or more instances

Эти следующие квантификаторы чрезвычайно важны в регулярных выражениях и могут быть расширены, чтобы различать «жадные» и «притяжательные» кванторы:

Pattern             Greedy          Reluctant        Possessive
----------------------------------------------------------------
0 or 1                ?                ??                 ?+
0 or more             *                *?                 *+
1 or more             +                +?                 ++
y times               {y}              {y}?               {y}+
>=y times             {y,}             {y,}?              {y,}+
>=y and <=z           {y,z}            {y,z}?             {y,z}+

Подводя итог, жадный пытается найти все, что соответствует критериям, в то время как притяжательные совпадения нацелены на точную точность. Это сильно влияет на скорость поиска в зависимости от размера набора данных. Множество наборов данных обработки естественного языка (NLP), например архивы газетных статей или твитов, которые можно найти на таких сайтах, как Kaggle, и могут быть размером в сотни гигабайт.

Пример или жадное совпадение: cool (.+) hat будет пытаться найти все, что находится между cool и последним вхождением слова hat и, следовательно, жадно. A cool (.+?) hat остановится при первом появлении hat и, следовательно, неохотно или ленивый. В зависимости от того, что ищется, один может быть быстрее другого.

  • Специальные символы, которым требуется \ escape для буквального совпадения
[ \ ^ $ . | ? * + ( ) { }

Пример: \. соответствует буквальному десятичному числу.

  • Скобки
( ) represent sub-expression or tokens

Пример: (\w+)\.?(\d+) вернет два токена, поскольку есть две круглые скобки

[ ] is used for exact matches or exact negations

Пример: [abc] соответствует a, b или c, [^abc] соответствует чему-либо, кроме a, b или c, [a-zA-Z0-9] соответствует любой букве или цифре,

{ } is used to define match ranges

Пример: f{3} найти совпадения, где f встречается 3 раза, x{3,6} соответствует, где x встречается от 3 до 6 раз, \w+{4} соответствует четырехбуквенному слову.

Я суммировал их в следующей шпаргалке, которую можно загрузить в формате pdf из моего каталога GitHub:

В шпаргалку будут добавлены и другие продвинутые операторы.

Надеюсь, вам понравится это руководство! Пожалуйста, присылайте предложения, отзывы, комментарии, запросы, если они у вас есть. Ваша поддержка поддерживает работу этих руководств.

Об авторе

Я экономист по профессии и использую Stata с 2003 года. В настоящее время я живу в Вене, Австрия, где я работаю в Венском университете экономики и бизнеса (WU) и в Международном институте прикладного системного анализа ( МИПСА) . Вы можете найти мою исследовательскую работу по ResearchGate и Google Scholar, а также репозиторий кода Stata на GitHub. Вы можете следить за моими визуализациями Stata, связанными с COVID-19, в Twitter. Я также фигурирую на странице Stata COVID-19 в разделе визуализации и графики.

Вы можете связаться со мной через Medium, Twitter, LinkedIn или просто по электронной почте: [email protected].

Мой блог на Medium, The Stata Guide, регулярно выпускает потрясающий новый контент. Хлопайте и / или следуйте, если вам нравятся эти руководства.