anyts 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
anyts/__init__.py ADDED
@@ -0,0 +1,49 @@
1
+ # Any Texts Statistics (anyTS)
2
+ #
3
+ # Copyright (C) 2026
4
+ # Author: Sergey Shkarin <kouki.sergey@gmail.com>
5
+ # URL: <https://github.com/SergeyShk/anyTS>
6
+
7
+ import logging
8
+ from importlib.metadata import PackageNotFoundError, version
9
+
10
+ from .diversity_stats import DiversityStats
11
+ from .exceptions import (
12
+ AnyTSError,
13
+ DataFileError,
14
+ DatasetNotFoundError,
15
+ DownloadError,
16
+ ParameterError,
17
+ SourceError,
18
+ SourceTypeError,
19
+ UnknownStatError,
20
+ )
21
+ from .extractors import CharNgramsExtractor, SentsExtractor, WordsExtractor
22
+
23
+ logging.getLogger(__name__).addHandler(logging.NullHandler())
24
+
25
+ try:
26
+ __version__ = version("anyts")
27
+ except PackageNotFoundError:
28
+ __version__ = "0.0.0"
29
+ __description__ = (
30
+ "The language-independent core of text statistics libraries. Requires Python 3.11+"
31
+ )
32
+ __author__ = "Sergey Shkarin"
33
+ __author_email__ = "kouki.sergey@gmail.com"
34
+
35
+ __all__ = [
36
+ "AnyTSError",
37
+ "CharNgramsExtractor",
38
+ "DataFileError",
39
+ "DatasetNotFoundError",
40
+ "DiversityStats",
41
+ "DownloadError",
42
+ "ParameterError",
43
+ "SentsExtractor",
44
+ "SourceError",
45
+ "SourceTypeError",
46
+ "UnknownStatError",
47
+ "WordsExtractor",
48
+ "__version__",
49
+ ]
anyts/cohesion.py ADDED
@@ -0,0 +1,292 @@
1
+ from collections import Counter
2
+ from collections.abc import Collection, Iterable, Sequence
3
+ from itertools import pairwise
4
+ from math import fsum, nan
5
+ from typing import NamedTuple
6
+
7
+ import numpy as np
8
+
9
+ from .utils import check_sequence, check_words, safe_divide
10
+
11
+
12
+ def _check_sents(sents: Iterable[Iterable[object]], what: str) -> None:
13
+ """Checking a list of sentences given as collections of their elements"""
14
+ check_sequence(sents, "sentences")
15
+ for sent in sents:
16
+ check_words(sent, what)
17
+
18
+
19
+ class Overlap(NamedTuple):
20
+ """
21
+ Overlaps of the sentences of a text
22
+
23
+ Attributes:
24
+ adjacent (float): Share of adjacent pairs of sentences with a shared element
25
+ all (float): Share of all pairs of sentences with a shared element
26
+ prop_adjacent (float): Mean share of shared elements in adjacent pairs
27
+ prop_all (float): Mean share of shared elements in all pairs
28
+ """
29
+
30
+ adjacent: float
31
+ all: float
32
+ prop_adjacent: float
33
+ prop_all: float
34
+
35
+
36
+ def calc_overlap(sets: Sequence[Collection[str]], adjacent: bool = True) -> float:
37
+ """
38
+ Computing the share of pairs of sentences with a shared element
39
+
40
+ Description:
41
+ The binary overlap of Coh-Metrix (CRFNO1, CRFAO1, CRFSO1 over the
42
+ adjacent pairs, CRFNOa, CRFAOa, CRFSOa over all): a pair of sentences is
43
+ cohesive when they share at least one element; all the pairs are counted
44
+ over bit masks of the sentences, without going through them
45
+
46
+ Arguments:
47
+ sets (list[set[str]]): Elements of every sentence
48
+ adjacent (bool): Count the adjacent pairs only, otherwise all the pairs
49
+
50
+ Returns:
51
+ float: Share of the pairs with a shared element, nan for a text shorter
52
+ than two sentences
53
+
54
+ Raises:
55
+ SourceTypeError: If the sentences are a string, a Doc or an iterator, or a sentence
56
+ is not a list of strings (check_words)
57
+ """
58
+ _check_sents(sets, "elements of a sentence")
59
+ frozen = [frozenset(elements) for elements in sets]
60
+ n_sents = len(frozen)
61
+ if n_sents < 2:
62
+ return nan
63
+ if adjacent:
64
+ return sum(1 for first, second in pairwise(frozen) if not first.isdisjoint(second)) / (
65
+ n_sents - 1
66
+ )
67
+ return _count_sharing_pairs(frozen) / (n_sents * (n_sents - 1) // 2)
68
+
69
+
70
+ def calc_proportional_overlap(sets: Sequence[Collection[str]], adjacent: bool = True) -> float:
71
+ """
72
+ Computing the mean share of shared elements in pairs of sentences
73
+
74
+ Description:
75
+ The proportional overlap of Coh-Metrix (CRFCWO1, CRFCWOa): the Dice
76
+ coefficient of a pair of sentences (dice) averaged over the pairs; over all
77
+ the pairs the sum comes from the histograms of the lengths of the sentences
78
+ holding every element (see calc_overlaps), without going through them
79
+
80
+ Arguments:
81
+ sets (list[set[str]]): Elements of every sentence
82
+ adjacent (bool): Count the adjacent pairs only, otherwise all the pairs
83
+
84
+ Returns:
85
+ float: Mean share of shared elements, nan for a text shorter than two sentences
86
+
87
+ Raises:
88
+ SourceTypeError: If the sentences are a string, a Doc or an iterator, or a sentence
89
+ is not a list of strings (check_words)
90
+ """
91
+ _check_sents(sets, "elements of a sentence")
92
+ frozen = [frozenset(elements) for elements in sets]
93
+ n_sents = len(frozen)
94
+ if n_sents < 2:
95
+ return nan
96
+ if adjacent:
97
+ return sum(dice(first, second) for first, second in pairwise(frozen)) / (n_sents - 1)
98
+ return _sum_dice(frozen) / (n_sents * (n_sents - 1) // 2)
99
+
100
+
101
+ def dice(first: frozenset[str], second: frozenset[str]) -> float:
102
+ """
103
+ Computing the Dice coefficient of two sets
104
+
105
+ Arguments:
106
+ first (frozenset[str]): First set
107
+ second (frozenset[str]): Second set
108
+
109
+ Returns:
110
+ float: The coefficient 2·|A ∩ B| / (|A| + |B|), 0 for two empty sets
111
+ """
112
+ return safe_divide(2 * len(first & second), len(first) + len(second))
113
+
114
+
115
+ def calc_overlaps(sets: Sequence[Collection[str]], proportional: bool = True) -> Overlap:
116
+ """
117
+ Computing the binary and the proportional overlap over adjacent and all pairs
118
+
119
+ Description:
120
+ Gives the values of calc_overlap and calc_proportional_overlap over the
121
+ adjacent and all the pairs at once
122
+
123
+ Arguments:
124
+ sets (list[set[str]]): Elements of every sentence
125
+ proportional (bool): Compute the proportional overlap as well; without it
126
+ prop_adjacent and prop_all are nan
127
+
128
+ Returns:
129
+ Overlap: Shares of the pairs with a shared element and the mean Dice
130
+ coefficients, nan for a text shorter than two sentences
131
+
132
+ Raises:
133
+ SourceTypeError: If the sentences are a string, a Doc or an iterator, or a sentence
134
+ is not a list of strings (check_words)
135
+ """
136
+ _check_sents(sets, "elements of a sentence")
137
+ frozen = [frozenset(elements) for elements in sets]
138
+ n_sents = len(frozen)
139
+ if n_sents < 2:
140
+ return Overlap(nan, nan, nan, nan)
141
+ n_all = n_sents * (n_sents - 1) // 2
142
+ return Overlap(
143
+ sum(1 for first, second in pairwise(frozen) if not first.isdisjoint(second))
144
+ / (n_sents - 1),
145
+ _count_sharing_pairs(frozen) / n_all,
146
+ sum(dice(first, second) for first, second in pairwise(frozen)) / (n_sents - 1)
147
+ if proportional
148
+ else nan,
149
+ _sum_dice(frozen) / n_all if proportional else nan,
150
+ )
151
+
152
+
153
+ def _count_sharing_pairs(sets: Sequence[frozenset[str]], block_size: int = 4096) -> int:
154
+ """
155
+ Number of pairs of sentences with a shared element, over bit masks of the occurrences
156
+
157
+ Description:
158
+ Every element gets a mask of the sentences of a block it occurs in; the
159
+ union of the masks of sentence i gives the sentences it shares an
160
+ element with, and the bits after i are counted
161
+ """
162
+ total = 0
163
+ for start in range(0, len(sets), block_size):
164
+ end = min(start + block_size, len(sets))
165
+ masks: dict[str, int] = {}
166
+ for j in range(start, end):
167
+ bit = 1 << (j - start)
168
+ for element in sets[j]:
169
+ masks[element] = masks.get(element, 0) | bit
170
+ for i in range(end):
171
+ union = 0
172
+ for element in sets[i]:
173
+ union |= masks.get(element, 0)
174
+ if i >= start:
175
+ union >>= i - start + 1
176
+ total += union.bit_count()
177
+ return total
178
+
179
+
180
+ def _sum_dice(sets: Sequence[frozenset[str]]) -> float:
181
+ """
182
+ Sum of the Dice coefficients over all the pairs, over histograms of the lengths
183
+
184
+ Description:
185
+ A pair of sentences of lengths k and l gives 2/(k + l) for every shared
186
+ element, so the sum over the pairs holding an element is
187
+ (h·W·h - h·diag(W)) / 2, where h is the histogram of the lengths of its
188
+ sentences and W[k, l] = 2/(k + l); the sums of the elements are added
189
+ by fsum, so the result does not depend on their order
190
+ """
191
+ sizes = sorted({len(elements) for elements in sets if elements})
192
+ columns = {size: column for column, size in enumerate(sizes)}
193
+ postings: dict[str, list[int]] = {}
194
+ for elements in sets:
195
+ if not elements:
196
+ continue
197
+ column = columns[len(elements)]
198
+ for element in elements:
199
+ postings.setdefault(element, []).append(column)
200
+ rows = [row for row in postings.values() if len(row) > 1]
201
+ if not rows:
202
+ return 0.0
203
+ histograms = np.zeros((len(rows), len(sizes)))
204
+ for index, row in enumerate(rows):
205
+ np.add.at(histograms[index], row, 1)
206
+ lengths = np.array(sizes, dtype=float)
207
+ weights = 2 / (lengths[:, None] + lengths[None, :])
208
+ full = np.einsum("ik,kl,il->i", histograms, weights, histograms)
209
+ diagonal = (histograms * np.diag(weights)).sum(axis=1)
210
+ return fsum(full - diagonal) / 2
211
+
212
+
213
+ def count_given(sents: Sequence[Sequence[str]]) -> int:
214
+ """
215
+ Counting the given elements - the ones used before in the text
216
+
217
+ Description:
218
+ An element is given when the same lemma was used earlier in any sentence,
219
+ the current one included; the first occurrence is new
220
+
221
+ Arguments:
222
+ sents (list[list[str]]): Lemmas of every sentence in the order of the text
223
+
224
+ Returns:
225
+ int: Number of given elements
226
+
227
+ Raises:
228
+ SourceTypeError: If the sentences are a string, a Doc or an iterator, or a sentence
229
+ is not a list of strings (check_words)
230
+ """
231
+ _check_sents(sents, "lemmas of a sentence")
232
+ seen: set[str] = set()
233
+ given = 0
234
+ for sent in sents:
235
+ for lemma in sent:
236
+ if lemma in seen:
237
+ given += 1
238
+ seen.add(lemma)
239
+ return given
240
+
241
+
242
+ def dominant(values: Sequence[str]) -> str | None:
243
+ """
244
+ Getting the dominant value
245
+
246
+ Description:
247
+ The most frequent value, the first one of the equally frequent
248
+
249
+ Arguments:
250
+ values (list[str]): Values
251
+
252
+ Returns:
253
+ str|None: Dominant value, None for an empty list
254
+
255
+ Raises:
256
+ SourceTypeError: If the values are not a list of strings (check_words)
257
+ """
258
+ check_words(values, "values")
259
+ if not values:
260
+ return None
261
+ return Counter(values).most_common(1)[0][0]
262
+
263
+
264
+ def calc_repetition(sents: Sequence[Sequence[str]]) -> float:
265
+ """
266
+ Computing the share of adjacent pairs of sentences with the same dominant value
267
+
268
+ Description:
269
+ The repetition of the tense and of the mood of Coh-Metrix (SMTEMP): a
270
+ pair of adjacent sentences is cohesive when the dominant values of the
271
+ feature of their verbs are equal; a pair where one of the sentences has
272
+ no verb with the feature is skipped
273
+
274
+ Arguments:
275
+ sents (list[list[str]]): Values of the feature of the verbs of every sentence
276
+
277
+ Returns:
278
+ float: Share of the cohesive pairs, nan without a single pair with the feature
279
+
280
+ Raises:
281
+ SourceTypeError: If the sentences are a string, a Doc or an iterator, or a sentence
282
+ is not a list of strings (check_words)
283
+ """
284
+ _check_sents(sents, "values of a sentence")
285
+ pairs = [
286
+ (first, second)
287
+ for first, second in pairwise(dominant(sent) for sent in sents)
288
+ if first and second
289
+ ]
290
+ if not pairs:
291
+ return nan
292
+ return sum(1 for first, second in pairs if first == second) / len(pairs)
anyts/constants.py ADDED
@@ -0,0 +1,84 @@
1
+ # Lexical diversity: the conventions of koRpus and lexical-diversity
2
+ MATTR_WINDOW_LEN = 50
3
+ MTLD_TTR_THRESHOLD = 0.72
4
+ MTLD_MIN_LEN = 10
5
+ # Block of factor starts and window of offsets in the MA-MTLD and MTLD-W computation
6
+ MTLD_BLOCK_SIZE = 4096
7
+ MTLD_WINDOW_LEN = 32
8
+ HDD_SAMPLE_SIZE = 42
9
+ DIVERSITY_LOG_BASE = 10
10
+ BRUNET_W_EXPONENT = 0.172
11
+ DIVERSITY_STATS_DESC = {
12
+ "ttr": "Type-Token Ratio (TTR)",
13
+ "rttr": "Root Type-Token Ratio (RTTR)",
14
+ "cttr": "Corrected Type-Token Ratio (CTTR)",
15
+ "httr": "Herdan Type-Token Ratio (HTTR)",
16
+ "sttr": "Summer Type-Token Ratio (STTR)",
17
+ "mttr": "Maas Type-Token Ratio (MTTR)",
18
+ "dttr": "Dugast Type-Token Ratio (DTTR)",
19
+ "mattr": "Moving Average Type-Token Ratio (MATTR)",
20
+ "msttr": "Mean Segmental Type-Token Ratio (MSTTR)",
21
+ "mtld": "Measure of Textual Lexical Diversity (MTLD)",
22
+ "mamtld": "Moving Average Measure of Textual Lexical Diversity (MA-MTLD)",
23
+ "mtldw": "Moving Average Measure of Textual Lexical Diversity with Wrap (MTLD-W)",
24
+ "hdd": "Hypergeometric Distribution D (HD-D)",
25
+ "simpson_index": "Simpson's index (D)",
26
+ "inverse_simpson_index": "Inverse Simpson's index (1/D)",
27
+ "gini_simpson_index": "Gini-Simpson index (1-D)",
28
+ "hapax_index": "Hapax index (Honoré's R)",
29
+ "yule_k": "Yule's characteristic K",
30
+ "yule_i": "Yule's inverse characteristic I",
31
+ "herdan_vm": "Herdan's Vm",
32
+ "sichel_s": "Sichel's S",
33
+ "michea_m": "Michéa's M",
34
+ "brunet_w": "Brunet's W",
35
+ "dugast_k": "Dugast's k",
36
+ "baayen_p": "Baayen's P",
37
+ "hapax_ratio": "Hapax ratio",
38
+ "alpha2": "Exponent α₂",
39
+ "entropy": "Shannon entropy (bits)",
40
+ "evenness": "Evenness",
41
+ "perplexity": "Perplexity",
42
+ "zipf_alpha": "Zipf's law slope (α)",
43
+ "heaps_beta": "Heaps' law exponent (β)",
44
+ }
45
+
46
+ # Relations that are not arguments of a word: coordination, parataxis, punctuation
47
+ VALENCY_IGNORED_DEPS = frozenset({"cc", "conj", "parataxis", "punct"})
48
+
49
+ # Measures of keyness, of association of collocations, of dispersion of words and of stylometry
50
+ KEYNESS_MEASURES = {
51
+ "log_likelihood": "Log-likelihood G²",
52
+ "chi2": "Chi-square with Yates's correction",
53
+ "diff": "Difference of normalized frequencies %DIFF",
54
+ "log_ratio": "Binary logarithm of the ratio of normalized frequencies",
55
+ "bic": "Bayesian information criterion",
56
+ "ell": "Effect size for the log-likelihood",
57
+ "odds_ratio": "Odds ratio",
58
+ }
59
+ # Critical values of G² with one degree of freedom, by the level of significance
60
+ G2_CRITICAL_VALUES = {0.05: 3.84, 0.01: 6.63, 0.001: 10.83, 0.0001: 15.13}
61
+ COLLOCATION_MEASURES = {
62
+ "mi": "Mutual information MI",
63
+ "mi3": "Cubic mutual information MI³",
64
+ "t_score": "t-score",
65
+ "dice": "Dice coefficient",
66
+ "logdice": "logDice",
67
+ "log_likelihood": "Log-likelihood G²",
68
+ "npmi": "Normalized pointwise mutual information",
69
+ "min_sensitivity": "Minimum sensitivity",
70
+ }
71
+ DISPERSION_STATS_DESC = {
72
+ "dp": "Deviation of proportions DP of Gries",
73
+ "dp_norm": "Normalized DP",
74
+ "juilland_d": "Juilland's D",
75
+ "carroll_d2": "Carroll's D2",
76
+ "rosengren_s": "Rosengren's S",
77
+ "kl_divergence": "Kullback-Leibler divergence",
78
+ }
79
+ DELTA_VARIANTS = {
80
+ "burrows": "Burrows's Delta - Manhattan distance of the z-scores divided by the number of units",
81
+ "quadratic": "Argamon's quadratic Delta - Euclidean distance of the z-scores divided by the number of units",
82
+ "eder": "Eder's Delta - Manhattan distance of the z-scores weighted by rank",
83
+ "cosine": "Cosine Delta - cosine distance of the z-scores",
84
+ }
@@ -0,0 +1,49 @@
1
+ from .collocations import Collocation, collocations
2
+ from .compare import (
3
+ COMPARISON_COLUMNS,
4
+ bootstrap_median_diff,
5
+ calc_cliff_delta,
6
+ calc_cohen_d,
7
+ compare_features,
8
+ compare_values,
9
+ holm_correction,
10
+ )
11
+ from .dispersion import Dispersion, dispersion
12
+ from .keyness import FrequencyReference, Keyword, keyness
13
+ from .stylometry import (
14
+ ZetaScore,
15
+ delta,
16
+ delta_profiles,
17
+ frequency_table,
18
+ kilgarriff_chi2,
19
+ mendenhall_curve,
20
+ mendenhall_distance,
21
+ z_scores,
22
+ zeta,
23
+ )
24
+
25
+ __all__ = [
26
+ "COMPARISON_COLUMNS",
27
+ "Collocation",
28
+ "Dispersion",
29
+ "FrequencyReference",
30
+ "Keyword",
31
+ "ZetaScore",
32
+ "bootstrap_median_diff",
33
+ "calc_cliff_delta",
34
+ "calc_cohen_d",
35
+ "collocations",
36
+ "compare_features",
37
+ "compare_values",
38
+ "delta",
39
+ "delta_profiles",
40
+ "dispersion",
41
+ "frequency_table",
42
+ "holm_correction",
43
+ "keyness",
44
+ "kilgarriff_chi2",
45
+ "mendenhall_curve",
46
+ "mendenhall_distance",
47
+ "z_scores",
48
+ "zeta",
49
+ ]