Сравнение корпусов¶
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, который не объектLanguagespaCy;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}