anyts 0.1.0__tar.gz

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-0.1.0/.gitignore ADDED
@@ -0,0 +1,27 @@
1
+ # Python
2
+ __pycache__/
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ target/
7
+
8
+ # Environments
9
+ .venv/
10
+ venv/
11
+ .env
12
+
13
+ # Tools
14
+ .coverage
15
+ coverage.xml
16
+ .mypy_cache
17
+ .pytest_cache
18
+ .ruff_cache
19
+ .ipynb_checkpoints
20
+
21
+ # Documentation
22
+ site/
23
+
24
+ # Editors and OS
25
+ .vscode
26
+ .idea
27
+ .DS_Store
@@ -0,0 +1,17 @@
1
+ MIT License
2
+ Copyright (c) 2026 Sergey Shkarin
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+ The above copyright notice and this permission notice shall be included in all
10
+ copies or substantial portions of the Software.
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17
+ SOFTWARE.
anyts-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,131 @@
1
+ Metadata-Version: 2.5
2
+ Name: anyts
3
+ Version: 0.1.0
4
+ Summary: Any Texts Statistics: the language-independent core of text statistics libraries
5
+ Project-URL: Homepage, https://github.com/SergeyShk/anyTS
6
+ Project-URL: Repository, https://github.com/SergeyShk/anyTS
7
+ Project-URL: Documentation, https://sergeyshk.github.io/anyTS/
8
+ Project-URL: Issues, https://github.com/SergeyShk/anyTS/issues
9
+ Author-email: Sergey Shkarin <kouki.sergey@gmail.com>
10
+ Maintainer-email: Sergey Shkarin <kouki.sergey@gmail.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE.txt
13
+ Keywords: CL,NLP,analytics,computational,language,linguistics,natural,processing,statistics,text
14
+ Classifier: Development Status :: 2 - Pre-Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Education
17
+ Classifier: Intended Audience :: Information Technology
18
+ Classifier: Intended Audience :: Science/Research
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3 :: Only
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
26
+ Classifier: Topic :: Text Processing
27
+ Classifier: Topic :: Text Processing :: Linguistic
28
+ Classifier: Typing :: Typed
29
+ Requires-Python: >=3.11
30
+ Requires-Dist: numpy>=1.26
31
+ Requires-Dist: pandas>=2.1
32
+ Requires-Dist: scipy>=1.11
33
+ Requires-Dist: spacy>=3.7
34
+ Description-Content-Type: text/markdown
35
+
36
+ # Any Texts Statistics (anyTS)
37
+
38
+ [![Version](https://img.shields.io/pypi/v/anyts?logo=pypi&logoColor=FFE873)](https://pypi.org/project/anyts/)
39
+ [![Supported Python versions](https://img.shields.io/pypi/pyversions/anyts.svg?logo=python&logoColor=FFE873)](https://pypi.org/project/anyts/)
40
+ [![Build](https://github.com/SergeyShk/anyTS/actions/workflows/ci.yml/badge.svg)](https://github.com/SergeyShk/anyTS/actions/workflows/ci.yml)
41
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
42
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/SergeyShk/anyTS/blob/master/LICENSE.txt)
43
+
44
+ **anyTS** is the language-independent core of the text statistics libraries [ruTS](https://github.com/SergeyShk/ruTS) (Russian) and [esTS](https://github.com/SergeyShk/esTS) (Spanish). It holds the code that does not depend on a language, so that it is written, tested and documented once; a language library adds its tokenizer, lemmatizer, [spaCy](https://github.com/explosion/spaCy) model and rules and re-exports the core under its own names.
45
+
46
+ [Documentation](https://sergeyshk.github.io/anyTS/) · [PyPI](https://pypi.org/project/anyts/)
47
+
48
+ ## Features
49
+
50
+ * **[Extraction](https://sergeyshk.github.io/anyTS/extractors/sentences/)** - sentence, word and character N-gram extractors whose tokenizer, lemmatizer and number pattern are hooks a language library overrides
51
+ * **[Lexical diversity](https://sergeyshk.github.io/anyTS/stats/diversity_stats/)** - 32 metrics: TTR and its variants, MATTR, MSTTR, MTLD, MA-MTLD, MTLD-W, HD-D, the indices of Simpson and Yule, entropy, the laws of Zipf and Heaps, over the whole text or over windows with confidence intervals
52
+ * **[Cohesion helpers](https://sergeyshk.github.io/anyTS/stats/cohesion/)** - the overlap of sentences, adjacent and over all pairs, Dice, givenness and repetition
53
+ * **[Dependency tree helpers](https://sergeyshk.github.io/anyTS/stats/syntax/)** - dependency distances, tree depth, valency and coordination chains on the labels shared by Universal Dependencies and ClearNLP
54
+ * **[Corpus measures](https://sergeyshk.github.io/anyTS/corpus/keyness/)** - keywords against a corpus or a frequency dictionary, collocations, the dispersion of a word, Burrows's Delta and its variants, Zeta, Kilgarriff's chi-square, the Mendenhall curve and the comparison of corpora with effect sizes, a bootstrap by texts and the Holm correction
55
+ * **[Exceptions](https://sergeyshk.github.io/anyTS/exceptions/)** - one hierarchy whose classes are also built-in exceptions
56
+
57
+ The reference of every function is a named section that the libraries include in their own documentation, so a formula is described in one place.
58
+
59
+ ## Installation
60
+
61
+ Requires Python 3.11 or newer.
62
+
63
+ ```bash
64
+ pip install anyts
65
+ ```
66
+
67
+ Or with [uv](https://docs.astral.sh/uv/):
68
+
69
+ ```bash
70
+ uv add anyts
71
+ ```
72
+
73
+ The dependencies are numpy, pandas, scipy and spaCy; no trained spaCy model is needed.
74
+
75
+ ## Quick start
76
+
77
+ ```python
78
+ >>> from anyts import DiversityStats, SentsExtractor, WordsExtractor
79
+
80
+ >>> text = "The cat sat on the mat. The dog sat on the log! The cat and the dog slept."
81
+
82
+ >>> SentsExtractor().extract(text)
83
+ ('The cat sat on the mat.', 'The dog sat on the log!', 'The cat and the dog slept.')
84
+
85
+ >>> ds = DiversityStats(WordsExtractor(lowercase=True).extract(text))
86
+ >>> ds.ttr, round(ds.entropy, 3)
87
+ (0.5, 2.864)
88
+ ```
89
+
90
+ A language library subclasses the extractors and overrides their hooks:
91
+
92
+ ```python
93
+ >>> class Words(WordsExtractor):
94
+ ... def lemmatize(self, word):
95
+ ... return {"sat": "sit", "slept": "sleep"}.get(word.lower(), word)
96
+
97
+ >>> Words(lowercase=True, use_lexemes=True).extract("The cat sat. The dog slept.")
98
+ ('the', 'cat', 'sit', 'the', 'dog', 'sleep')
99
+ ```
100
+
101
+ The corpus measures take lists of words:
102
+
103
+ ```python
104
+ >>> from anyts.corpus import keyness
105
+
106
+ >>> cats = WordsExtractor(lowercase=True).extract("The cat sat on the mat. The cat saw a bird. The cat slept.")
107
+ >>> dogs = WordsExtractor(lowercase=True).extract("The dog sat on the log. The dog saw a cat. The dog barked.")
108
+ >>> [(keyword.word, round(keyword.log_ratio, 2)) for keyword in keyness(cats, dogs, min_freq=2)]
109
+ [('cat', 1.58)]
110
+ ```
111
+
112
+ ## Development
113
+
114
+ The project uses [uv](https://docs.astral.sh/uv/) for dependencies and [ruff](https://docs.astral.sh/ruff/) for linting and formatting.
115
+
116
+ ```bash
117
+ git clone https://github.com/SergeyShk/anyTS.git
118
+ cd anyTS
119
+
120
+ make deps # create the environment and install all dependencies
121
+ uv run pre-commit install # hooks: linters on commit, tests on push
122
+ make lint # ruff check, ruff format --check, mypy
123
+ make test-cov # pytest with doctests and full coverage
124
+ make docs-build # mkdocs build --strict
125
+ ```
126
+
127
+ The full list of commands is in `make help`. Contribution guidelines are in [CONTRIBUTING.md](https://github.com/SergeyShk/anyTS/blob/master/CONTRIBUTING.md).
128
+
129
+ ## License
130
+
131
+ [MIT](https://github.com/SergeyShk/anyTS/blob/master/LICENSE.txt)
anyts-0.1.0/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # Any Texts Statistics (anyTS)
2
+
3
+ [![Version](https://img.shields.io/pypi/v/anyts?logo=pypi&logoColor=FFE873)](https://pypi.org/project/anyts/)
4
+ [![Supported Python versions](https://img.shields.io/pypi/pyversions/anyts.svg?logo=python&logoColor=FFE873)](https://pypi.org/project/anyts/)
5
+ [![Build](https://github.com/SergeyShk/anyTS/actions/workflows/ci.yml/badge.svg)](https://github.com/SergeyShk/anyTS/actions/workflows/ci.yml)
6
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/SergeyShk/anyTS/blob/master/LICENSE.txt)
8
+
9
+ **anyTS** is the language-independent core of the text statistics libraries [ruTS](https://github.com/SergeyShk/ruTS) (Russian) and [esTS](https://github.com/SergeyShk/esTS) (Spanish). It holds the code that does not depend on a language, so that it is written, tested and documented once; a language library adds its tokenizer, lemmatizer, [spaCy](https://github.com/explosion/spaCy) model and rules and re-exports the core under its own names.
10
+
11
+ [Documentation](https://sergeyshk.github.io/anyTS/) · [PyPI](https://pypi.org/project/anyts/)
12
+
13
+ ## Features
14
+
15
+ * **[Extraction](https://sergeyshk.github.io/anyTS/extractors/sentences/)** - sentence, word and character N-gram extractors whose tokenizer, lemmatizer and number pattern are hooks a language library overrides
16
+ * **[Lexical diversity](https://sergeyshk.github.io/anyTS/stats/diversity_stats/)** - 32 metrics: TTR and its variants, MATTR, MSTTR, MTLD, MA-MTLD, MTLD-W, HD-D, the indices of Simpson and Yule, entropy, the laws of Zipf and Heaps, over the whole text or over windows with confidence intervals
17
+ * **[Cohesion helpers](https://sergeyshk.github.io/anyTS/stats/cohesion/)** - the overlap of sentences, adjacent and over all pairs, Dice, givenness and repetition
18
+ * **[Dependency tree helpers](https://sergeyshk.github.io/anyTS/stats/syntax/)** - dependency distances, tree depth, valency and coordination chains on the labels shared by Universal Dependencies and ClearNLP
19
+ * **[Corpus measures](https://sergeyshk.github.io/anyTS/corpus/keyness/)** - keywords against a corpus or a frequency dictionary, collocations, the dispersion of a word, Burrows's Delta and its variants, Zeta, Kilgarriff's chi-square, the Mendenhall curve and the comparison of corpora with effect sizes, a bootstrap by texts and the Holm correction
20
+ * **[Exceptions](https://sergeyshk.github.io/anyTS/exceptions/)** - one hierarchy whose classes are also built-in exceptions
21
+
22
+ The reference of every function is a named section that the libraries include in their own documentation, so a formula is described in one place.
23
+
24
+ ## Installation
25
+
26
+ Requires Python 3.11 or newer.
27
+
28
+ ```bash
29
+ pip install anyts
30
+ ```
31
+
32
+ Or with [uv](https://docs.astral.sh/uv/):
33
+
34
+ ```bash
35
+ uv add anyts
36
+ ```
37
+
38
+ The dependencies are numpy, pandas, scipy and spaCy; no trained spaCy model is needed.
39
+
40
+ ## Quick start
41
+
42
+ ```python
43
+ >>> from anyts import DiversityStats, SentsExtractor, WordsExtractor
44
+
45
+ >>> text = "The cat sat on the mat. The dog sat on the log! The cat and the dog slept."
46
+
47
+ >>> SentsExtractor().extract(text)
48
+ ('The cat sat on the mat.', 'The dog sat on the log!', 'The cat and the dog slept.')
49
+
50
+ >>> ds = DiversityStats(WordsExtractor(lowercase=True).extract(text))
51
+ >>> ds.ttr, round(ds.entropy, 3)
52
+ (0.5, 2.864)
53
+ ```
54
+
55
+ A language library subclasses the extractors and overrides their hooks:
56
+
57
+ ```python
58
+ >>> class Words(WordsExtractor):
59
+ ... def lemmatize(self, word):
60
+ ... return {"sat": "sit", "slept": "sleep"}.get(word.lower(), word)
61
+
62
+ >>> Words(lowercase=True, use_lexemes=True).extract("The cat sat. The dog slept.")
63
+ ('the', 'cat', 'sit', 'the', 'dog', 'sleep')
64
+ ```
65
+
66
+ The corpus measures take lists of words:
67
+
68
+ ```python
69
+ >>> from anyts.corpus import keyness
70
+
71
+ >>> cats = WordsExtractor(lowercase=True).extract("The cat sat on the mat. The cat saw a bird. The cat slept.")
72
+ >>> dogs = WordsExtractor(lowercase=True).extract("The dog sat on the log. The dog saw a cat. The dog barked.")
73
+ >>> [(keyword.word, round(keyword.log_ratio, 2)) for keyword in keyness(cats, dogs, min_freq=2)]
74
+ [('cat', 1.58)]
75
+ ```
76
+
77
+ ## Development
78
+
79
+ The project uses [uv](https://docs.astral.sh/uv/) for dependencies and [ruff](https://docs.astral.sh/ruff/) for linting and formatting.
80
+
81
+ ```bash
82
+ git clone https://github.com/SergeyShk/anyTS.git
83
+ cd anyTS
84
+
85
+ make deps # create the environment and install all dependencies
86
+ uv run pre-commit install # hooks: linters on commit, tests on push
87
+ make lint # ruff check, ruff format --check, mypy
88
+ make test-cov # pytest with doctests and full coverage
89
+ make docs-build # mkdocs build --strict
90
+ ```
91
+
92
+ The full list of commands is in `make help`. Contribution guidelines are in [CONTRIBUTING.md](https://github.com/SergeyShk/anyTS/blob/master/CONTRIBUTING.md).
93
+
94
+ ## License
95
+
96
+ [MIT](https://github.com/SergeyShk/anyTS/blob/master/LICENSE.txt)
@@ -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
+ ]
@@ -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)