tidyverse
Я ожидаю, что вы знакомы со следующими функциями из tidyverse
%>%
Подробнее
%>%
– пайп, конвеер, велосипед (добавляется горячими клавишами Ctrl+Shift+M
/Cmd+Shift+M
). Этот оператор передает результат работы одной функции в другую, что повышает читаемость. Сравните:
## [1] 1.00 1.00 0.99 0.98 0.95 0.92 0.87 0.81 0.81 0.74 0.73 0.65 0.64 0.53 0.38
и
## [1] 1.00 1.00 0.99 0.98 0.95 0.92 0.87 0.81 0.81 0.74 0.73 0.65 0.64 0.53 0.38
filter()
Подробнее
Функция filter()
позволяет фильтровать колонки по одному и более условию:
arrange()
Подробнее
Функция arrange()
позволяет сортировать одну и более колонок:
select()
Подробнее
Функция select()
позволяет выбирать колонки:
Отрицательный индекс позволяет выкидывать колонки:
distinct()
Подробнее
Функция distinct()
позволяет фильтровать колонки по одному и более условию:
group_by() %>% summarise()
Подробнее
Функции group_by()
и summarise()
часто используют вместе. Для начала разберемся с функцией summarise()
, эта функция агрегирует какое-то саммари по всей таблице:
Функция group_by()
позволяет создавать в датасете группировки на основании одной или нескольких перемнных, так что summurise()
работает на каждой из групп:
## `summarise()` ungrouping output (override with `.groups` argument)
В результате работы функции group_by()
получается сгруппированная таблица, в которой нельзя, например, менять группирующие переменные. Если все же хочется это сделать, то можно использовать функцию ungroup()
.
mutate()
Подробнее
Функция mutate()
позволяет создавать новые переменные в датасете:
В комбинации с функцией group_by()
позовляет сохранить какую-то описательную статистику как отдельную колонку (сравни с summarise()
):
count()
Подробнее
Функция count()
позволяет создать поддатасет с уникальными комбинациями признака/признаков:
Иногда бывает полезно использовать аргумент sort = TRUE
, чтобы получить отсортированный датасет.
top_n()
Подробнее
Функция top_n()
полезно использовать с функций count()
, так как она возвращает первые n строчек, где n – количество:
## Selecting by n
ggplot()
read_csv()
В tidyverse
входит пакет stringr
, который позволяет работать со строками, мы посмотрим несколько функций, которые будут нам сегодня полезны:
str_length()
str_detect()
Подробнее смотрите материалы с мастерской АнДана на Летней Школе.
В пакете readr
(входит в tidyverse
) для чтения текста есть функция read_lines()
. В качестве первой переменной может выступать путь к файлу на компьютере или интернет ссылка:
t <- read_lines("https://raw.githubusercontent.com/agricolamz/2020.11.01_appcogn_text_analysis/master/data/Chang.txt")
head(t)
## [1] "Тед Чан"
## [2] "История твоей жизни"
## [3] "Твой отец собирается задать мне вопрос. Это самый важный момент в нашей жизни, и я хочу"
## [4] "запомнить все до малейшей детали. Уже за полночь, но мы только что вернулись домой после"
## [5] "ужина в ресторане и веселого шоу и сразу выходим в патио полюбоваться полной луной. Хочу"
## [6] "танцевать! — объявляю я, и твой отец подтрунивает надо мной, но мы начинаем скользить в"
Тексты хранятся в интернете по разному. Часто бывает так, что текст дигитализировали так, как он напечатан, так что в результате каждая строка в печатной книжке соответствует строке в текстовом файле (так, например, в нашем примере). Такой файл следует склеить воедино, используя пробел в качестве разделителя:
## [1] 1
## [1] 117398
При таком слиянии, стоит проверить, не было ли в анализируемом тексте знаков переноса, иначе они сольются неправильно:
## [1] "... она запо- лучила ..."
Проблемы с кодировкой
Ошибка с кодировкой бывает нескольких типов:
html
-вьювере (после команды View()
)Лечение всегда находится, но оно частное для разных операционных систем
gutenbergr
Пакет gutenbergr
является API для очень старого проекта Gutenberg.
Все самое важное в этом пакете хранится в датасете gutenberg_metadata
## tibble [51,997 × 8] (S3: tbl_df/tbl/data.frame)
## $ gutenberg_id : int [1:51997] 0 1 2 3 4 5 6 7 8 9 ...
## $ title : chr [1:51997] NA "The Declaration of Independence of the United States of America" "The United States Bill of Rights\r\nThe Ten Original Amendments to the Constitution of the United States" "John F. Kennedy's Inaugural Address" ...
## $ author : chr [1:51997] NA "Jefferson, Thomas" "United States" "Kennedy, John F. (John Fitzgerald)" ...
## $ gutenberg_author_id: int [1:51997] NA 1638 1 1666 3 1 4 NA 3 3 ...
## $ language : chr [1:51997] "en" "en" "en" "en" ...
## $ gutenberg_bookshelf: chr [1:51997] NA "United States Law/American Revolutionary War/Politics" "American Revolutionary War/Politics/United States Law" NA ...
## $ rights : chr [1:51997] "Public domain in the USA." "Public domain in the USA." "Public domain in the USA." "Public domain in the USA." ...
## $ has_text : logi [1:51997] TRUE TRUE TRUE TRUE TRUE TRUE ...
## - attr(*, "date_updated")= Date[1:1], format: "2016-05-05"
Например, сейчас мы можем понять, сколько книг на разных языках можно скачать из проекта:
Как видно, в основном это тексты на английском. Сколько авторов в датасете?
Сколько произведений Джейн Остин (не перепутайте с другими Остин) есть в датасете?
Давайте скачаем “Эмму”:
## Determining mirror for Project Gutenberg from http://www.gutenberg.org/robot/harvest
## Using mirror http://aleph.gutenberg.org
Можно скачивать сразу несколько книг. Давайте добавим еще “Леди Сьюзен”:
Сколько уникальных заголовков из базы данных содержит “Sherlock Holmes”?
tidytext
Сейчас скачанные книги записаны в таблицу, где одна строка это один абзац. Хочется мочь посчитать слова. Для этого книги нужно привести в tidy формат и для этого написан пакет tidytext
(онлайн книга доступна здесь). Основное “оружие” пакета tidytext
функция unnest_tokens()
, которая переводит текст в tidy формат. В аргумент output
подается вектор с именем будущей переменной, а аргумент input
принимает переменную с текстом.
Теперь можно посчитать самые частотные слова в обоих произведениях:
Ну… Это было ожидаемо. Нужно убрать стопслова. Английские стопслова встроены в пакет (переменная stop_words
):
## Joining, by = "word"
Постройте следующий график, на котором представлены самые частотные 20 слов каждого из произведений.
reorder_within()
Как видно, на графике все не упорядочено, давайте начнем с такого примера:
books %>%
unnest_tokens(word, text) %>%
count(word, sort = TRUE) %>%
top_n(1:20) %>%
ggplot(aes(n, word))+
geom_col()
## Selecting by n
## Warning in if (n > 0) {: the condition has length > 1 and only the first element
## will be used
## Warning in min_rank(desc(wt)) <= n: longer object length is not a multiple of
## shorter object length
Если мы работаем с одним фасетом, то все проблемы может решить функция fct_reorder()
, которая упорядочивает на основании некоторой переменной:
books %>%
unnest_tokens(word, text) %>%
count(word, sort = TRUE) %>%
top_n(20) %>%
mutate(word = fct_reorder(word, n)) %>%
ggplot(aes(n, word))+
geom_col()
## Selecting by n
Однако, если мы применим это к нашим данным, то получится неупорядочено:
books %>%
unnest_tokens(word, text) %>%
count(title, word, sort = TRUE) %>%
group_by(title) %>%
top_n(20) %>%
ungroup() %>%
mutate(word = fct_reorder(word, n)) %>%
ggplot(aes(n, word))+
geom_col()+
facet_wrap(~title, scales = "free")
## Selecting by n
В пакете tidytext
есть функция reorder_within()
, которая позволяет упорядочить нужным образом:
books %>%
unnest_tokens(word, text) %>%
count(title, word, sort = TRUE) %>%
group_by(title) %>%
top_n(20) %>%
ungroup() %>%
mutate(word = reorder_within(x = word, by = n, within = title)) %>%
ggplot(aes(n, word))+
geom_col()+
facet_wrap(~title, scales = "free")
## Selecting by n
Чтобы избавиться от дополнительной подписи нужно использовать scale_y_reordered()
или scale_x_reordered()
:
books %>%
unnest_tokens(word, text) %>%
count(title, word, sort = TRUE) %>%
group_by(title) %>%
top_n(20) %>%
ungroup() %>%
mutate(word = reorder_within(x = word, by = n, within = title)) %>%
ggplot(aes(n, word))+
geom_col()+
facet_wrap(~title, scales = "free")+
scale_y_reordered()
## Selecting by n
Функция unnest_tokens()
позволяет работать не только со словами, но и, напрмиер, с биграммами:
Поиск самых частотных слов — не едиснственная задача, которую можно решать при работе с текстом. Иногда имеет смысл узнать распределение слов в произведении. Давайте посмотрим как распределены в романе “Эмма” фамилии главных героев:
stopwords
Выше мы упомянули, что в пакет tidytext
встроен список английских стопслов. Стопслова для других язков можно раздобыть списки для других языков, используя пакет stopwords
. Вместо имени языка, функция принимает ISO код языыка:
## [1] "и" "в" "во" "не" "что" "он" "на"
## [8] "я" "с" "со" "как" "а" "то" "все"
## [15] "она" "так" "его" "но" "да" "ты" "к"
## [22] "у" "же" "вы" "за" "бы" "по" "только"
## [29] "ее" "мне" "было" "вот" "от" "меня" "еще"
## [36] "нет" "о" "из" "ему" "теперь" "когда" "даже"
## [43] "ну" "вдруг" "ли" "если" "уже" "или" "ни"
## [50] "быть" "был" "него" "до" "вас" "нибудь" "опять"
## [57] "уж" "вам" "сказал" "ведь" "там" "потом" "себя"
## [64] "ничего" "ей" "может" "они" "тут" "где" "есть"
## [71] "надо" "ней" "для" "мы" "тебя" "их" "чем"
## [78] "была" "сам" "чтоб" "без" "будто" "человек" "чего"
## [85] "раз" "тоже" "себе" "под" "жизнь" "будет" "ж"
## [92] "тогда" "кто" "этот" "говорил" "того" "потому" "этого"
## [99] "какой" "совсем" "ним" "здесь" "этом" "один" "почти"
## [106] "мой" "тем" "чтобы" "нее" "кажется" "сейчас" "были"
## [113] "куда" "зачем" "сказать" "всех" "никогда" "сегодня" "можно"
## [120] "при" "наконец" "два" "об" "другой" "хоть" "после"
## [127] "над" "больше" "тот" "через" "эти" "нас" "про"
## [134] "всего" "них" "какая" "много" "разве" "сказала" "три"
## [141] "эту" "моя" "впрочем" "хорошо" "свою" "этой" "перед"
## [148] "иногда" "лучше" "чуть" "том" "нельзя" "такой" "им"
## [155] "более" "всегда" "конечно" "всю" "между"
Пакет предоставляет несколько источников списков:
## [1] "snowball" "stopwords-iso" "misc" "smart"
## [5] "marimo" "ancient" "nltk"
Давайте посмотрем какие языки сейчас доступны:
## [[1]]
## [1] "da" "de" "en" "es" "fi" "fr" "hu" "ir" "it" "nl" "no" "pt" "ro" "ru" "sv"
##
## [[2]]
## [1] "af" "ar" "hy" "eu" "bn" "br" "bg" "ca" "zh" "hr" "cs" "da" "nl" "en" "eo"
## [16] "et" "fi" "fr" "gl" "de" "el" "ha" "he" "hi" "hu" "id" "ga" "it" "ja" "ko"
## [31] "ku" "la" "lt" "lv" "ms" "mr" "no" "fa" "pl" "pt" "ro" "ru" "sk" "sl" "so"
## [46] "st" "es" "sw" "sv" "th" "tl" "tr" "uk" "ur" "vi" "yo" "zu"
##
## [[3]]
## [1] "ar" "ca" "el" "gu" "zh"
##
## [[4]]
## [1] "en"
##
## [[5]]
## [1] "en" "ja" "ar" "he" "zh_tw" "zh_cn"
##
## [[6]]
## [1] "grc" "la"
##
## [[7]]
## [1] "ar" "az" "da" "nl" "en" "fi" "fr" "de" "el" "hu" "id" "it" "kk" "ne" "no"
## [16] "pt" "ro" "ru" "sl" "es" "sv" "tg" "tr"
Мы видим, что есть несколько источников для русского языка:
## [1] 159
## [1] 559
udpipe
Пакет udpipe
представляет лемматизацию, морфологический и синтаксический анализ разных языков. Туториал можно найти здесь, там же есть список доступных языков.
Модели качаются очень долго.
## Downloading udpipe model from https://raw.githubusercontent.com/jwijffels/udpipe.models.ud.2.5/master/inst/udpipe-ud-2.5-191206/english-ewt-ud-2.5-191206.udpipe to /home/agricolamz/work/materials/2020.11.01_appcogn_text_analysis/english-ewt-ud-2.5-191206.udpipe
## - This model has been trained on version 2.5 of data from https://universaldependencies.org
## - The model is distributed under the CC-BY-SA-NC license: https://creativecommons.org/licenses/by-nc-sa/4.0
## - Visit https://github.com/jwijffels/udpipe.models.ud.2.5 for model license details.
## - For a list of all models and their licenses (most models you can download with this package have either a CC-BY-SA or a CC-BY-SA-NC license) read the documentation at ?udpipe_download_model. For building your own models: visit the documentation by typing vignette('udpipe-train', package = 'udpipe')
## Downloading finished, model stored at '/home/agricolamz/work/materials/2020.11.01_appcogn_text_analysis/english-ewt-ud-2.5-191206.udpipe'
Теперь можно распарсить какое-нибудь предложение:
Скачаем русскую модель:
## Downloading udpipe model from https://raw.githubusercontent.com/jwijffels/udpipe.models.ud.2.5/master/inst/udpipe-ud-2.5-191206/russian-syntagrus-ud-2.5-191206.udpipe to /home/agricolamz/work/materials/2020.11.01_appcogn_text_analysis/russian-syntagrus-ud-2.5-191206.udpipe
## - This model has been trained on version 2.5 of data from https://universaldependencies.org
## - The model is distributed under the CC-BY-SA-NC license: https://creativecommons.org/licenses/by-nc-sa/4.0
## - Visit https://github.com/jwijffels/udpipe.models.ud.2.5 for model license details.
## - For a list of all models and their licenses (most models you can download with this package have either a CC-BY-SA or a CC-BY-SA-NC license) read the documentation at ?udpipe_download_model. For building your own models: visit the documentation by typing vignette('udpipe-train', package = 'udpipe')
## Downloading finished, model stored at '/home/agricolamz/work/materials/2020.11.01_appcogn_text_analysis/russian-syntagrus-ud-2.5-191206.udpipe'
udpipe("Жила-была на свете крыса в морском порту Вальпараисо, на складе мяса и маиса, какао и вина.", object = rumodel)
После того, как модель скачана можно уже к ней обращаться просто по имени файла:
udpipe("Жила-была на свете крыса в морском порту Вальпараисо, на складе мяса и маиса, какао и вина.", object = "russian-syntagrus-ud-2.5-191206.udpipe")
Для работы мы воспользуемся двумя датасетами:
zo <- read_csv("https://raw.githubusercontent.com/agricolamz/2020.11.01_appcogn_text_analysis/master/data/zoshenko.csv")
zo
geom <- read_csv("https://raw.githubusercontent.com/agricolamz/2020.11.01_appcogn_text_analysis/master/data/gordon_geometry.csv")
Для начала лемматизируем полуичвшиеся тексты:
library(udpipe)
rus <- udpipe_load_model("russian-syntagrus-ud-2.5-191206.udpipe")
geom_tokenized <- udpipe(geom, object = rus)
zo_tokenized <- udpipe(zo, object = rus)
Уберем стопслова и леммы, содержащие цифры и знаки препинания
library(stopwords)
sw <- tibble(lemma = stopwords(language = "ru"))
geom_tokenized %>%
bind_rows(zo_tokenized) %>%
filter(!str_detect(lemma, "\\W|\\d")) %>%
anti_join(sw) %>%
select(doc_id, sentence_id, lemma) ->
all_texts
## Joining, by = "lemma"
Используйте библиотеку gutenbergr
и скачайте “Чувство и чувствительность”(Sense and Sensibility, gutenberg_id = 161) и “Гордость и предубеждение” (“Pride and Prejudice”, gutenberg_id = 1342). Приведите тексты к tidy формату и уберите стопслова (английские стопслова есть в переменной stop_words
пакета tidytext
).
tf-idf — важная мера, которая позволяет выделять важные для текста слова.
\[tf = \frac{количество\ употреблений\ единицы\ в\ тексте}{количество\ уникальных\ единиц\ в тексте}\] \[idf = log\left(\frac{количество\ документов\ в\ корпусе}{количество\ документов\ с\ исследуемой\ единицей}\right)\] \[TfIdf = tf \times idf\]
library(tidytext)
all_texts %>%
count(doc_id, lemma) %>%
bind_tf_idf(lemma, doc_id, n) %>%
arrange(tf_idf) %>%
group_by(doc_id) %>%
top_n(5) %>%
ungroup() %>%
mutate(lemma = reorder_within(lemma, tf_idf, doc_id)) %>%
ggplot(aes(tf_idf, lemma))+
geom_col()+
facet_wrap(~doc_id, scales = "free")+
scale_y_reordered()
Давайте попробуем посчитать всего Зощенко одним корпусом:
all_texts %>%
mutate(doc_id = ifelse(doc_id != "gordon_geometry", "zoshenko", "gordon_geometry")) %>%
count(doc_id, lemma) %>%
bind_tf_idf(lemma, doc_id, n) %>%
arrange(tf_idf) %>%
group_by(doc_id) %>%
top_n(20) %>%
ungroup() %>%
mutate(lemma = reorder_within(lemma, tf_idf, doc_id)) %>%
ggplot(aes(tf_idf, lemma))+
geom_col()+
facet_wrap(~doc_id, scales = "free")+
scale_y_reordered()
## Selecting by tf_idf
Используя созданную ранее переменную с текстами Джейн Остин без стопслов, выделите по 20 слов, важных для каждого романа.
На прошлом занятии мы разобрались, что пакет tidytext
позволяет делить не только на отдльные слова, но и смотреть на биграммы. Частотность биграмм можно использовать в подсказке слова, которую мы видим в наших телефонах:
zo %>%
unnest_tokens("bigram", text, token = "ngrams", n = 2) %>%
separate(bigram, into = c("word_1", "word_2"), sep = " ") %>%
count(word_1, word_2, sort = TRUE) ->
bigrams
Теперь у нас есть биграмы:
Вот мы и получили предложение “Однажды днем присела ждет, а я говорю: ‘не могу’”. На большом корпусе результаты будут лучше, но легко предсатвить, как сделать из этого рабочую функцию. Можно переиначить задачу и работать с символами, тогда это будет ближе к T9 на современных телефонах.
Используя тексты обоих романов создайте генератор текстов, основанный на биграммах. Какое трехсловное предложение получится, если выбирать самое частотную пару, и начать со слова I?
Мы будем использовать датасет, составленный на базе Linis Crowd
ru_sentiments <- read_csv("https://raw.githubusercontent.com/agricolamz/2020_HSE_DPO/master/data/ru_sentiment_linis-crowd.csv")
##
## ── Column specification ────────────────────────────────────────────────────────
## cols(
## words = col_character(),
## value = col_double()
## )
all_texts %>%
group_by(doc_id) %>%
left_join(ru_sentiments, by = c("lemma" = "words")) %>%
mutate(value = ifelse(is.na(value), 0, value)) %>%
group_by(doc_id, sentence_id) %>%
summarise(value = sum(value)) %>%
mutate(color = ifelse(value >= 0, "positive", "negative")) %>%
ggplot(aes(sentence_id, value, fill = color))+
geom_col()+
facet_wrap(~doc_id, scales = "free")
## `summarise()` regrouping output by 'doc_id' (override with `.groups` argument)
NLP имеет достаточно много областей применения и мы не покрыли достаточно много тем, так что если вам будет интересно, поищите следующие ключевые слова: