Old French matières

Author

lettres_d_Anne

Published

11.02.2025

Гипотеза

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

“Характерно, что самая маленькая средневековая пародия всегда построена так, как если бы она была обломком целого и единого комического мира”

Предобработка текста

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

Также задачей было лемматизировать тексты. В udpipe даже нашлась модель обработки текста на старофранцузском языке (old_french-srcmf), однако она не сработала (по неопознанным причинам) так, как нужно (в колонке lemma все показатели были NA).

#install.packages("readtext")
library(readtext)
library(stringr)
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ purrr     1.0.4
✔ forcats   1.0.0     ✔ readr     2.1.5
✔ ggplot2   3.5.1     ✔ tibble    3.2.1
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(tidytext)
library(tokenizers)
#library(udpipe)
library(ggplot2)
data1 <- readtext("aucassin.txt")
data2 <- readtext("ErecKu.txt")
data3 <- readtext("PercevalKu.txt")
text1 <- data1$text
text2 <- data2$text
text3 <- data3$text
get_clean_texts <- function(text) {
  #text <- str_replace_all(text,"^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$"," ")
  text <- str_replace_all(str_to_lower(text), "\n", " ")
  text <- str_replace_all(text,"\t", " ")
  text <- str_replace_all(text, "[:digit:]", " ")
  text <- str_replace_all(text, "’", "'")
  text <- str_replace_all(text, "[[:punct:]]", "")
  text <- str_replace_all(text, "\\s+", " ")
  text <- str_replace_all(text, "txmbfmcorpusorg", " ")
  text <- str_replace_all(text, "\fbase de français médiéval aucassin et nicolette", " ")
  text <- str_replace_all(text,"\fbase de français médiéval chrétien de troyes erec et enide", " ")
  text <- str_replace_all(text, "\fbase de français médiéval chrétien de troyes conte du graal perceval", " ")
  }
listoftexts <- c(text1,text2,text3)
cleantexts <- map(listoftexts, get_clean_texts) #map (есть функция, написанная для одного текста (очистка и map нужен для того, чтобы применить очистку ко всем текстам))
textnames <- c("Aucassin et Nicolette","Perceval","Erec et Enide") #названия текстов для первой колонки (с использованием вектора, вектор это создание списка)
roman <- tibble("textname"=textnames,"text"=cleantexts) #таблица с названиями текстов и текстами 
text_tokens <- roman |> #таблица с токенами
  unnest_tokens("word", "text") #только выделение токенов

Расчет относительной частоты встречаемости

1 шаг — раcчет частоты встречаемости

2 шаг — расчет количества всех слов в каждом документе

3 шаг — объеденение таблиц с этими показателями

4 шаг — расчет относительной частоты встречаемости (частота встречаемости слова / количество всех слов в документе)

roman_word_counts <- text_tokens |>  #частота встречаемости слов
  count(textname, word, sort = TRUE) #функция count считает тут количество токенов (сортировка по частотности): и получаем какой токен в каком тексте

total_counts <- roman_word_counts |>
  group_by(textname) |> #группирует по указанной колонке (добавляем знак пайпа чтобы он знал с чем именно работать дальше: с группированным текстом)
  summarise(total = sum(as.integer(n))) #для получения соотношения одного слова на весь текст #as integer для того, чтобы показать что n превращаем именно в цифру

roman_word_counts <- roman_word_counts |> #перезаписываем чтобы на следующем шаге работать с таблицей вместо того чтобы создавать новую таблицу (вместо этого одну из имеющихся перезаписываем)
  left_join(total_counts) # к roman_word_counts присоединяем тоутал каунтс
Joining with `by = join_by(textname)`
roman_word_thefrequency <- roman_word_counts |>
  mutate(thefrequency = round((n / total), 5)) #mutate для вычисления (в данном случае для деления: меньшее делим на большее; процент/доля)

head(roman_word_thefrequency)
# A tibble: 6 × 5
  textname      word      n total thefrequency
  <chr>         <chr> <int> <int>        <dbl>
1 Erec et Enide et     2636 53004       0.0497
2 Perceval      et     1881 39880       0.0472
3 Erec et Enide que    1464 53004       0.0276
4 Erec et Enide de     1365 53004       0.0258
5 Erec et Enide li     1213 53004       0.0229
6 Perceval      de     1169 39880       0.0293

TF IDF

roman_word_thefrequencyidf <- roman_word_thefrequency |> #(обозначаем что конкретно с этой таблицей работаем с частотнотсью)
  bind_tf_idf(word, textname, n) #idf=inverse document frequency (важность какого-то слова для коллекции текстов)

head(roman_word_thefrequencyidf)
# A tibble: 6 × 8
  textname      word      n total thefrequency     tf   idf tf_idf
  <chr>         <chr> <int> <int>        <dbl>  <dbl> <dbl>  <dbl>
1 Erec et Enide et     2636 53004       0.0497 0.0497     0      0
2 Perceval      et     1881 39880       0.0472 0.0472     0      0
3 Erec et Enide que    1464 53004       0.0276 0.0276     0      0
4 Erec et Enide de     1365 53004       0.0258 0.0258     0      0
5 Erec et Enide li     1213 53004       0.0229 0.0229     0      0
6 Perceval      de     1169 39880       0.0293 0.0293     0      0

Визуализация

Визуализированные слова с наиболее высокими показателями TF IDF по каждому из документов.

roman_word_thefrequencyidf |> 
  arrange(-tf_idf) |> #сортировка
  group_by(textname) |> #группирует по названиям документа
  top_n(20) |> 
  ungroup() |> #без этого сломается
  ggplot(aes(reorder_within(word, tf_idf, textname), tf_idf, fill = textname)) +
  geom_col(show.legend = F) +
  labs(x = NULL, y = "tf-idf") +
  facet_wrap(~textname, scales = "free") +
  scale_x_reordered() +
  coord_flip()
Selecting by tf_idf

Постскриптум

Результаты TF IDF по каждому из документов предложили не так много вариантов для интерпретаций. Возможно, и даже абсолютно точно стоило бы улучшить качество предобработки текста (убрать стоп-слова, добиться лемматизации слов на старофранцузском и др.)