Перейти к содержанию

Сравнение корпусов

compare_corpora(a, b, window=1000, features=None, labels=('A', 'B'), n_bootstrap=1000, seed=0, min_words=None)

Сравнивает два корпуса русских текстов по всем признакам текста сразу: какие статистики различают авторов, жанры, переводы, человеческие и сгенерированные тексты - и насколько. Для отдельных слов то же делает keyness, для расстояний между текстами - delta.

Тексты режутся подряд на окна ровно по window слов функцией split_windows, так что окна всех текстов одной длины; неполный остаток в конце текста и текст короче окна отбрасываются, если min_words не разрешает окна короче. Признаки каждого окна считает text_features или своя функция, а таблицы двух корпусов сравнивает compare_features ядра anyTS.

Чехов против Толстого по прозе из набора RussianLiterature: 77 и 42 произведения, 249 и 1422 окна по 1000 слов, около минуты.

from ruts.corpus import compare_corpora
from ruts.datasets import RussianLiterature

rl = RussianLiterature()
chekhov = list(rl.get_texts(genre="prose", author="Чехов"))
tolstoy = list(rl.get_texts(genre="prose", author="Толстой"))

result = compare_corpora(chekhov, tolstoy, window=1000, labels=("Чехов", "Толстой"))
columns = [
    "median_Чехов",
    "median_Толстой",
    "ci_low",
    "ci_high",
    "cohen_d",
    "cliff_delta",
    "auc",
    "p_holm",
]
result[columns].head(10).round(3)
result.loc["sents_mean", ["n_Чехов", "n_Толстой", "n_texts_Чехов", "n_texts_Толстой"]].to_dict()
                                   median_Чехов  median_Толстой  ci_low  ci_high  cohen_d  cliff_delta    auc  p_holm
cohesion_content_overlap_2_sent           0.061           0.099  -0.042   -0.030   -0.976       -0.815  0.092     0.0
cohesion_noun_overlap_2_sent              0.081           0.141  -0.069   -0.050   -1.077       -0.759  0.121     0.0
cohesion_content_overlap_all              0.072           0.146  -0.097   -0.055   -1.030       -0.722  0.139     0.0
cohesion_content_overlap_adjacent         0.171           0.310  -0.171   -0.116   -1.222       -0.715  0.142     0.0
cohesion_content_overlap_prop_all         0.013           0.021  -0.011   -0.007   -0.446       -0.714  0.143     0.0
punct_ellipsis                           15.000           2.000   6.000   25.000    1.868        0.706  0.853     0.0
cohesion_noun_overlap_adjacent            0.106           0.218  -0.138   -0.087   -1.049       -0.668  0.166     0.0
cohesion_noun_overlap_all                 0.045           0.095  -0.071   -0.035   -0.846       -0.658  0.171     0.0
punct_exclamation                        14.000           4.000   7.000   14.000    1.671        0.657  0.829     0.0
cohesion_argument_overlap_2_sent          0.161           0.215  -0.083   -0.038   -1.013       -0.616  0.192     0.0
{'n_Чехов': 249, 'n_Толстой': 1422, 'n_texts_Чехов': 57, 'n_texts_Толстой': 41}

Сильнее всего корпуса различает связность: Толстой чаще повторяет существительные и знаменательные слова в соседних и следующих предложениях, а у Чехова в разы больше многоточий и восклицаний на 1000 слов. Повторы в соседних и всех парах предложений отчасти повторяют длину предложения: в длинном предложении больше слов, которые могут повториться (ρ Спирмена cohesion_content_overlap_all с sents_mean по окнам Чехова - 0.86, Толстого - 0.66), а доля повторенных лемм в двух следующих предложениях (cohesion_content_overlap_2_sent) с ней почти не связана (0.07 и 0.23). Дальше по списку у Чехова короче предложения, выше доля личных форм глагола и ниже - причастий и деепричастий, а Толстой сложнее по всем формулам удобочитаемости. Двадцать произведений Чехова и одно Толстого короче 1000 слов и не дают ни одного окна, так что окна - из 57 и 41 произведения; с окном поменьше в сравнение вошли бы и они.

p-значения считают 1671 окно независимыми (см. предупреждение ниже): у 111 из 147 признаков поправленное p-значение ниже 0.01, но большой эффект по дельте Клиффа только у 20. Интервал разности медиан пересэмплирует целые произведения и надежнее: он не включает нуль у 81 из этих 111 признаков, а для длины предложения охватывает от −5.2 до −1.6 слова, тогда как по одним окнам вышло бы от −4.1 до −2.8. Для многоточий он охватывает от 6 до 25 на 1000 слов: произведения Чехова сильно различаются по ним - в среднем от долей единицы до 84 на 1000 слов.

Параметры

Параметр Тип По умолчанию Описание
a list[str] - Тексты первого корпуса
b list[str] - Тексты второго корпуса
window int 1000 Размер окна в словах; None - тексты целиком
features Callable None Функция признаков текста; None - text_features
min_words int None Наименьшее число слов в окне, не больше window; None - окно целиком, при window=None - одно слово

labels, n_bootstrap и seed передаются в compare_features. compare_corpora первым делом проверяет их функцией check_comparison_params, так что неверное имя корпуса или зерно дает ошибку до обработки текстов.

Исключения:

  • SourceTypeError - тексты, которые не список строк, features, который нельзя вызвать;
  • SourceError - корпус без окна из min_words и более слов;
  • ParameterError - window или min_words, который не целое число не меньше единицы, min_words больше window, метки, число выборок или зерно, которые не проходят check_comparison_params.

Признаки

text_features(text, nlp=None)

Возвращает 147 признаков текста с префиксом по источнику, с пайплайном nlp - 192; неопределенный признак равен nan.

Префикс Признаки Источник
basic_ доли длинных, сложных, простых, одно- и многосложных слов, букв, пробелов и знаков препинания; буквы и слоги на слово BasicStats
readability_ все формулы удобочитаемости и сводный класс ReadabilityStats
diversity_ меры лексического разнообразия, кроме неустойчивой на окне Orlov's Z DiversityStats
morph_ доли частей речи и признаков с одним значением от числа слов (morph_pos_NOUN, morph_polarity_Neg, morph_style_variant_Short) и доли значений внутри остальных признаков (morph_case_Gen, morph_tense_Past, morph_degree_Cmp; у двузначных - одна доля, morph_number_Plur) MorphStats по ruMorph
sents_ средняя длина предложения в словах, стандартное отклонение, коэффициент вариации, автокорреляция соседних длин (sentence_rhythm) - ритм текста sentence_lengths
punct_ частоты знаков по типам на 1000 слов и доля буквы ё punctuation_profile
cohesion_ повторы слов в предложениях, местоимения, данность, темпоральная связность и коннекторы; без повторов между абзацами (*_para): окно режет абзацы текста CohesionStats по ruMorph
syntax_ все синтаксические статистики разбора, только с пайплайном nlp; предложения разбора - те же, что в остальных группах. Листья, поддеревья, наибольшая длина зависимости и глубина дерева почти повторяют длину предложения sents_mean SyntaxStats

На окно в 1000 слов уходит около 0.04 с. Синтаксическим признакам нужен разбор, поэтому в набор по умолчанию они не входят: их добавляет пайплайн spaCy с парсером и частями речи в nlp, а в compare_corpora и corpus_features он передается через functools.partial, см. пример ниже.

Доли пробелов, букв и знаков (basic_p_spaces, basic_p_letters, basic_p_punctuations) считаются по символам как есть: отступы строк, двойные и неразрывные пробелы в файлах отражают верстку издания, а не текст. В корпусе из разных источников их стоит схлопнуть заранее, например re.sub(r"[^\S\n]+", " ", text).

Параметр Тип По умолчанию Описание
text str - Строка текста
nlp Language None Пайплайн spaCy с парсером и частями речи для признаков syntax_; None - без них

Исключения:

  • SourceTypeError - текст, который не строка, nlp, который не объект Language spaCy;
  • SourceError - текст без слов, текст длиннее max_length пайплайна, пайплайн без парсера или частей речи.
corpus_features(texts, window=1000, features=text_features, min_words=None)

Возвращает матрицу признаков окон с индексом (номер текста, номер окна), в том числе для своих классификаторов; уровень text этого индекса и пересэмплирует бутстрэп compare_features. Тексты без слов и окна короче min_words пропускаются. compare_corpora - это corpus_features для каждого корпуса и compare_features; разделение нужно, когда признаки посчитаны один раз для нескольких корпусов, а сравнить надо пары, например всех авторов попарно.

Параметр Тип По умолчанию Описание
texts list[str] - Тексты корпуса
window int 1000 Размер окна в словах; None - тексты целиком
features Callable text_features Функция признаков текста
min_words int None Наименьшее число слов в окне, не больше window; None - окно целиком, при window=None - одно слово

Исключения:

  • SourceTypeError - тексты, которые не список строк, features, который нельзя вызвать;
  • SourceError - корпус без окна из min_words и более слов;
  • ParameterError - window или min_words, который не целое число не меньше единицы, min_words больше window.

Свои признаки передаются в features функцией текста, в том числе text_features с пайплайном; с разбором то же сравнение занимает около пяти минут:

from functools import partial

from ruts import load_pipeline
from ruts.corpus import compare_corpora, text_features

nlp = load_pipeline()
result = compare_corpora(
    chekhov,
    tolstoy,
    window=1000,
    features=partial(text_features, nlp=nlp),
    labels=("Чехов", "Толстой"),
)
result.filter(like="syntax_", axis=0)[columns].head(5).round(3)
                                    median_Чехов  median_Толстой  ci_low  ci_high  cohen_d  cliff_delta    auc  p_holm
syntax_converb_clauses_per_sent            0.101           0.207  -0.138   -0.086   -1.034       -0.608  0.196     0.0
syntax_participle_clauses_per_sent         0.040           0.117  -0.098   -0.056   -0.877       -0.586  0.207     0.0
syntax_nominal_chains_per_sent             0.075           0.164  -0.121   -0.062   -0.819       -0.579  0.210     0.0
syntax_tree_depth                          3.023           3.634  -0.906   -0.405   -1.012       -0.541  0.229     0.0
syntax_mean_clause_len                     4.965           5.808  -1.257   -0.479   -0.886       -0.534  0.233     0.0

Сравнение таблиц признаков

compare_features(table_a, table_b, labels=('A', 'B'), n_bootstrap=1000, seed=0)

Сравнивает два корпуса признак за признаком. Каждый корпус - таблица признаков своих окон, частей текстов примерно одинакового размера, чтобы признаки не зависели от длины текстов. Результат - по строке на столбец, по убыванию модуля дельты Клиффа, признаки без статистик - в конце. Бутстрэп пересэмплирует целые тексты по уровню text индекса; без него каждая строка считается отдельным текстом. У каждого признака свой генератор, по зерну и имени признака, так что его интервал не зависит от остальных столбцов. Столбец, которого нет в одной из таблиц, дает nan.

Параметр Тип По умолчанию Описание
table_a DataFrame - Признаки окон первого корпуса, по строке на окно
table_b DataFrame - Признаки окон второго корпуса
labels tuple[str, str] ('A', 'B') Имена корпусов для столбцов, две строки, дающие разные столбцы
n_bootstrap int 1000 Число выборок бутстрэпа
seed int / Generator 0 Зерно генератора случайных чисел, неотрицательное целое число, или Generator numpy; None - случайное

Таблица, которая не DataFrame, повторяет имя столбца или содержит столбец, который не числовой, дает SourceTypeError; остальные параметры проверяет check_comparison_params.

check_comparison_params(labels=('A', 'B'), n_bootstrap=1000, seed=0)

Проверяет параметры compare_features до обработки текстов: дает ParameterError, если метки - не две строки, дающие разные столбцы (("diff", "B") повторило бы median_diff), число выборок бутстрэпа - не целое число не меньше единицы или зерно - не None, не неотрицательное целое число и не Generator numpy.

Статистики

Для признака со значениями \(x_1 \dots x_{n_A}\) в корпусе A и \(y_1 \dots y_{n_B}\) в корпусе B (неопределенные и бесконечные значения отброшены; при менее чем двух значениях на стороне - nan):

Столбец Описание
mean_A, mean_B, median_A, median_B средние и медианы
median_diff, ci_low, ci_high разность медиан и ее перцентильный бутстрэп-интервал 95%: оба набора пересэмплируются n_bootstrap раз (bootstrap_median_diff)
cohen_d \(d = (\bar{x} - \bar{y}) / s\), \(s\) - объединенное стандартное отклонение; 0.2 - малый эффект, 0.5 - средний, 0.8 - большой (calc_cohen_d)
cliff_delta \(\delta = P(x > y) - P(x < y)\) от −1 до 1; \(\lvert\delta\rvert\) < 0.147 - пренебрежимый эффект, < 0.33 - малый, < 0.474 - средний, иначе большой (Romano и др. 2006; calc_cliff_delta)
auc признак как классификатор: доля пар окон, где значение в A больше, чем в B, ничьи за половину; \(\delta = 2 \cdot AUC - 1\), 0.5 - признак не различает корпуса
u, p_value U-критерий Манна-Уитни и двустороннее p-значение (scipy.stats.mannwhitneyu)
p_holm p-значение с поправкой Холма на число признаков (holm_correction)
n_A, n_B число окон с определенным значением
n_texts_A, n_texts_B число текстов, из которых взяты эти окна

Столбцы - anyts.corpus.COMPARISON_COLUMNS, где суффиксы a и b заменены метками корпусов. Дельта Клиффа и AUC считаются из той же U-статистики и согласованы; d Коэна чувствителен к выбросам и отклонениям от нормальности, его стоит смотреть рядом с дельтой.

Окна одного текста не независимы

Критерий и размеры эффекта считают окна независимыми наблюдениями, а окна одного текста не независимы: при небольшом числе текстов p-значения слишком малы и отражают выбор текстов не меньше, чем сами корпуса. Бутстрэп пересэмплирует целые тексты (кластерный бутстрэп), так что его интервал учитывает разброс между текстами; ему нужно не меньше двух текстов с каждой стороны, а при немногих текстах он груб.

Функции статистик

Статистики строки доступны по отдельности из ruts.corpus:

calc_cohen_d(values_a, values_b)

d Коэна с объединенными выборочными дисперсиями (ddof=1); nan при менее чем двух значениях на стороне или без разброса.

Параметр Тип По умолчанию Описание
values_a list[float] - Значения в первом корпусе
values_b list[float] - Значения во втором корпусе

Значение, которое не число и не пропуск (None, NA), и значения, которые не плоский список, дают SourceTypeError.

calc_cliff_delta(values_a, values_b)

Дельта Клиффа - доля пар, где первое значение больше, минус доля пар, где оно меньше; nan для пустого набора или неопределенного значения. Считается сортировкой, за время \(O((n_A + n_B) \log n_B)\) и с линейной памятью.

Параметр Тип По умолчанию Описание
values_a list[float] - Значения в первом корпусе
values_b list[float] - Значения во втором корпусе

Значение, которое не число и не пропуск (None, NA), и значения, которые не плоский список, дают SourceTypeError.

bootstrap_median_diff(values_a, values_b, n_bootstrap=1000, rng=None, confidence=0.95, texts_a=None, texts_b=None)

Перцентильный бутстрэп-интервал разности медиан. С texts_a и texts_b пересэмплируются целые тексты, а медиана выборки берется по значениям ее текстов; при менее чем двух текстах на стороне интервал - nan.

Параметр Тип По умолчанию Описание
values_a list[float] - Значения в первом корпусе
values_b list[float] - Значения во втором корпусе
n_bootstrap int 1000 Число выборок бутстрэпа
rng Generator None Генератор случайных чисел numpy; None - новый без зерна
confidence float 0.95 Уровень доверия
texts_a list None Тексты значений в первом корпусе; None - каждое значение пересэмплируется отдельно
texts_b list None Тексты значений во втором корпусе

Значение, которое не число и не пропуск, значения, которые не плоский список, и тексты, которые не метки одного типа, дают SourceTypeError; n_bootstrap, который не целое число не меньше единицы, confidence, который не число в интервале (0, 1), число текстов, не равное числу значений, и rng, который не Generator numpy, - ParameterError.

holm_correction(p_values)

Поправка Холма на множественные сравнения: p-значения сортируются по возрастанию, i-е умножается на (m − i + 1), где m - число определенных значений, затем берется накопленный максимум, ограниченный единицей; nan остается nan.

Параметр Тип По умолчанию Описание
p_values list[float] - p-значения

P-значение, которое не число и не пропуск, и p-значения, которые не плоский список, дают SourceTypeError, а p-значение вне [0, 1] - ParameterError.

from ruts.corpus import calc_cliff_delta, calc_cohen_d, holm_correction

round(calc_cohen_d([2, 4, 6, 8], [1, 3, 5, 7]), 3)
calc_cliff_delta([2, 4, 6, 8], [1, 3, 5, 7])
holm_correction([0.01, 0.04, 0.03]).round(3).tolist()
0.387
0.25
[0.03, 0.06, 0.06]

Окна текста

split_windows(text, window=1000, min_words=None)

Режет текст подряд на окна ровно по window слов, так что признаки, зависящие от длины, сравнимы между текстами. Остаток становится окном, только если в нем не меньше min_words слов (по умолчанию окно целиком, то есть остаток отбрасывается). Граница проходит перед первым словом окна и его открывающими знаками; симметричный знак, например прямая кавычка или тире вставки, закрывает предыдущее окно, если примыкает к его последнему слову или стоит после закрывающего знака. Пробельные символы по краям окон снимаются; window=None дает текст целиком. Слова - слова WordsExtractor, знаки - из ruts.constants: кавычки, скобки и тире открывают фразу (OPENING_MARKS), прямые и английские кавычки и тире симметричны (SYMMETRIC_MARKS), а конечные знаки, запятая, точка с запятой, закрывающие скобки и кавычка-елочка закрывают ее (CLOSING_MARKS).

Параметр Тип По умолчанию Описание
text str - Строка текста
window int 1000 Размер окна в словах; None - текст целиком
min_words int None Наименьшее число слов в окне; None - окно целиком, при window=None - одно слово

Текст без слов или короче min_words дает пустой список.

Исключения:

  • SourceTypeError - текст, который не строка;
  • ParameterError - window или min_words, который не целое число не меньше единицы, min_words больше окна.
from ruts.corpus import split_windows

split_windows("Раз два три четыре пять", 2, min_words=1)
split_windows("Он сказал: «Иди домой». Потом он вышел из комнаты", 4)
['Раз два', 'три четыре', 'пять']
['Он сказал: «Иди домой».', 'Потом он вышел из']

Ритм предложений

sentence_rhythm(lengths)

Дает ритм предложений по их длинам в словах: среднее sents_mean, стандартное отклонение sents_std, коэффициент вариации sents_cv и автокорреляцию лага 1 - связь длин соседних предложений sents_autocorr (Yule, 1939). Отклонению нужно два предложения, автокорреляции - три неравной длины, коэффициенту вариации - среднее больше нуля; иначе, как и без предложений, признак равен nan.

Параметр Тип По умолчанию Описание
lengths list[int] - Длины предложений в словах

Исключения:

  • SourceTypeError - длины, которые не список целых чисел;
  • ParameterError - отрицательная длина.
from ruts.corpus import sentence_rhythm

{key: round(value, 3) for key, value in sentence_rhythm([4, 8, 2, 7]).items()}
{'sents_mean': 5.25, 'sents_std': 2.754, 'sents_cv': 0.525, 'sents_autocorr': -0.794}